From 3ba0a5a22a02fb091c9d09861e39878bf03491a1 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 17 Apr 2018 14:04:55 -0400 Subject: [PATCH 01/10] Adds single-pass prerendering via a new `prerender-loader` --- config/prerender-loader.js | 244 ++++++++++++++++++++++++++++++------- global.d.ts | 1 + package.json | 5 +- src/index.html | 12 +- src/index.tsx | 8 +- webpack.config.js | 7 +- 6 files changed, 211 insertions(+), 66 deletions(-) diff --git a/config/prerender-loader.js b/config/prerender-loader.js index 91f0647a8..67b8456a7 100644 --- a/config/prerender-loader.js +++ b/config/prerender-loader.js @@ -1,64 +1,214 @@ +const jsdom = require('jsdom'); +const os = require('os'); +const util = require('util'); const path = require('path'); -const vm = require('vm'); +const loaderUtils = require('loader-utils'); +const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin'); +const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); +const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); +const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); +const DefinePlugin = require('webpack').DefinePlugin; +const MemoryFs = require('memory-fs'); -module.exports = function (content) { - const jsdom = require('jsdom'); - const preact = require('preact'); - const renderToString = require('preact-render-to-string'); +const FILENAME = 'ssr-bundle.js'; - this.cacheable && this.cacheable(); +const PRERENDER_REG = /\{\{prerender(?::\s*([^}]+)\s*)?\}\}/; + +module.exports = function PrerenderLoader (content) { + const options = loaderUtils.getOptions(this) || {}; + const outputFilter = options.as === 'string' || options.string ? stringToModule : String; + + if (options.disabled === true) { + return outputFilter(content); + } + + // When applied to HTML, attempts to inject into a specified {{prerender}} field. + // @note: this is only used when the entry module exports a String or function + // that resolves to a String, otherwise the whole document is serialized. + let inject = false; + if (!this.request.match(/.(js|ts)x?$/i)) { + const matches = content.match(PRERENDER_REG); + if (matches) { + inject = true; + options.entry = matches[1]; + } + options.templateContent = content; + } const callback = this.async(); - // const dom = new jsdom.JSDOM(``, { - const dom = new jsdom.JSDOM(content, { - includeNodeLocations: false, - runScripts: 'outside-only' - }); - const { window } = dom; - const { document } = window; + prerender(this, options, inject) + .then(output => { + callback(null, outputFilter(output)); + }) + .catch(err => { + console.error(err); + callback(err); + }); +}; - // console.log(content); +async function prerender (loaderContext, options, inject) { + const parentCompilation = loaderContext._compilation; + const parentCompiler = rootCompiler(parentCompilation.compiler); + const request = loaderContext.request; + const context = parentCompiler.options.context || process.cwd(); + const entry = './' + ((options.entry && [].concat(options.entry).pop().trim()) || path.relative(context, parentCompiler.options.entry)); - const root = document.getElementById('app'); - this.loadModule(path.join(__dirname, 'client-boot.js'), (err, source) => { - if (err) return callback(err); + if (!inject && options.template) { + const loadModule = util.promisify(loaderContext.loadModule); + const source = await loadModule('!!raw-loader!' + path.resolve(context, options.template)); + options.templateContent = source; + } - console.log(source); + const outputOptions = { + // fix for plugins not using outputfilesystem + path: os.tmpdir(), + filename: FILENAME + }; - let mod = eval(source); - let props = {}; - // console.log(mod); - let vnode = preact.createElement(mod, props); - let frag = document.createElement('div'); - frag.innerHTML = renderToString(vnode); - root.parentNode.replaceChild(frag.firstChild, root); + // Only copy over mini-extract-text-plugin (excluding it breaks extraction entirely) + const plugins = (parentCompiler.options.plugins || []).filter(c => /MiniCssExtractPlugin/i.test(c.constructor.name)); - let html = dom.serialize(); - callback(null, html); - // return html = `module.exports = ${JSON.stringify(html)}`; - // return 'module.exports = ' + JSON.stringify(content).replace(/\{\{PRERENDER\}\}/gi, `" + require("preact-render-to-string")(require("app-entry-point")) + "`); - }); + // Compile to an in-memory filesystem since we just want the resulting bundled code as a string + const compiler = parentCompilation.createChildCompiler('prerender', outputOptions, plugins); + compiler.outputFileSystem = new MemoryFs(); + + // Define PRERENDER to be true within the SSR bundle + new DefinePlugin({ + PRERENDER: 'true' + }).apply(compiler); + + // ... then define PRERENDER to be false within the client bundle + new DefinePlugin({ + PRERENDER: 'false' + }).apply(parentCompiler); + + // Compile to CommonJS to be executed by Node + new NodeTemplatePlugin(outputOptions).apply(compiler); + new NodeTargetPlugin().apply(compiler); + + new LibraryTemplatePlugin('PRERENDER_RESULT', 'var').apply(compiler); + + // Kick off compilation at our entry module (either the parent compiler's entry or a custom one defined via `{{prerender:entry.js}}`) + new SingleEntryPlugin(context, entry, undefined).apply(compiler); + + // Set up cache inheritance for the child compiler + const subCache = 'subcache ' + request; + function addChildCache (compilation, data) { + if (compilation.cache) { + if (!compilation.cache[subCache]) compilation.cache[subCache] = {}; + compilation.cache = compilation.cache[subCache]; + } + } + if (compiler.hooks) { + compiler.hooks.compilation.tap('prerender-loader', addChildCache); + } else { + compiler.plugin('compilation', addChildCache); + } + + const compilation = await runChildCompiler(compiler); + let result = ''; + let dom, injectParent; + + if (compilation.assets[compilation.options.output.filename]) { + // Get the compiled main bundle + const output = compilation.assets[compilation.options.output.filename].source(); + + const tpl = options.templateContent || ''; + dom = new jsdom.JSDOM(tpl.replace(PRERENDER_REG, '
'), { + includeNodeLocations: false, + runScripts: 'outside-only' + }); + const { window } = dom; - // global.window = global; - // global.document = {}; - // return 'module.exports = ' + JSON.stringify(content).replace(/\{\{PRERENDER\}\}/gi, `" + require("preact-render-to-string")(require("app-entry-point")) + "`); + // Find the placeholder node for injection & remove it + const injectPlaceholder = window.document.getElementById('PRERENDER_INJECT'); + if (injectPlaceholder) { + injectParent = injectPlaceholder.parentNode; + injectPlaceholder.remove(); + } - /* - let callback = this.async(); + // These are missing from JSDOM + window.requestAnimationFrame = setTimeout; + window.cancelAnimationFrame = clearTimeout; - let parts = content.split(/\{\{prerender\}\}/gi); + // Invoke the SSR bundle within the JSDOM document and grab the exported/returned result + result = window.eval(output + '\nPRERENDER_RESULT') || result; - if (parts.length<2) { - // callback(null, `module.exports = ${JSON.stringify(content)}`); - callback(null, content); - return; + if (window.PRERENDER_RESULT != null) { + result = window.PRERENDER_RESULT; + } + } + + // Deal with ES Module exports (just use the best guess): + if (result && result.__esModule === true) { + result = getBestModuleExport(result); + } + + if (typeof result === 'function') { + // @todo any arguments worth passing here? + result = result(); + } + + // The entry can export or return a Promise in order to perform fully async prerendering: + if (result && result.then) { + result = await result; + } + + // Returning or resolving to `null` / `undefined` defaults to serializing the whole document. + // Note: this pypasses `inject` because the document is already derived from the template. + if (result == null && dom) { + result = dom.serialize(); + } else if (inject) { + // @todo determine if this is really necessary for the string return case + if (injectParent) { + injectParent.insertAdjacentHTML('beforeend', result || ''); + } else { + // Otherwise inject the prerendered HTML into the template + result = options.templateContent.replace(PRERENDER_REG, result || ''); + } + } + + return result; +} + +// Promisified version of compiler.runAsChild() with error hoisting and isolated output/assets +function runChildCompiler (compiler) { + return new Promise((resolve, reject) => { + // runAsChild() merges assets into the parent compilation, we don't want that. + compiler.compile((err, compilation) => { + compiler.parentCompilation.children.push(compilation); + if (err) return reject(err); + + if (compilation.errors && compilation.errors.length) { + const errorDetails = compilation.errors.map(error => error.details).join('\n'); + return reject(Error('Child compilation failed:\n' + errorDetails)); + } + + resolve(compilation); + }); + }); +} + +// Crawl up the compiler tree and return the outermost compiler instance +function rootCompiler (compiler) { + while (compiler.parentCompilation && compiler.parentCompilation.compiler) { + compiler = compiler.parentCompilation.compiler; + } + return compiler; +} + +// Find the best possible export for an ES Module. Returns `undefined` for no exports. +function getBestModuleExport (exports) { + if (exports.default) { + return exports.default; + } + for (const prop in exports) { + if (prop !== '__esModule') { + return exports[prop]; + } } +} - // let html = ` - // window = {}; - // module.exports = ${JSON.stringify(parts[0])} + require("preact-render-to-string")(require("app-entry-point")) + ${JSON.stringify(parts[1])}`; - let html = `module.exports = ${JSON.stringify(parts[0])} + require("preact-render-to-string")(require("app-entry-point")) + ${JSON.stringify(parts[1])}`; - callback(null, html); - */ -}; \ No newline at end of file +// Wrap a String up into an ES Module that exports it +const stringToModule = str => 'export default ' + JSON.stringify(str); diff --git a/global.d.ts b/global.d.ts index 7b9d325dc..c91b407ab 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,4 +1,5 @@ declare const __webpack_public_path__: string; +declare const PRERENDER: boolean; declare interface NodeModule { hot: any; diff --git a/package.json b/package.json index 780bc154f..791334b4f 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,9 @@ "fork-ts-checker-webpack-plugin": "^0.4.1", "html-webpack-plugin": "^3.0.6", "if-env": "^1.0.4", - "jsdom": "^11.6.2", + "jsdom": "^11.5.1", + "loader-utils": "^1.1.0", + "memory-fs": "^0.4.1", "mini-css-extract-plugin": "^0.3.0", "node-sass": "^4.7.2", "nwmatcher": "^1.4.4", @@ -70,6 +72,7 @@ "preload-webpack-plugin": "github:GoogleChromeLabs/preload-webpack-plugin", "pretty-bytes": "^4.0.2", "progress-bar-webpack-plugin": "^1.11.0", + "raw-loader": "^0.5.1", "sass-loader": "^6.0.7", "script-ext-html-webpack-plugin": "^2.0.1", "source-map-loader": "^0.2.3", diff --git a/src/index.html b/src/index.html index dac706e11..d8af6770d 100644 --- a/src/index.html +++ b/src/index.html @@ -10,14 +10,6 @@ -
- - +
- \ No newline at end of file + diff --git a/src/index.tsx b/src/index.tsx index 60f92c96b..9bfb15cba 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,7 @@ import './style'; import App from './components/app'; // Find the outermost Element in our server-rendered HTML structure. -let root = document.querySelector('[prerender]') || undefined; +let root = document.querySelector('#app') || undefined; // "attach" the client-side rendering to it, updating the DOM in-place instead of replacing: root = render(, document.body, root); @@ -26,7 +26,7 @@ if (process.env.NODE_ENV === 'development') { }); } -/** @todo SSR */ -// if (typeof module==='object') { -// module.exports = app; +/** @todo Async SSR if we need it */ +// export default async () => { +// // render here, then resolve to a string of HTML (or null to serialize the document) // } diff --git a/webpack.config.js b/webpack.config.js index 61a9aa80e..7d3066d98 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -165,15 +165,14 @@ module.exports = function(_, env) { ]), // For now we're not doing SSR. - new HtmlWebpackPlugin({ + new HtmlPlugin({ filename: path.join(__dirname, 'build/index.html'), - template: '!!ejs-loader!src/index.html', - // template: '!!'+path.join(__dirname, 'config/prerender-loader')+'!src/index.html', + template: '!' + path.join(__dirname, 'config/prerender-loader') + '?string' + (isProd ? '' : '&disabled') + '!src/index.html', minify: isProd && { collapseWhitespace: true, removeScriptTypeAttributes: true, - removeRedundantAttributes: true, removeStyleLinkTypeAttributes: true, + removeRedundantAttributes: true, removeComments: true }, manifest: readJson('./src/manifest.json'), From 5936c57a8245c4333b78a99e16767ac27d1a4514 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 17 Apr 2018 14:05:50 -0400 Subject: [PATCH 02/10] Clean up app to remove old prerendering bits --- src/components/home/index.tsx | 9 +++++++-- src/components/home/style.scss | 15 ++++++++------- src/components/home/style.scss.d.ts | 4 ++-- src/style/index.scss | 2 +- src/style/material-icons.scss | 20 ++++++++------------ 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/components/home/index.tsx b/src/components/home/index.tsx index ac801711a..ad96ad84d 100644 --- a/src/components/home/index.tsx +++ b/src/components/home/index.tsx @@ -26,9 +26,14 @@ export default class Home extends Component { render({ files }: Props, { active }: State) { return ( -
+
{ files && files[0] && ( - + + ) || ( +
+

Squoosh

+

Test home content

+
) }
); diff --git a/src/components/home/style.scss b/src/components/home/style.scss index 09d9fa5b5..2abb508cc 100644 --- a/src/components/home/style.scss +++ b/src/components/home/style.scss @@ -7,14 +7,15 @@ .home { padding: 20px; - opacity: 0; } -.active { - animation: fadeIn 2s forwards ease 1; +.image { + width: 100%; } -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } -} \ No newline at end of file +.content { + max-width: 600px; + margin: 50px auto 0; + font-size: 120%; + text-align: center; +} diff --git a/src/components/home/style.scss.d.ts b/src/components/home/style.scss.d.ts index c9e5bf62f..406166b6c 100644 --- a/src/components/home/style.scss.d.ts +++ b/src/components/home/style.scss.d.ts @@ -1,3 +1,3 @@ export const home: string; -export const active: string; -export const fadeIn: string; +export const image: string; +export const content: string; diff --git a/src/style/index.scss b/src/style/index.scss index 5d7073eeb..953b2cb44 100644 --- a/src/style/index.scss +++ b/src/style/index.scss @@ -1,5 +1,5 @@ -// @import './material-icons.scss'; // @import 'material-components-web/material-components-web'; +@import './material-icons.scss'; @import './reset.scss'; // @import url('https://fonts.googleapis.com/icon?family=Material+Icons'); diff --git a/src/style/material-icons.scss b/src/style/material-icons.scss index 1ad3e638f..8847a8372 100644 --- a/src/style/material-icons.scss +++ b/src/style/material-icons.scss @@ -2,11 +2,9 @@ font-family: 'Material Icons'; font-style: normal; font-weight: 400; - src: local('Material Icons'), - local('MaterialIcons-Regular'), - url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'), - url(https://example.com/MaterialIcons-Regular.woff) format('woff'), - url(https://example.com/MaterialIcons-Regular.ttf) format('truetype'); + font-display: swap; + // @todo woff fallback! + src: url(https://fonts.gstatic.com/s/materialicons/v36/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2'); } .material-icons { @@ -14,15 +12,13 @@ font-weight: normal; font-style: normal; font-size: 24px; - display: inline-block; line-height: 1; - text-transform: none; letter-spacing: normal; - word-wrap: normal; + text-transform: none; + display: inline-block; white-space: nowrap; + word-wrap: normal; direction: ltr; + -webkit-font-feature-settings: 'liga'; -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; - -moz-osx-font-smoothing: grayscale; - font-feature-settings: 'liga'; -} \ No newline at end of file +} From e0c59577a43ff57e49fc7d2cda4e7cd57741b1b8 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 17 Apr 2018 14:07:22 -0400 Subject: [PATCH 03/10] Lint webpack config, and only preload initial chunks --- webpack.config.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 7d3066d98..6e75cc495 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,7 +1,7 @@ const fs = require('fs'); const path = require('path'); const webpack = require('webpack'); -const CleanWebpackPlugin = require('clean-webpack-plugin'); +const CleanPlugin = require('clean-webpack-plugin'); const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); @@ -14,11 +14,11 @@ const CrittersPlugin = require('./config/critters-webpack-plugin'); const WatchTimestampsPlugin = require('./config/watch-timestamps-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; -function readJson(filename) { +function readJson (filename) { return JSON.parse(fs.readFileSync(filename)); } -module.exports = function(_, env) { +module.exports = function (_, env) { const isProd = env.mode === 'production'; const nodeModules = path.join(__dirname, 'node_modules'); const componentStyleDirs = [ @@ -129,9 +129,10 @@ module.exports = function(_, env) { // Remove old files before outputting a production build: isProd && new CleanPlugin([ 'assets', - '**/*.{css,js,json,html}' + '**/*.{css,js,json,html,map}' ], { root: path.join(__dirname, 'build'), + verbose: false, beforeEmit: true }), @@ -181,7 +182,9 @@ module.exports = function(_, env) { }), // Inject for resources - isProd && new PreloadWebpackPlugin(), + isProd && new PreloadPlugin({ + include: 'initial' + }), isProd && new CrittersPlugin({ // Don't inline fonts into critical CSS, but do preload them: From 896d267de5dad03d19a03649e27be7f15ec1f956 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 17 Apr 2018 14:07:42 -0400 Subject: [PATCH 04/10] Add plugin to make script loading async --- webpack.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webpack.config.js b/webpack.config.js index 6e75cc495..6ec40a2b4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,7 @@ const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const HtmlPlugin = require('html-webpack-plugin'); +const ScriptExtHtmlPlugin = require('script-ext-html-webpack-plugin'); const PreloadPlugin = require('preload-webpack-plugin'); const ReplacePlugin = require('webpack-plugin-replace'); const CopyPlugin = require('copy-webpack-plugin'); @@ -181,6 +182,10 @@ module.exports = function (_, env) { compile: true }), + new ScriptExtHtmlPlugin({ + defaultAttribute: 'async' + }), + // Inject for resources isProd && new PreloadPlugin({ include: 'initial' From 411614b73132ddc0f441b2ec495d45a0165b848a Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 17 Apr 2018 14:07:57 -0400 Subject: [PATCH 05/10] Output minimal stats to the console during build --- webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.config.js b/webpack.config.js index 6ec40a2b4..b48f3daef 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -31,6 +31,7 @@ module.exports = function (_, env) { mode: isProd ? 'production' : 'development', entry: './src/index', devtool: isProd ? 'source-map' : 'inline-source-map', + stats: 'minimal', output: { filename: isProd ? '[name].[chunkhash:5].js' : '[name].js', chunkFilename: '[name].chunk.[chunkhash:5].js', From dec93a724f716a749dddf537ea9286ac766362de Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 17 Apr 2018 14:08:23 -0400 Subject: [PATCH 06/10] Fix for Workbox precaching way too many files --- webpack.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/webpack.config.js b/webpack.config.js index b48f3daef..1b5c41fb2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -241,6 +241,12 @@ module.exports = function (_, env) { swDest: 'sw.js', clientsClaim: true, skipWaiting: true, + exclude: [ + 'report.html', + 'manifest.json', + /(report\.html|manifest\.json|\.precache-manifest\..*\.json)$/, + /\.(?:map|pem|DS_Store)$/ + ], // allow for offline client-side routing: navigateFallback: '/', navigateFallbackBlacklist: [/\.[a-z0-9]+$/i] From 11bebfc836cd85b4fe5b1efca32e285629e910dd Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 17 Apr 2018 14:08:49 -0400 Subject: [PATCH 07/10] A11y: add aria attribute to give the upload field a name --- src/components/header/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx index 1499d0615..f513a16c4 100644 --- a/src/components/header/index.tsx +++ b/src/components/header/index.tsx @@ -40,13 +40,13 @@ export default class Header extends Component { - file_upload + file_upload menu - + ); } From 3037d17c8406dc4d0edbfe38f2b1f11eb25b3f0d Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 24 Apr 2018 14:03:44 -0400 Subject: [PATCH 08/10] improve tests --- config/prerender-loader.js | 26 ++- package-lock.json | 448 +++++++++++++++++++------------------ package.json | 4 - 3 files changed, 249 insertions(+), 229 deletions(-) diff --git a/config/prerender-loader.js b/config/prerender-loader.js index 67b8456a7..05998585f 100644 --- a/config/prerender-loader.js +++ b/config/prerender-loader.js @@ -1,6 +1,6 @@ const jsdom = require('jsdom'); const os = require('os'); -const util = require('util'); +// const util = require('util'); const path = require('path'); const loaderUtils = require('loader-utils'); const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin'); @@ -15,6 +15,7 @@ const FILENAME = 'ssr-bundle.js'; const PRERENDER_REG = /\{\{prerender(?::\s*([^}]+)\s*)?\}\}/; module.exports = function PrerenderLoader (content) { + // const { string, disabled, ...options } = getOptions() const options = loaderUtils.getOptions(this) || {}; const outputFilter = options.as === 'string' || options.string ? stringToModule : String; @@ -54,11 +55,13 @@ async function prerender (loaderContext, options, inject) { const context = parentCompiler.options.context || process.cwd(); const entry = './' + ((options.entry && [].concat(options.entry).pop().trim()) || path.relative(context, parentCompiler.options.entry)); - if (!inject && options.template) { - const loadModule = util.promisify(loaderContext.loadModule); - const source = await loadModule('!!raw-loader!' + path.resolve(context, options.template)); - options.templateContent = source; - } + // undocumented option (to remove): + // !!prerender-loader?template=src/index.html!src/index.js + // if (!inject && options.template) { + // const loadModule = util.promisify(loaderContext.loadModule); + // const source = await loadModule('!!raw-loader!' + path.resolve(context, options.template)); + // options.templateContent = source; + // } const outputOptions = { // fix for plugins not using outputfilesystem @@ -75,7 +78,8 @@ async function prerender (loaderContext, options, inject) { // Define PRERENDER to be true within the SSR bundle new DefinePlugin({ - PRERENDER: 'true' + PRERENDER: 'true', + document: 'undefined' // if (typeof document==='undefined') {} }).apply(compiler); // ... then define PRERENDER to be false within the client bundle @@ -116,7 +120,9 @@ async function prerender (loaderContext, options, inject) { const tpl = options.templateContent || ''; dom = new jsdom.JSDOM(tpl.replace(PRERENDER_REG, '
'), { + // don't track source locations for performance reasons includeNodeLocations: false, + // don't allow inline event handlers & script tag exec runScripts: 'outside-only' }); const { window } = dom; @@ -129,12 +135,14 @@ async function prerender (loaderContext, options, inject) { } // These are missing from JSDOM - window.requestAnimationFrame = setTimeout; - window.cancelAnimationFrame = clearTimeout; + let counter = 0; + window.requestAnimationFrame = () => ++counter; + window.cancelAnimationFrame = () => {}; // Invoke the SSR bundle within the JSDOM document and grab the exported/returned result result = window.eval(output + '\nPRERENDER_RESULT') || result; + // @todo this seems pointless if (window.PRERENDER_RESULT != null) { result = window.PRERENDER_RESULT; } diff --git a/package-lock.json b/package-lock.json index d6171f3b0..6caf552cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,131 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.40.tgz", - "integrity": "sha512-eVXQSbu/RimU6OKcK2/gDJVTFcxXJI4sHbIqw2mhwMZeQ2as/8AhS9DGkEDoHMBBNJZ5B0US63lF56x+KDcxiA==", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.40" - } - }, - "@babel/generator": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.40.tgz", - "integrity": "sha512-c91BQcXyTq/5aFV4afgOionxZS1dxWt8OghEx5Q52SKssdGRFSiMKnk9tGkev1pYULPJBqjSDZU2Pcuc58ffZw==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.40", - "jsesc": "2.5.1", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - } - } - }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.40.tgz", - "integrity": "sha512-cK9BVLtOfisSISTTHXKGvBc2OBh65tjEk4PgXhsSnnH0i8RP2v+5RCxoSlh2y/i+l2fxQqKqv++Qo5RMiwmRCA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.40", - "@babel/template": "7.0.0-beta.40", - "@babel/types": "7.0.0-beta.40" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.40.tgz", - "integrity": "sha512-MwquaPznI4cUoZEgHC/XGkddOXtqKqD4DvZDOyJK2LR9Qi6TbMbAhc6IaFoRX7CRTFCmtGeu8gdXW2dBotBBTA==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.40" - } - }, - "@babel/highlight": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.40.tgz", - "integrity": "sha512-mOhhTrzieV6VO7odgzFGFapiwRK0ei8RZRhfzHhb6cpX3QM8XXuCLXWjN8qBB7JReDdUR80V3LFfFrGUYevhNg==", - "dev": true, - "requires": { - "chalk": "2.3.1", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "@babel/template": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.40.tgz", - "integrity": "sha512-RlQiVB7eL7fxsKN6JvnCCwEwEL28CBYalXSgWWULuFlEHjtMoXBqQanSie3bNyhrANJx67sb+Sd/vuGivoMwLQ==", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.40", - "@babel/types": "7.0.0-beta.40", - "babylon": "7.0.0-beta.40", - "lodash": "4.17.5" - }, - "dependencies": { - "babylon": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", - "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==", - "dev": true - } - } - }, - "@babel/traverse": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.40.tgz", - "integrity": "sha512-h96SQorjvdSuxQ6hHFIuAa3oxnad1TA5bU1Zz88+XqzwmM5QM0/k2D+heXGGy/76gT5ajl7xYLKGiPA/KTyVhQ==", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.40", - "@babel/generator": "7.0.0-beta.40", - "@babel/helper-function-name": "7.0.0-beta.40", - "@babel/types": "7.0.0-beta.40", - "babylon": "7.0.0-beta.40", - "debug": "3.1.0", - "globals": "11.3.0", - "invariant": "2.2.3", - "lodash": "4.17.5" - }, - "dependencies": { - "babylon": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", - "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.40.tgz", - "integrity": "sha512-uXCGCzTgMZxcSUzutCPtZmXbVC+cvENgS2e0tRuhn+Y1hZnMb8IHP0Trq7Q2MB/eFmG5pKrAeTIUfQIe5kA4Tg==", - "dev": true, - "requires": { - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "2.0.0" - }, - "dependencies": { - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, "@material/animation": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@material/animation/-/animation-0.25.0.tgz", @@ -1235,28 +1110,6 @@ } } }, - "babel-eslint": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.2.tgz", - "integrity": "sha512-Qt2lz2egBxNYWqN9JIO2z4NOOf8i4b5JS6CFoYrOZZTDssueiV1jH/jsefyg+86SeNY3rB361/mi3kE1WK2WYQ==", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.40", - "@babel/traverse": "7.0.0-beta.40", - "@babel/types": "7.0.0-beta.40", - "babylon": "7.0.0-beta.40", - "eslint-scope": "3.7.1", - "eslint-visitor-keys": "1.0.0" - }, - "dependencies": { - "babylon": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", - "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==", - "dev": true - } - } - }, "babel-generator": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", @@ -2709,29 +2562,29 @@ "dev": true }, "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { - "ansi-styles": "3.2.0", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.2.0" + "supports-color": "5.4.0" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "1.9.1" } }, "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { "has-flag": "3.0.0" @@ -3446,6 +3299,12 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -4390,7 +4249,7 @@ "requires": { "ajv": "5.5.2", "babel-code-frame": "6.26.0", - "chalk": "2.3.1", + "chalk": "2.4.0", "concat-stream": "1.6.0", "cross-spawn": "5.1.0", "debug": "3.1.0", @@ -4427,47 +4286,193 @@ "text-table": "0.2.0" } }, - "eslint-config-developit": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/eslint-config-developit/-/eslint-config-developit-1.1.1.tgz", - "integrity": "sha512-Wc2jbDhWixq9IDfFBVcFHYaOo2i3qwdVH236jQKsiNpJlfErxm0KDT5SWZiDSWU/h6ppHsru94W3DGbUg/MjzA==", + "eslint-config-standard": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz", + "integrity": "sha512-oDdENzpViEe5fwuRCWla7AXQd++/oyIp8zP+iP9jiUPG6NBj3SHgdgtl/kTn00AjeN+1HNvavTKmYbMo+xMOlw==", + "dev": true + }, + "eslint-config-standard-jsx": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-5.0.0.tgz", + "integrity": "sha512-rLToPAEqLMPBfWnYTu6xRhm2OWziS2n40QFqJ8jAM8NSVzeVKTa3nclhsU4DpPJQRY60F34Oo1wi/71PN/eITg==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "dev": true, "requires": { - "babel-eslint": "8.2.2", - "eslint-plugin-compat": "2.2.0", - "eslint-plugin-jest": "21.13.0", - "eslint-plugin-mocha": "4.12.1", - "eslint-plugin-react": "7.7.0" + "debug": "2.6.9", + "resolve": "1.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, - "eslint-plugin-compat": { + "eslint-module-utils": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-2.2.0.tgz", - "integrity": "sha512-nMH9Ibga+VZVLGtyxx8Dry69NN+a7YykNoUA9NtL3QNTeciV4QAAOtBLykKDR0Q37wwtKME+Inlce7JOhy5dbg==", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "browserslist": "2.11.3", - "caniuse-db": "1.0.30000810", - "mdn-browser-compat-data": "0.0.20", - "requireindex": "1.2.0" + "debug": "2.6.9", + "pkg-dir": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + } } }, - "eslint-plugin-jest": { - "version": "21.13.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-21.13.0.tgz", - "integrity": "sha512-tR8bn4tZk1I5i6HstvucC5tprxJKeuyh0baP7xYasQq/RS/hPcmAgUE42d52R1ysg719OPKsFqkJDuyvNeolvg==", - "dev": true + "eslint-plugin-import": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.11.0.tgz", + "integrity": "sha1-Fa7qN6Z0mdhI6OmBgG1GJ7VQOBY=", + "dev": true, + "requires": { + "contains-path": "0.1.0", + "debug": "2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "0.3.2", + "eslint-module-utils": "2.2.0", + "has": "1.0.1", + "lodash": "4.17.5", + "minimatch": "3.0.4", + "read-pkg-up": "2.0.0", + "resolve": "1.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } }, - "eslint-plugin-mocha": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-4.12.1.tgz", - "integrity": "sha512-hxWtYHvLA0p/PKymRfDYh9Mxt5dYkg2Goy1vZDarTEEYfELP9ksga7kKG1NUKSQy27C8Qjc7YrSWTLUhOEOksA==", + "eslint-plugin-node": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-6.0.1.tgz", + "integrity": "sha512-Q/Cc2sW1OAISDS+Ji6lZS2KV4b7ueA/WydVWd1BECTQwVvfQy5JAi3glhINoKzoMnfnuRgNP+ZWKrGAbp3QDxw==", "dev": true, "requires": { - "ramda": "0.25.0" + "ignore": "3.3.7", + "minimatch": "3.0.4", + "resolve": "1.6.0", + "semver": "5.5.0" } }, + "eslint-plugin-promise": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz", + "integrity": "sha512-2WO+ZFh7vxUKRfR0cOIMrWgYKdR6S1AlOezw6pC52B6oYpd5WFghN+QHxvrRdZMtbo8h3dfUZ2o1rWb0UPbKtg==", + "dev": true + }, "eslint-plugin-react": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz", @@ -4480,6 +4485,12 @@ "prop-types": "15.6.1" } }, + "eslint-plugin-standard": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz", + "integrity": "sha1-NNDJFbRe3G8BA5PH7vOCOwhWXPI=", + "dev": true + }, "eslint-scope": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", @@ -7129,7 +7140,7 @@ "dev": true, "requires": { "ansi-escapes": "3.0.0", - "chalk": "2.3.1", + "chalk": "2.4.0", "cli-cursor": "2.1.0", "cli-width": "2.2.0", "external-editor": "2.1.0", @@ -8034,6 +8045,16 @@ "is-buffer": "1.1.6" } }, + "last-call-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", + "dev": true, + "requires": { + "lodash": "4.17.5", + "webpack-sources": "1.1.0" + } + }, "lazy-cache": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", @@ -8427,7 +8448,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.3.1" + "chalk": "2.4.0" } }, "log-update": { @@ -8882,15 +8903,6 @@ } } }, - "mdn-browser-compat-data": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/mdn-browser-compat-data/-/mdn-browser-compat-data-0.0.20.tgz", - "integrity": "sha512-DImQhKtc7umi/LI0licM3GVnKTxYoYmFUKnMjomfIvW8dO4B6UeQLWYQhf1jDTfEV9WZGjFTz3DOfcsnZO6WdA==", - "dev": true, - "requires": { - "extend": "3.0.1" - } - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -9864,6 +9876,16 @@ "is-wsl": "1.1.0" } }, + "optimize-css-assets-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-wcQJMk23VQAFdOYnF7pSTg3yvRsSmF3FBwuPf2MDE7e/AG4hoD5V+xxZkrhZEYZ6ZQezfu2qegprs0Z7Xc9xKA==", + "dev": true, + "requires": { + "cssnano": "3.10.0", + "last-call-webpack-plugin": "3.0.0" + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -11301,12 +11323,6 @@ "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", "dev": true }, - "ramda": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", - "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==", - "dev": true - }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", @@ -11385,6 +11401,12 @@ "unpipe": "1.0.0" } }, + "raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, "read-chunk": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", @@ -11775,12 +11797,6 @@ "resolve-from": "1.0.1" } }, - "requireindex": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", - "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", - "dev": true - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -13102,7 +13118,7 @@ "requires": { "ajv": "5.5.2", "ajv-keywords": "2.1.1", - "chalk": "2.3.1", + "chalk": "2.4.0", "lodash": "4.17.5", "slice-ansi": "1.0.0", "string-width": "2.1.1" @@ -13360,7 +13376,7 @@ "integrity": "sha512-dzgQnkAGY4sLqVw6t4LJH8FGR8j0zsPmC1Ff2ChzKhYO+hgWJkSEyrHTlSSZqn5oWr0Po7q/IRoeq8O4SNKQ9A==", "dev": true, "requires": { - "chalk": "2.3.1", + "chalk": "2.4.0", "enhanced-resolve": "4.0.0", "loader-utils": "1.1.0", "micromatch": "3.1.9", @@ -13648,7 +13664,7 @@ "requires": { "babel-code-frame": "6.26.0", "builtin-modules": "1.1.1", - "chalk": "2.3.1", + "chalk": "2.4.0", "commander": "2.14.1", "diff": "3.5.0", "glob": "7.1.2", @@ -14849,7 +14865,7 @@ "requires": { "acorn": "5.5.0", "bfj-node4": "5.2.1", - "chalk": "2.3.1", + "chalk": "2.4.0", "commander": "2.14.1", "ejs": "2.5.7", "express": "4.16.2", @@ -15191,7 +15207,7 @@ "integrity": "sha512-B53SD4N4BHpZdUwZcj4st2QT7gVfqZtqHDruC1N+K2sciq0Rt/3F1Dx6RlylVkcrToMLTaiaeT48k9Lq4iDVDA==", "dev": true, "requires": { - "chalk": "2.3.1", + "chalk": "2.4.0", "log-symbols": "2.2.0", "loglevelnext": "1.0.3", "uuid": "3.2.1" @@ -15630,7 +15646,7 @@ "integrity": "sha512-6/W7/B54OPHJXob0n0+pmkwFsirC8cokuQkPSmT/D0lCcSxkKtg/BA6ZnjUBIwjuGqmw3DTrT4en++htaUju5g==", "dev": true, "requires": { - "chalk": "2.3.1", + "chalk": "2.4.0", "debug": "3.1.0", "diff": "3.5.0", "escape-string-regexp": "1.0.5", @@ -15667,7 +15683,7 @@ "dev": true, "requires": { "async": "2.6.0", - "chalk": "2.3.1", + "chalk": "2.4.0", "cli-table": "0.3.1", "cross-spawn": "5.1.0", "dargs": "5.1.0", diff --git a/package.json b/package.json index 791334b4f..c592706cb 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "chalk": "^2.3.2", "clean-webpack-plugin": "^0.1.19", "copy-webpack-plugin": "^4.5.1", - "css": "^2.2.1", "css-loader": "^0.28.11", "ejs-loader": "^0.3.1", "eslint": "^4.18.2", @@ -65,12 +64,9 @@ "memory-fs": "^0.4.1", "mini-css-extract-plugin": "^0.3.0", "node-sass": "^4.7.2", - "nwmatcher": "^1.4.4", "optimize-css-assets-webpack-plugin": "^4.0.0", - "parse5": "^4.0.0", "preact-render-to-string": "^3.7.0", "preload-webpack-plugin": "github:GoogleChromeLabs/preload-webpack-plugin", - "pretty-bytes": "^4.0.2", "progress-bar-webpack-plugin": "^1.11.0", "raw-loader": "^0.5.1", "sass-loader": "^6.0.7", From 9db8fd8e7fb57b9ce219c1b98296a9e9de8223a1 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Tue, 1 May 2018 09:52:58 -0400 Subject: [PATCH 09/10] Remove all prerendering & critical CSS stuff --- config/critters-webpack-plugin.js | 409 ----------------- config/prerender-loader.js | 222 --------- config/prerender.js | 20 - package-lock.json | 727 +----------------------------- package.json | 11 +- webpack.config.js | 16 +- 6 files changed, 25 insertions(+), 1380 deletions(-) delete mode 100644 config/critters-webpack-plugin.js delete mode 100644 config/prerender-loader.js delete mode 100644 config/prerender.js diff --git a/config/critters-webpack-plugin.js b/config/critters-webpack-plugin.js deleted file mode 100644 index d0a9894cd..000000000 --- a/config/critters-webpack-plugin.js +++ /dev/null @@ -1,409 +0,0 @@ -const path = require('path'); -const parse5 = require('parse5'); -const nwmatcher = require('nwmatcher'); -const css = require('css'); -const prettyBytes = require('pretty-bytes'); - -const treeAdapter = parse5.treeAdapters.htmlparser2; - -const PLUGIN_NAME = 'critters-webpack-plugin'; - -const PARSE5_OPTS = { - treeAdapter -}; - -/** Critters: Webpack Plugin Edition! - * @class - * @param {Object} options - * @param {Boolean} [options.external=true] Fetch and inline critical styles from external stylesheets - * @param {Boolean} [options.async=false] Convert critical-inlined external stylesheets to load asynchronously (via link rel="preload" - see https://filamentgroup.com/lab/async-css.html) - * @param {Boolean} [options.preload=false] (requires `async` option) Append a new into instead of swapping the preload's rel attribute - * @param {Boolean} [options.fonts] If `true`, keeps critical `@font-face` rules and preloads them. If `false`, removes the rules and does not preload the fonts - * @param {Boolean} [options.preloadFonts=false] Preloads critical fonts (even those removed by `{fonts:false}`) - * @param {Boolean} [options.removeFonts=false] Remove all fonts (even critical ones) - * @param {Boolean} [options.compress=true] Compress resulting critical CSS - */ -module.exports = class CrittersWebpackPlugin { - constructor (options) { - this.options = options || {}; - this.urlFilter = this.options.filter; - if (this.urlFilter instanceof RegExp) { - this.urlFilter = this.urlFilter.test.bind(this.urlFilter); - } - } - - /** Invoked by Webpack during plugin initialization */ - apply (compiler) { - // hook into the compiler to get a Compilation instance... - compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => { - // ... which is how we get an "after" hook into html-webpack-plugin's HTML generation. - compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync(PLUGIN_NAME, (htmlPluginData, callback) => { - this.process(compiler, compilation, htmlPluginData) - .then(result => { callback(null, result); }) - .catch(callback); - }); - }); - } - - readFile (filename, encoding) { - return new Promise((resolve, reject) => { - this.fs.readFile(filename, encoding, (err, data) => { - if (err) reject(err); - else resolve(data); - }); - }); - } - - async process (compiler, compilation, htmlPluginData) { - const outputPath = compiler.options.output.path; - - // Parse the generated HTML in a DOM we can mutate - const document = parse5.parse(htmlPluginData.html, PARSE5_OPTS); - makeDomInteractive(document); - - // `external:false` skips processing of external sheets - if (this.options.external !== false) { - const externalSheets = document.querySelectorAll('link[rel="stylesheet"]'); - await Promise.all(externalSheets.map( - link => this.embedLinkedStylesheet(link, compilation, outputPath) - )); - } - - // go through all the style tags in the document and reduce them to only critical CSS - const styles = document.querySelectorAll('style'); - await Promise.all(styles.map( - style => this.processStyle(style, document) - )); - - // serialize the document back to HTML and we're done - const html = parse5.serialize(document, PARSE5_OPTS); - return { html }; - } - - /** Inline the target stylesheet referred to by a (assuming it passes `options.filter`) */ - async embedLinkedStylesheet (link, compilation, outputPath) { - const href = link.getAttribute('href'); - const document = link.ownerDocument; - - // skip filtered resources, or network resources if no filter is provided - if (this.urlFilter ? this.urlFilter(href) : href.match(/^(https?:)?\/\//)) return Promise.resolve(); - - // path on disk - const filename = path.resolve(outputPath, href.replace(/^\//, '')); - - // try to find a matching asset by filename in webpack's output (not yet written to disk) - const asset = compilation.assets[path.relative(outputPath, filename).replace(/^\.\//, '')]; - - // CSS loader is only injected for the first sheet, then this becomes an empty string - let cssLoaderPreamble = `function $loadcss(u,l){(l=document.createElement('link')).rel='stylesheet';l.href=u;document.head.appendChild(l)}`; - - const media = typeof this.options.media === 'string' ? this.options.media : 'all'; - - // { preload:'js', media:true } - // { preload:'js', media:'print' } - if (this.options.media) { - cssLoaderPreamble = cssLoaderPreamble.replace('l.href', "l.media='only x';l.onload=function(){l.media='" + media + "'};l.href"); - } - - // Attempt to read from assets, falling back to a disk read - const sheet = asset ? asset.source() : await this.readFile(filename, 'utf8'); - - // the reduced critical CSS gets injected into a new