Skip to content

Commit e970738

Browse files
committed
Fix fetch module definition for Fastboot
Define fetch module with a setupFastboot method export to set host and protocol for every instance. Rename public/fastboot-fetch.js to public/fetch-fastboot.js to avoid lower version ember-fetch overwrite this file. Overrides treeForPublic in index.js to only include public asset if top level addon to avoid any future rename.
1 parent 9a1c3e3 commit e970738

File tree

7 files changed

+112
-66
lines changed

7 files changed

+112
-66
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export default DS.RESTAdapter.extend(AdapterFetch, {
4949
```
5050

5151
### Use with Fastboot
52+
#### ajax-service
5253
Currently, Fastboot supplies its own server-side ajax functionality, and including `ember-fetch` and the `adapter-fetch` mixin in a Fastboot app will not work without some modifications. To allow the `node-fetch` polyfill that is included with this addon to make your API calls, you must add an initializer to the consuming app's `fastboot` directory that overrides the one Fastboot utilizes to inject its own ajax.
5354

5455
Example:
@@ -65,6 +66,17 @@ export default {
6566
}
6667
```
6768

69+
#### relative url
70+
`ember-fetch` uses `node-fetch` in Fastboot, which [doesn't allow relative URL](https://github.com/bitinn/node-fetch/tree/v2.3.0#fetchurl-options).
71+
72+
> `url` should be an absolute url, such as `https://example.com/`.
73+
> A path-relative URL (`/file/under/root`) or protocol-relative URL (`//can-be-http-or-https.com/`)
74+
> will result in a rejected promise.
75+
76+
However, `ember-fetch` grabs the `protocol` and `host` info from fastboot request after the `instance-initializes`.
77+
This allows you to make a relative URL request unless the app is not initialized, e.g. `initializers` and `app.js`.
78+
79+
#### top-level addon
6880
For addon authors, if the addon supports Fastboot mode, `ember-fetch` should also be listed as a [peer dependency](https://docs.npmjs.com/files/package.json#peerdependencies).
6981
This is because Fastboot only invokes top-level addon's `updateFastBootManifest` ([detail](https://github.com/ember-fastboot/ember-cli-fastboot/issues/597)), thus `ember-fetch` has to be a top-level addon installed by the host app.
7082

fastboot/instance-initializers/setup-fetch.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import setupFetch from 'fetch/setup';
1+
import { setupFastboot } from 'fetch';
22

33
/**
44
* To allow relative URLs for Fastboot mode, we need the per request information
5-
* from the fastboot service. Then we re-define the `fetch` amd module.
5+
* from the fastboot service. Then we set the protocol and host to fetch module.
66
*/
77
function patchFetchForRelativeURLs(instance) {
88
const fastboot = instance.lookup('service:fastboot');
99
const request = fastboot.get('request');
1010
// Prember is not sending protocol
1111
const protocol = request.protocol === 'undefined:' ? 'http:' : request.protocol;
1212
// host is cp
13-
setupFetch(protocol, request.get('host'))();
13+
setupFastboot(protocol, request.get('host'));
1414
}
1515

1616
export default {

index.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ module.exports = {
9797
* in a FastBoot build or not. Based on that, we return a tree that contains
9898
* the correct version of the polyfill at the `vendor/ember-fetch.js` path.
9999
*/
100-
treeForVendor: function() {
100+
treeForVendor() {
101101
let babelAddon = this.addons.find(addon => addon.name === 'ember-cli-babel');
102102

103103
let browserTree = this.treeForBrowserFetch();
@@ -121,9 +121,22 @@ module.exports = {
121121
}`), 'wrapped');
122122
},
123123

124-
//add node version of fetch.js into fastboot package.json manifest vendorFiles array
125-
updateFastBootManifest: function(manifest) {
126-
manifest.vendorFiles.push('ember-fetch/fastboot-fetch.js');
124+
// Only include public/fetch-fastboot.js if top level addon
125+
treeForPublic() {
126+
return !this.parent.parent ? this._super.treeForPublic.apply(this, arguments) : null;
127+
},
128+
129+
cacheKeyForTree(treeType) {
130+
if (treeType === 'public') {
131+
return require('calculate-cache-key-for-tree')('public', this, [!this.parent.parent]);
132+
} else {
133+
return this._super.cacheKeyForTree.call(this, treeType);
134+
}
135+
},
136+
137+
// Add node version of fetch.js into fastboot package.json manifest vendorFiles array
138+
updateFastBootManifest(manifest) {
139+
manifest.vendorFiles.push('ember-fetch/fetch-fastboot.js');
127140
return manifest;
128141
},
129142

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"broccoli-rollup": "^2.1.1",
3030
"broccoli-stew": "^2.0.0",
3131
"broccoli-templater": "^2.0.1",
32+
"calculate-cache-key-for-tree": "^1.1.0",
3233
"ember-cli-babel": "^6.8.2",
3334
"node-fetch": "^2.3.0",
3435
"whatwg-fetch": "^3.0.0"

public/fastboot-fetch.js

Lines changed: 0 additions & 55 deletions
This file was deleted.

public/fetch-fastboot.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* globals define FastBoot */
2+
define('fetch', ['exports'], function(exports) {
3+
var httpRegex = /^https?:\/\//;
4+
var protocolRelativeRegex = /^\/\//;
5+
6+
var AbortControllerPolyfill = FastBoot.require(
7+
'abortcontroller-polyfill/dist/cjs-ponyfill'
8+
);
9+
var nodeFetch = FastBoot.require('node-fetch');
10+
11+
/**
12+
* Build the absolute url if it's not, can handle:
13+
* - protocol-relative URL (//can-be-http-or-https.com/)
14+
* - path-relative URL (/file/under/root)
15+
*
16+
* @param {string} url
17+
* @param {string} protocol
18+
* @param {string} host
19+
* @returns {string}
20+
*/
21+
function buildAbsoluteUrl(url, protocol, host) {
22+
if (protocolRelativeRegex.test(url)) {
23+
url = host + url;
24+
} else if (!httpRegex.test(url)) {
25+
if (!host) {
26+
throw new Error(
27+
'You are using using fetch with a path-relative URL, but host is missing from Fastboot request. Please set the hostWhitelist property in your environment.js.'
28+
);
29+
}
30+
url = protocol + '//' + host + url;
31+
}
32+
return url;
33+
}
34+
35+
var FastbootHost, FastbootProtocol;
36+
37+
/**
38+
* Isomorphic `fetch` API for both browser and fastboot
39+
*
40+
* node-fetch doesn't allow relative URLs, we patch it with Fastboot runtime info.
41+
* Before instance-initializers Absolute URL is still not allowed, in this case
42+
* node-fetch will throw error.
43+
* `FastbootProtocol` and `FastbootHost` are re-set for every instance during its
44+
* initializers through calling `setupFastboot`.
45+
*
46+
* @param {String|Object} input
47+
* @param {Object} [options]
48+
*/
49+
exports.default = function fetch(input, options) {
50+
if (typeof input === 'object') {
51+
input.url = buildAbsoluteUrl(input.url, FastbootProtocol, FastbootHost);
52+
} else {
53+
input = buildAbsoluteUrl(input, FastbootProtocol, FastbootHost);
54+
}
55+
return nodeFetch(input, options);
56+
};
57+
/**
58+
* Assign the local protocol and host being used for building absolute URLs
59+
* @private
60+
*/
61+
exports.setupFastboot = function setupFastboot(protocol, host) {
62+
FastbootProtocol = protocol;
63+
FastbootHost = host;
64+
}
65+
exports.Request = nodeFetch.Request;
66+
exports.Headers = nodeFetch.Headers;
67+
exports.Response = nodeFetch.Response;
68+
exports.AbortController = AbortControllerPolyfill.AbortController;
69+
});
70+
71+
define('fetch/ajax', ['exports'], function() {
72+
throw new Error(
73+
'You included `fetch/ajax` but it was renamed to `ember-fetch/ajax`'
74+
);
75+
});

test/fastboot-build-test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('it builds with ember-cli-fastboot', function() {
2828
it('builds into dist/ember-fetch/fetch-fastboot.js', function() {
2929
return app.runEmberCommand('build').then(function() {
3030
expect(app.filePath('dist/index.html')).to.be.a.file();
31-
expect(app.filePath('dist/ember-fetch/fastboot-fetch.js')).to.be.a.file();
31+
expect(app.filePath('dist/ember-fetch/fetch-fastboot.js')).to.be.a.file();
3232
expect(app.filePath('dist/assets/dummy-fastboot.js')).to.be.a.file();
3333
});
3434
});
@@ -38,9 +38,9 @@ describe('it builds with ember-cli-fastboot', function() {
3838
.runEmberCommand('build', '--environment=production')
3939
.then(function() {
4040
expect(app.filePath('dist/index.html')).to.be.a.file();
41-
expect(find('dist/ember-fetch/fastboot-fetch-*.js')).to.be.a.file();
42-
expect(find('dist/ember-fetch/fastboot-fetch-*.js')).to.match(
43-
/fastboot-fetch-\w{32}/,
41+
expect(find('dist/ember-fetch/fetch-fastboot-*.js')).to.be.a.file();
42+
expect(find('dist/ember-fetch/fetch-fastboot-*.js')).to.match(
43+
/fetch-fastboot-\w{32}/,
4444
'file name should contain MD5 fingerprint'
4545
);
4646

0 commit comments

Comments
 (0)