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/babel/index.js b/packages/cli/babel/index.js
deleted file mode 100644
index e9dd1113c..000000000
--- a/packages/cli/babel/index.js
+++ /dev/null
@@ -1,52 +0,0 @@
-var isProd = (process.env.BABEL_ENV || process.env.NODE_ENV) === 'production';
-/**
- * test env detection is used to default mode for
- * preset-env modules to "commonjs" otherwise, testing framework
- * will struggle
- */
-var isTest = (process.env.BABEL_ENV || process.env.NODE_ENV) === 'test';
-
-// default supported browsers for prod nomodule bundles:
-var defaultBrowserList = ['> 0.25%', 'IE >= 9'];
-
-// default supported browsers for all dev bundles (module/nomodule is not used):
-// see https://github.com/babel/babel/blob/master/packages/babel-compat-data/data/native-modules.json
-var defaultBrowserListDev = [
- 'chrome >= 61',
- 'and_chr >= 61',
- 'android >= 61',
- 'firefox >= 60',
- 'and_ff >= 60',
- 'safari >= 10.1',
- 'ios_saf >= 10.3',
- 'edge >= 16',
- 'opera >= 48',
- 'samsung >= 8.2',
-];
-
-// preact-cli babel configs
-var babelConfigs = require('../src/lib/babel-config');
-
-/**
- * preset as a function means allow users to override some options
- * like env, modules for environment
- */
-module.exports = function preactCli(ctx, userOptions = {}) {
- // set default configs based on user environment
- var presetOptions = {
- env: isProd ? 'production' : 'development',
- modules: isTest ? 'commonjs' : false,
- browsers: isProd ? defaultBrowserList : defaultBrowserListDev,
- };
-
- // user specified options always the strongest
- Object.keys(presetOptions).forEach(function (key) {
- presetOptions[key] = userOptions[key] || presetOptions[key];
- });
-
- // yay! return the configs
- return babelConfigs(
- { env: { production: presetOptions.env === 'production' } },
- { modules: presetOptions.modules, browsers: presetOptions.browsers }
- );
-};
diff --git a/packages/cli/package.json b/packages/cli/package.json
index c5ce8c9ca..c348fe2d1 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,60 @@
"@preact/async-loader": "^3.0.2",
"@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.1",
"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 +110,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 e6e7f4863..04ed46c66 100755
--- a/packages/cli/src/index.js
+++ b/packages/cli/src/index.js
@@ -33,7 +33,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)
@@ -85,7 +84,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 dcb375bff..f1d6471b7 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, mkdtempSync, readFileSync, writeFileSync } = require('fs');
+const { Compilation, sources } = require('webpack');
const {
HtmlWebpackSkipAssetsPlugin,
} = require('html-webpack-skip-assets-plugin');
@@ -183,20 +184,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 ecbf31354..989303532 100644
--- a/packages/cli/src/lib/webpack/run-webpack.js
+++ b/packages/cli/src/lib/webpack/run-webpack.js
@@ -95,26 +95,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..8b76df71c 100644
--- a/packages/cli/src/lib/webpack/webpack-base-config.js
+++ b/packages/cli/src/lib/webpack/webpack-base-config.js
@@ -6,13 +6,11 @@ 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 +45,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 +123,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 +132,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,19 +144,13 @@ module.exports = function createBaseConfig(env) {
module: {
rules: [
{
- // ES2015
enforce: 'pre',
test: /\.m?[jt]sx?$/,
- resolve: { mainFields: ['module', 'jsnext:main', 'browser', 'main'] },
type: 'javascript/auto',
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 +185,12 @@ module.exports = function createBaseConfig(env) {
options: {
cwd,
loader: tryResolveOptionalLoader('sass-loader'),
- options: getSassConfiguration(...nodeModules),
+ options: {
+ sourceMap: true,
+ sassOptions: {
+ includePaths: [...nodeModules],
+ },
+ },
},
},
],
@@ -298,13 +274,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 +305,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
@@ -355,37 +328,20 @@ module.exports = function createBaseConfig(env) {
// 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(),
-
- // 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(' ')}(`,
- },
- ],
- }),
- ]
- : []),
- ],
+ tsconfig &&
+ new ForkTsCheckerWebpackPlugin({
+ typescript: {
+ configFile: tsconfig,
+ diagnosticOptions: {
+ syntactic: true,
+ },
+ },
+ }),
+ new webpack.optimize.ModuleConcatenationPlugin(),
+ ].filter(Boolean),
optimization: {
+ ...(isProd && { moduleIds: 'deterministic' }),
splitChunks: {
minChunks: 3,
},
@@ -393,15 +349,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 935619c01..4774e4872 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(
@@ -35,11 +35,6 @@ async function clientConfig(env) {
? require.resolve('@preact/async-loader')
: require.resolve('@preact/async-loader/legacy');
- let entry = {
- bundle: resolve(__dirname, './../entry'),
- polyfills: resolve(__dirname, './polyfills'),
- };
-
let swInjectManifest = [];
if (env.sw) {
let swPath = join(__dirname, '..', '..', '..', 'sw', 'sw.js');
@@ -50,36 +45,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$/,
+ /(? {
- 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 +154,24 @@ function isProd(env) {
),
plugins: [
- new webpack.DefinePlugin({
- 'process.env.ESM': env.esm,
+ new OptimizePlugin({
+ polyfillsFilename: 'es-polyfills.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 +189,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 +215,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
-
-
+
+
+
+