Skip to content

Commit 2a98cca

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 1d0a675 commit 2a98cca

File tree

6 files changed

+110
-64
lines changed

6 files changed

+110
-64
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: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ module.exports = {
6363
* which allows us to use the `import()` method to tell it to include a file
6464
* from our `vendor` tree into the final built app.
6565
*/
66-
included: function(app) {
66+
included(app) {
6767
this._super.included.apply(this, arguments);
6868

6969
let target = app;
@@ -113,7 +113,7 @@ module.exports = {
113113
* in a FastBoot build or not. Based on that, we return a tree that contains
114114
* the correct version of the polyfill at the `vendor/ember-fetch.js` path.
115115
*/
116-
treeForVendor: function() {
116+
treeForVendor() {
117117
let babelAddon = this.addons.find(addon => addon.name === 'ember-cli-babel');
118118

119119
let browserTree = this.treeForBrowserFetch();
@@ -136,9 +136,22 @@ module.exports = {
136136
}`), 'wrapped');
137137
},
138138

139-
//add node version of fetch.js into fastboot package.json manifest vendorFiles array
140-
updateFastBootManifest: function(manifest) {
141-
manifest.vendorFiles.push('ember-fetch/fastboot-fetch.js');
139+
// Only include public/fetch-fastboot.js if top level addon
140+
treeForPublic() {
141+
return !this.parent.parent ? this._super.treeForPublic.apply(this, arguments) : null;
142+
},
143+
144+
cacheKeyForTree(treeType) {
145+
if (treeType === 'public') {
146+
return require('calculate-cache-key-for-tree')('public', this, [!this.parent.parent]);
147+
} else {
148+
return this._super.cacheKeyForTree.call(this, treeType);
149+
}
150+
},
151+
152+
// Add node version of fetch.js into fastboot package.json manifest vendorFiles array
153+
updateFastBootManifest(manifest) {
154+
manifest.vendorFiles.push('ember-fetch/fetch-fastboot.js');
142155
return manifest;
143156
},
144157

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"broccoli-rollup": "^2.1.1",
2929
"broccoli-stew": "^2.0.0",
3030
"broccoli-templater": "^2.0.1",
31+
"calculate-cache-key-for-tree": "^1.1.0",
3132
"ember-cli-babel": "^6.8.2",
3233
"node-fetch": "^2.3.0",
3334
"whatwg-fetch": "^3.0.0"
@@ -39,9 +40,9 @@
3940
"co": "^4.6.0",
4041
"ember-cli": "~3.5.1",
4142
"ember-cli-dependency-checker": "^3.0.0",
42-
"ember-cli-htmlbars": "^3.0.1",
4343
"ember-cli-eslint": "^4.2.1",
4444
"ember-cli-fastboot": "^2.0.0",
45+
"ember-cli-htmlbars": "^3.0.1",
4546
"ember-cli-inject-live-reload": "^2.0.1",
4647
"ember-cli-pretender": "^3.0.0",
4748
"ember-cli-qunit": "^4.3.2",

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+
});

0 commit comments

Comments
 (0)