From afecaa709cebd84a90c360ea304c132100fdcded Mon Sep 17 00:00:00 2001 From: Asa Kusuma Date: Tue, 20 Dec 2022 10:31:48 -0800 Subject: [PATCH 1/3] Move to new ember-fetch import path This will ensure there is no confusion around folks thinking they are importing from the actual `fetch` package. For backwards compatibility, we need to still support consumers importing from the `fetch` path. In the future, we should release a breaking change which removes this alias and requires consumers to import from `ember-fetch`. --- README.md | 9 ++++++-- assets/fetch-legacy-alias.js.t | 13 +++++++++++ fastboot/instance-initializers/setup-fetch.js | 2 +- index.js | 13 +++++++---- test/fixtures/dummy/app/routes/index.js | 2 +- tests/acceptance/error-test.js | 2 +- tests/acceptance/root-test.js | 17 +++++++++++++- tests/dummy/app/controllers/index.js | 2 +- .../app/controllers/legacy-import-path.js | 23 +++++++++++++++++++ tests/dummy/app/router.js | 1 + tests/dummy/app/routes/index.js | 2 +- tests/dummy/app/routes/legacy-import-path.js | 13 +++++++++++ .../app/templates/legacy-import-path.hbs | 13 +++++++++++ tests/unit/abortcontroller-test.js | 17 +++++++++++++- tests/unit/error-test.js | 2 +- tests/unit/headers-test.js | 12 +++++++++- .../unit/utils/determine-body-promise-test.js | 14 ++++++++++- 17 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 assets/fetch-legacy-alias.js.t create mode 100644 tests/dummy/app/controllers/legacy-import-path.js create mode 100644 tests/dummy/app/routes/legacy-import-path.js create mode 100644 tests/dummy/app/templates/legacy-import-path.hbs diff --git a/README.md b/README.md index dd751b33..f879e7be 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ ember-fetch requries ember-cli 2.13 or above. ```js import Route from '@ember/routing/route'; -import fetch from 'fetch'; +import fetch from 'ember-fetch'; export default Route.extend({ model() { @@ -34,6 +34,11 @@ export default Route.extend({ Available imports: ```js +import fetch, { Headers, Request, Response, AbortController } from 'ember-fetch'; +``` + +`ember-fetch` still supports importing from the old import path: +```js import fetch, { Headers, Request, Response, AbortController } from 'fetch'; ``` @@ -126,7 +131,7 @@ otherwise you can read the status code to determine the bad response type. ```js import Route from '@ember/routing/route'; -import fetch from 'fetch'; +import fetch from 'ember-fetch'; import { isAbortError, isServerErrorResponse, diff --git a/assets/fetch-legacy-alias.js.t b/assets/fetch-legacy-alias.js.t new file mode 100644 index 00000000..7b9b8ccd --- /dev/null +++ b/assets/fetch-legacy-alias.js.t @@ -0,0 +1,13 @@ +/* + * For backwards compatibility, we need to still support consumers importing from the `fetch` path. + * In the future, we should release a breaking change which removes this alias and requires consumers + * to import from `ember-fetch`. This will ensure there is no confusion around folks thinking they are + * importing from the actual `fetch` package. + */ +define('fetch', ['exports', 'ember-fetch'], function(exports, emberFetch) { + const exportKeys = Object.keys(emberFetch); + for (let i = 0; i < exportKeys.length; i++) { + const key = exportKeys[i]; + exports[key] = emberFetch[key]; + } +}); \ No newline at end of file diff --git a/fastboot/instance-initializers/setup-fetch.js b/fastboot/instance-initializers/setup-fetch.js index c41a7b5b..981c5fee 100644 --- a/fastboot/instance-initializers/setup-fetch.js +++ b/fastboot/instance-initializers/setup-fetch.js @@ -1,4 +1,4 @@ -import { setupFastboot } from 'fetch'; +import { setupFastboot } from 'ember-fetch'; /** * To allow relative URLs for Fastboot mode, we need the per request information diff --git a/index.js b/index.js index 00f29664..d52e6f41 100644 --- a/index.js +++ b/index.js @@ -28,12 +28,15 @@ const Rollup = require('broccoli-rollup'); const BroccoliDebug = require('broccoli-debug'); const calculateCacheKeyForTree = require('calculate-cache-key-for-tree'); const VersionChecker = require('ember-cli-version-checker'); +const { readFileSync } = require('fs'); const debug = BroccoliDebug.buildDebugCallback('ember-fetch'); // Path to the template that contains the shim wrapper around the browser polyfill const TEMPLATE_PATH = path.resolve(__dirname + '/assets/browser-fetch.js.t'); +const LEGACY_ALIAS_TEMPLATE = readFileSync(path.resolve(__dirname + '/assets/fetch-legacy-alias.js.t')); + /* * The `index.js` file is the main entry point for all Ember CLI addons. The * object we export from this file is turned into an Addon class @@ -222,7 +225,7 @@ module.exports = { sourceMapConfig: { enabled: false } }), 'after-concat'); - const moduleHeader = this._getModuleHeader(options); + const moduleHeader = LEGACY_ALIAS_TEMPLATE + this._getModuleHeader(options); return debug( new Template(polyfillNode, TEMPLATE_PATH, function (content) { @@ -238,14 +241,14 @@ module.exports = { _getModuleHeader({ hasEmberSourceModules, nativePromise }) { if (hasEmberSourceModules && nativePromise) { return ` -define('fetch', ['exports', 'ember'], function(exports, Ember__module) { +define('ember-fetch', ['exports', 'ember'], function(exports, Ember__module) { 'use strict'; var Ember = 'default' in Ember__module ? Ember__module['default'] : Ember__module;`; } if (hasEmberSourceModules) { return ` -define('fetch', ['exports', 'ember', 'rsvp'], function(exports, Ember__module, RSVP__module) { +define('ember-fetch', ['exports', 'ember', 'rsvp'], function(exports, Ember__module, RSVP__module) { 'use strict'; var Ember = 'default' in Ember__module ? Ember__module['default'] : Ember__module; var RSVP = 'default' in RSVP__module ? RSVP__module['default'] : RSVP__module; @@ -254,13 +257,13 @@ define('fetch', ['exports', 'ember', 'rsvp'], function(exports, Ember__module, R if (nativePromise) { return ` -define('fetch', ['exports'], function(exports) { +define('ember-fetch', ['exports'], function(exports) { 'use strict'; var Ember = originalGlobal.Ember;`; } return ` -define('fetch', ['exports'], function(exports) { +define('ember-fetch', ['exports'], function(exports) { 'use strict'; var Ember = originalGlobal.Ember; var Promise = Ember.RSVP.Promise;`; diff --git a/test/fixtures/dummy/app/routes/index.js b/test/fixtures/dummy/app/routes/index.js index d31658f4..e7ce7846 100644 --- a/test/fixtures/dummy/app/routes/index.js +++ b/test/fixtures/dummy/app/routes/index.js @@ -1,6 +1,6 @@ import Route from '@ember/routing/route'; import { hash } from 'rsvp'; -import fetch, { Request } from 'fetch'; +import fetch, { Request } from 'ember-fetch'; import ajax from 'ember-fetch/ajax'; export default Route.extend({ diff --git a/tests/acceptance/error-test.js b/tests/acceptance/error-test.js index a67924d7..c258ece2 100644 --- a/tests/acceptance/error-test.js +++ b/tests/acceptance/error-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; import Pretender from 'pretender'; -import fetch, { AbortController } from 'fetch'; +import fetch, { AbortController } from 'ember-fetch'; import { isUnauthorizedResponse, isForbiddenResponse, diff --git a/tests/acceptance/root-test.js b/tests/acceptance/root-test.js index a8ee0365..e542b9e2 100644 --- a/tests/acceptance/root-test.js +++ b/tests/acceptance/root-test.js @@ -2,7 +2,7 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { visit, click, find, currentRouteName } from '@ember/test-helpers'; import Pretender from 'pretender'; -import fetch from 'fetch'; +import fetch from 'ember-fetch'; var server; @@ -32,6 +32,21 @@ module('Acceptance: Root', function(hooks) { assert.equal(this.element.querySelector('.fetch').textContent.trim(), 'Hello World! fetch'); }); + test('legacy import path still works', async function(assert) { + server.get('/omg.json', function() { + return [ + 200, + { 'Content-Type': 'text/json'}, + JSON.stringify({ name: 'World' }) + ]; + }); + + await visit('/legacy-import-path'); + + assert.equal(currentRouteName(), 'legacy-import-path'); + assert.equal(this.element.querySelector('.fetch').textContent.trim(), 'Hello World! fetch'); + }); + test('posting a string', function(assert) { server.post('/upload', function(req) { assert.equal(req.requestBody, 'foo'); diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index 357444be..3acbbdae 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -1,6 +1,6 @@ import Controller from '@ember/controller'; import { run } from '@ember/runloop'; -import fetch from 'fetch'; +import fetch from 'ember-fetch'; export default Controller.extend({ actions: { diff --git a/tests/dummy/app/controllers/legacy-import-path.js b/tests/dummy/app/controllers/legacy-import-path.js new file mode 100644 index 00000000..357444be --- /dev/null +++ b/tests/dummy/app/controllers/legacy-import-path.js @@ -0,0 +1,23 @@ +import Controller from '@ember/controller'; +import { run } from '@ember/runloop'; +import fetch from 'fetch'; + +export default Controller.extend({ + actions: { + fetchSlowData() { + fetch('/slow-data.json') + .then((r) => r.json(), (e) => { + run(() => this.set('fetchedSlowDataFailed', true)); + throw e; + }) + .then((data) => { + run(() => this.setProperties({ fetchedSlowData: data, fetchedSlowDataFailed: false })); + }); + }, + + badFetch() { + fetch('http://localhost:9999') // probably there is nothing listening here :D + .then((r) => r.json(), () => run(() => this.set('fetchFailed', true))); + } + } +}); diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index d0bb0095..33fa8060 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -7,6 +7,7 @@ const Router = EmberRouter.extend({ }); Router.map(function() { + this.route('legacy-import-path') }); export default Router; diff --git a/tests/dummy/app/routes/index.js b/tests/dummy/app/routes/index.js index 6f26bf0d..f747b60f 100644 --- a/tests/dummy/app/routes/index.js +++ b/tests/dummy/app/routes/index.js @@ -1,6 +1,6 @@ import Route from '@ember/routing/route'; import { hash } from 'rsvp'; -import fetch from 'fetch'; +import fetch from 'ember-fetch'; export default Route.extend({ model: function() { diff --git a/tests/dummy/app/routes/legacy-import-path.js b/tests/dummy/app/routes/legacy-import-path.js new file mode 100644 index 00000000..6f26bf0d --- /dev/null +++ b/tests/dummy/app/routes/legacy-import-path.js @@ -0,0 +1,13 @@ +import Route from '@ember/routing/route'; +import { hash } from 'rsvp'; +import fetch from 'fetch'; + +export default Route.extend({ + model: function() { + return hash({ + fetch: fetch('/omg.json').then(function(request) { + return request.json(); + }) + }); + } +}); diff --git a/tests/dummy/app/templates/legacy-import-path.hbs b/tests/dummy/app/templates/legacy-import-path.hbs new file mode 100644 index 00000000..70c7c96e --- /dev/null +++ b/tests/dummy/app/templates/legacy-import-path.hbs @@ -0,0 +1,13 @@ +
+ Hello {{model.fetch.name}}! fetch +
+ + +{{#if fetchedSlowData}} + {{fetchedSlowData.content}} +{{/if}} + + +{{#if fetchFailed}} + Fetch failed +{{/if}} \ No newline at end of file diff --git a/tests/unit/abortcontroller-test.js b/tests/unit/abortcontroller-test.js index 1398582d..b89d3712 100644 --- a/tests/unit/abortcontroller-test.js +++ b/tests/unit/abortcontroller-test.js @@ -1,5 +1,6 @@ import { module, test } from 'qunit'; -import { AbortController } from 'fetch'; +import { AbortController } from 'ember-fetch'; +import { AbortController as LegacyAbortController } from 'ember-fetch'; module('AbortController', function() { test('signal', function(assert) { @@ -15,4 +16,18 @@ module('AbortController', function() { controller.abort(); }) + + test('signal works from legacy import', function(assert) { + assert.expect(1); + let controller = new LegacyAbortController(); + let signal = controller.signal; + + let done = assert.async(); + signal.addEventListener('abort', function() { + assert.ok(true); + done(); + }); + + controller.abort(); + }) }); diff --git a/tests/unit/error-test.js b/tests/unit/error-test.js index 42626fc2..86d8333e 100644 --- a/tests/unit/error-test.js +++ b/tests/unit/error-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { Response } from 'fetch'; +import { Response } from 'ember-fetch'; import { isUnauthorizedResponse, diff --git a/tests/unit/headers-test.js b/tests/unit/headers-test.js index 54bff0d9..763cdcd3 100644 --- a/tests/unit/headers-test.js +++ b/tests/unit/headers-test.js @@ -1,5 +1,6 @@ import { module, test } from 'qunit'; -import { Headers } from 'fetch'; +import { Headers } from 'ember-etch'; +import { Headers as LegacyImportHeaders } from 'fetch'; module('Headers', function() { test('iterator', function(assert) { @@ -10,4 +11,13 @@ module('Headers', function() { assert.ok(headers.keys()[Symbol.iterator]); assert.ok(headers.entries()[Symbol.iterator]); }); + + test('iterator from legacy import', function(assert) { + let headers = new LegacyImportHeaders(); + + assert.ok(headers[Symbol.iterator]); + assert.ok(headers.values()[Symbol.iterator]); + assert.ok(headers.keys()[Symbol.iterator]); + assert.ok(headers.entries()[Symbol.iterator]); + }); }); diff --git a/tests/unit/utils/determine-body-promise-test.js b/tests/unit/utils/determine-body-promise-test.js index 3554da1e..80941bca 100644 --- a/tests/unit/utils/determine-body-promise-test.js +++ b/tests/unit/utils/determine-body-promise-test.js @@ -1,5 +1,6 @@ import { module, test } from 'qunit'; -import { Response } from 'fetch'; +import { Response } from 'ember-fetch'; +import { Response as LegacyImportResponse } from 'fetch'; import determineBodyPromise from 'ember-fetch/utils/determine-body-promise'; module('Unit | determineBodyPromise', function() { @@ -14,6 +15,17 @@ module('Unit | determineBodyPromise', function() { }); }); + test('works when imported from legacy import path', function(assert) { + assert.expect(1); + + const response = new LegacyImportResponse('{"data": "foo"}', { status: 200 }); + const bodyPromise = determineBodyPromise(response, {}); + + return bodyPromise.then(body => { + assert.deepEqual(body, { data: 'foo' }); + }); + }); + test('determineBodyResponse returns the body even if it is not json', function(assert) { assert.expect(1); From cf1814cf976b859180379cadb2e837e4bc34571d Mon Sep 17 00:00:00 2001 From: Asa Kusuma Date: Tue, 20 Dec 2022 11:56:31 -0800 Subject: [PATCH 2/3] Add note about ts --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f879e7be..884b24f3 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,13 @@ import fetch, { Headers, Request, Response, AbortController } from 'fetch'; ``` ### Use with TypeScript -To use `ember-fetch` with TypeScript or enable editor's type support, You can add `"fetch": ["node_modules/ember-fetch"]` to your `tsconfig.json`. +If you import from `ember-fetch`, the types just work: + +```ts +import fetch from 'ember-fetch'; +``` + +If you're still using the legacy `fetch` import path, you need to add `"fetch": ["node_modules/ember-fetch"]` to your `tsconfig.json`. ```json { From d09c0bf4cbb078080d88f5f685129464c20ad92f Mon Sep 17 00:00:00 2001 From: Asa Kusuma Date: Tue, 20 Dec 2022 14:26:31 -0800 Subject: [PATCH 3/3] Update README.md to discourage using old import path Co-authored-by: Chris Krycho --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 884b24f3..7ec23b12 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Available imports: import fetch, { Headers, Request, Response, AbortController } from 'ember-fetch'; ``` -`ember-fetch` still supports importing from the old import path: +`ember-fetch` still supports importing from the old import path, but this will shortly be deprecated and ***will be removed in the next major***: ```js import fetch, { Headers, Request, Response, AbortController } from 'fetch'; ```