diff --git a/.changeset/sweet-snakes-cheat.md b/.changeset/sweet-snakes-cheat.md
new file mode 100644
index 000000000..4f87f1b9e
--- /dev/null
+++ b/.changeset/sweet-snakes-cheat.md
@@ -0,0 +1,12 @@
+---
+'preact-cli': major
+---
+
+- Upgrades to Webpack v5
+ - Any custom configuration you do in your `preact.config.js` may need to be altered to account for this. Plugins may need replacements or different option formats.
+
+- `--esm` flag has been removed
+ - Dual output is now enabled by default in production builds.
+
+- `.babelrc` no longer overwrites matching keys
+ - Instead, the config will be merged in to the default. The default still takes precedence when there are conflicts, so you will still need to use your `preact.config.js` if you want to edit or remove default plugins or presets.
diff --git a/.eslintignore b/.eslintignore
index 66e817fc8..4ec2f1666 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
**/node_modules
**/tests/output
+**/tests/subjects/*/preact.config.js
**/*.d.ts
diff --git a/README.md b/README.md
index 743d83e4b..a33f96eef 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ Note: If you don't specify enough data to the `npx preact-cli create` command, i
Create a production build
-You can disable `default: true` flags by prefixing them with `--no-`; for example, `--no-sw`, `--no-esm`, and `--no-inline-css`.
+You can disable `default: true` flags by prefixing them with `--no- `; for example, `--no-sw`, `--no-prerender`, and `--no-inline-css`.
```
$ preact build
@@ -114,7 +114,6 @@ $ preact build
--src Specify source directory (default src)
--dest Specify output directory (default build)
--cwd A directory to use instead of $PWD (default .)
- --esm Builds ES-2015 bundles for your code (default true)
--sw Generate and attach a Service Worker (default true)
--babelConfig Path to custom Babel config (default .babelrc)
--json Generate build stats for bundle analysis
@@ -139,7 +138,6 @@ $ preact watch
--src Specify source directory (default src)
--cwd A directory to use instead of $PWD (default .)
- --esm Builds ES-2015 bundles for your code (default false)
--clear Clear the console (default true)
--sw Generate and attach a Service Worker (default false)
--babelConfig Path to custom Babel config (default .babelrc)
@@ -192,7 +190,7 @@ To make customizing your configuration easier, preact-cli supports plugins. Visi
#### Browserslist
-You may customize your list of supported browser versions by declaring a [`"browserslist"`] key within your `package.json`. Changing these values will modify your JavaScript (via [`@babel/preset-env`]) and your CSS (via [`autoprefixer`](https://github.com/postcss/autoprefixer)) output.
+You may customize your list of supported browser versions by declaring a [`"browserslist"`] key within your `package.json`. Changing these values will modify your legacy JavaScript (via [`@babel/preset-env`]) and your CSS (via [`autoprefixer`](https://github.com/postcss/autoprefixer)) output.
By default, `preact-cli` emulates the following config:
@@ -200,7 +198,7 @@ By default, `preact-cli` emulates the following config:
```json
{
- "browserslist": ["> 0.25%", "IE >= 9"]
+ "browserslist": ["> 0.5%", "last 2 versions", "Firefox ESR", "not dead"]
}
```
@@ -208,9 +206,9 @@ By default, `preact-cli` emulates the following config:
To customize Babel, you have two options:
-1. You may create a [`.babelrc`] file in your project's root directory. Any settings you define here will overwrite matching config-keys within [Preact CLI preset]. For example, if you pass a `"plugins"` object, it will replace & reset all Babel plugins that Preact CLI defaults to.
+1. You may create a [`.babelrc`] file in your project's root directory, or use the `--babelConfig` path to point at any valid [Babel config file]. Any settings you define here will be merged into the [Preact CLI preset]. For example, if you pass a `"plugins"` object containing different plugins from those already in use, they will simply be added on top of the existing config. If there are conflicts, you'll want to look into option 2, as the default will take precedence.
-2. If you'd like to modify or add to the existing Babel config, you must use a `preact.config.js` file. Visit the [Webpack](#webpack) section for more info, or check out the [Customize Babel] example!
+2. If you'd like to modify the existing Babel config you must use a `preact.config.js` file. Visit the [Webpack](#webpack) section for more info, or check out the [Customize Babel] example!
#### Webpack
@@ -402,9 +400,9 @@ Automatic code splitting is applied to all JavaScript and TypeScript files in th
[preact]: https://github.com/preactjs/preact
[webpackconfighelpers]: docs/webpack-helpers.md
[`.babelrc`]: https://babeljs.io/docs/usage/babelrc
+[babel config file]: https://babeljs.io/docs/en/config-files
[simple]: https://github.com/preactjs-templates/simple
[`"browserslist"`]: https://github.com/ai/browserslist
-[```.babelrc```]: https://babeljs.io/docs/usage/babelrc
[default]: https://github.com/preactjs-templates/default
[workbox]: https://developers.google.com/web/tools/workbox
[preact-router]: https://github.com/preactjs/preact-router
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 557292304..adab5e5dc 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -35,10 +35,7 @@
},
"dependencies": {
"@babel/core": "^7.13.16",
- "@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-decorators": "^7.13.15",
- "@babel/plugin-proposal-object-rest-spread": "^7.13.8",
- "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-object-assign": "^7.12.13",
"@babel/plugin-transform-react-jsx": "^7.13.12",
"@babel/preset-env": "^7.13.15",
@@ -46,67 +43,61 @@
"@preact/async-loader": "^3.0.1",
"@prefresh/babel-plugin": "^0.4.1",
"@prefresh/webpack": "^3.2.2",
- "@types/webpack": "^4.38.0",
"autoprefixer": "^10.4.7",
- "babel-esm-plugin": "^0.9.0",
"babel-loader": "^8.2.5",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"browserslist": "^4.20.3",
- "compression-webpack-plugin": "^6.1.1",
+ "compression-webpack-plugin": "^9.2.0",
"console-clear": "^1.0.0",
- "copy-webpack-plugin": "^6.4.0",
+ "copy-webpack-plugin": "^9.1.0",
"critters-webpack-plugin": "^3.0.2",
"cross-spawn-promise": "^0.10.1",
- "css-loader": "^5.2.7",
+ "css-loader": "^6.6.0",
+ "css-minimizer-webpack-plugin": "3.4.1",
"dotenv": "^16.0.0",
"ejs-loader": "^0.5.0",
"envinfo": "^7.8.1",
"esm": "^3.2.25",
- "file-loader": "^6.2.0",
- "fork-ts-checker-webpack-plugin": "^4.0.4",
+ "fork-ts-checker-webpack-plugin": "^7.1.1",
"get-port": "^5.0.0",
"gittar": "^0.1.0",
"glob": "^8.0.3",
- "html-webpack-plugin": "^4.5.2",
+ "html-webpack-plugin": "^5.5.0",
"html-webpack-skip-assets-plugin": "1.0.3",
"ip": "^1.1.8",
"isomorphic-unfetch": "^3.1.0",
"kleur": "^4.1.4",
- "loader-utils": "^2.0.0",
- "mini-css-extract-plugin": "^1.6.2",
+ "mini-css-extract-plugin": "^2.5.3",
"minimatch": "^3.0.3",
"native-url": "0.3.4",
- "optimize-css-assets-webpack-plugin": "^6.0.1",
+ "optimize-plugin": "^1.3.0",
"ora": "^5.4.1",
- "pnp-webpack-plugin": "^1.7.0",
"postcss": "^8.4.13",
"postcss-load-config": "^3.1.4",
- "postcss-loader": "^4.2.0",
+ "postcss-loader": "^6.2.1",
"progress-bar-webpack-plugin": "^2.1.0",
"promise-polyfill": "^8.2.3",
"prompts": "^2.4.2",
- "raw-loader": "^4.0.2",
- "react-refresh": "0.10.0",
+ "react-refresh": "0.11.0",
"rimraf": "^3.0.2",
"sade": "^1.8.1",
- "size-plugin": "^3.0.0",
+ "size-plugin": "^2.0.2",
"source-map": "^0.7.2",
"source-map-loader": "^1.1.1",
"stack-trace": "0.0.10",
- "style-loader": "^2.0.0",
- "terser-webpack-plugin": "^4.2.3",
- "typescript": "~4.6.4",
+ "style-loader": "^3.3.1",
+ "terser-webpack-plugin": "^5.3.0",
+ "typescript": "^4.6.4",
"update-notifier": "^5.1.0",
- "url-loader": "^4.1.1",
"validate-npm-package-name": "^4.0.0",
- "webpack": "^4.44.2",
+ "webpack": "^5.67.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-dev-server": "^4.9.0",
- "webpack-fix-style-only-entries": "^0.6.1",
- "webpack-manifest-plugin": "^4.1.1",
+ "webpack-manifest-plugin": "^5.0.0",
"webpack-merge": "^5.8.0",
"webpack-plugin-replace": "^1.2.0",
+ "webpack-remove-empty-scripts": "^0.7.2",
"which": "^2.0.2",
"workbox-cacheable-response": "^6.5.3",
"workbox-core": "^6.5.3",
@@ -120,27 +111,28 @@
"@types/jest": "^24.9.1",
"html-looks-like": "^1.0.2",
"jest": "^24.9.0",
- "less": "^4.1.1",
- "less-loader": "^7.3.0",
+ "less": "^4.1.3",
+ "less-loader": "^10.2.0",
+ "ncp": "^2.0.0",
"p-retry": "^4.5.0",
"polka": "^0.5.2",
- "preact": "^10.5.13",
- "preact-render-to-string": "^5.1.19",
+ "preact": "^10.11.3",
+ "preact-render-to-string": "^5.2.6",
"preact-router": "^3.0.1",
- "puppeteer": "^13.7.0",
- "sass": "^1.34.0",
- "sass-loader": "^10.2.0",
- "shelljs": "^0.8.3",
+ "puppeteer": "^17.1.3",
+ "sass": "^1.56.1",
+ "sass-loader": "^12.4.0",
+ "shelljs": "^0.8.5",
"sirv": "^1.0.11",
- "stylus": "^0.54.8",
- "stylus-loader": "^4.3.3"
+ "stylus": "^0.59.0",
+ "stylus-loader": "^6.2.0"
},
"peerDependencies": {
- "less-loader": "^7.3.0",
- "preact": "*",
- "preact-render-to-string": "*",
- "sass-loader": "^10.2.0",
- "stylus-loader": "^4.3.3"
+ "less-loader": "^10.2.0",
+ "preact": "^10.0.0",
+ "preact-render-to-string": "^5.0.0",
+ "sass-loader": "^12.4.0",
+ "stylus-loader": "^6.2.0"
},
"peerDependenciesMeta": {
"less-loader": {
diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js
index 9773a06ba..be4f6652d 100755
--- a/packages/cli/src/index.js
+++ b/packages/cli/src/index.js
@@ -37,7 +37,6 @@ prog
.option('--src', 'Specify source directory', 'src')
.option('--dest', 'Specify output directory', 'build')
.option('--cwd', 'A directory to use instead of $PWD', '.')
- .option('--esm', 'Builds ES-2015 bundles for your code', true)
.option('--sw', 'Generate and attach a Service Worker', true)
.option('--babelConfig', 'Path to custom Babel config', '.babelrc')
.option('--json', 'Generate build stats for bundle analysis', false)
@@ -89,7 +88,6 @@ prog
.describe('Start a live-reload server for development')
.option('--src', 'Specify source directory', 'src')
.option('--cwd', 'A directory to use instead of $PWD', '.')
- .option('--esm', 'Builds ES-2015 bundles for your code', false)
.option('--clear', 'Clears the console when the devServer updates', true)
.option('--sw', 'Generate and attach a Service Worker')
.option('--babelConfig', 'Path to custom Babel config', '.babelrc')
diff --git a/packages/cli/src/lib/babel-config.js b/packages/cli/src/lib/babel-config.js
index df2bb1a90..288de4f2c 100644
--- a/packages/cli/src/lib/babel-config.js
+++ b/packages/cli/src/lib/babel-config.js
@@ -1,26 +1,31 @@
-module.exports = function (env, options = {}) {
- const { production: isProd, refresh } = env || {};
+const { tryResolveConfig } = require('../util');
+
+module.exports = function (env) {
+ const { babelConfig, cwd, isProd, refresh } = env;
+
+ const resolvedConfig =
+ babelConfig &&
+ tryResolveConfig(cwd, babelConfig, babelConfig === '.babelrc');
return {
+ babelrc: false,
+ configFile: resolvedConfig,
presets: [
- [
+ !isProd && [
require.resolve('@babel/preset-env'),
{
+ loose: true,
+ modules: false,
bugfixes: true,
- modules: options.modules || false,
targets: {
- browsers: options.browsers,
+ esmodules: true,
},
exclude: ['transform-regenerator'],
},
],
- ],
+ ].filter(Boolean),
plugins: [
- require.resolve('@babel/plugin-syntax-dynamic-import'),
- require.resolve('@babel/plugin-transform-object-assign'),
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
- require.resolve('@babel/plugin-proposal-class-properties'),
- require.resolve('@babel/plugin-proposal-object-rest-spread'),
isProd &&
require.resolve('babel-plugin-transform-react-remove-prop-types'),
require.resolve('babel-plugin-macros'),
diff --git a/packages/cli/src/lib/entry.js b/packages/cli/src/lib/entry.js
index 5be51642e..910ea9965 100644
--- a/packages/cli/src/lib/entry.js
+++ b/packages/cli/src/lib/entry.js
@@ -18,14 +18,12 @@ if (process.env.NODE_ENV === 'development') {
);
} else if (process.env.ADD_SW && 'serviceWorker' in navigator) {
navigator.serviceWorker.register(
- normalizeURL(__webpack_public_path__) +
- (process.env.ES_BUILD ? 'sw-esm.js' : 'sw.js')
+ normalizeURL(__webpack_public_path__) + 'sw.js'
);
}
} else if (process.env.ADD_SW && 'serviceWorker' in navigator) {
navigator.serviceWorker.register(
- normalizeURL(__webpack_public_path__) +
- (process.env.ES_BUILD ? 'sw-esm.js' : 'sw.js')
+ normalizeURL(__webpack_public_path__) + 'sw.js'
);
}
diff --git a/packages/cli/src/lib/webpack/proxy-loader.js b/packages/cli/src/lib/webpack/proxy-loader.js
index 27c7b56fb..f02648b8d 100644
--- a/packages/cli/src/lib/webpack/proxy-loader.js
+++ b/packages/cli/src/lib/webpack/proxy-loader.js
@@ -1,7 +1,5 @@
-var utils = require('loader-utils');
-
function proxyLoader(source, map) {
- var options = utils.getOptions(this);
+ var options = this.getOptions();
// First run proxy-loader run
if (!this.query.__proxy_loader__) {
diff --git a/packages/cli/src/lib/webpack/push-manifest.js b/packages/cli/src/lib/webpack/push-manifest.js
index b9966b814..5e8938748 100644
--- a/packages/cli/src/lib/webpack/push-manifest.js
+++ b/packages/cli/src/lib/webpack/push-manifest.js
@@ -1,4 +1,4 @@
-const webpack = require('webpack');
+const { Compilation, sources } = require('webpack');
const createLoadManifest = require('./create-load-manifest');
module.exports = class PushManifestPlugin {
@@ -6,32 +6,27 @@ module.exports = class PushManifestPlugin {
this.isProd = isProd;
}
apply(compiler) {
- compiler.hooks.emit.tap(
- {
- name: 'PushManifestPlugin',
- stage: webpack.Compiler.PROCESS_ASSETS_STAGE_REPORT,
- },
- compilation => {
- const manifest = createLoadManifest(
- compilation.assets,
- compilation.namedChunkGroups,
- this.isProd
- );
+ compiler.hooks.thisCompilation.tap('PushManifestPlugin', compilation => {
+ compilation.hooks.processAssets.tap(
+ {
+ name: 'PushManifestPlugin',
+ stage: Compilation.PROCESS_ASSETS_STAGE_ANALYSE,
+ },
+ () => {
+ const manifest = JSON.stringify(
+ createLoadManifest(
+ compilation.assets,
+ compilation.namedChunkGroups,
+ this.isProd
+ )
+ );
- let output = JSON.stringify(manifest);
- compilation.assets['push-manifest.json'] = {
- source() {
- return output;
- },
- size() {
- return output.length;
- },
- };
-
- return compilation;
-
- // callback();
- }
- );
+ compilation.emitAsset(
+ 'push-manifest.json',
+ new sources.RawSource(manifest)
+ );
+ }
+ );
+ });
}
};
diff --git a/packages/cli/src/lib/webpack/render-html-plugin.js b/packages/cli/src/lib/webpack/render-html-plugin.js
index 547423125..c534a32c2 100644
--- a/packages/cli/src/lib/webpack/render-html-plugin.js
+++ b/packages/cli/src/lib/webpack/render-html-plugin.js
@@ -1,6 +1,7 @@
const { resolve, join } = require('path');
const os = require('os');
const { existsSync, readFileSync, writeFileSync, mkdirSync } = require('fs');
+const { Compilation, sources } = require('webpack');
const {
HtmlWebpackSkipAssetsPlugin,
} = require('html-webpack-skip-assets-plugin');
@@ -114,7 +115,6 @@ module.exports = async function renderHTMLPlugin(config) {
};
},
inject: true,
- scriptLoading: 'defer',
favicon: existsSync(resolve(src, 'assets/favicon.ico'))
? 'assets/favicon.ico'
: '',
@@ -187,20 +187,28 @@ class PrerenderDataExtractPlugin {
this.data_ = JSON.stringify(page || {});
}
apply(compiler) {
- 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;
- }
- let path = this.location_ + 'preact_prerender_data.json';
- if (path.startsWith('/')) {
- path = path.substr(1);
+ compiler.hooks.thisCompilation.tap(
+ 'PrerenderDataExtractPlugin',
+ compilation => {
+ compilation.hooks.processAssets.tap(
+ {
+ name: 'PrerenderDataExtractPlugin',
+ stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
+ },
+ () => {
+ if (this.location_ === `${PREACT_FALLBACK_URL}/`) {
+ // We dont build prerender data for `200.html`. It can re-use the one for homepage.
+ return;
+ }
+ let path = this.location_ + 'preact_prerender_data.json';
+ if (path.startsWith('/')) {
+ path = path.substr(1);
+ }
+ compilation.emitAsset(path, new sources.RawSource(this.data_));
+ }
+ );
}
- compilation.assets[path] = {
- source: () => this.data_,
- size: () => this.data_.length,
- };
- });
+ );
}
}
diff --git a/packages/cli/src/lib/webpack/run-webpack.js b/packages/cli/src/lib/webpack/run-webpack.js
index 80047b680..037461a1a 100644
--- a/packages/cli/src/lib/webpack/run-webpack.js
+++ b/packages/cli/src/lib/webpack/run-webpack.js
@@ -96,26 +96,13 @@ function showStats(stats, isProd) {
if (stats.hasErrors()) {
allFields(stats, 'errors')
.map(stripLoaderPrefix)
- .forEach(msg => error(msg, isProd ? 1 : 0));
+ .forEach(({ message }) => error(message, isProd ? 1 : 0));
}
if (stats.hasWarnings()) {
allFields(stats, 'warnings')
.map(stripLoaderPrefix)
- .forEach(msg => {
- if (
- msg.match(
- /Conflict: Multiple assets emit different content to the same filename .*\.(css|map)/
- )
- ) {
- /**
- * This particular warning is expected due to `babel-esm-plugin`.
- * This can be removed when upgrading to webpack5 with https://webpack.js.org/configuration/output/#outputcomparebeforeemit
- */
- return;
- }
- warn(msg);
- });
+ .forEach(({ message }) => warn(message));
}
}
diff --git a/packages/cli/src/lib/webpack/transform-config.js b/packages/cli/src/lib/webpack/transform-config.js
index a36ae6425..79cee333c 100644
--- a/packages/cli/src/lib/webpack/transform-config.js
+++ b/packages/cli/src/lib/webpack/transform-config.js
@@ -240,6 +240,7 @@ class WebpackConfigHelpers {
)
.reduce((arr, loaders) => arr.concat(loaders), [])
.filter(({ loader }) => {
+ if (!loader) return false;
if (typeof loader === 'string') return loader.includes(name);
return typeof loader.loader === 'string' &&
loader.loader.includes('proxy-loader')
diff --git a/packages/cli/src/lib/webpack/webpack-base-config.js b/packages/cli/src/lib/webpack/webpack-base-config.js
index 31f95e4e1..45b169d13 100644
--- a/packages/cli/src/lib/webpack/webpack-base-config.js
+++ b/packages/cli/src/lib/webpack/webpack-base-config.js
@@ -6,13 +6,12 @@ const { isInstalledVersionPreactXOrAbove } = require('./utils');
const autoprefixer = require('autoprefixer');
const browserslist = require('browserslist');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
+const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const ReplacePlugin = require('webpack-plugin-replace');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const createBabelConfig = require('../babel-config');
const loadPostcssConfig = require('postcss-load-config');
-const PnpWebpackPlugin = require('pnp-webpack-plugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
function readJson(file) {
@@ -47,41 +46,24 @@ function resolveTsconfig(cwd, isProd) {
}
}
-function getSassConfiguration(...includePaths) {
- const config = {
- sourceMap: true,
- sassOptions: {
- includePaths,
- },
- };
-
- Object.defineProperty(config, 'includePaths', { value: includePaths });
-
- return config;
-}
-
/**
* @returns {import('webpack').Configuration}
*/
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);
// Apply base-level `env` values
env.dest = resolve(cwd, env.dest || 'build');
env.manifest = readJson(source('manifest.json')) || {};
env.pkg = readJson(resolve(cwd, 'package.json')) || {};
- let babelrc = readJson(resolve(cwd, babelConfigFile)) || {};
-
// use browserslist config environment, config default, or default browsers
- // default browsers are > 0.25% global market share or Internet Explorer >= 9
- const browserslistDefaults = ['> 0.25%', 'IE >= 9'];
+ // default browsers are '> 0.5%, last 2 versions, Firefox ESR, not dead'
const browserlistConfig = Object(browserslist.findConfig(cwd));
const browsers =
(isProd ? browserlistConfig.production : browserlistConfig.development) ||
browserlistConfig.defaults ||
- browserslistDefaults;
+ 'defaults';
let userNodeModules = findAllNodeModules(cwd);
let cliNodeModules = findAllNodeModules(__dirname);
@@ -142,7 +124,7 @@ module.exports = function createBaseConfig(env) {
style: source('style'),
'preact-cli-entrypoint': source('index'),
url: dirname(require.resolve('native-url/package.json')),
- // preact-compat aliases for supporting React dependencies:
+ // preact/compat aliases for supporting React dependencies:
react: compat,
'react-dom': compat,
'preact-compat': compat,
@@ -151,10 +133,6 @@ module.exports = function createBaseConfig(env) {
? require.resolve('@preact/async-loader/async')
: require.resolve('@preact/async-loader/async-legacy'),
},
- plugins: [
- // TODO: Remove when upgrading to webpack 5
- PnpWebpackPlugin,
- ],
},
resolveLoader: {
@@ -167,7 +145,6 @@ module.exports = function createBaseConfig(env) {
module: {
rules: [
{
- // ES2015
enforce: 'pre',
test: /\.m?[jt]sx?$/,
resolve: { mainFields: ['module', 'jsnext:main', 'browser', 'main'] },
@@ -175,11 +152,7 @@ module.exports = function createBaseConfig(env) {
use: [
{
loader: require.resolve('babel-loader'),
- options: Object.assign(
- { babelrc: false },
- createBabelConfig(env, { browsers }),
- babelrc // intentionally overwrite our settings
- ),
+ options: createBabelConfig(env),
},
require.resolve('source-map-loader'),
],
@@ -214,7 +187,12 @@ module.exports = function createBaseConfig(env) {
options: {
cwd,
loader: tryResolveOptionalLoader('sass-loader'),
- options: getSassConfiguration(...nodeModules),
+ options: {
+ sourceMap: true,
+ sassOptions: {
+ includePaths: [...nodeModules],
+ },
+ },
},
},
],
@@ -298,13 +276,11 @@ module.exports = function createBaseConfig(env) {
},
{
test: /\.(xml|html|txt|md)$/,
- loader: require.resolve('raw-loader'),
+ type: 'asset/source',
},
{
test: /\.(svg|woff2?|ttf|eot|jpe?g|png|webp|avif|gif|mp4|mov|ogg|webm)(\?.*)?$/i,
- loader: isProd
- ? require.resolve('file-loader')
- : require.resolve('url-loader'),
+ type: isProd ? 'asset/resource' : 'asset/inline',
},
],
},
@@ -331,8 +307,7 @@ module.exports = function createBaseConfig(env) {
Fragment: ['preact', 'Fragment'],
}),
// Fix for https://github.com/webpack-contrib/mini-css-extract-plugin/issues/151
- new FixStyleOnlyEntriesPlugin(),
- // Extract CSS
+ new RemoveEmptyScriptsPlugin(),
new MiniCssExtractPlugin({
filename: isProd ? '[name].[contenthash:5].css' : '[name].css',
chunkFilename: isProd
@@ -348,44 +323,38 @@ module.exports = function createBaseConfig(env) {
}),
new WebpackManifestPlugin({
fileName: 'asset-manifest.json',
- assetHookStage: webpack.Compiler.PROCESS_ASSETS_STAGE_ANALYSE,
+ assetHookStage: webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
// TODO: Remove this next breaking change and use the full filepath from this manifest
// when referring to built assets, i.e.:
// https://github.com/preactjs/preact-cli/blob/master/packages/cli/src/resources/head-end.ejs#L1
// This is just to avoid any potentially breaking changes for right now.
publicPath: '',
}),
- ...(tsconfig
- ? [
- new ForkTsCheckerWebpackPlugin({
- checkSyntacticErrors: true,
- async: !isProd,
- tsconfig: tsconfig,
- silent: !isWatch,
- }),
- ]
- : []),
- ...(isProd
- ? [
- new webpack.HashedModuleIdsPlugin(),
- new webpack.LoaderOptionsPlugin({ minimize: true }),
- new webpack.optimize.ModuleConcatenationPlugin(),
+ tsconfig &&
+ new ForkTsCheckerWebpackPlugin({
+ typescript: {
+ configFile: tsconfig,
+ diagnosticOptions: {
+ syntactic: true,
+ },
+ },
+ }),
+ new webpack.optimize.ModuleConcatenationPlugin(),
- // strip out babel-helper invariant checks
- new ReplacePlugin({
- include: /babel-helper$/,
- patterns: [
- {
- regex: /throw\s+(new\s+)?(Type|Reference)?Error\s*\(/g,
- value: s => `return;${Array(s.length - 7).join(' ')}(`,
- },
- ],
- }),
- ]
- : []),
- ],
+ // strip out babel-helper invariant checks
+ new ReplacePlugin({
+ include: /babel-helper$/,
+ patterns: [
+ {
+ regex: /throw\s+(new\s+)?(Type|Reference)?Error\s*\(/g,
+ value: s => `return;${Array(s.length - 7).join(' ')}(`,
+ },
+ ],
+ }),
+ ].filter(Boolean),
optimization: {
+ ...(isProd && { moduleIds: 'deterministic' }),
splitChunks: {
minChunks: 3,
},
@@ -393,15 +362,11 @@ module.exports = function createBaseConfig(env) {
mode: isProd ? 'production' : 'development',
- devtool: isWatch ? 'cheap-module-eval-source-map' : 'source-map',
+ devtool: isWatch ? 'eval-cheap-module-source-map' : 'source-map',
node: {
- console: false,
- process: false,
- Buffer: false,
__filename: false,
__dirname: false,
- setImmediate: false,
},
};
};
diff --git a/packages/cli/src/lib/webpack/webpack-client-config.js b/packages/cli/src/lib/webpack/webpack-client-config.js
index f5da2a0e7..8bb864a52 100644
--- a/packages/cli/src/lib/webpack/webpack-client-config.js
+++ b/packages/cli/src/lib/webpack/webpack-client-config.js
@@ -7,17 +7,17 @@ const { filter } = require('minimatch');
const SizePlugin = require('size-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
-const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
+const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const CrittersPlugin = require('critters-webpack-plugin');
const renderHTMLPlugin = require('./render-html-plugin');
const PushManifestPlugin = require('./push-manifest');
const baseConfig = require('./webpack-base-config');
-const BabelEsmPlugin = require('babel-esm-plugin');
const { InjectManifest } = require('workbox-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const RefreshPlugin = require('@prefresh/webpack');
const { normalizePath, warn } = require('../../util');
+const OptimizePlugin = require('optimize-plugin');
const cleanFilename = name =>
name.replace(
@@ -37,7 +37,7 @@ async function clientConfig(env) {
let entry = {
bundle: resolve(__dirname, './../entry'),
- polyfills: resolve(__dirname, './polyfills'),
+ 'dom-polyfills': resolve(__dirname, './polyfills'),
};
let swInjectManifest = [];
@@ -50,36 +50,15 @@ async function clientConfig(env) {
warn(`Could not find sw.js in ${src}. Using the default service worker.`);
}
- if (env.esm) {
- swInjectManifest.push(
- new InjectManifest({
- swSrc: swPath,
- swDest: 'sw-esm.js',
- include: [
- /200\.html$/,
- /\.esm.js$/,
- /\.css$/,
- /\.(png|jpg|svg|gif|webp|avif)$/,
- ],
- webpackCompilationPlugins: [
- new webpack.DefinePlugin({
- 'process.env.ESM': true,
- }),
- ],
- })
- );
- }
-
swInjectManifest.push(
new InjectManifest({
swSrc: swPath,
include: [
/200\.html$/,
- /\.js$/,
+ /(? {
+ if (pathData.chunk.name === 'dom-polyfills') {
+ return env.isProd
+ ? '[name].[chunkhash:5].legacy.js'
+ : '[name].legacy.js';
+ }
+ return env.isProd ? '[name].[chunkhash:5].js' : '[name].js';
+ },
chunkFilename: '[name].chunk.[chunkhash:5].js',
},
@@ -147,13 +133,11 @@ async function clientConfig(env) {
plugins: [
new webpack.DefinePlugin({
- 'process.env.ES_BUILD': false,
'process.env.ADD_SW': env.sw,
'process.env.PRERENDER': env.prerender,
}),
new PushManifestPlugin(env.isProd),
...(await renderHTMLPlugin(env)),
- ...getBabelEsmPlugin(env),
copyPatterns.length !== 0 &&
new CopyWebpackPlugin({
patterns: copyPatterns,
@@ -163,41 +147,6 @@ async function clientConfig(env) {
};
}
-function getBabelEsmPlugin(env) {
- const esmPlugins = [];
- if (env.esm) {
- esmPlugins.push(
- new BabelEsmPlugin({
- filename: env.isProd ? '[name].[chunkhash:5].esm.js' : '[name].esm.js',
- chunkFilename: '[name].chunk.[chunkhash:5].esm.js',
- excludedPlugins: ['BabelEsmPlugin', 'InjectManifest'],
- beforeStartExecution: plugins => {
- plugins.forEach(plugin => {
- if (
- plugin.constructor.name === 'DefinePlugin' &&
- plugin.definitions
- ) {
- for (const definition in plugin.definitions) {
- if (definition === 'process.env.ES_BUILD') {
- plugin.definitions[definition] = true;
- }
- }
- } else if (
- plugin.constructor.name === 'DefinePlugin' &&
- !plugin.definitions
- ) {
- throw new Error(
- 'WebpackDefinePlugin found but not `process.env.ES_BUILD`.'
- );
- }
- });
- },
- })
- );
- }
- return esmPlugins;
-}
-
/**
* @returns {import('webpack').Configuration}
*/
@@ -214,17 +163,24 @@ function isProd(env) {
),
plugins: [
- new webpack.DefinePlugin({
- 'process.env.ESM': env.esm,
+ new OptimizePlugin({
+ polyfillsFilename: 'es-polyfills.legacy.js',
+ exclude: [/^sw.*\.js/, /^dom-polyfills.*\.js/],
+ modernize: false,
+ verbose: false,
+ }),
+ new SizePlugin({
+ stripHash: name =>
+ name.replace(/\.[a-z0-9]{5}((\.legacy)?\.(?:js|css)$)/i, '.*****$1'),
}),
- new SizePlugin(),
],
+ cache: true,
optimization: {
minimizer: [
new TerserPlugin({
- cache: true,
- parallel: true,
+ extractComments: false,
+ test: /(sw|dom-polyfills).*\.js$/,
terserOptions: {
output: { comments: false },
mangle: true,
@@ -242,15 +198,8 @@ function isProd(env) {
],
},
},
- extractComments: false,
- sourceMap: true,
- }),
- new OptimizeCssAssetsPlugin({
- cssProcessorOptions: {
- // Fix keyframes in different CSS chunks minifying to colliding names:
- reduceIdents: false,
- },
}),
+ new CssMinimizerPlugin(),
],
},
};
@@ -275,7 +224,7 @@ function isProd(env) {
new CompressionPlugin({
filename: '[path][base].br[query]',
algorithm: 'brotliCompress',
- test: /\.esm\.js$/,
+ test: /(?
<% } %>
-<% 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 { %>
-
-
-<% } %>
+
+<% /*
+ 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/src/resources/head-end.ejs b/packages/cli/src/resources/head-end.ejs
index 8ca472a8b..07ff50c69 100644
--- a/packages/cli/src/resources/head-end.ejs
+++ b/packages/cli/src/resources/head-end.ejs
@@ -6,6 +6,6 @@
<% for (const file in cli.loadManifest[cli.url]) { %>
<% if (cli.preload && file && file.match(filesRegexp)) { %>
<% /* crossorigin for main bundle as that is loaded from `
This is an app with custom template
-
-
+
+
+
+