mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge pull request #10029 from meteor/isomorphic-fetch
Provide isomorphic implementation of WHATWG fetch() API.
This commit is contained in:
@@ -1,28 +1,28 @@
|
||||
const manifestUrl = '/app.manifest';
|
||||
|
||||
const appcacheTest = (name, cb) => {
|
||||
Tinytest.addAsync(`appcache - ${name}`, (test, next) => {
|
||||
HTTP.get(manifestUrl, (err, res) => {
|
||||
err ? test.fail(err) : cb(test, res);
|
||||
next();
|
||||
});
|
||||
function appcacheTest(name, cb) {
|
||||
Tinytest.addAsync(`appcache - ${name}`, test => {
|
||||
return fetch(manifestUrl).then(
|
||||
res => cb(test, res),
|
||||
err => test.fail(err)
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Verify that the code status of the HTTP response is "OK"
|
||||
appcacheTest('presence', (test, manifest) =>
|
||||
test.equal(manifest.statusCode, 200, 'manifest not served'));
|
||||
test.equal(manifest.status, 200, 'manifest not served'));
|
||||
|
||||
|
||||
// Verify the content-type HTTP header
|
||||
appcacheTest('content type', (test, manifest) =>
|
||||
test.equal(manifest.headers['content-type'], 'text/cache-manifest'));
|
||||
test.equal(manifest.headers.get('content-type'), 'text/cache-manifest'));
|
||||
|
||||
|
||||
// Verify that each section header is only set once.
|
||||
appcacheTest('sections uniqueness', (test, manifest) => {
|
||||
const { content } = manifest;
|
||||
appcacheTest('sections uniqueness', async (test, manifest) => {
|
||||
const content = await manifest.text();
|
||||
const mandatorySectionHeaders = ['CACHE:', 'NETWORK:', 'FALLBACK:'];
|
||||
const optionalSectionHeaders = ['SETTINGS'];
|
||||
const allSectionHeaders = [
|
||||
@@ -46,8 +46,8 @@ appcacheTest('sections uniqueness', (test, manifest) => {
|
||||
// regular expressions. Regular expressions matches malformed URIs but that's
|
||||
// not what we're trying to catch here (the user is free to add its own content
|
||||
// in the manifest -- even malformed).
|
||||
appcacheTest('sections validity', (test, manifest) => {
|
||||
const lines = manifest.content.split('\n');
|
||||
appcacheTest('sections validity', async (test, manifest) => {
|
||||
const lines = (await manifest.text()).split('\n');
|
||||
let i = 0;
|
||||
let currentRegex = null;
|
||||
let line = null;
|
||||
@@ -112,7 +112,7 @@ appcacheTest('sections validity', (test, manifest) => {
|
||||
// are present in the network section of the manifest. The `appcache` package
|
||||
// also automatically add the manifest (`app.manifest`) add the star symbol to
|
||||
// this list and therefore we also check the presence of these two elements.
|
||||
appcacheTest('network section content', (test, manifest) => {
|
||||
appcacheTest('network section content', async (test, manifest) => {
|
||||
const shouldBePresentInNetworkSection = [
|
||||
"/app.manifest",
|
||||
"/online/",
|
||||
@@ -120,7 +120,7 @@ appcacheTest('network section content', (test, manifest) => {
|
||||
"/largedata.json",
|
||||
"*"
|
||||
];
|
||||
const lines = manifest.content.split('\n');
|
||||
const lines = (await manifest.text()).split('\n');
|
||||
const startNetworkSection = lines.indexOf('NETWORK:');
|
||||
|
||||
// We search the end of the 'NETWORK:' section by looking at the beginning
|
||||
|
||||
@@ -15,7 +15,7 @@ Package.onUse(api => {
|
||||
Package.onTest(api => {
|
||||
api.use('tinytest');
|
||||
api.use('appcache');
|
||||
api.use('http', 'client');
|
||||
api.use('fetch');
|
||||
api.use('webapp', 'server');
|
||||
api.addFiles('appcache_tests-server.js', 'server');
|
||||
api.addFiles('appcache_tests-client.js', 'client');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Update the client when new client code is available",
|
||||
version: '1.4.0'
|
||||
version: '1.4.1'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
@@ -20,8 +20,6 @@ Package.onUse(function (api) {
|
||||
'mongo',
|
||||
], ['client', 'server']);
|
||||
|
||||
api.use(['http', 'random'], 'web.cordova');
|
||||
|
||||
api.addFiles('autoupdate_server.js', 'server');
|
||||
api.addFiles('autoupdate_client.js', 'web.browser');
|
||||
api.addFiles('autoupdate_cordova.js', 'web.cordova');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Meteor's latency-compensated distributed data client",
|
||||
version: '2.3.2',
|
||||
version: '2.3.3',
|
||||
documentation: null
|
||||
});
|
||||
|
||||
@@ -55,8 +55,6 @@ Package.onTest((api) => {
|
||||
'check'
|
||||
]);
|
||||
|
||||
api.use('http', 'client');
|
||||
|
||||
api.addFiles('test/stub_stream.js');
|
||||
api.addFiles('test/livedata_connection_tests.js');
|
||||
api.addFiles('test/livedata_tests.js');
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
var Module = module.constructor;
|
||||
var cache = require("./cache.js");
|
||||
var HTTP = require("meteor/http").HTTP;
|
||||
var meteorInstall = require("meteor/modules").meteorInstall;
|
||||
|
||||
// Call module.dynamicImport(id) to fetch a module and any/all of its
|
||||
@@ -120,21 +119,26 @@ exports.setSecretKey = function (key) {
|
||||
var fetchURL = require("./common.js").fetchURL;
|
||||
|
||||
function fetchMissing(missingTree) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
// If the hostname of the URL returned by Meteor.absoluteUrl differs
|
||||
// from location.host, then we'll be making a cross-origin request
|
||||
// here, but that's fine because the dynamic-import server sets
|
||||
// appropriate CORS headers to enable fetching dynamic modules from
|
||||
// any origin. Browsers that check CORS do so by sending an additional
|
||||
// preflight OPTIONS request, which may add latency to the first
|
||||
// dynamic import() request, so it's a good idea for ROOT_URL to match
|
||||
// location.host if possible, though not strictly necessary.
|
||||
HTTP.call("POST", Meteor.absoluteUrl(fetchURL), {
|
||||
query: secretKey ? "key=" + secretKey : void 0,
|
||||
data: missingTree
|
||||
}, function (error, result) {
|
||||
error ? reject(error) : resolve(result.data);
|
||||
});
|
||||
// If the hostname of the URL returned by Meteor.absoluteUrl differs
|
||||
// from location.host, then we'll be making a cross-origin request here,
|
||||
// but that's fine because the dynamic-import server sets appropriate
|
||||
// CORS headers to enable fetching dynamic modules from any
|
||||
// origin. Browsers that check CORS do so by sending an additional
|
||||
// preflight OPTIONS request, which may add latency to the first dynamic
|
||||
// import() request, so it's a good idea for ROOT_URL to match
|
||||
// location.host if possible, though not strictly necessary.
|
||||
var url = Meteor.absoluteUrl(fetchURL);
|
||||
|
||||
if (secretKey) {
|
||||
url += "key=" + secretKey;
|
||||
}
|
||||
|
||||
return fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(missingTree)
|
||||
}).then(function (res) {
|
||||
if (! res.ok) throw res;
|
||||
return res.json();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
name: "dynamic-import",
|
||||
version: "0.4.1",
|
||||
version: "0.5.0",
|
||||
summary: "Runtime support for Meteor 1.5 dynamic import(...) syntax",
|
||||
documentation: "README.md"
|
||||
});
|
||||
@@ -11,7 +11,7 @@ Package.onUse(function (api) {
|
||||
|
||||
api.use("modules");
|
||||
api.use("promise");
|
||||
api.use("http");
|
||||
api.use("fetch");
|
||||
api.use("modern-browsers");
|
||||
|
||||
api.mainModule("client.js", "client");
|
||||
|
||||
1
packages/fetch/.npm/package/.gitignore
vendored
Normal file
1
packages/fetch/.npm/package/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
7
packages/fetch/.npm/package/README
Normal file
7
packages/fetch/.npm/package/README
Normal file
@@ -0,0 +1,7 @@
|
||||
This directory and the files immediately inside it are automatically generated
|
||||
when you change this package's NPM dependencies. Commit the files in this
|
||||
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
|
||||
so that others run the same versions of sub-dependencies.
|
||||
|
||||
You should NOT check in the node_modules directory that Meteor automatically
|
||||
creates; if you are using git, the .gitignore file tells git to ignore it.
|
||||
15
packages/fetch/.npm/package/npm-shrinkwrap.json
generated
Normal file
15
packages/fetch/.npm/package/npm-shrinkwrap.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
|
||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
|
||||
}
|
||||
}
|
||||
}
|
||||
25
packages/fetch/README.md
Normal file
25
packages/fetch/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# fetch
|
||||
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/fetch) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/fetch)
|
||||
***
|
||||
|
||||
Isomorphic polyfill for the [WHATWG `fetch()` API](https://fetch.spec.whatwg.org/).
|
||||
|
||||
In [modern browsers](https://github.com/meteor/meteor/tree/release-1.7/packages/modern-browsers),
|
||||
the native `fetch()` API can be used without a polyfill. In other words,
|
||||
this package has almost no footprint in modern browsers. This package
|
||||
[calls `setMinimumBrowserVersions`](./server.js) to enforce minimum modern
|
||||
browser versions. However, `fetch()` has been supported natively by most
|
||||
browsers for long enough that these minimum versions are unlikely to make
|
||||
any difference in the `isModern` test, compared to more recent features
|
||||
like `async` functions.
|
||||
|
||||
In legacy browsers, the
|
||||
[`whatwg-fetch`](http://npmjs.org/package/whatwg-fetch) polyfill is
|
||||
used. Thanks to Meteor's modern/legacy system, this polyfill adds no weight
|
||||
to the modern JS bundle.
|
||||
|
||||
In Node, the [`node-fetch`](https://www.npmjs.com/package/node-fetch)
|
||||
polyfill is used. Note: unlike the client polyfills, the Node polyfill
|
||||
does not define the `fetch` function globally. However, any application or
|
||||
package that depends on the Meteor `fetch` package can refer to `fetch` as
|
||||
if it was a global function (or `import { fetch } from "meteor/fetch"`).
|
||||
6
packages/fetch/legacy.js
Normal file
6
packages/fetch/legacy.js
Normal file
@@ -0,0 +1,6 @@
|
||||
require("whatwg-fetch");
|
||||
|
||||
exports.fetch = global.fetch;
|
||||
exports.Headers = global.Headers;
|
||||
exports.Request = global.Request;
|
||||
exports.Response = global.Response;
|
||||
4
packages/fetch/modern.js
Normal file
4
packages/fetch/modern.js
Normal file
@@ -0,0 +1,4 @@
|
||||
exports.fetch = global.fetch;
|
||||
exports.Headers = global.Headers;
|
||||
exports.Request = global.Request;
|
||||
exports.Response = global.Response;
|
||||
33
packages/fetch/package.js
Normal file
33
packages/fetch/package.js
Normal file
@@ -0,0 +1,33 @@
|
||||
Package.describe({
|
||||
name: "fetch",
|
||||
version: "0.1.0",
|
||||
summary: "Isomorphic modern/legacy/Node polyfill for WHATWG fetch()",
|
||||
documentation: "README.md"
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
"node-fetch": "2.1.2",
|
||||
"whatwg-fetch": "2.0.4"
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.use("modules");
|
||||
api.use("modern-browsers");
|
||||
api.use("promise");
|
||||
|
||||
api.mainModule("modern.js", "web.browser");
|
||||
api.mainModule("legacy.js", "legacy");
|
||||
api.mainModule("server.js", "server");
|
||||
|
||||
// The other exports (Headers, Request, Response) can be imported
|
||||
// explicitly from the "meteor/fetch" package.
|
||||
api.export("fetch");
|
||||
});
|
||||
|
||||
Package.onTest(function(api) {
|
||||
api.use("ecmascript");
|
||||
api.use("tinytest");
|
||||
api.use("fetch");
|
||||
api.mainModule("tests/main.js");
|
||||
api.addAssets("tests/asset.json", ["client", "server"]);
|
||||
});
|
||||
21
packages/fetch/server.js
Normal file
21
packages/fetch/server.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
exports.fetch = fetch;
|
||||
exports.Headers = fetch.Headers;
|
||||
exports.Request = fetch.Request;
|
||||
exports.Response = fetch.Response;
|
||||
|
||||
const { setMinimumBrowserVersions } = require("meteor/modern-browsers");
|
||||
|
||||
// https://caniuse.com/#feat=fetch
|
||||
setMinimumBrowserVersions({
|
||||
chrome: 42,
|
||||
edge: 14,
|
||||
firefox: 39,
|
||||
mobile_safari: [10, 3],
|
||||
opera: 29,
|
||||
safari: [10, 1],
|
||||
phantomjs: Infinity,
|
||||
// https://github.com/Kilian/electron-to-chromium/blob/master/full-versions.js
|
||||
electron: [0, 25],
|
||||
}, module.id);
|
||||
5
packages/fetch/tests/asset.json
Normal file
5
packages/fetch/tests/asset.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"word": "oyez",
|
||||
"times": 3,
|
||||
"where": "SCOTUS"
|
||||
}
|
||||
17
packages/fetch/tests/main.js
Normal file
17
packages/fetch/tests/main.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Tinytest } from "meteor/tinytest";
|
||||
|
||||
Tinytest.add("fetch - sanity", function (test) {
|
||||
test.equal(typeof fetch, "function");
|
||||
});
|
||||
|
||||
Tinytest.addAsync("fetch - asset", function (test) {
|
||||
return fetch(
|
||||
Meteor.absoluteUrl("/packages/local-test_fetch/tests/asset.json")
|
||||
).then(res => {
|
||||
if (! res.ok) throw res;
|
||||
return res.json();
|
||||
}).then(json => {
|
||||
test.equal(json.word, "oyez");
|
||||
test.equal(json.times, 3);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { HTTP } from "meteor/http";
|
||||
import {
|
||||
classPrefix,
|
||||
methodNameStats,
|
||||
@@ -13,7 +12,7 @@ Meteor.startup(() => {
|
||||
import("./sunburst.js").then(s => main(s.Sunburst));
|
||||
});
|
||||
|
||||
function main(builder) {
|
||||
async function main(builder) {
|
||||
const { container, mask } = frameStage();
|
||||
|
||||
document.body.appendChild(mask);
|
||||
@@ -21,26 +20,23 @@ function main(builder) {
|
||||
|
||||
// Always match the protocol (http or https) and the domain:port of the
|
||||
// current page.
|
||||
const url = "//" + location.host + methodNameStats;
|
||||
const url = [
|
||||
"//" +
|
||||
location.host +
|
||||
methodNameStats +
|
||||
"?cacheBuster=" +
|
||||
Math.random().toString(36).slice(2)
|
||||
].join();
|
||||
|
||||
HTTP.call("GET", url, {
|
||||
params: {
|
||||
cacheBuster: Math.random().toString(36).slice(2)
|
||||
}
|
||||
}, (error, { data }) => {
|
||||
if (error) {
|
||||
console.error([
|
||||
packageName + ": Couldn't load stats for visualization.",
|
||||
"Are you using standard-minifier-js >= 2.1.0 as the minifier?",
|
||||
].join(" "));
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the JSON, which is `d3-hierarchy` digestible.
|
||||
if (data) {
|
||||
new builder({ container }).loadJson(data);
|
||||
}
|
||||
});
|
||||
try {
|
||||
const data = await fetch(url, { method: "GET" });
|
||||
new builder({ container }).loadJson(await data.json())
|
||||
} catch (err) {
|
||||
console.error([
|
||||
packageName + ": Couldn't load stats for visualization.",
|
||||
"Are you using standard-minifier-js >= 2.1.0 as the minifier?",
|
||||
].join(" "))
|
||||
}
|
||||
}
|
||||
|
||||
function frameStage() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package.describe({
|
||||
version: '1.2.1',
|
||||
version: '1.2.2',
|
||||
summary: 'Meteor bundle analysis and visualization.',
|
||||
documentation: 'README.md',
|
||||
});
|
||||
@@ -18,7 +18,7 @@ Package.onUse(function(api) {
|
||||
api.use([
|
||||
'ecmascript',
|
||||
'dynamic-import',
|
||||
'http',
|
||||
'fetch',
|
||||
'webapp',
|
||||
]);
|
||||
api.mainModule('server.js', 'server');
|
||||
|
||||
Reference in New Issue
Block a user