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 00114ac8f..bae6ee996 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');
@@ -15,8 +17,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)) {
@@ -32,14 +34,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);
@@ -54,53 +53,69 @@ 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,
+ 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, 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: '/' }];
@@ -151,7 +166,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([
@@ -163,10 +178,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 0ffb77a3d..dc3f26b61 100644
--- a/packages/cli/lib/lib/webpack/webpack-client-config.js
+++ b/packages/cli/lib/lib/webpack/webpack-client-config.js
@@ -256,7 +256,7 @@ function isProd(env) {
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 `
+ <%= cli.ssr %>
+
<% preact.bodyEnd %>