Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ on:
branches:
- master
- next
pull_request:
branches:
- master
- next
pull_request: {}

jobs:
test:
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
**/tests/output
**/package.json
**/*.ejs
.changeset/*.md
113 changes: 66 additions & 47 deletions packages/cli/lib/lib/webpack/render-html-plugin.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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)) {
Expand All @@ -25,8 +27,9 @@ module.exports = async function (config) {

if (config.template) {
const templatePathFromArg = resolve(cwd, config.template);
if (existsSync(templatePathFromArg)) template = templatePathFromArg;
else {
if (existsSync(templatePathFromArg)) {
template = templatePathFromArg;
} else {
warn(`Template not found at ${templatePathFromArg}`);
}
}
Expand All @@ -36,10 +39,7 @@ module.exports = async function (config) {
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);

Expand All @@ -54,47 +54,66 @@ module.exports = async function (config) {
}

const htmlWebpackConfig = values => {
const { url, title, ...routeData } = values;
return Object.assign(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';

return {
title,
filename: resolve(dest, url.substring(1), 'index.html'),
template: `!!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: 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 ? prerender({ cwd, dest, src }, values) : '';
},
scriptLoading: 'defer',
CLI_DATA: { preRenderData: { url, ...routeData } },
});
// excludeChunks: ['bundle', 'polyfills'],
};
};

let pages = [{ url: '/' }];
Expand Down Expand Up @@ -141,18 +160,18 @@ module.exports = async function (config) {
return pages
.map(htmlWebpackConfig)
.map(conf => new HtmlWebpackPlugin(conf))
.concat([new HtmlWebpackExcludeAssetsPlugin()])
.concat([new HtmlWebpackSkipAssetsPlugin()])
.concat([...pages.map(page => new PrerenderDataExtractPlugin(page))]);
};

// 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 => {
let path = this.location_ + PRERENDER_DATA_FILE_NAME;
Expand Down
12 changes: 7 additions & 5 deletions packages/cli/lib/lib/webpack/webpack-base-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function getSassConfiguration(...includePaths) {
return config;
}

module.exports = function (env) {
module.exports = function createBaseConfig(env) {
const { cwd, isProd, isWatch, src, source } = env;
const babelConfigFile = env.babelConfig || '.babelrc';
const IS_SOURCE_PREACT_X_OR_ABOVE = isInstalledVersionPreactXOrAbove(cwd);
Expand Down Expand Up @@ -241,9 +241,10 @@ module.exports = function (env) {
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
sourceMap: true,
plugins: postcssPlugins,
postcssOptions: {
plugins: postcssPlugins,
},
},
},
],
Expand All @@ -263,9 +264,10 @@ module.exports = function (env) {
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
sourceMap: true,
plugins: postcssPlugins,
postcssOptions: {
plugins: postcssPlugins,
},
},
},
],
Expand Down
12 changes: 6 additions & 6 deletions packages/cli/lib/lib/webpack/webpack-client-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const webpack = require('webpack');
const { resolve, join } = require('path');
const { existsSync } = require('fs');
const { isInstalledVersionPreactXOrAbove } = require('./utils');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const { filter } = require('minimatch');
const SizePlugin = require('size-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
Expand Down Expand Up @@ -86,8 +86,8 @@ async function clientConfig(env) {
new PushManifestPlugin(env),
...(await renderHTMLPlugin(env)),
...getBabelEsmPlugin(env),
new CopyWebpackPlugin(
[
new CopyWebpackPlugin({
patterns: [
existsSync(source('manifest.json')) && { from: 'manifest.json' },
// copy any static files
existsSync(source('assets')) && { from: 'assets', to: 'assets' },
Expand All @@ -101,8 +101,8 @@ async function clientConfig(env) {
from: resolve(source('static')),
to: '.',
},
].filter(Boolean)
),
].filter(Boolean),
}),
],
};
}
Expand Down Expand Up @@ -321,7 +321,7 @@ function isDev(config) {
};
}

module.exports = async function (env) {
module.exports = async function createClientConfig(env) {
return merge(
baseConfig(env),
await clientConfig(env),
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/lib/lib/webpack/webpack-server-config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { resolve } = require('path');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack-base-config');

function serverConfig(env) {
Expand All @@ -26,6 +26,6 @@ function serverConfig(env) {
};
}

module.exports = function (env) {
module.exports = function createServerConfig(env) {
return merge(baseConfig(env), serverConfig(env));
};
16 changes: 8 additions & 8 deletions packages/cli/lib/resources/body-end.ejs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<%= htmlWebpackPlugin.options.ssr() %>
<%= cli.ssr %>
<script type="__PREACT_CLI_DATA__">
<%= encodeURI(JSON.stringify(htmlWebpackPlugin.options.CLI_DATA)) %>
<%= encodeURI(JSON.stringify(cli.CLI_DATA)) %>
</script>
<% 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) { %>
<% /* Fix for safari < 11 nomodule bug. TODO: Do the following only for safari. */ %>
<script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
<script crossorigin="anonymous" src="<%= htmlWebpackPlugin.files.publicPath %><%= webpack.assets.filter(entry => entry.name.match(/bundle(\.\w{5})?.esm.js$/))[0].name %>" type="module"></script>
<script crossorigin="anonymous" src="<%= htmlWebpackPlugin.files.js.filter(entry => entry.match(/bundle(\.\w{5})?.esm.js$/))[0] %>" type="module"></script>
<%
/*Fetch and Promise polyfills are not needed for browsers that support type=module
Please re-evaluate below line if adding more polyfills.*/
%>
<script nomodule src="<%= htmlWebpackPlugin.files.chunks["polyfills"].entry %>"></script>
<script nomodule defer src="<%= htmlWebpackPlugin.files.chunks['bundle'].entry %>"></script>
<script nomodule src="<%= cli.entrypoints['polyfills'] %>"></script>
<script nomodule defer src="<%= cli.entrypoints['bundle'] %>"></script>
<% } else { %>
<script <%= htmlWebpackPlugin.options.scriptLoading %> src="<%= htmlWebpackPlugin.files.chunks['bundle'].entry %>"></script>
<script nomodule src="<%= htmlWebpackPlugin.files.chunks["polyfills"].entry %>"></script>
<script <%= htmlWebpackPlugin.options.scriptLoading %> src="<%= cli.entrypoints['bundle'] %>"></script>
<script nomodule src="<%= cli.entrypoints['polyfills'] %>"></script>
<% } %>
11 changes: 5 additions & 6 deletions packages/cli/lib/resources/head-end.ejs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<link rel="manifest" href="<%= htmlWebpackPlugin.files.publicPath %>manifest.json">
<% if (htmlWebpackPlugin.options.manifest.theme_color) { %>
<meta name="theme-color" content="<%= htmlWebpackPlugin.options.manifest.theme_color %>">
<% if (cli.manifest.theme_color) { %>
<meta name="theme-color" content="<%= 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 `<script type=module` tag, other lazy loaded bundles are from webpack so its not needed */ %>
<link rel="preload" href="<%= htmlWebpackPlugin.files.publicPath + file %>" as="<%= file.match(/\.css$/)?'style':'script' %>" <%= file.match(/bundle\.\w{5}\.esm\.js$/)?'crossorigin="anonymous"':'' %>>
<% } %>
Expand Down
Loading