diff --git a/History.md b/History.md index 596df3402c..69d7f6154f 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,106 @@ this is to prevent duplicate name error on reloads. Initial data is now properly serialized. +## v1.5, 2017-05-30 + +* The `meteor-base` package implies a new `dynamic-import` package, which + provides runtime support for [the proposed ECMAScript dynamic + `import(...)` syntax](https://github.com/tc39/proposal-dynamic-import), + enabling asynchronous module fetching or "code splitting." If your app + does not use the `meteor-base` package, you can use the package by + simply running `meteor add dynamic-import`. See this [blog + post](https://blog.meteor.com/meteor-1-5-react-loadable-f029a320e59c) + and [PR #8327](https://github.com/meteor/meteor/pull/8327) for more + information about how dynamic `import(...)` works in Meteor, and how to + use it in your applications. + +* The `ecmascript-runtime` package, which provides polyfills for various + new ECMAScript runtime APIs and language features, has been split into + `ecmascript-runtime-client` and `ecmascript-runtime-server`, to reflect + the different needs of browsers versus Node 4. The client runtime now + relies on the `core-js` library found in the `node_modules` directory of + the application, rather than a private duplicate installed via + `Npm.depends`. This is unlikely to be a disruptive change for most + developers, since the `babel-runtime` npm package is expected to be + installed, and `core-js` is a dependency of `babel-runtime`, so + `node_modules/core-js` should already be present. If that's not the + case, just run `meteor npm install --save core-js` to install it. + +* The `npm` npm package has been upgraded to version 4.6.1. + +* The `meteor-babel` npm package has been upgraded to version 0.21.4, + enabling the latest Reify compiler and the transform-class-properties + plugin, among other improvements. + +* The `reify` npm package has been upgraded to version 0.11.21, fixing + [issue #8595](https://github.com/meteor/meteor/issues/8595) and + improving compilation and runtime performance. + +> Note: With this version of Reify, `import` declarations are compiled to + `module.watch(require(id), ...)` instead of `module.importSync(id, ...)` + or the older `module.import(id, ...)`. The behavior of the compiled code + should be the same as before, but the details seemed different enough to + warrant a note. + +* The `install` npm package has been upgraded to version 0.10.1. + +* The `meteor-promise` npm package has been upgraded to version 0.8.4. + +* The `uglify-js` npm package has been upgraded to version 3.0.13, fixing + [#8704](https://github.com/meteor/meteor/issues/8704). + +* If you're using the `standard-minifier-js` Meteor package, as most + Meteor developers do, it will now produce a detailed analysis of package + and module sizes within your production `.js` bundle whenever you run + `meteor build` or `meteor run --production`. These data are served by + the application web server at the same URL as the minified `.js` bundle, + except with a `.stats.json` file extension instead of `.js`. If you're + using a different minifier plugin, and would like to support similar + functionality, refer to + [these](https://github.com/meteor/meteor/pull/8327/commits/084801237a8c288d99ec82b0fbc1c76bdf1aab16) + [commits](https://github.com/meteor/meteor/pull/8327/commits/1c8bc7353e9a8d526880634a58c506b423c4a55e) + for inspiration. + +* To visualize the bundle size data produced by `standard-minifier-js`, + run `meteor add bundle-visualizer` and then start your development + server in production mode with `meteor run --production`. Be sure to + remove the `bundle-visualizer` package before actually deploying your + app, or the visualization will be displayed to your users. + +* If you've been developing an app with multiple versions of Meteor, or + testing with beta versions, and you haven't recently run `meteor reset`, + your `.meteor/local/bundler-cache` directory may have become quite + large. This is just a friendly reminder that this directory is perfectly + safe to delete, and Meteor will repopulate it with only the most recent + cached bundles. + +* Apps created with `meteor create --bare` now use the `static-html` + package for processing `.html` files instead of `blaze-html-templates`, + to avoid large unnecessary dependencies like the `jquery` package. + +* Babel plugins now receive file paths without leading `/` characters, + which should prevent confusion about whether the path should be treated + as absolute. [PR #8610](https://github.com/meteor/meteor/pull/8610) + +* It is now possible to override the Cordova iOS and/or Android + compatibility version by setting the `METEOR_CORDOVA_COMPAT_VERSION_IOS` + and/or `METEOR_CORDOVA_COMPAT_VERSION_ANDROID` environment variables. + [PR #8581](https://github.com/meteor/meteor/pull/8581) + +* Modules in `node_modules` directories will no longer automatically have + access to the `Buffer` polyfill on the client, since that polyfill + contributed more than 22KB of minified JavaScript to the client bundle, + and was rarely used. If you really need the Buffer API on the client, + you should now obtain it explicitly with `require("buffer").Buffer`. + [Issue #8645](https://github.com/meteor/meteor/issues/8645). + +* Packages in `node_modules` directories are now considered non-portable + (and thus may be automatically rebuilt for the current architecture), if + their `package.json` files contain any of the following install hooks: + `install`, `preinstall`, or `postinstall`. Previously, a package was + considered non-portable only if it contained any `.node` binary modules. + [Issue #8225](https://github.com/meteor/meteor/issues/8225) + ## v1.4.4.3, 2017-05-22 * Node has been upgraded to version 4.8.3. @@ -310,6 +410,8 @@ fixing [#8021](https://github.com/meteor/meteor/issues/8021) and [#7662](https://github.com/meteor/meteor/issues/7662). +* The `reify` npm package has been upgraded to 0.4.7. + * Added support for frame-ancestors CSP option in browser-policy. [#7970](https://github.com/meteor/meteor/pull/7970) diff --git a/meteor b/meteor index 500777dcdd..3eddddedb6 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=4.7.27 +BUNDLE_VERSION=4.8.17 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json index f297b0adf2..58b7e288b5 100644 --- a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json +++ b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json @@ -81,9 +81,9 @@ "from": "babel-helper-is-void-0@>=0.0.1 <0.0.2" }, "babel-helper-mark-eval-scopes": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.0.3.tgz", - "from": "babel-helper-mark-eval-scopes@>=0.0.3 <0.0.4" + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.1.1.tgz", + "from": "babel-helper-mark-eval-scopes@>=0.1.1 <0.2.0" }, "babel-helper-optimise-call-expression": { "version": "6.24.1", @@ -96,9 +96,9 @@ "from": "babel-helper-regex@>=6.24.1 <7.0.0" }, "babel-helper-remove-or-void": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.0.1.tgz", - "from": "babel-helper-remove-or-void@>=0.0.1 <0.0.2" + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.1.1.tgz", + "from": "babel-helper-remove-or-void@>=0.1.1 <0.2.0" }, "babel-helper-replace-supers": { "version": "6.24.1", @@ -131,15 +131,15 @@ "from": "babel-plugin-minify-constant-folding@>=0.0.4 <0.0.5", "dependencies": { "jsesc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.0.tgz", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", "from": "jsesc@>=2.4.0 <3.0.0" } } }, "babel-plugin-minify-dead-code-elimination": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.1.4.tgz", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.1.6.tgz", "from": "babel-plugin-minify-dead-code-elimination@>=0.1.3 <0.2.0" }, "babel-plugin-minify-flip-comparisons": { @@ -199,6 +199,11 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", "from": "babel-plugin-syntax-async-generators@>=6.13.0 <7.0.0" }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "from": "babel-plugin-syntax-class-properties@>=6.8.0 <7.0.0" + }, "babel-plugin-syntax-dynamic-import": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", @@ -224,6 +229,11 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", "from": "babel-plugin-syntax-trailing-function-commas@>=6.22.0 <7.0.0" }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "from": "babel-plugin-transform-class-properties@>=6.24.1 <7.0.0" + }, "babel-plugin-transform-es2015-arrow-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", @@ -270,9 +280,9 @@ "from": "babel-plugin-transform-es2015-modules-commonjs@>=6.22.0 <7.0.0" }, "babel-plugin-transform-es2015-modules-reify": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-reify/-/babel-plugin-transform-es2015-modules-reify-0.7.0.tgz", - "from": "babel-plugin-transform-es2015-modules-reify@>=0.7.0 <0.8.0" + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-reify/-/babel-plugin-transform-es2015-modules-reify-0.11.0.tgz", + "from": "babel-plugin-transform-es2015-modules-reify@>=0.11.0 <0.12.0" }, "babel-plugin-transform-es2015-object-super": { "version": "6.24.1", @@ -330,18 +340,18 @@ "from": "babel-plugin-transform-inline-consecutive-adds@>=0.0.2 <0.0.3" }, "babel-plugin-transform-member-expression-literals": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.8.1.tgz", + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.8.3.tgz", "from": "babel-plugin-transform-member-expression-literals@>=6.8.1 <7.0.0" }, "babel-plugin-transform-merge-sibling-variables": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.8.2.tgz", + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.8.4.tgz", "from": "babel-plugin-transform-merge-sibling-variables@>=6.8.2 <7.0.0" }, "babel-plugin-transform-minify-booleans": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.8.0.tgz", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.8.2.tgz", "from": "babel-plugin-transform-minify-booleans@>=6.8.0 <7.0.0" }, "babel-plugin-transform-object-rest-spread": { @@ -350,8 +360,8 @@ "from": "babel-plugin-transform-object-rest-spread@>=6.22.0 <7.0.0" }, "babel-plugin-transform-property-literals": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.8.1.tgz", + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.8.3.tgz", "from": "babel-plugin-transform-property-literals@>=6.8.1 <7.0.0" }, "babel-plugin-transform-react-display-name": { @@ -385,13 +395,13 @@ "from": "babel-plugin-transform-regexp-constructors@>=0.0.5 <0.0.6" }, "babel-plugin-transform-remove-console": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.8.1.tgz", + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.8.3.tgz", "from": "babel-plugin-transform-remove-console@>=6.8.0 <7.0.0" }, "babel-plugin-transform-remove-debugger": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.8.1.tgz", + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.8.3.tgz", "from": "babel-plugin-transform-remove-debugger@>=6.8.0 <7.0.0" }, "babel-plugin-transform-remove-undefined": { @@ -405,8 +415,8 @@ "from": "babel-plugin-transform-runtime@>=6.22.0 <7.0.0" }, "babel-plugin-transform-simplify-comparison-operators": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.8.1.tgz", + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.8.3.tgz", "from": "babel-plugin-transform-simplify-comparison-operators@>=6.8.1 <7.0.0" }, "babel-plugin-transform-strict-mode": { @@ -415,8 +425,8 @@ "from": "babel-plugin-transform-strict-mode@>=6.24.1 <7.0.0" }, "babel-plugin-transform-undefined-to-void": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.8.0.tgz", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.8.2.tgz", "from": "babel-plugin-transform-undefined-to-void@>=6.8.0 <7.0.0" }, "babel-preset-babili": { @@ -465,8 +475,8 @@ "from": "babel-types@>=6.22.0 <7.0.0" }, "babylon": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.16.1.tgz", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.1.tgz", "from": "babylon@>=6.15.0 <7.0.0" }, "balanced-match": { @@ -477,7 +487,7 @@ "brace-expansion": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", - "from": "brace-expansion@>=1.0.0 <2.0.0" + "from": "brace-expansion@>=1.1.7 <2.0.0" }, "chalk": { "version": "1.1.3", @@ -500,8 +510,8 @@ "from": "core-js@>=2.4.0 <3.0.0" }, "debug": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.3.tgz", + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", "from": "debug@>=2.1.1 <3.0.0" }, "detect-indent": { @@ -580,9 +590,9 @@ "from": "loose-envify@>=1.0.0 <2.0.0" }, "meteor-babel": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/meteor-babel/-/meteor-babel-0.20.1.tgz", - "from": "meteor-babel@0.20.1" + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/meteor-babel/-/meteor-babel-0.21.4.tgz", + "from": "meteor-babel@0.21.4" }, "meteor-babel-helpers": { "version": "0.0.3", @@ -590,8 +600,8 @@ "from": "meteor-babel-helpers@0.0.3" }, "minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "from": "minimatch@>=3.0.2 <4.0.0" }, "minimist": { @@ -599,15 +609,25 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "from": "minimist@0.0.8" }, + "minipass": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.0.2.tgz", + "from": "minipass@>=2.0.0 <3.0.0" + }, + "minizlib": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.0.3.tgz", + "from": "minizlib@>=1.0.3 <2.0.0" + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "from": "mkdirp@>=0.5.1 <0.6.0" }, "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "from": "ms@0.7.2" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "from": "ms@2.0.0" }, "number-is-nan": { "version": "1.0.1", @@ -640,8 +660,8 @@ "from": "regenerate@>=1.2.1 <2.0.0" }, "regenerator-runtime": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", "from": "regenerator-runtime@>=0.10.0 <0.11.0" }, "regenerator-transform": { @@ -672,15 +692,20 @@ } }, "reify": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/reify/-/reify-0.7.4.tgz", - "from": "reify@>=0.7.2 <0.8.0" + "version": "0.11.21", + "resolved": "https://registry.npmjs.org/reify/-/reify-0.11.21.tgz", + "from": "reify@>=0.11.18 <0.12.0" }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "from": "repeating@>=2.0.0 <3.0.0" }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "from": "semver@>=5.3.0 <6.0.0" + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -692,8 +717,8 @@ "from": "source-map@>=0.5.0 <0.6.0" }, "source-map-support": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.14.tgz", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", "from": "source-map-support@>=0.4.2 <0.5.0" }, "strip-ansi": { @@ -707,14 +732,19 @@ "from": "supports-color@>=2.0.0 <3.0.0" }, "to-fast-properties": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.2.tgz", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", "from": "to-fast-properties@>=1.0.1 <2.0.0" }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "from": "trim-right@>=1.0.1 <2.0.0" + }, + "yallist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "from": "yallist@>=3.0.0 <4.0.0" } } } diff --git a/packages/babel-compiler/babel.js b/packages/babel-compiler/babel.js index ac518d1034..13306f306b 100644 --- a/packages/babel-compiler/babel.js +++ b/packages/babel-compiler/babel.js @@ -21,6 +21,10 @@ Babel = { // Deprecated, now a no-op. validateExtraFeatures: Function.prototype, + parse: function (source) { + return Npm.require('meteor-babel').parse(source); + }, + compile: function (source, options) { var meteorBabel = Npm.require('meteor-babel'); options = options || getDefaultOptions(); diff --git a/packages/babel-compiler/package.js b/packages/babel-compiler/package.js index dbd098e545..f58e5656ba 100644 --- a/packages/babel-compiler/package.js +++ b/packages/babel-compiler/package.js @@ -6,15 +6,15 @@ Package.describe({ // isn't possible because you can't publish a non-recommended // release with package versions that don't have a pre-release // identifier at the end (eg, -dev) - version: '6.18.2' + version: '6.19.1' }); Npm.depends({ - 'meteor-babel': '0.20.1' + 'meteor-babel': '0.21.4' }); Package.onUse(function (api) { - api.use('ecmascript-runtime'); + api.use('ecmascript-runtime', 'server'); api.addFiles([ 'babel.js', diff --git a/packages/boilerplate-generator/boilerplate-generator.js b/packages/boilerplate-generator/boilerplate-generator.js index 8385aef6b1..22e06e98f0 100644 --- a/packages/boilerplate-generator/boilerplate-generator.js +++ b/packages/boilerplate-generator/boilerplate-generator.js @@ -73,7 +73,10 @@ Boilerplate.prototype._generateBoilerplateFromManifestAndSource = if (item.type === 'css' && item.where === 'client') { boilerplateBaseData.css.push(itemObj); } - if (item.type === 'js' && item.where === 'client') { + if (item.type === 'js' && item.where === 'client' && + // Dynamic JS modules should not be loaded eagerly in the + // initial HTML of the app. + ! item.path.startsWith('dynamic/')) { boilerplateBaseData.js.push(itemObj); } if (item.type === 'head') { diff --git a/packages/boilerplate-generator/package.js b/packages/boilerplate-generator/package.js index 357701a5a2..3faf0a4e51 100644 --- a/packages/boilerplate-generator/package.js +++ b/packages/boilerplate-generator/package.js @@ -1,15 +1,15 @@ Package.describe({ summary: "Generates the boilerplate html from program's manifest", - version: '1.0.11' + version: '1.1.0' }); Package.onUse(function (api) { api.use([ - 'underscore@1.0.9', - 'spacebars-compiler@1.0.12', - 'spacebars@1.0.12', - 'htmljs@1.0.10', - 'ui@1.0.11', + 'underscore', + 'spacebars-compiler', + 'spacebars', + 'htmljs', + 'ui', ], 'server'); api.addFiles(['boilerplate-generator.js'], 'server'); api.export(['Boilerplate'], 'server'); diff --git a/packages/dynamic-import/README.md b/packages/dynamic-import/README.md new file mode 100644 index 0000000000..fe315fdaa1 --- /dev/null +++ b/packages/dynamic-import/README.md @@ -0,0 +1,13 @@ +This package implements the `Module.prototype.dynamicImport(id)` runtime +API needed for fetching modules dynamically from the server, so that those +modules don't have to be included in the initial JavaScript bundle. + +With this package installed, supporting the [dynamic `import(...)` +proposal](https://github.com/tc39/proposal-dynamic-import) is as easy as +compiling `import(...)` to `module.dynamicImport(...)`. + +Any version of a module that has been fetched previously will be +permanently cached and should never need to be fetched again by the same +client, even after the window is closed or the browser is restarted. + +Meteor 1.5 is necessary for this package to work properly. diff --git a/packages/dynamic-import/TODO.md b/packages/dynamic-import/TODO.md new file mode 100644 index 0000000000..804a8de8b6 --- /dev/null +++ b/packages/dynamic-import/TODO.md @@ -0,0 +1,43 @@ +### Basic implementation: + +- [x] Future-proof `findImportedModuleIdentifiers` for real `import(...)` +- [x] Source maps in development +- [x] Debugger stops at reasonable points in dev tools +- [x] Open another WebSocket? NO +- [x] Make `import(...)` work on the server +- [x] Modules are minified but not merged in production + - [x] Wrap modules with function to enable better minification +- [x] Babel transform from `import(...)` to `module.importAsync(...)` +- [x] Local module caching. + - [x] Prototype with `localStorage`. + - [x] Reimplement using `indexedDB` (much larger size limits). +- [x] Compact `previous` state representation +- [x] Improve dependency resolution in `packages/dynamic-import/server.js` +- [x] Report static import/syntax/etc. errors for async files +- [x] What about old/new versions of code? +- [x] What about package pseudo-globals (imports)? +- [x] What about dynamic stubs? +- [x] Avoid creating dynamic files on the server. +- [ ] ~~`Mp.dynamicImport` could be implemented without the fallback on the server if we were sure the server had no dynamic files.~~ +- [x] Make sure client-only reloads work (revisit _read caching). +- [x] Make sure path manipulation is Windows-safe. +- [x] Install dynamic modules with correct `meteorInstall` options. +- [x] Tests! + +### Future work: + +- [ ] Batch multiple `__dynamicImport` method calls? +- [ ] Detect modules unevaluated during page load and recommend importing them dynamically. +- [ ] Quantify the impact of using `import(...)`. +- [ ] Report initial bundle sizes. +- [ ] Warn about `import(...)` calls before `Meteor.startup`, since they should probably be static. +- [ ] Analyze module graph to suggest dynamic cut points (e.g. in router callbacks). +- [ ] Warn if dynamically imported modules are imported statically elsewhere (killing the benefit of the dynamic import). +- [ ] Use `Cache-Control: immutable` for the initial bundle. +- [ ] Upgrade caching to `ServiceWorker` and `Cache` in supporting browsers (if actually faster!). +- [ ] Preload modules that are often dynamically imported, when page becomes idle. +- [ ] Allow the client to overfetch soon-to-be-needed modules to avoid waterfalls. +- [ ] Write [Meteor Guide](https://guide.meteor.com/) article about techniques for optimizing page load times. + - [ ] Inlining imports. + - [ ] Making eager modules in apps and packages lazy. + - [ ] Using dynamic `import(...)` in the right places. diff --git a/packages/dynamic-import/cache.js b/packages/dynamic-import/cache.js new file mode 100644 index 0000000000..402b2b298b --- /dev/null +++ b/packages/dynamic-import/cache.js @@ -0,0 +1,189 @@ +var hasOwn = Object.prototype.hasOwnProperty; +var dbPromise; + +var canUseCache = + // The server doesn't benefit from dynamic module fetching, and almost + // certainly doesn't support IndexedDB. + Meteor.isClient && + // Cordova bundles all modules into the monolithic initial bundle, so + // the dynamic module cache won't be necessary. + ! Meteor.isCordova && + // Caching can be confusing in development, and is designed to be a + // transparent optimization for production performance. + Meteor.isProduction; + +function getIDB() { + if (typeof indexedDB !== "undefined") return indexedDB; + if (typeof webkitIndexedDB !== "undefined") return webkitIndexedDB; + if (typeof mozIndexedDB !== "undefined") return mozIndexedDB; + if (typeof OIndexedDB !== "undefined") return OIndexedDB; + if (typeof msIndexedDB !== "undefined") return msIndexedDB; +} + +function withDB(callback) { + dbPromise = dbPromise || new Promise(function (resolve, reject) { + var idb = getIDB(); + if (! idb) { + throw new Error("IndexedDB not available"); + } + + // Incrementing the version number causes all existing object stores + // to be deleted and recreates those specified by objectStoreMap. + var request = idb.open("MeteorDynamicImportCache", 2); + + request.onupgradeneeded = function (event) { + var db = event.target.result; + + // It's fine to delete existing object stores since onupgradeneeded + // is only called when we change the DB version number, and the data + // we're storing is disposable/reconstructible. + Array.from(db.objectStoreNames).forEach(db.deleteObjectStore, db); + + Object.keys(objectStoreMap).forEach(function (name) { + db.createObjectStore(name, objectStoreMap[name]); + }); + }; + + request.onerror = makeOnError(reject, "indexedDB.open"); + request.onsuccess = function (event) { + resolve(event.target.result); + }; + }); + + return dbPromise.then(callback, function (error) { + return callback(null); + }); +} + +var objectStoreMap = { + sourcesByVersion: { keyPath: "version" } +}; + +function makeOnError(reject, source) { + return function (event) { + reject(new Error( + "IndexedDB failure in " + source + " " + + JSON.stringify(event.target) + )); + + // Returning true from an onerror callback function prevents an + // InvalidStateError in Firefox during Private Browsing. Silencing + // that error is safe because we handle the error more gracefully by + // passing it to the Promise reject function above. + // https://github.com/meteor/meteor/issues/8697 + return true; + }; +} + +var checkCount = 0; + +exports.checkMany = function (versions) { + var ids = Object.keys(versions); + var sourcesById = Object.create(null); + + // Initialize sourcesById with null values to indicate all sources are + // missing (unless replaced with actual sources below). + ids.forEach(function (id) { + sourcesById[id] = null; + }); + + if (! canUseCache) { + return Promise.resolve(sourcesById); + } + + return withDB(function (db) { + if (! db) { + // We thought we could used IndexedDB, but something went wrong + // while opening the database, so err on the side of safety. + return sourcesById; + } + + var txn = db.transaction([ + "sourcesByVersion" + ], "readonly"); + + var sourcesByVersion = txn.objectStore("sourcesByVersion"); + + ++checkCount; + + function finish() { + --checkCount; + return sourcesById; + } + + return Promise.all(ids.map(function (id) { + return new Promise(function (resolve, reject) { + var version = versions[id]; + if (version) { + var sourceRequest = sourcesByVersion.get(versions[id]); + sourceRequest.onerror = makeOnError(reject, "sourcesByVersion.get"); + sourceRequest.onsuccess = function (event) { + var result = event.target.result; + if (result) { + sourcesById[id] = result.source; + } + resolve(); + }; + } else resolve(); + }); + })).then(finish, finish); + }); +}; + +var pendingVersionsAndSourcesById = Object.create(null); + +exports.setMany = function (versionsAndSourcesById) { + if (canUseCache) { + Object.assign( + pendingVersionsAndSourcesById, + versionsAndSourcesById + ); + + // Delay the call to flushSetMany so that it doesn't contribute to the + // amount of time it takes to call module.dynamicImport. + if (! flushSetMany.timer) { + flushSetMany.timer = setTimeout(flushSetMany, 100); + } + } +}; + +function flushSetMany() { + if (checkCount > 0) { + // If checkMany is currently underway, postpone the flush until later, + // since updating the cache is less important than reading from it. + return flushSetMany.timer = setTimeout(flushSetMany, 100); + } + + flushSetMany.timer = null; + + var versionsAndSourcesById = pendingVersionsAndSourcesById; + pendingVersionsAndSourcesById = Object.create(null); + + return withDB(function (db) { + if (! db) { + // We thought we could used IndexedDB, but something went wrong + // while opening the database, so err on the side of safety. + return; + } + + var setTxn = db.transaction([ + "sourcesByVersion" + ], "readwrite"); + + var sourcesByVersion = setTxn.objectStore("sourcesByVersion"); + + return Promise.all( + Object.keys(versionsAndSourcesById).map(function (id) { + var info = versionsAndSourcesById[id]; + return new Promise(function (resolve, reject) { + var request = sourcesByVersion.put({ + version: info.version, + source: info.source + }); + request.onerror = makeOnError(reject, "sourcesByVersion.put"); + request.onsuccess = resolve; + }); + }) + ); + }); +} diff --git a/packages/dynamic-import/client.js b/packages/dynamic-import/client.js new file mode 100644 index 0000000000..e3c261b700 --- /dev/null +++ b/packages/dynamic-import/client.js @@ -0,0 +1,158 @@ +var Module = module.constructor; +var cache = require("./cache.js"); + +// Call module.dynamicImport(id) to fetch a module and any/all of its +// dependencies that have not already been fetched, and evaluate them as +// soon as they arrive. This runtime API makes it very easy to implement +// ECMAScript dynamic import(...) syntax. +Module.prototype.dynamicImport = function (id) { + var module = this; + return module.prefetch(id).then(function () { + return getNamespace(module, id); + }); +}; + +// Called by Module.prototype.prefetch if there are any missing dynamic +// modules that need to be fetched. +meteorInstall.fetch = function (ids) { + var tree = Object.create(null); + var versions = Object.create(null); + var dynamicVersions = require("./dynamic-versions.js"); + var missing; + + Object.keys(ids).forEach(function (id) { + var version = getFromTree(dynamicVersions, id); + if (version) { + versions[id] = version; + } else { + addToTree(missing = missing || Object.create(null), id, 1); + } + }); + + return cache.checkMany(versions).then(function (sources) { + Object.keys(sources).forEach(function (id) { + var source = sources[id]; + if (source) { + var info = ids[id]; + addToTree(tree, id, makeModuleFunction(id, source, info.options)); + } else { + addToTree(missing = missing || Object.create(null), id, 1); + } + }); + + return missing && fetchMissing(missing).then(function (results) { + var versionsAndSourcesById = Object.create(null); + var flatResults = flattenModuleTree(results); + + Object.keys(flatResults).forEach(function (id) { + var source = flatResults[id]; + var info = ids[id]; + + addToTree(tree, id, makeModuleFunction(id, source, info.options)); + + var version = getFromTree(dynamicVersions, id); + if (version) { + versionsAndSourcesById[id] = { + version: version, + source: source + }; + } + }); + + cache.setMany(versionsAndSourcesById); + }); + + }).then(function () { + return tree; + }); +}; + +function flattenModuleTree(tree) { + var parts = [""]; + var result = Object.create(null); + + function walk(t) { + if (t && typeof t === "object") { + Object.keys(t).forEach(function (key) { + parts.push(key); + walk(t[key]); + parts.pop(); + }); + } else if (typeof t === "string") { + result[parts.join("/")] = t; + } + } + + walk(tree); + + return result; +} + +function makeModuleFunction(id, source, options) { + // By calling (options && options.eval || eval) in a wrapper function, + // we delay the cost of parsing and evaluating the module code until the + // module is first imported. + return function () { + // If an options.eval function was provided in the second argument to + // meteorInstall when this bundle was first installed, use that + // function to parse and evaluate the dynamic module code in the scope + // of the package. Otherwise fall back to indirect (global) eval. + return (options && options.eval || eval)( + // Wrap the function(require,exports,module){...} expression in + // parentheses to force it to be parsed as an expression. + "(" + source + ")\n//# sourceURL=" + id + ).apply(this, arguments); + }; +} + +function fetchMissing(missingTree) { + // Update lastFetchMissingPromise immediately, without waiting for + // the results to be delivered. + return new Promise(function (resolve, reject) { + Meteor.call( + "__dynamicImport", + missingTree, + function (error, resultsTree) { + error ? reject(error) : resolve(resultsTree); + } + ); + }); +} + +function getFromTree(tree, id) { + id.split("/").every(function (part) { + return ! part || (tree = tree[part]); + }); + + return tree; +} + +function addToTree(tree, id, value) { + var parts = id.split("/"); + var lastIndex = parts.length - 1; + parts.forEach(function (part, i) { + if (part) { + tree = tree[part] = tree[part] || + (i < lastIndex ? Object.create(null) : value); + } + }); +} + +function getNamespace(module, id) { + var namespace; + + module.watch(module.require(id), { + "*": function (ns) { + namespace = ns; + } + }); + + // This helps with Babel interop, since we're not just returning the + // module.exports object. + Object.defineProperty(namespace, "__esModule", { + value: true, + enumerable: false + }); + + return namespace; +} diff --git a/packages/dynamic-import/dynamic-versions.js b/packages/dynamic-import/dynamic-versions.js new file mode 100644 index 0000000000..7599278028 --- /dev/null +++ b/packages/dynamic-import/dynamic-versions.js @@ -0,0 +1,4 @@ +// This magic double-underscored identifier gets replaced in +// tools/isobuild/bundler.js with a tree of hashes of all dynamic +// modules, for use in client.js and cache.js. +module.exports = __DYNAMIC_VERSIONS__; diff --git a/packages/dynamic-import/package.js b/packages/dynamic-import/package.js new file mode 100644 index 0000000000..7333cd1d16 --- /dev/null +++ b/packages/dynamic-import/package.js @@ -0,0 +1,23 @@ +Package.describe({ + name: "dynamic-import", + version: "0.1.0", + summary: "Runtime support for Meteor 1.5 dynamic import(...) syntax", + documentation: "README.md" +}); + +Package.onUse(function (api) { + // Do not allow this package to be used in pre-Meteor 1.5 apps. + api.use("isobuild:dynamic-import@1.5.0"); + + // Modify browser policy only if browser-policy packages are used. + api.use("browser-policy-content", { weak: true }); + + api.use("modules"); + api.use("promise"); + api.use("ddp"); + api.use("check"); + api.use("ecmascript", "server"); + + api.mainModule("client.js", "client"); + api.mainModule("server.js", "server"); +}); diff --git a/packages/dynamic-import/security.js b/packages/dynamic-import/security.js new file mode 100644 index 0000000000..f8ccb576f2 --- /dev/null +++ b/packages/dynamic-import/security.js @@ -0,0 +1,20 @@ +const bpc = Package["browser-policy-content"]; +const BP = bpc && bpc.BrowserPolicy; +const BPc = BP && BP.content; +if (BPc) { + // The ability to evaluate new code is essential for loading dynamic + // modules. Without eval, we would be forced to load modules using + //