diff --git a/.changeset/rude-walls-dress.md b/.changeset/rude-walls-dress.md new file mode 100644 index 000000000..188b4e587 --- /dev/null +++ b/.changeset/rude-walls-dress.md @@ -0,0 +1,6 @@ +--- +'preact-cli': minor +'@preact/prerender-data-provider': patch +--- + +Updates to use html-webpack-plugin v4 diff --git a/packages/cli/lib/lib/webpack/render-html-plugin.js b/packages/cli/lib/lib/webpack/render-html-plugin.js index 782521a08..1b0a19f33 100644 --- a/packages/cli/lib/lib/webpack/render-html-plugin.js +++ b/packages/cli/lib/lib/webpack/render-html-plugin.js @@ -1,7 +1,9 @@ const { resolve, join } = require('path'); const os = require('os'); const { existsSync, readFileSync, writeFileSync, mkdirSync } = require('fs'); -const HtmlWebpackExcludeAssetsPlugin = require('html-webpack-exclude-assets-plugin'); +const { + HtmlWebpackSkipAssetsPlugin, +} = require('html-webpack-skip-assets-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const prerender = require('./prerender'); const createLoadManifest = require('./create-load-manifest'); @@ -17,8 +19,8 @@ function read(path) { return readFileSync(resolve(__dirname, path), 'utf-8'); } -module.exports = async function (config) { - const { cwd, dest, isProd, src } = config; +module.exports = async function renderHTMLPlugin(config) { + const { cwd, dest, src } = config; const inProjectTemplatePath = resolve(src, 'template.html'); let template = defaultTemplate; if (existsSync(inProjectTemplatePath)) { @@ -34,14 +36,11 @@ module.exports = async function (config) { } let content = read(template); - if (/preact\.headEnd|preact\.bodyEnd/.test(content)) { + if (/preact\.(title|headEnd|bodyEnd)/.test(content)) { const headEnd = read('../../resources/head-end.ejs'); const bodyEnd = read('../../resources/body-end.ejs'); content = content - .replace( - /<%[=]?\s+preact\.title\s+%>/, - '<%= htmlWebpackPlugin.options.title %>' - ) + .replace(/<%[=]?\s+preact\.title\s+%>/, '<%= cli.title %>') .replace(/<%\s+preact\.headEnd\s+%>/, headEnd) .replace(/<%\s+preact\.bodyEnd\s+%>/, bodyEnd); @@ -56,53 +55,70 @@ module.exports = async function (config) { } const htmlWebpackConfig = values => { - const { url, title, ...routeData } = values; + let { url, title, ...routeData } = values; + + title = + title || + config.title || + config.manifest.name || + config.manifest.short_name || + (config.pkg.name || '').replace(/^@[a-z]\//, '') || + 'Preact App'; + // Do not create a folder if the url is for a specific file. const filename = url.endsWith('.html') ? resolve(dest, url.substring(1)) : resolve(dest, url.substring(1), 'index.html'); - return Object.assign(values, { + + return { + title, filename, template: `!!${require.resolve('ejs-loader')}?esModule=false!${template}`, - minify: isProd && { - collapseWhitespace: true, - removeScriptTypeAttributes: true, - removeRedundantAttributes: true, - removeStyleLinkTypeAttributes: true, - removeComments: true, + templateParameters: (compilation, assets, assetTags, options) => { + let entrypoints = {}; + compilation.entrypoints.forEach((entrypoint, name) => { + let entryFiles = entrypoint.getFiles(); + entrypoints[name] = + assets.publicPath + + entryFiles.find(file => /\.(m?js)(\?|$)/.test(file)); + }); + + let loadManifest = compilation.assets['push-manifest.json'] + ? JSON.parse(compilation.assets['push-manifest.json'].source()) + : createLoadManifest( + compilation.assets, + config.esm, + compilation.namedChunkGroups + ); + + return { + cli: { + title, + url, + manifest: config.manifest, + inlineCss: config['inline-css'], + preload: config.preload, + config, + preRenderData: values, + CLI_DATA: { preRenderData: { url, ...routeData } }, + ssr: config.prerender ? prerender({ cwd, dest, src }, values) : '', + loadManifest, + entrypoints, + }, + htmlWebpackPlugin: { + tags: assetTags, + files: assets, + options, + }, + }; }, + inject: true, + scriptLoading: 'defer', favicon: existsSync(resolve(src, 'assets/favicon.ico')) ? 'assets/favicon.ico' : '', - inject: true, - compile: true, - inlineCss: config['inline-css'], - preload: config.preload, - manifest: config.manifest, - title: - title || - config.title || - config.manifest.name || - config.manifest.short_name || - (config.pkg.name || '').replace(/^@[a-z]\//, '') || - 'Preact App', excludeAssets: [/(bundle|polyfills)(\..*)?\.js$/], - createLoadManifest: (assets, namedChunkGroups) => { - if (assets['push-manifest.json']) { - return JSON.parse(assets['push-manifest.json'].source()); - } - return createLoadManifest(assets, config.esm, namedChunkGroups); - }, - config, - url, - ssr() { - return config.prerender && url !== PREACT_FALLBACK_URL - ? prerender({ cwd, dest, src }, values) - : ''; - }, - scriptLoading: 'defer', - CLI_DATA: { preRenderData: { url, ...routeData } }, - }); + }; }; let pages = [{ url: '/' }]; @@ -158,7 +174,7 @@ module.exports = async function (config) { const resultPages = pages .map(htmlWebpackConfig) .map(conf => new HtmlWebpackPlugin(conf)) - .concat([new HtmlWebpackExcludeAssetsPlugin()]); + .concat([new HtmlWebpackSkipAssetsPlugin()]); return config.prerender ? resultPages.concat([ @@ -170,10 +186,9 @@ module.exports = async function (config) { // Adds a preact_prerender_data in every folder so that the data could be fetched separately. class PrerenderDataExtractPlugin { constructor(page) { - const cliData = page.CLI_DATA || {}; - const { url } = cliData.preRenderData || {}; + const url = page.url; this.location_ = url.endsWith('/') ? url : url + '/'; - this.data_ = JSON.stringify(cliData.preRenderData || {}); + this.data_ = JSON.stringify(page || {}); } apply(compiler) { compiler.hooks.emit.tap('PrerenderDataExtractPlugin', compilation => { diff --git a/packages/cli/lib/lib/webpack/webpack-client-config.js b/packages/cli/lib/lib/webpack/webpack-client-config.js index 31e906621..3d9e2dfd1 100644 --- a/packages/cli/lib/lib/webpack/webpack-client-config.js +++ b/packages/cli/lib/lib/webpack/webpack-client-config.js @@ -252,7 +252,7 @@ function isProd(config) { preload: 'media', pruneSource: false, logLevel: 'silent', - additionalStylesheets: ['*.css'], + additionalStylesheets: ['route-*.css'], }) ); } diff --git a/packages/cli/lib/resources/body-end.ejs b/packages/cli/lib/resources/body-end.ejs index 902141242..0444b0725 100644 --- a/packages/cli/lib/resources/body-end.ejs +++ b/packages/cli/lib/resources/body-end.ejs @@ -1,18 +1,18 @@ -<%= htmlWebpackPlugin.options.ssr() %> -<% if (htmlWebpackPlugin.options.config.prerender === true) { %> +<%= cli.ssr %> +<% if (cli.config.prerender === true) { %> <% } %> -<% if (webpack.assets.filter(entry => entry.name.match(/bundle(\.\w{5})?.esm.js$/)).length > 0) { %> - +<% if (htmlWebpackPlugin.files.js.filter(entry => entry.match(/bundle(\.\w{5})?.esm.js$/)).length > 0) { %> + <% /*Fetch and Promise polyfills are not needed for browsers that support type=module Please re-evaluate below line if adding more polyfills.*/ %> - - + + <% } else { %> - - + + <% } %> diff --git a/packages/cli/lib/resources/head-end.ejs b/packages/cli/lib/resources/head-end.ejs index b5bb729aa..8ca472a8b 100644 --- a/packages/cli/lib/resources/head-end.ejs +++ b/packages/cli/lib/resources/head-end.ejs @@ -1,11 +1,10 @@ -<% if (htmlWebpackPlugin.options.manifest.theme_color) { %> - +<% if (cli.manifest.theme_color) { %> + <% } %> -<% const loadManifest = htmlWebpackPlugin.options.createLoadManifest(compilation.assets, webpack.namedChunkGroups);%> -<% const filesRegexp = htmlWebpackPlugin.options.inlineCss ? /\.(chunk\.\w{5}\.css|js)$/ : /\.(css|js)$/;%> -<% for (const file in loadManifest[htmlWebpackPlugin.options.url]) { %> - <% if (htmlWebpackPlugin.options.preload && file && file.match(filesRegexp)) { %> +<% const filesRegexp = cli.inlineCss ? /\.(chunk\.\w{5}\.css|js)$/ : /\.(css|js)$/;%> +<% for (const file in cli.loadManifest[cli.url]) { %> + <% if (cli.preload && file && file.match(filesRegexp)) { %> <% /* crossorigin for main bundle as that is loaded from ` +