From f6ce506fbd6754bdde3cda85ed51af6b11d91ed7 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Tue, 28 Nov 2017 20:06:52 -0500 Subject: [PATCH 001/976] Revoke eagerness of overridden api.mainModule files. --- tools/isobuild/package-api.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/isobuild/package-api.js b/tools/isobuild/package-api.js index 1bd236889a5..e4cfd7d6155 100644 --- a/tools/isobuild/package-api.js +++ b/tools/isobuild/package-api.js @@ -363,6 +363,15 @@ _.extend(PackageAPI.prototype, { // It's not an error to call api.mainModule multiple times, but // the last call takes precedence over the earlier calls. oldMain.fileOptions.mainModule = false; + + if (! _.has(oldMain.fileOptions, "lazy")) { + // If the laziness of the old main module was not explicitly + // specified, then it would have been implicitly eager just + // because it was the main module. Since we are revoking its + // status as main module now, we should also explicitly revoke + // the eagerness that came with that status. + oldMain.fileOptions.lazy = true; + } } filesForArch.main = source; From ed28140071d0665a206ab7a48f7c90c35054eb82 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Tue, 28 Nov 2017 16:39:15 -0500 Subject: [PATCH 002/976] Support a new web.browser.legacy platform. --- packages/boilerplate-generator/generator.js | 18 ++++---- packages/webapp/webapp_server.js | 1 + tools/cli/commands.js | 9 ++-- tools/isobuild/compiler.js | 7 +++- tools/isobuild/isopack-cache.js | 46 +++++++++------------ tools/project-context.js | 5 ++- 6 files changed, 48 insertions(+), 38 deletions(-) diff --git a/packages/boilerplate-generator/generator.js b/packages/boilerplate-generator/generator.js index ecfc99880cb..e49bcd20981 100644 --- a/packages/boilerplate-generator/generator.js +++ b/packages/boilerplate-generator/generator.js @@ -22,7 +22,7 @@ let shouldWarnAboutToHTMLDeprecation = ! Meteor.isProduction; export class Boilerplate { constructor(arch, manifest, options = {}) { - const { headTemplate, closeTemplate } = _getTemplate(arch); + const { headTemplate, closeTemplate } = getTemplate(arch); this.headTemplate = headTemplate; this.closeTemplate = closeTemplate; this.baseData = null; @@ -154,12 +154,16 @@ export class Boilerplate { // Returns a template function that, when called, produces the boilerplate // html as a string. -const _getTemplate = arch => { - if (arch === 'web.browser') { +function getTemplate(arch) { + const prefix = arch.split(".", 2).join("."); + + if (prefix === "web.browser") { return WebBrowserTemplate; - } else if (arch === 'web.cordova') { + } + + if (prefix === "web.cordova") { return WebCordovaTemplate; - } else { - throw new Error('Unsupported arch: ' + arch); } -}; + + throw new Error("Unsupported arch: " + arch); +} diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 7a1d3b3d5f1..834734642e4 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -36,6 +36,7 @@ WebAppInternals.NpmModules = { } }; +// TODO Accommodate web.browser.legacy, too. WebApp.defaultArch = 'web.browser'; // XXX maps archs to manifests diff --git a/tools/cli/commands.js b/tools/cli/commands.js index b588c9e32af..550a26e77e3 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -382,9 +382,12 @@ function doRunCommand(options) { runLog.setRawLogs(true); } - let webArchs = ['web.browser']; - if (!_.isEmpty(runTargets) || options['mobile-server']) { - webArchs.push("web.cordova"); + let webArchs = projectContext.platformList.getWebArchs(); + if (! _.isEmpty(runTargets) || + options['mobile-server']) { + if (webArchs.indexOf("web.cordova") < 0) { + webArchs.push("web.cordova"); + } } let cordovaRunner; diff --git a/tools/isobuild/compiler.js b/tools/isobuild/compiler.js index a38bde4db2c..27b02d909f0 100644 --- a/tools/isobuild/compiler.js +++ b/tools/isobuild/compiler.js @@ -38,7 +38,12 @@ compiler.BUILT_BY = 'meteor/30'; // This is a list of all possible architectures that a build can target. (Client // is expanded into 'web.browser' and 'web.cordova') -compiler.ALL_ARCHES = [ "os", "web.browser", "web.cordova" ]; +compiler.ALL_ARCHES = [ + "os", + "web.browser", + "web.browser.legacy", + "web.cordova" +]; compiler.compile = Profile(function (packageSource, options) { return `compiler.compile(${ packageSource.name || 'the app' })`; diff --git a/tools/isobuild/isopack-cache.js b/tools/isobuild/isopack-cache.js index b6952151e7f..b4d52376067 100644 --- a/tools/isobuild/isopack-cache.js +++ b/tools/isobuild/isopack-cache.js @@ -151,20 +151,17 @@ export class IsopackCache { return true; } - arch = arch && archinfo.withoutSpecificOs(arch); - - return _.some(isopack.unibuilds, u => { - if (arch && ! archinfo.matches(u.arch, arch)) { - return false; - } + const unibuild = isopack.getUnibuildAtArch(arch); + if (! unibuild) { + return false; + } - return _.some(u.uses, use => { - return this.implies( - this._isopacks[use.package], - name, - arch, - ); - }); + return _.some(unibuild.uses, use => { + return this.implies( + this._isopacks[use.package], + name, + arch, + ); }); } @@ -178,20 +175,17 @@ export class IsopackCache { return true; } - arch = arch && archinfo.withoutSpecificOs(arch); - - return _.some(isopack.unibuilds, u => { - if (arch && ! archinfo.matches(u.arch, arch)) { - return false; - } + const unibuild = isopack.getUnibuildAtArch(arch); + if (! unibuild) { + return false; + } - return _.some(u.implies, imp => { - return this.implies( - this._isopacks[imp.package], - name, - arch, - ); - }); + return _.some(unibuild.implies, imp => { + return this.implies( + this._isopacks[imp.package], + name, + arch, + ); }); } diff --git a/tools/project-context.js b/tools/project-context.js index 095ca9a83d0..b7f98498288 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -1281,7 +1281,10 @@ _.extend(exports.PlatformList.prototype, { getWebArchs: function () { var self = this; - var archs = [ "web.browser" ]; + var archs = [ + "web.browser", + "web.browser.legacy", + ]; if (self.usesCordova()) { archs.push("web.cordova"); } From a94db9b6aed40b4084eeeb307cd6d5a1ab73152f Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Thu, 30 Nov 2017 18:27:59 -0500 Subject: [PATCH 003/976] Convert PackageAPI to an ECMAScript class. --- tools/isobuild/package-api.js | 97 +++++++++++++++++------------------ 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/tools/isobuild/package-api.js b/tools/isobuild/package-api.js index e4cfd7d6155..df720e67aaf 100644 --- a/tools/isobuild/package-api.js +++ b/tools/isobuild/package-api.js @@ -78,44 +78,41 @@ function forAllMatchingArchs (archs, f) { * @global * @summary Type of the API object passed into the `Package.onUse` function. */ -export function PackageAPI(options) { - var self = this; - assert.ok(self instanceof PackageAPI); - - options = options || {}; - - self.buildingIsopackets = !!options.buildingIsopackets; - - // source files used. - // It's a multi-level map structured as: - // arch -> sources|assets -> relPath -> {relPath, fileOptions} - self.files = {}; - - // symbols exported - self.exports = {}; - - // packages used and implied (keys are 'package', 'unordered', and - // 'weak'). an "implied" package is a package that will be used by a unibuild - // which uses us. - self.uses = {}; - self.implies = {}; - - _.each(compiler.ALL_ARCHES, function (arch) { - self.files[arch] = { - assets: [], - sources: [], - main: null, - }; - - self.exports[arch] = []; - self.uses[arch] = []; - self.implies[arch] = []; - }); +export class PackageAPI { + constructor(options) { + options = options || {}; - self.releaseRecords = []; -} + this.buildingIsopackets = !!options.buildingIsopackets; + + // source files used. + // It's a multi-level map structured as: + // arch -> sources|assets -> relPath -> {relPath, fileOptions} + this.files = {}; + + // symbols exported + this.exports = {}; + + // packages used and implied (keys are 'package', 'unordered', and + // 'weak'). an "implied" package is a package that will be used by a unibuild + // which uses us. + this.uses = {}; + this.implies = {}; + + _.each(compiler.ALL_ARCHES, arch => { + this.files[arch] = { + assets: [], + sources: [], + main: null, + }; + + this.exports[arch] = []; + this.uses[arch] = []; + this.implies[arch] = []; + }); + + this.releaseRecords = []; + } -_.extend(PackageAPI.prototype, { // Called when this package wants to make another package be // used. Can also take literal package objects, if you have // anonymous packages you want to use (eg, app packages) @@ -183,7 +180,7 @@ _.extend(PackageAPI.prototype, { * are loaded before your package.) You can use this option to break * circular dependencies. */ - use: function (names, arch, options) { + use(names, arch, options) { var self = this; // Support `api.use(package, {weak: true})` without arch. @@ -231,7 +228,7 @@ _.extend(PackageAPI.prototype, { }); }); } - }, + } // Called when this package wants packages using it to also use // another package. eg, for umbrella packages which want packages @@ -254,7 +251,7 @@ _.extend(PackageAPI.prototype, { * architectures by passing in an array, for example `['web.cordova', * 'os.linux']`. */ - imply: function (names, arch) { + imply(names, arch) { var self = this; // We currently disallow build plugins in @@ -306,7 +303,7 @@ _.extend(PackageAPI.prototype, { }); }); } - }, + } // Top-level call to add a source file to a package. It will // be processed according to its extension (eg, *.coffee @@ -332,7 +329,7 @@ _.extend(PackageAPI.prototype, { * resulting file in a closure. Has the same effect as putting a file into the * `client/compatibility` directory in an app. */ - addFiles: function (paths, arch, fileOptions) { + addFiles(paths, arch, fileOptions) { if (fileOptions && fileOptions.isAsset) { // XXX it would be great to print a warning here, see the issue: // https://github.com/meteor/meteor/issues/5495 @@ -343,7 +340,7 @@ _.extend(PackageAPI.prototype, { // Watch out - we rely on the levels of stack traces inside this // function so don't wrap it in another function without changing that logic this._addFiles("sources", paths, arch, fileOptions); - }, + } mainModule(path, arch, fileOptions = {}) { arch = toArchArray(arch); @@ -379,7 +376,7 @@ _.extend(PackageAPI.prototype, { this._forbidExportWithLazyMain(a); }); - }, + } _forbidExportWithLazyMain(arch) { const filesForArch = this.files[arch]; @@ -391,7 +388,7 @@ _.extend(PackageAPI.prototype, { "export symbols and have a lazy main module" ); } - }, + } /** * @memberOf PackageAPI @@ -418,7 +415,7 @@ _.extend(PackageAPI.prototype, { // Watch out - we rely on the levels of stack traces inside this // function so don't wrap it in another function without changing that logic this._addFiles("assets", paths, arch); - }, + } /** * Internal method used by addFiles and addAssets. @@ -485,7 +482,7 @@ _.extend(PackageAPI.prototype, { for (var i = 0; i < errors.length; ++i) { buildmessage.error(errors[i], { useMyCaller: 1 }); } - }, + } // Use this release to resolve unclear dependencies for this package. If // you don't fill in dependencies for some of your implies/uses, we will @@ -511,7 +508,7 @@ _.extend(PackageAPI.prototype, { * track@version. Just 'version' (e.g. `"0.9.0"`) is sufficient if using the * default release track `METEOR`. Can be an array of specifications. */ - versionsFrom: function (releases) { + versionsFrom(releases) { var self = this; // Packages in isopackets really ought to be in the core release, by @@ -554,7 +551,7 @@ _.extend(PackageAPI.prototype, { self.releaseRecords.push(releaseRecord); } } - }, + } // Export symbols from this package. // @@ -588,7 +585,7 @@ _.extend(PackageAPI.prototype, { * @param {Boolean} exportOptions.testOnly If true, this symbol will only be * exported when running tests for this package. */ - export: function (symbols, arch, options) { + "export"(symbols, arch, options) { var self = this; // Support `api.export("FooTest", {testOnly: true})` without @@ -621,7 +618,7 @@ _.extend(PackageAPI.prototype, { }); }); } -}); +} // XXX COMPAT WITH 0.8.x PackageAPI.prototype.add_files = PackageAPI.prototype.addFiles; From 7fceffbe00ee09a0ecab52ccf7ca40f0f09076d1 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Thu, 30 Nov 2017 18:41:59 -0500 Subject: [PATCH 004/976] Avoid duplicating calls in forAllMatchingArchs. --- tools/isobuild/package-api.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/isobuild/package-api.js b/tools/isobuild/package-api.js index df720e67aaf..c46dfde9d17 100644 --- a/tools/isobuild/package-api.js +++ b/tools/isobuild/package-api.js @@ -61,10 +61,11 @@ function mapWhereToArch (where) { // Iterates over the list of target archs and calls f(arch) for all archs // that match an element of self.allarchs. function forAllMatchingArchs (archs, f) { - _.each(archs, function (arch) { - _.each(compiler.ALL_ARCHES, function (matchArch) { + compiler.ALL_ARCHES.forEach(matchArch => { + archs.some(arch => { if (archinfo.matches(matchArch, arch)) { f(matchArch); + return true; } }); }); From 2add13e2d40b5b2341491de7d6071455437c974f Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 1 Dec 2017 10:51:41 -0500 Subject: [PATCH 005/976] Introduce package for specifying minimum "modern" browser versions. --- packages/modern-browsers/README.md | 7 ++ packages/modern-browsers/TODO.md | 46 +++++++++ packages/modern-browsers/modern-tests.js | 27 +++++ packages/modern-browsers/modern.js | 121 +++++++++++++++++++++++ packages/modern-browsers/package.js | 19 ++++ 5 files changed, 220 insertions(+) create mode 100644 packages/modern-browsers/README.md create mode 100644 packages/modern-browsers/TODO.md create mode 100644 packages/modern-browsers/modern-tests.js create mode 100644 packages/modern-browsers/modern.js create mode 100644 packages/modern-browsers/package.js diff --git a/packages/modern-browsers/README.md b/packages/modern-browsers/README.md new file mode 100644 index 00000000000..11d1d9b56b1 --- /dev/null +++ b/packages/modern-browsers/README.md @@ -0,0 +1,7 @@ +# modern +[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/modern-browsers) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/modern-browsers) +*** + +API for defining the boundary between modern and legacy JavaScript +clients, with runtime support for choosing between the `web.browser` +(modern) bundle architecture and `web.browser.legacy`. diff --git a/packages/modern-browsers/TODO.md b/packages/modern-browsers/TODO.md new file mode 100644 index 00000000000..2161ea6a15d --- /dev/null +++ b/packages/modern-browsers/TODO.md @@ -0,0 +1,46 @@ +## Remaining work + +- [x] Come up with a system for constraining minimum browser versions. + +- [x] Research minimum versions for key ECMAScript features. + +- [ ] Use the minimum versions in `webapp` to determine JS bundle. + +- [ ] Import different `core-js` polyfills in `ecmascript-runtime-client` + depending on modern/legacy classification. + +- [ ] Really vet the set of imported `core-js` polyfills based on known + minimum versions imposed by `setMinimumBrowserVersions`. + +- [ ] Create an `isobuild:web-browser-legacy` pseudopackage. + +- [ ] Add tests to the `modules` test app. + +- [ ] Expose `Meteor.isModern` on both client and server. + +- [ ] Make sure in-browser tests run with both `web.browser` and + `web.browser.legacy`. + +- [ ] Use `web.browser.legacy` to handle `es5-shim` instead. + +- [ ] Use `web.browser.legacy` to handle SockJS instead? + +- [ ] Load `SockJS` using dynamic `import()` if necessary in modern + `web.browser` clients. + +- [ ] Use different plugins in babel-compiler for `web.browser.legacy`. + +- [ ] Fix dynamic module source map URLs (prepend `/__arch`). + +- [ ] Fix tests failing because of changes to static resource URLs. + +- [ ] In development, save time by only rebuilding `web.browser` (modern)? + +- [ ] Try adding a `web.worker` platform and see if it works as expected. + +- [ ] Update `History.md` to reflect all these changes. + +- [ ] Write a blog post about the new modern/legacy system. + +- [ ] Update `compiler.BUILT_BY` and `LINKER_CACHE_SALT` to force + recompilation and relinking. diff --git a/packages/modern-browsers/modern-tests.js b/packages/modern-browsers/modern-tests.js new file mode 100644 index 00000000000..581ce5b5401 --- /dev/null +++ b/packages/modern-browsers/modern-tests.js @@ -0,0 +1,27 @@ +import { Tinytest } from "meteor/tinytest"; +import { isModern } from "meteor/modern-browsers"; + +Tinytest.add('modern-browsers - versions - basic', function (test) { + test.isTrue(isModern({ + name: "chrome", + major: 50, + })); + + test.isFalse(isModern({ + name: "firefox", + major: 44, + })); + + test.isTrue(isModern({ + name: "safari", + major: 10, + minor: 2, + })); + + test.isFalse(isModern({ + name: "safari", + major: 9, + minor: 5, + patch: 2, + })); +}); diff --git a/packages/modern-browsers/modern.js b/packages/modern-browsers/modern.js new file mode 100644 index 00000000000..e04ded67d21 --- /dev/null +++ b/packages/modern-browsers/modern.js @@ -0,0 +1,121 @@ +const minimumVersions = Object.create(null); +const hasOwn = Object.prototype.hasOwnProperty; + +// TODO Should it be possible for callers to setMinimumBrowserVersions to +// forbid any version of a particular browser? + +// Given a { name, major, minor, patch } object like the one provided by +// webapp via request.browser, return true if that browser qualifies as +// "modern" according to all requested version constraints. +function isModern(browser) { + return browser && + typeof browser.name === "string" && + hasOwn.call(minimumVersions, browser.name) && + greaterThanOrEqualTo([ + ~~browser.major, + ~~browser.minor, + ~~browser.patch, + ], minimumVersions[browser.name]); +} + +// Any package that depends on the modern-browsers package can call this +// function to communicate its expectations for the minimum browser +// versions that qualify as "modern." The final decision between +// web.browser.legacy and web.browser will be based on the maximum of all +// requested minimum versions for each browser. +function setMinimumBrowserVersions(versions) { + Object.keys(versions).forEach(browserName => { + const newMinVersion = versions[browserName]; + if (hasOwn.call(minimumVersions, browserName)) { + if (greaterThan(newMinVersion, minimumVersions[browserName])) { + minimumVersions[browserName] = copy(newMinVersion); + } + } else { + minimumVersions[browserName] = copy(newMinVersion); + } + }); +} + +Object.assign(exports, { + isModern, + setMinimumBrowserVersions, +}); + +// For making defensive copies of [major, minor, ...] version arrays, so +// they don't change unexpectedly. +function copy(version) { + if (typeof version === "number") { + return version; + } + + if (Array.isArray(version)) { + return version.map(copy); + } + + return version; +} + +function greaterThanOrEqualTo(a, b) { + return ! greaterThan(b, a); +} + +function greaterThan(a, b) { + const as = (typeof a === "number") ? [a] : a; + const bs = (typeof b === "number") ? [b] : b; + const maxLen = Math.max(as.length, bs.length); + + for (let i = 0; i < maxLen; ++i) { + a = (i < as.length) ? as[i] : 0; + b = (i < bs.length) ? bs[i] : 0; + + if (a > b) { + return true; + } + + if (a < b) { + return false; + } + } + + return false; +} + +// ECMAScript 2015 Classes +setMinimumBrowserVersions({ + chrome: 49, + edge: 12, + firefox: 45, + mobile_safari: [9, 2], + opera: 36, + safari: 9, +}); + +// ECMAScript 2015 Generator Functions +setMinimumBrowserVersions({ + chrome: 39, + edge: 13, + firefox: 26, + mobile_safari: 10, + opera: 26, + safari: 10, +}); + +// ECMAScript 2015 Template Literals +setMinimumBrowserVersions({ + chrome: 41, + edge: 13, + firefox: 34, + mobile_safari: [9, 2], + opera: 29, + safari: [9, 1], +}); + +// ECMAScript 2015 Symbols +setMinimumBrowserVersions({ + chrome: 38, + edge: 12, + firefox: 36, + mobile_safari: 9, + opera: 25, + safari: 9, +}); diff --git a/packages/modern-browsers/package.js b/packages/modern-browsers/package.js new file mode 100644 index 00000000000..102b1429ac4 --- /dev/null +++ b/packages/modern-browsers/package.js @@ -0,0 +1,19 @@ +Package.describe({ + name: "modern-browsers", + version: "0.1.0", + summary: "API for defining the boundary between modern and legacy " + + "JavaScript clients", + documentation: "README.md" +}); + +Package.onUse(function(api) { + api.use("modules"); + api.mainModule("modern.js", "server"); +}); + +Package.onTest(function(api) { + api.use("ecmascript"); + api.use("tinytest"); + api.use("modern-browsers"); + api.mainModule("modern-tests.js", "server"); +}); From e18ef9eb0ee1547acf52093c6e184b73b30b2ae6 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 1 Dec 2017 12:07:43 -0500 Subject: [PATCH 006/976] Use isModern in webapp to determine which boilerplate to generate. Surely it can't be this easy, can it? --- packages/modern-browsers/TODO.md | 2 +- packages/webapp/package.js | 10 ++++++++-- packages/webapp/webapp_server.js | 16 +++++++++++----- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/modern-browsers/TODO.md b/packages/modern-browsers/TODO.md index 2161ea6a15d..19a461aabf2 100644 --- a/packages/modern-browsers/TODO.md +++ b/packages/modern-browsers/TODO.md @@ -4,7 +4,7 @@ - [x] Research minimum versions for key ECMAScript features. -- [ ] Use the minimum versions in `webapp` to determine JS bundle. +- [x] Use the minimum versions in `webapp` to determine JS bundle. - [ ] Import different `core-js` polyfills in `ecmascript-runtime-client` depending on modern/legacy classification. diff --git a/packages/webapp/package.js b/packages/webapp/package.js index 31fab07ce52..4de8ecc3c54 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -27,8 +27,14 @@ Cordova.depends({ Package.onUse(function (api) { api.use('ecmascript'); - api.use(['logging', 'underscore', 'routepolicy', 'boilerplate-generator', - 'webapp-hashing'], 'server'); + api.use([ + 'logging', + 'underscore', + 'routepolicy', + 'modern-browsers', + 'boilerplate-generator', + 'webapp-hashing' + ], 'server'); // At response serving time, webapp uses browser-policy if it is loaded. If // browser-policy is loaded, then it must be loaded after webapp diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 834734642e4..62afb39cc45 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -14,6 +14,7 @@ import query from "qs-middleware"; import parseRequest from "parseurl"; import basicAuth from "basic-auth-connect"; import { lookup as lookupUserAgent } from "useragent"; +import { isModern } from "meteor/modern-browsers"; import send from "send"; import { removeExistingSocketFile, @@ -36,8 +37,9 @@ WebAppInternals.NpmModules = { } }; -// TODO Accommodate web.browser.legacy, too. -WebApp.defaultArch = 'web.browser'; +// Though we might prefer to use web.browser (modern) as the default +// architecture, safety requires a more compatible defaultArch. +WebApp.defaultArch = 'web.browser.legacy'; // XXX maps archs to manifests WebApp.clientPrograms = {}; @@ -771,10 +773,14 @@ function runWebAppServer() { var archKey = pathname.split('/')[1]; var archKeyCleaned = 'web.' + archKey.replace(/^__/, ''); - if (!/^__/.test(archKey) || !_.has(archPath, archKeyCleaned)) { - archKey = WebApp.defaultArch; - } else { + if (/^__/.test(archKey) && + _.has(archPath, archKeyCleaned)) { archKey = archKeyCleaned; + } else if (isModern(request.browser) && + _.has(WebApp.clientPrograms, "web.browser")) { + archKey = "web.browser"; + } else { + archKey = WebApp.defaultArch; } return getBoilerplateAsync( From 3a17a032c74ad252cef75cddd1155ec8e84a40af Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Tue, 28 Nov 2017 20:06:33 -0500 Subject: [PATCH 007/976] Leverage web.browser.legacy for ecmascript-runtime-client. --- .../{runtime.js => legacy.js} | 2 ++ packages/ecmascript-runtime-client/modern.js | 2 ++ packages/ecmascript-runtime-client/package.js | 16 ++++++++++++---- packages/modern-browsers/TODO.md | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) rename packages/ecmascript-runtime-client/{runtime.js => legacy.js} (98%) create mode 100644 packages/ecmascript-runtime-client/modern.js diff --git a/packages/ecmascript-runtime-client/runtime.js b/packages/ecmascript-runtime-client/legacy.js similarity index 98% rename from packages/ecmascript-runtime-client/runtime.js rename to packages/ecmascript-runtime-client/legacy.js index b4c98a5822f..1c9be0eee72 100644 --- a/packages/ecmascript-runtime-client/runtime.js +++ b/packages/ecmascript-runtime-client/legacy.js @@ -55,3 +55,5 @@ require('core-js/modules/es6.number.parse-int'); // Typed Arrays require('core-js/modules/es6.typed.uint8-array'); require('core-js/modules/es6.typed.uint32-array'); + +require("./modern.js"); diff --git a/packages/ecmascript-runtime-client/modern.js b/packages/ecmascript-runtime-client/modern.js new file mode 100644 index 00000000000..82a8ced8fe4 --- /dev/null +++ b/packages/ecmascript-runtime-client/modern.js @@ -0,0 +1,2 @@ +require("core-js/modules/es7.string.pad-start"); +require("core-js/modules/es7.string.pad-end"); diff --git a/packages/ecmascript-runtime-client/package.js b/packages/ecmascript-runtime-client/package.js index eea3a6adb97..c9e1dbbed54 100644 --- a/packages/ecmascript-runtime-client/package.js +++ b/packages/ecmascript-runtime-client/package.js @@ -7,10 +7,18 @@ Package.describe({ }); Package.onUse(function(api) { + // If the es5-shim package is installed, make sure it loads before + // ecmascript-runtime-server, since the runtime uses some ES5 APIs like + // Object.defineProperties that are buggy in older browsers. + api.use("es5-shim", { weak: true }); + api.use("modules", "client"); api.use("promise", "client"); - api.mainModule("runtime.js", "client"); - api.export("Symbol", "client"); - api.export("Map", "client"); - api.export("Set", "client"); + + api.mainModule("modern.js", "client"); + + api.mainModule("legacy.js", "web.browser.legacy"); + api.export("Symbol", "web.browser.legacy"); + api.export("Map", "web.browser.legacy"); + api.export("Set", "web.browser.legacy"); }); diff --git a/packages/modern-browsers/TODO.md b/packages/modern-browsers/TODO.md index 19a461aabf2..7ebce950ce4 100644 --- a/packages/modern-browsers/TODO.md +++ b/packages/modern-browsers/TODO.md @@ -6,7 +6,7 @@ - [x] Use the minimum versions in `webapp` to determine JS bundle. -- [ ] Import different `core-js` polyfills in `ecmascript-runtime-client` +- [x] Import different `core-js` polyfills in `ecmascript-runtime-client` depending on modern/legacy classification. - [ ] Really vet the set of imported `core-js` polyfills based on known From 911dae81bbb34fb8ac6f1be8d97477b647b31907 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 1 Dec 2017 12:57:49 -0500 Subject: [PATCH 008/976] Expose `Meteor.isModern` on both client and server. --- packages/meteor/browser_environment_test.js | 1 + packages/meteor/client_environment.js | 28 +++++++++++++++++---- packages/meteor/server_environment.js | 5 +++- packages/modern-browsers/TODO.md | 2 +- packages/webapp/webapp_server.js | 14 ++++++++++- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/packages/meteor/browser_environment_test.js b/packages/meteor/browser_environment_test.js index cd31ec479de..23ca3592b1b 100644 --- a/packages/meteor/browser_environment_test.js +++ b/packages/meteor/browser_environment_test.js @@ -2,4 +2,5 @@ Tinytest.add("environment - browser basics", function (test) { test.isTrue(Meteor.isClient); test.isFalse(Meteor.isServer); test.isFalse(Meteor.isCordova); + test.equal(typeof Meteor.isModern, "boolean"); }); diff --git a/packages/meteor/client_environment.js b/packages/meteor/client_environment.js index 57a5fd1c7ab..7f8b035d1a1 100644 --- a/packages/meteor/client_environment.js +++ b/packages/meteor/client_environment.js @@ -1,4 +1,5 @@ -meteorEnv = __meteor_runtime_config__.meteorEnv; +var config = __meteor_runtime_config__; +meteorEnv = config.meteorEnv; /** * @summary The Meteor namespace @@ -36,15 +37,32 @@ Meteor = { * @type {Boolean} */ isServer: false, - isCordova: false + + /** + * @summary Boolean variable. True if running in Cordova environment. + * @locus Anywhere + * @static + * @type {Boolean} + */ + isCordova: false, + + /** + * @summary Boolean variable. True if running in a "modern" JS + * environment, as determined by the `modern` package. + * @locus Anywhere + * @static + * @type {Boolean} + */ + isModern: config.isModern }; -if (typeof __meteor_runtime_config__ === 'object' && - __meteor_runtime_config__.PUBLIC_SETTINGS) { +if (config.PUBLIC_SETTINGS) { /** * @summary `Meteor.settings` contains deployment-specific configuration options. You can initialize settings by passing the `--settings` option (which takes the name of a file containing JSON data) to `meteor run` or `meteor deploy`. When running your server directly (e.g. from a bundle), you instead specify settings by putting the JSON directly into the `METEOR_SETTINGS` environment variable. If the settings object contains a key named `public`, then `Meteor.settings.public` will be available on the client as well as the server. All other properties of `Meteor.settings` are only defined on the server. You can rely on `Meteor.settings` and `Meteor.settings.public` being defined objects (not undefined) on both client and server even if there are no settings specified. Changes to `Meteor.settings.public` at runtime will be picked up by new client connections. * @locus Anywhere * @type {Object} */ - Meteor.settings = { 'public': __meteor_runtime_config__.PUBLIC_SETTINGS }; + Meteor.settings = { + "public": config.PUBLIC_SETTINGS + }; } diff --git a/packages/meteor/server_environment.js b/packages/meteor/server_environment.js index 362e3595a97..61f169c3130 100644 --- a/packages/meteor/server_environment.js +++ b/packages/meteor/server_environment.js @@ -12,7 +12,10 @@ Meteor = { isDevelopment: meteorEnv.NODE_ENV !== "production", isClient: false, isServer: true, - isCordova: false + isCordova: false, + // Server code runs in Node 8+, which is decidedly "modern" by any + // reasonable definition. + isModern: true }; Meteor.settings = {}; diff --git a/packages/modern-browsers/TODO.md b/packages/modern-browsers/TODO.md index 7ebce950ce4..42c5a311551 100644 --- a/packages/modern-browsers/TODO.md +++ b/packages/modern-browsers/TODO.md @@ -16,7 +16,7 @@ - [ ] Add tests to the `modules` test app. -- [ ] Expose `Meteor.isModern` on both client and server. +- [x] Expose `Meteor.isModern` on both client and server. - [ ] Make sure in-browser tests run with both `web.browser` and `web.browser.legacy`. diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 62afb39cc45..9ecde158e3f 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -606,7 +606,19 @@ function runWebAppServer() { ROOT_URL: process.env.MOBILE_ROOT_URL || Meteor.absoluteUrl() } - } + }, + + "web.browser": { + runtimeConfigOverrides: { + isModern: true, + } + }, + + "web.browser.legacy": { + runtimeConfigOverrides: { + isModern: false, + } + }, }; syncQueue.runTask(function() { From 6c5c386c4aa765945ce38efbe8e43e02dfdd8c02 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 1 Dec 2017 13:35:32 -0500 Subject: [PATCH 009/976] Allow serving CSS for all client architectures. --- packages/webapp/webapp_server.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 9ecde158e3f..ebcfc7dd6e9 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -622,24 +622,25 @@ function runWebAppServer() { }; syncQueue.runTask(function() { + const allCss = []; + _.each(WebApp.clientPrograms, function (program, archName) { boilerplateByArch[archName] = WebAppInternals.generateBoilerplateInstance( - archName, program.manifest, - defaultOptionsForArch[archName]); + archName, + program.manifest, + defaultOptionsForArch[archName], + ); + + const cssFiles = boilerplateByArch[archName].baseData.css; + cssFiles.forEach(file => allCss.push({ + url: bundledJsCssUrlRewriteHook(file.url), + })); }); // Clear the memoized boilerplate cache. memoizedBoilerplate = {}; - // Configure CSS injection for the default arch - // XXX implement the CSS injection for all archs? - var cssFiles = boilerplateByArch[WebApp.defaultArch].baseData.css; - // Rewrite all CSS files (which are written directly to