Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default DS.RESTAdapter.extend(AdapterFetch, {
```

### Use with Fastboot
#### ajax-service
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.

Example:
Expand All @@ -65,6 +66,17 @@ export default {
}
```

#### relative url
`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).

> `url` should be an absolute url, such as `https://example.com/`.
> A path-relative URL (`/file/under/root`) or protocol-relative URL (`//can-be-http-or-https.com/`)
> will result in a rejected promise.

However, `ember-fetch` grabs the `protocol` and `host` info from fastboot request after the `instance-initializes`.
This allows you to make a relative URL request unless the app is not initialized, e.g. `initializers` and `app.js`.

#### top-level addon
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).
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.

Expand Down
6 changes: 3 additions & 3 deletions fastboot/instance-initializers/setup-fetch.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import setupFetch from 'fetch/setup';
import { setupFastboot } from 'fetch';

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

export default {
Expand Down
21 changes: 17 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ module.exports = {
* in a FastBoot build or not. Based on that, we return a tree that contains
* the correct version of the polyfill at the `vendor/ember-fetch.js` path.
*/
treeForVendor: function() {
treeForVendor() {
let babelAddon = this.addons.find(addon => addon.name === 'ember-cli-babel');

let browserTree = this.treeForBrowserFetch();
Expand All @@ -121,9 +121,22 @@ module.exports = {
}`), 'wrapped');
},

//add node version of fetch.js into fastboot package.json manifest vendorFiles array
updateFastBootManifest: function(manifest) {
manifest.vendorFiles.push('ember-fetch/fastboot-fetch.js');
// Only include public/fetch-fastboot.js if top level addon
treeForPublic() {
return !this.parent.parent ? this._super.treeForPublic.apply(this, arguments) : null;
},

cacheKeyForTree(treeType) {
if (treeType === 'public') {
return require('calculate-cache-key-for-tree')('public', this, [!this.parent.parent]);
} else {
return this._super.cacheKeyForTree.call(this, treeType);
}
},

// Add node version of fetch.js into fastboot package.json manifest vendorFiles array
updateFastBootManifest(manifest) {
manifest.vendorFiles.push('ember-fetch/fetch-fastboot.js');
return manifest;
},

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"broccoli-rollup": "^2.1.1",
"broccoli-stew": "^2.0.0",
"broccoli-templater": "^2.0.1",
"calculate-cache-key-for-tree": "^1.1.0",
"ember-cli-babel": "^6.8.2",
"node-fetch": "^2.3.0",
"whatwg-fetch": "^3.0.0"
Expand Down
55 changes: 0 additions & 55 deletions public/fastboot-fetch.js

This file was deleted.

75 changes: 75 additions & 0 deletions public/fetch-fastboot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* globals define FastBoot */
define('fetch', ['exports'], function(exports) {
var httpRegex = /^https?:\/\//;
var protocolRelativeRegex = /^\/\//;

var AbortControllerPolyfill = FastBoot.require(
'abortcontroller-polyfill/dist/cjs-ponyfill'
);
var nodeFetch = FastBoot.require('node-fetch');

/**
* Build the absolute url if it's not, can handle:
* - protocol-relative URL (//can-be-http-or-https.com/)
* - path-relative URL (/file/under/root)
*
* @param {string} url
* @param {string} protocol
* @param {string} host
* @returns {string}
*/
function buildAbsoluteUrl(url, protocol, host) {
if (protocolRelativeRegex.test(url)) {
url = host + url;
} else if (!httpRegex.test(url)) {
if (!host) {
throw new Error(
'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.'
);
}
url = protocol + '//' + host + url;
}
return url;
}

var FastbootHost, FastbootProtocol;

/**
* Isomorphic `fetch` API for both browser and fastboot
*
* node-fetch doesn't allow relative URLs, we patch it with Fastboot runtime info.
* Before instance-initializers Absolute URL is still not allowed, in this case
* node-fetch will throw error.
* `FastbootProtocol` and `FastbootHost` are re-set for every instance during its
* initializers through calling `setupFastboot`.
*
* @param {String|Object} input
* @param {Object} [options]
*/
exports.default = function fetch(input, options) {
if (typeof input === 'object') {
input.url = buildAbsoluteUrl(input.url, FastbootProtocol, FastbootHost);
} else {
input = buildAbsoluteUrl(input, FastbootProtocol, FastbootHost);
}
return nodeFetch(input, options);
};
/**
* Assign the local protocol and host being used for building absolute URLs
* @private
*/
exports.setupFastboot = function setupFastboot(protocol, host) {
FastbootProtocol = protocol;
FastbootHost = host;
}
exports.Request = nodeFetch.Request;
exports.Headers = nodeFetch.Headers;
exports.Response = nodeFetch.Response;
exports.AbortController = AbortControllerPolyfill.AbortController;
});

define('fetch/ajax', ['exports'], function() {
throw new Error(
'You included `fetch/ajax` but it was renamed to `ember-fetch/ajax`'
);
});
8 changes: 4 additions & 4 deletions test/fastboot-build-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('it builds with ember-cli-fastboot', function() {
it('builds into dist/ember-fetch/fetch-fastboot.js', function() {
return app.runEmberCommand('build').then(function() {
expect(app.filePath('dist/index.html')).to.be.a.file();
expect(app.filePath('dist/ember-fetch/fastboot-fetch.js')).to.be.a.file();
expect(app.filePath('dist/ember-fetch/fetch-fastboot.js')).to.be.a.file();
expect(app.filePath('dist/assets/dummy-fastboot.js')).to.be.a.file();
});
});
Expand All @@ -38,9 +38,9 @@ describe('it builds with ember-cli-fastboot', function() {
.runEmberCommand('build', '--environment=production')
.then(function() {
expect(app.filePath('dist/index.html')).to.be.a.file();
expect(find('dist/ember-fetch/fastboot-fetch-*.js')).to.be.a.file();
expect(find('dist/ember-fetch/fastboot-fetch-*.js')).to.match(
/fastboot-fetch-\w{32}/,
expect(find('dist/ember-fetch/fetch-fastboot-*.js')).to.be.a.file();
expect(find('dist/ember-fetch/fetch-fastboot-*.js')).to.match(
/fetch-fastboot-\w{32}/,
'file name should contain MD5 fingerprint'
);

Expand Down
8 changes: 8 additions & 0 deletions test/fastboot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ describe('renders in fastboot build', function() {
.then(app =>
app.editPackageJSON(pkg => {
pkg.devDependencies['ember-cli-fastboot'] = '*';
// ember-fetch-adapter@0.4.0 has ember-fetch as dependency, we want to test
pkg.devDependencies['ember-fetch-adapter'] = '0.4.0';
// These 2 are in ember-fetch's package.json, symlinking to dummy won't help resolve
pkg.devDependencies['abortcontroller-polyfill'] = '*';
pkg.devDependencies['node-fetch'] = '*';
Expand All @@ -39,6 +41,12 @@ describe('renders in fastboot build', function() {
return app.stopServer();
});

it('builds into dist/ember-fetch/fetch-fastboot.js ignoring sub dependency version conflict', function() {
expect(app.filePath('dist/index.html')).to.be.a.file();
expect(app.filePath('dist/ember-fetch/fetch-fastboot.js')).to.be.a.file();
expect(app.filePath('dist/assets/dummy-fastboot.js')).to.be.a.file();
});

it('fetches in fastboot mode', function() {
return get({
url: 'http://localhost:49741/',
Expand Down