Skip to content

Commit

Permalink
Merge pull request #63 from xg-wang/fix/browser-fetch
Browse files Browse the repository at this point in the history
use browser fetch if exists
  • Loading branch information
stefanpenner authored Jul 20, 2018
2 parents 80e77c8 + fdb5005 commit ed52bfd
Show file tree
Hide file tree
Showing 13 changed files with 1,924 additions and 1,800 deletions.
44 changes: 35 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
[![Ember Observer Score](https://emberobserver.com/badges/ember-fetch.svg)](https://emberobserver.com/addons/ember-fetch)
[![npm version](https://badge.fury.io/js/ember-fetch.svg)](https://badge.fury.io/js/ember-fetch)

HTML5 [fetch](https://fetch.spec.whatwg.org) polyfill from [Netflix](https://github.com/Netflix/yetch) that supports AbortController/AbortSignal wrapped and bundled for ember-cli users.
HTML5 [fetch](https://fetch.spec.whatwg.org) polyfill from [github](https://github.com/github/fetch) wrapped and bundled for ember-cli users.

* [intro to fetch](https://developers.google.com/web/updates/2015/03/introduction-to-fetch)
* [spec](https://fetch.spec.whatwg.org)
* [usage](https://github.com/Netflix/yetch#usage)
* [origin repo](https://github.com/Netflix/yetch)
* [usage](https://github.com/github/fetch#usage)
* [origin repo](https://github.com/github/fetch)

## Installation

Expand All @@ -30,6 +30,11 @@ export default Ember.Route.extend({
});
```

Available imports:
```js
import fetch, { Headers, Request, Response, AbortController } from 'fetch';
```

### Use with Ember Data
To have Ember Data utilize `fetch` instead of jQuery.ajax to make calls to your backend, extend your project's `application` adapter with the `adapter-fetch` mixin.

Expand Down Expand Up @@ -60,12 +65,33 @@ export default {
}
```

### Browser Support
### Allow native fetch
`ember-fetch` allows access to native fetch in browser through a build config flag:
```js
// ember-cli-build.js
let app = new EmberAddon(defaults, {
// Add options here
'ember-fetch': {
preferNative: true
}
});
```
If set to `true`, the fetch polyfill will be skipped if native `fetch` is available,
otherwise the polyfilled `fetch` will be installed during the first pass of the vendor js file.

If set to `false`, the polyfilled `fetch` will replace native `fetch` be there or not.

The way you do import remains same.

## Browser Support

* evergreen / IE10+ / Safari 6.1+ https://github.com/Netflix/yetch#browser-support
* evergreen / IE10+ / Safari 6.1+ https://github.com/github/fetch#browser-support

## Q & A
### Does it work with pretender?
Yes, [pretender v2.1](https://github.com/pretenderjs/pretender/tree/v2.1.0) comes with `fetch` support.

### does this replace ic-ajax?
### Does this replace ic-ajax?

* ideally yes, but only if you cater to IE9+
* for basic drop-in compat `import ajax from 'ember-fetch/ajax'`
Expand All @@ -74,14 +100,14 @@ export default {

* taken care of for you

### why is this wrapper needed?
### Why is this wrapper needed?

* original emits a global
* original requires a Promise polyfill (ember users have RSVP)
* original isn't Ember run-loop aware

### Won't this wrapper get out-of-sync?

* we actually don't bundle Netflix/yetch rather we merely wrap/transform what
* we actually don't bundle github/fetch rather we merely wrap/transform what
comes from `node_modules`, so we should be resilient to changes assuming
semver from the yetch module
semver from the fetch module
50 changes: 31 additions & 19 deletions assets/browser-fetch.js.t
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,34 @@
define('fetch', ['exports'], function(self) {
'use strict';
var Promise = global.Ember.RSVP.Promise;
var window = self;
if (global.FormData) {
self.FormData = global.FormData;
}
if (global.FileReader) {
self.FileReader = global.FileReader;
}
if (global.Blob) {
self.Blob = global.Blob;
}
if (global.ArrayBuffer) {
self.ArrayBuffer = global.ArrayBuffer;
}
if (global.Symbol) {
self.Symbol = global.Symbol;
}
if (global.URLSearchParams) {
self.URLSearchParams = global.URLSearchParams;
var supportProps = [
'FormData',
'FileReader',
'Blob',
'URLSearchParams',
'Symbol',
'ArrayBuffer'
];
var polyfillProps = [
'fetch',
'Headers',
'Request',
'Response',
'AbortController'
];
var combinedProps = supportProps;
if (preferNative) {
combinedProps = supportProps.concat(polyfillProps);
}
combinedProps.forEach(function(prop) {
if (global[prop]) {
Object.defineProperty(self, prop, {
configurable: true,
get: function() { return global[prop] },
set: function(v) { global[prop] = v }
});
}
});

<%= moduleBody %>

Expand All @@ -38,7 +47,7 @@
self['default'] = function() {
pending++;

return self.fetch.apply(self, arguments).then(function(response){
return self.fetch.apply(global, arguments).then(function(response){
response.clone().blob().then(decrement, decrement);
return response;
}, function(reason) {
Expand All @@ -49,6 +58,9 @@
} else {
self['default'] = self.fetch;
}
supportProps.forEach(function(prop) {
delete self[prop];
});
});

define('fetch/ajax', ['exports'], function() {
Expand Down
3 changes: 3 additions & 0 deletions ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
module.exports = function(defaults) {
let app = new EmberAddon(defaults, {
// Add options here
'ember-fetch': {
preferNative: true
}
});

/*
Expand Down
77 changes: 41 additions & 36 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
'use strict';

var path = require('path');
const path = require('path');
// We use a few different Broccoli plugins to build our trees:
//
// broccoli-templater: renders the contents of a file inside a template.
// Used to wrap the browser polyfill in a shim that prevents it from exporting
// a global.
//
// broccoli-merge-trees: merge several broccoli trees (folders) to a single tree
//
// broccoli-concat: concatenate input files to single output file
//
// broccoli-stew: super useful library of Broccoli utilities. We use:
//
// * find - finds files in a tree based on a glob pattern
// * map - map content of files in a tree
//
var stew = require('broccoli-stew');
var Template = require('broccoli-templater');
var MergeTrees = require('broccoli-merge-trees');
var concat = require('broccoli-concat');
var map = stew.map;
var find = stew.find;
const stew = require('broccoli-stew');
const Template = require('broccoli-templater');
const MergeTrees = require('broccoli-merge-trees');
const concat = require('broccoli-concat');
const map = stew.map;

/*
* The `index.js` file is the main entry point for all Ember CLI addons. The
Expand Down Expand Up @@ -53,7 +56,7 @@ module.exports = {
*/
included: function(app) {
this._super.included.apply(this, arguments);

let target = app;

if (typeof this.import === 'function') {
Expand All @@ -63,18 +66,22 @@ module.exports = {
// use that.
if (typeof this._findHost === 'function') {
target = this._findHost();
} else {
// Otherwise, we'll use this implementation borrowed from the _findHost()
// method in ember-cli.
// Keep iterating upward until we don't have a grandparent.
// Has to do this grandparent check because at some point we hit the project.
let current = this;
do {
target = current.app || app;
} while (current.parent.parent && (current = current.parent));
}

// Otherwise, we'll use this implementation borrowed from the _findHost()
// method in ember-cli.
// Keep iterating upward until we don't have a grandparent.
// Has to do this grandparent check because at some point we hit the project.
let current = this;
do {
target = current.app || app;
} while (current.parent.parent && (current = current.parent));
// If this.import is not a function, app and target should point to the same EmberApp
app = target;
}

this.buildConfig = app.options['ember-fetch'] || { preferNative: false };

target.import('vendor/ember-fetch.js', {
exports: {
default: [
Expand All @@ -93,13 +100,17 @@ module.exports = {
* directory is kind of a junk drawer; nothing we put in it is used unless we
* explicitly `import()` a file (which we do in the `included` hook, above).
*
* To build our tree, we first detect whether we're in a FastBoot build or
* not. Based on that, we return a tree that contains the correct version of
* the polyfill at the `vendor/fetch.js` path.
* To build our tree, we first pass in option flags and detect whether we're
* 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() {
var browserTree = treeForBrowserFetch();
browserTree = map(browserTree, (content) => `if (typeof FastBoot === 'undefined') { ${content} }`);
let browserTree = treeForBrowserFetch();
const preferNative = this.buildConfig.preferNative;
browserTree = map(browserTree, (content) => `if (typeof FastBoot === 'undefined') {
var preferNative = ${preferNative};
${content}
}`);
return browserTree;
},

Expand All @@ -112,20 +123,19 @@ module.exports = {

// Path to the template that contains the shim wrapper around the browser
// polyfill
var templatePath = path.resolve(__dirname + '/assets/browser-fetch.js.t');
const templatePath = path.resolve(__dirname + '/assets/browser-fetch.js.t');


// Returns a tree containing the browser polyfill (from `yetch` and `abortcontroller-polyfill`),
// Returns a tree containing the browser polyfill (from `whatwg-fetch` and `abortcontroller-polyfill`),
// wrapped in a shim that stops it from exporting a global and instead turns it into a module
// that can be used by the Ember app.
function treeForBrowserFetch() {
var fetchPath = require.resolve('yetch/polyfill');
var abortcontrollerPath = require.resolve('abortcontroller-polyfill');

var expandedFetchPath = expand(fetchPath, 'dist/yetch-polyfill.js');
var expandedAbortcontrollerPath = expand(abortcontrollerPath, 'abortcontroller-polyfill-only.js');

var polyfillTree = concat(new MergeTrees([find(expandedFetchPath), find(expandedAbortcontrollerPath)]), {
// Fork whatwg-fetch to provide umd build before official release, no extra change made.
// We will get back to the official one when new version released.
const fetchTree = path.dirname(require.resolve('@xg-wang/whatwg-fetch'));
const abortcontrollerTree = path.dirname(require.resolve('abortcontroller-polyfill'));
const polyfillTree = concat(new MergeTrees([abortcontrollerTree, fetchTree]), {
inputFiles: ['abortcontroller-polyfill-only.js', 'fetch.umd.js'],
outputFile: 'ember-fetch.js',
sourceMapConfig: { enabled: false }
});
Expand All @@ -136,8 +146,3 @@ function treeForBrowserFetch() {
};
});
}

function expand(input, file) {
var dirname = path.dirname(input);
return path.join(dirname, file);
}
20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,29 @@
"build": "ember build",
"lint:js": "eslint ./*.js addon addon-test-support app config lib server test-support tests",
"start": "ember server",
"test": "ember try:each"
"test": "npm run test:node && ember try:each",
"test:node": "mocha"
},
"dependencies": {
"@xg-wang/whatwg-fetch": "^3.0.0",
"abortcontroller-polyfill": "^1.1.9",
"broccoli-concat": "^3.2.2",
"broccoli-merge-trees": "^2.0.0",
"broccoli-stew": "^1.4.2",
"broccoli-merge-trees": "^3.0.0",
"broccoli-stew": "^2.0.0",
"broccoli-templater": "^2.0.1",
"ember-cli-babel": "^6.8.2",
"node-fetch": "^2.0.0-alpha.9",
"yetch": "^0.0.1"
"node-fetch": "^2.0.0-alpha.9"
},
"devDependencies": {
"broccoli-asset-rev": "^2.4.5",
"broccoli-test-helper": "^1.2.0",
"chai": "^4.1.2",
"ember-cli": "~3.0.2",
"ember-cli-dependency-checker": "^2.0.0",
"ember-cli-dependency-checker": "^3.0.0",
"ember-cli-eslint": "^4.2.1",
"ember-cli-htmlbars": "^2.0.1",
"ember-cli-inject-live-reload": "^1.4.1",
"ember-cli-pretender": "^1.0.1",
"ember-cli-pretender": "^3.0.0",
"ember-cli-qunit": "^4.3.2",
"ember-cli-release": "^0.2.9",
"ember-cli-uglify": "^2.0.0",
Expand All @@ -49,7 +52,8 @@
"ember-try": "^0.2.23",
"eslint-plugin-ember": "^5.0.0",
"eslint-plugin-node": "^5.2.1",
"loader.js": "^4.2.3"
"loader.js": "^4.2.3",
"mocha": "^5.2.0"
},
"engines": {
"node": "6.* || 8.* || >= 10"
Expand Down
Loading

0 comments on commit ed52bfd

Please sign in to comment.