diff --git a/packages/cli/lib/commands/build.js b/packages/cli/lib/commands/build.js index 44a4b304d..fb91815c0 100644 --- a/packages/cli/lib/commands/build.js +++ b/packages/cli/lib/commands/build.js @@ -1,8 +1,9 @@ const rimraf = require('rimraf'); const { resolve } = require('path'); const { promisify } = require('util'); -const { isDir, error } = require('../util'); +const { isDir, error, warn } = require('../util'); const runWebpack = require('../lib/webpack/run-webpack'); +const fastPrerender = require('../lib/fast-render'); const { validateArgs } = require('./validate-args'); const toBool = val => val === void 0 || (val === 'false' ? false : val); @@ -84,6 +85,10 @@ const options = [ name: '-v, --verbose', description: 'Verbose output', }, + { + name: '--experimental-fast-rendering', + description: 'Experimental pre-rendering. Highly unstable!', + }, ]; async function command(src, argv) { @@ -113,6 +118,13 @@ async function command(src, argv) { if (argv.json) { await runWebpack.writeJsonStats(stats); } + + if (argv['experimental-fast-rendering']) { + warn( + 'You have enabled experimental and unstable fast rendering. You might have to adjust template.html according to the new API.' + ); + await fastPrerender(argv, stats); + } } module.exports = { diff --git a/packages/cli/lib/lib/fast-render/index.js b/packages/cli/lib/lib/fast-render/index.js new file mode 100644 index 000000000..dec5f6cdd --- /dev/null +++ b/packages/cli/lib/lib/fast-render/index.js @@ -0,0 +1,92 @@ +const { info, warn } = require('../../util'); +const { join, resolve } = require('path'); +const { Worker } = require('worker_threads'); +const { writeFile, mkdir } = require('../../fs'); +const { PRERENDER_DATA_FILE_NAME } = require('../constants'); +const pool = require('./pool'); + +module.exports = async function fastPrerender( + { src, dest, cwd, prerenderUrls }, + stats +) { + let pages = [{ url: '/' }]; + if (prerenderUrls) { + try { + let result = require(resolve(cwd, prerenderUrls)); + if (typeof result.default !== 'undefined') { + result = result.default(); + } + if (typeof result === 'function') { + result = await result(); + } + if (typeof result === 'string') { + result = JSON.parse(result); + } + if (result instanceof Array) { + pages = result; + } + } catch (error) { + warn('Failed to load prerenderUrls file, using default!\n'); + } + } + + // eslint-disable-next-line no-console + console.log('\n\n'); + info( + `Prerendering ${pages.length} page${ + pages.length > 0 && 's' + } from ${prerenderUrls}.` + ); + const pageData = {}; + const renderedContents = await pool( + pages.map(data => { + pageData[data.url] = data; + return { + src, + dest, + cwd, + webpack: { + publicPath: stats.compilation.outputOptions.publicPath, + assets: Object.keys(stats.compilation.assets), + }, + data, + }; + }), + ({ src, dest, cwd, webpack, data }) => { + return new Promise(resolve => { + const worker = new Worker(join(__dirname, 'renderer.js'), { + workerData: { + src, + dest, + cwd, + webpack, + data, + }, + }); + worker.once('message', async preRenderContent => { + worker.terminate(); + resolve(preRenderContent); + }); + }); + } + ); + renderedContents.forEach(async preRenderedContent => { + const dirPath = preRenderedContent.url.endsWith('.html') + ? preRenderedContent.url.substring( + 0, + preRenderedContent.url.lastIndexOf('/') + ) + : preRenderedContent.url; + const filePath = preRenderedContent.url.endsWith('.html') + ? preRenderedContent.url + : join(preRenderedContent.url, 'index.html'); + await mkdir(join(dest, dirPath), { + recursive: true, + }); + await writeFile(join(dest, filePath), preRenderedContent.content); + await writeFile( + join(dest, dirPath, PRERENDER_DATA_FILE_NAME), + JSON.stringify(pageData[preRenderedContent.url]) + ); + }); +}; diff --git a/packages/cli/lib/lib/fast-render/pool.js b/packages/cli/lib/lib/fast-render/pool.js new file mode 100644 index 000000000..37899ad06 --- /dev/null +++ b/packages/cli/lib/lib/fast-render/pool.js @@ -0,0 +1,25 @@ +const { cpus } = require('os'); +const resolved = Promise.resolve(); +module.exports = async function pool( + items, + iteratorFn, + concurrency = cpus().length +) { + const itemsLength = items.length; + const returnable = []; + const executing = []; + for (const item of items) { + const promise = resolved.then(() => iteratorFn(item, items)); + returnable.push(promise); + if (concurrency <= itemsLength) { + const execute = promise.then(() => + executing.splice(executing.indexOf(execute), 1) + ); + executing.push(execute); + if (executing.length >= concurrency) { + await Promise.race(executing); + } + } + } + return Promise.all(returnable); +}; diff --git a/packages/cli/lib/lib/fast-render/renderer.js b/packages/cli/lib/lib/fast-render/renderer.js new file mode 100644 index 000000000..057a49564 --- /dev/null +++ b/packages/cli/lib/lib/fast-render/renderer.js @@ -0,0 +1,72 @@ +const { isMainThread, parentPort, workerData } = require('worker_threads'); +const { readFile } = require('../../fs'); +const { join } = require('path'); +const prerender = require('../webpack/prerender'); +const ejs = require('ejs'); +const { minify } = require('html-minifier'); + +async function render(src, dest, cwd, webpack, data) { + const { url, title, ...routeData } = data; + const templateSrc = await readFile(join(src, 'template.html'), 'utf-8'); + const manifest = await readFile(join(dest, 'manifest.json'), 'utf-8'); + const headEndTemplate = await readFile( + join(__dirname, '..', '..', 'resources', 'head-end-modern.ejs'), + 'utf-8' + ); + const bodyEndTemplate = await readFile( + join(__dirname, '..', '..', 'resources', 'body-end-modern.ejs'), + 'utf-8' + ); + const options = { + url, + manifest, + ssr: () => { + const params = { + ...data, + CLI_DATA: { + preRenderData: data, + }, + }; + return prerender({ cwd, dest, src }, params); + }, + CLI_DATA: { url, ...routeData }, + webpack, + }; + const htmlWebpackPlugin = { + options: { + ...options, + title: title || manifest.name || manifest.short_name || 'Preact App', + }, + }; + + const headEnd = ejs.render(headEndTemplate, { + options, + htmlWebpackPlugin, + }); + + const bodyEnd = ejs.render(bodyEndTemplate, { + options, + htmlWebpackPlugin, + }); + const template = templateSrc + .replace(/<%[=]?\s+preact\.title\s+%>/, '<%= options.title %>') + .replace(/<%\s+preact\.headEnd\s+%>/, headEnd) + .replace(/<%\s+preact\.bodyEnd\s+%>/, bodyEnd); + parentPort.postMessage({ + url, + content: minify( + ejs.render(template, { + options, + htmlWebpackPlugin, + }), + { + collapseWhitespace: true, + } + ), + }); +} + +if (!isMainThread) { + const { src, dest, cwd, webpack, data } = workerData; + render(src, dest, cwd, webpack, data); +} diff --git a/packages/cli/lib/lib/webpack/render-html-plugin.js b/packages/cli/lib/lib/webpack/render-html-plugin.js index 55d365e87..1e2828e0b 100644 --- a/packages/cli/lib/lib/webpack/render-html-plugin.js +++ b/packages/cli/lib/lib/webpack/render-html-plugin.js @@ -55,7 +55,7 @@ module.exports = async function (config) { writeFileSync(template, content); } - const htmlWebpackConfig = (values) => { + const htmlWebpackConfig = values => { const { url, title, ...routeData } = values; // Do not create a folder if the url is for a specific file. const filename = url.endsWith('.html') @@ -107,7 +107,8 @@ module.exports = async function (config) { let pages = [{ url: '/' }]; - if (config.prerenderUrls) { + // eslint-disable-next-line no-constant-condition + if (config.prerenderUrls && !config['experimental-fast-rendering']) { if (existsSync(resolve(cwd, config.prerenderUrls))) { try { let result = require(resolve(cwd, config.prerenderUrls)); @@ -151,14 +152,14 @@ module.exports = async function (config) { * And we dont have to cache every single html file. * Go easy on network usage of clients. */ - !pages.find((page) => page.url === PREACT_FALLBACK_URL) && + !pages.find(page => page.url === PREACT_FALLBACK_URL) && pages.push({ url: PREACT_FALLBACK_URL }); const resultPages = pages .map(htmlWebpackConfig) - .map((conf) => new HtmlWebpackPlugin(conf)) + .map(conf => new HtmlWebpackPlugin(conf)) .concat([new HtmlWebpackExcludeAssetsPlugin()]) - .concat([...pages.map((page) => new PrerenderDataExtractPlugin(page))]); + .concat([...pages.map(page => new PrerenderDataExtractPlugin(page))]); return resultPages; }; @@ -171,7 +172,7 @@ class PrerenderDataExtractPlugin { this.data_ = JSON.stringify(cliData.preRenderData || {}); } apply(compiler) { - compiler.hooks.emit.tap('PrerenderDataExtractPlugin', (compilation) => { + compiler.hooks.emit.tap('PrerenderDataExtractPlugin', compilation => { if (this.location_ === `${PREACT_FALLBACK_URL}/`) { // We dont build prerender data for `200.html`. It can re-use the one for homepage. return; diff --git a/packages/cli/lib/resources/body-end-modern.ejs b/packages/cli/lib/resources/body-end-modern.ejs new file mode 100644 index 000000000..3ae32ece3 --- /dev/null +++ b/packages/cli/lib/resources/body-end-modern.ejs @@ -0,0 +1,13 @@ +<%- options.ssr() %> +<% /* Fix for safari < 11 nomodule bug. TODO: Do the following only for safari. */ %> + + +<% + /*Fetch and Promise polyfills are not needed for browsers that support type=module + Please re-evaluate below line if adding more polyfills.*/ +%> + + + diff --git a/packages/cli/lib/resources/head-end-modern.ejs b/packages/cli/lib/resources/head-end-modern.ejs new file mode 100644 index 000000000..6736b4df4 --- /dev/null +++ b/packages/cli/lib/resources/head-end-modern.ejs @@ -0,0 +1,5 @@ + +<% if (options.manifest.theme_color) { %> + +<% } %> + diff --git a/packages/cli/package.json b/packages/cli/package.json index 7a5b916d2..24cc8ef93 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -77,6 +77,7 @@ "@babel/plugin-transform-react-jsx": "^7.9.0", "@babel/preset-env": "^7.9.0", "@babel/preset-typescript": "^7.9.0", + "@kristoferbaxter/async": "^1.0.0", "@preact/async-loader": "^3.0.1", "@prefresh/webpack": "^1.0.2", "autoprefixer": "^9.6.0", @@ -100,6 +101,7 @@ "get-port": "^5.0.0", "gittar": "^0.1.0", "glob": "^7.1.4", + "html-minifier": "^4.0.0", "html-webpack-exclude-assets-plugin": "0.0.7", "html-webpack-plugin": "^3.2.0", "ip": "^1.1.5", @@ -135,6 +137,7 @@ "webpack-bundle-analyzer": "^3.3.2", "webpack-dev-server": "^3.4.1", "webpack-fix-style-only-entries": "^0.5.1", + "webpack-manifest-plugin": "^2.2.0", "webpack-merge": "^4.1.0", "webpack-plugin-replace": "^1.2.0", "which": "^2.0.2", diff --git a/packages/cli/tests/build.test.js b/packages/cli/tests/build.test.js index 65b4ddb12..f10313a24 100644 --- a/packages/cli/tests/build.test.js +++ b/packages/cli/tests/build.test.js @@ -244,4 +244,25 @@ describe('preact build', () => { expect(mockExit).toHaveBeenCalledWith(1); mockExit.mockRestore(); }); + + it('should build with experimental fast rendering', async () => { + let dir = await subject('experimental-rendering'); + await build(dir); + const buildDir = join(dir, 'build'); + const prerenderedContentByHTMLWebpackPlugin = await readFile( + join(buildDir, 'index.html'), + 'utf-8' + ); + dir = await subject('experimental-rendering'); + await build(dir, { + 'experimental-fast-rendering': true, + }); + const prerenderedContentByExperimentalRendered = await readFile( + join(buildDir, 'index.html'), + 'utf-8' + ); + expect(prerenderedContentByHTMLWebpackPlugin).toEqual( + prerenderedContentByExperimentalRendered + ); + }); }); diff --git a/packages/cli/tests/subjects/experimental-rendering/index.js b/packages/cli/tests/subjects/experimental-rendering/index.js new file mode 100644 index 000000000..dcf3bb109 --- /dev/null +++ b/packages/cli/tests/subjects/experimental-rendering/index.js @@ -0,0 +1,3 @@ +import { h } from 'preact'; + +export default () =>

This is an app with custom template

; diff --git a/packages/cli/tests/subjects/experimental-rendering/manifest.json b/packages/cli/tests/subjects/experimental-rendering/manifest.json new file mode 100644 index 000000000..307721828 --- /dev/null +++ b/packages/cli/tests/subjects/experimental-rendering/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "Experimental renderer", + "short_name": "Experimental renderer", + "start_url": "/", + "display": "standalone", + "orientation": "portrait", + "background_color": "#fff", + "theme_color": "#fff", + "icons": [ + { + "src": "/assets/logo/android-chrome-192x192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "/assets/logo/android-chrome-512x512.png", + "type": "image/png", + "sizes": "512x512" + }, + { + "src": "/assets/logo/apple-touch-icon.png", + "type": "image/png", + "sizes": "180x180" + } + ] +} diff --git a/packages/cli/tests/subjects/experimental-rendering/package.json b/packages/cli/tests/subjects/experimental-rendering/package.json new file mode 100644 index 000000000..94671fbec --- /dev/null +++ b/packages/cli/tests/subjects/experimental-rendering/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "name": "preact-webpack" +} diff --git a/packages/cli/tests/subjects/experimental-rendering/template.html b/packages/cli/tests/subjects/experimental-rendering/template.html new file mode 100644 index 000000000..770c48b2b --- /dev/null +++ b/packages/cli/tests/subjects/experimental-rendering/template.html @@ -0,0 +1,15 @@ + + + + + <% preact.title %> + + + + + <% preact.headEnd %> + + + <% preact.bodyEnd %> + + diff --git a/yarn.lock b/yarn.lock index f10cda2b7..79ba32fa3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1458,6 +1458,11 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@kristoferbaxter/async@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@kristoferbaxter/async/-/async-1.0.0.tgz#33c8e8c6b055f15f4ee8a711b9594336a6fb1671" + integrity sha512-CAQQQ8mUUBKI2P7stYojHwHb+ShdFN8SrC90/96V4m2XlDSctXAA+5PD5CqCICs0o5c/83cYJCeub+FSetKLrw== + "@lerna/add@3.21.0": version "3.21.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b" @@ -3860,6 +3865,14 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + call-limit@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/call-limit/-/call-limit-1.1.1.tgz#ef15f2670db3f1992557e2d965abc459e6e358d4" @@ -3894,7 +3907,7 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@3.0.x: +camel-case@3.0.x, camel-case@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= @@ -4131,7 +4144,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -clean-css@4.2.x: +clean-css@4.2.x, clean-css@^4.2.1: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== @@ -4361,7 +4374,7 @@ commander@2.17.x: resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@^2.18.0, commander@^2.20.0: +commander@^2.18.0, commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -5724,6 +5737,24 @@ es-abstract@^1.18.0-next.0: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" +es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -6516,7 +6547,7 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^7.0.1: +fs-extra@^7.0.0, fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== @@ -6663,6 +6694,15 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" + integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -7087,7 +7127,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@1.2.x: +he@1.2.x, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -7188,6 +7228,19 @@ html-minifier@^3.2.3: relateurl "0.2.x" uglify-js "3.4.x" +html-minifier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56" + integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig== + dependencies: + camel-case "^3.0.0" + clean-css "^4.2.1" + commander "^2.19.0" + he "^1.2.0" + param-case "^2.1.1" + relateurl "^0.2.7" + uglify-js "^3.5.1" + html-webpack-exclude-assets-plugin@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/html-webpack-exclude-assets-plugin/-/html-webpack-exclude-assets-plugin-0.0.7.tgz#ee69906adb3d869e4e29f29b0f3e99b53fa87c99" @@ -7679,6 +7732,11 @@ is-callable@^1.1.4, is-callable@^1.2.0: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== +is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + is-ci@^1.0.10: version "1.2.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" @@ -9248,7 +9306,7 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.3, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.10: +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.3, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.10: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -10493,6 +10551,16 @@ object.assign@^4.1.1: has-symbols "^1.0.1" object-keys "^1.1.1" +object.entries@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" + integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + object.entries@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" @@ -10904,7 +10972,7 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -param-case@2.1.x: +param-case@2.1.x, param-case@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= @@ -12262,7 +12330,7 @@ regjsparser@^0.6.4: dependencies: jsesc "~0.5.0" -relateurl@0.2.x: +relateurl@0.2.x, relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= @@ -14150,6 +14218,11 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.10.4.tgz#dd680f5687bc0d7a93b14a3482d16db6eba2bfbb" integrity sha512-kBFT3U4Dcj4/pJ52vfjCSfyLyvG9VYYuGYPmrPvAxRw/i7xHiT4VvCev+uiEMcEEiu6UNB6KgWmGtSUYIWScbw== +uglify-js@^3.5.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.1.tgz#78307f539f7b9ca5557babb186ea78ad30cc0375" + integrity sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ== + uid-number@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -14662,6 +14735,16 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" +webpack-manifest-plugin@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz#19ca69b435b0baec7e29fbe90fb4015de2de4f16" + integrity sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ== + dependencies: + fs-extra "^7.0.0" + lodash ">=3.5 <5" + object.entries "^1.1.0" + tapable "^1.0.0" + webpack-merge@^4.1.0: version "4.2.2" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d"