From dfe91b872d5ad808834370fae170e5663d727025 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Fri, 18 Nov 2022 01:35:02 -0600 Subject: [PATCH] refactor: Separate internal concepts of config and env (#1734) * refactor: Separate internal concepts of config and env * docs: Adding changeset --- .changeset/great-dryers-cross.md | 9 +++ packages/cli/src/commands/build.js | 3 +- packages/cli/src/commands/watch.js | 3 +- packages/cli/src/lib/babel-config.js | 7 +- .../cli/src/lib/webpack/render-html-plugin.js | 7 +- packages/cli/src/lib/webpack/run-webpack.js | 69 +++++++++++-------- .../cli/src/lib/webpack/transform-config.js | 27 ++++---- .../src/lib/webpack/webpack-base-config.js | 17 +++-- .../src/lib/webpack/webpack-client-config.js | 63 +++++++++-------- .../src/lib/webpack/webpack-server-config.js | 13 ++-- .../subjects/custom-template/template.html | 2 +- 11 files changed, 129 insertions(+), 91 deletions(-) create mode 100644 .changeset/great-dryers-cross.md diff --git a/.changeset/great-dryers-cross.md b/.changeset/great-dryers-cross.md new file mode 100644 index 000000000..aeda7e193 --- /dev/null +++ b/.changeset/great-dryers-cross.md @@ -0,0 +1,9 @@ +--- +'preact-cli': major +--- + +Reduces the `env` parameter of `preact.config.js` to only contain 3 values: `isProd`, `isWatch`, and `isServer`. + +Previously, `env` contained many semi-duplicated values (`production` and `isProd`, etc) as well as values that were unlikely to be of much use to many users (what flags were set, for instance). Because of this, the signal-to-noise ratio was rather low which we didn't like. As such, we reduced `env` down to the most basic environment info: what type of build is `preact-cli` doing and for which environement? + +If you customize your Webpack config using a `preact.config.js`, please be aware that you may need to update which values you consume from `env`. diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index 05d30b2db..f1d2d3c87 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -8,7 +8,6 @@ exports.build = async function buildCommand(src, argv) { argv.src = src || argv.src; // add `default:true`s, `--no-*` disables argv.prerender = toBool(argv.prerender); - argv.production = toBool(argv.production); let cwd = resolve(argv.cwd); @@ -23,7 +22,7 @@ exports.build = async function buildCommand(src, argv) { await promisify(rimraf)(dest); } - let stats = await runWebpack(argv, false); + let stats = await runWebpack(argv, true); if (argv.json) { await runWebpack.writeJsonStats(cwd, stats); diff --git a/packages/cli/src/commands/watch.js b/packages/cli/src/commands/watch.js index 0ceedcacf..1529b8822 100644 --- a/packages/cli/src/commands/watch.js +++ b/packages/cli/src/commands/watch.js @@ -9,7 +9,6 @@ exports.watch = async function watchCommand(src, argv) { argv.refresh = argv.rhl; } argv.src = src || argv.src; - argv.production = false; if (argv.sw) { argv.sw = toBool(argv.sw); } @@ -33,7 +32,7 @@ exports.watch = async function watchCommand(src, argv) { } } - return runWebpack(argv, true); + return runWebpack(argv, false); }; const determinePort = (exports.determinePort = async function (port) { diff --git a/packages/cli/src/lib/babel-config.js b/packages/cli/src/lib/babel-config.js index 5a4859787..c420a8da2 100644 --- a/packages/cli/src/lib/babel-config.js +++ b/packages/cli/src/lib/babel-config.js @@ -1,7 +1,10 @@ const { tryResolveConfig } = require('../util'); -module.exports = function (env) { - const { babelConfig, cwd, isProd, refresh } = env; +/** + * @param {boolean} isProd + */ +module.exports = function (config, isProd) { + const { babelConfig, cwd, refresh } = config; const resolvedConfig = babelConfig && diff --git a/packages/cli/src/lib/webpack/render-html-plugin.js b/packages/cli/src/lib/webpack/render-html-plugin.js index 9d1a9d12f..4c9db6b40 100644 --- a/packages/cli/src/lib/webpack/render-html-plugin.js +++ b/packages/cli/src/lib/webpack/render-html-plugin.js @@ -17,8 +17,12 @@ function read(path) { return readFileSync(resolve(__dirname, path), 'utf-8'); } -module.exports = async function renderHTMLPlugin(config) { +/** + * @param {import('../../../types').Env} env + */ +module.exports = async function renderHTMLPlugin(config, env) { const { cwd, dest, src } = config; + const inProjectTemplatePath = resolve(src, 'template.html'); let template = defaultTemplate; if (existsSync(inProjectTemplatePath)) { @@ -89,6 +93,7 @@ module.exports = async function renderHTMLPlugin(config) { inlineCss: config['inline-css'], preload: config.preload, config, + env, preRenderData: values, CLI_DATA: { preRenderData: { url, ...routeData } }, ssr: config.prerender ? prerender({ cwd, dest, src }, values) : '', diff --git a/packages/cli/src/lib/webpack/run-webpack.js b/packages/cli/src/lib/webpack/run-webpack.js index f142f2d25..648b04105 100644 --- a/packages/cli/src/lib/webpack/run-webpack.js +++ b/packages/cli/src/lib/webpack/run-webpack.js @@ -10,26 +10,29 @@ const serverConfig = require('./webpack-server-config'); const transformConfig = require('./transform-config'); const { error, isDir, warn } = require('../../util'); -async function devBuild(env) { - let config = await clientConfig(env); +/** + * @param {import('../../../types').Env} env + */ +async function devBuild(config, env) { + const webpackConfig = await clientConfig(config, env); - await transformConfig(env, config); + await transformConfig(webpackConfig, config, env); - let compiler = webpack(config); + let compiler = webpack(webpackConfig); return new Promise((res, rej) => { compiler.hooks.beforeCompile.tap('CliDevPlugin', () => { if (env['clear']) clear(true); }); compiler.hooks.done.tap('CliDevPlugin', stats => { - let devServer = config.devServer; + let devServer = webpackConfig.devServer; let protocol = process.env.HTTPS || devServer.https ? 'https' : 'http'; let host = process.env.HOST || devServer.host || 'localhost'; if (host === '0.0.0.0' && process.platform === 'win32') { host = 'localhost'; } - let serverAddr = `${protocol}://${host}:${bold(env.port)}`; - let localIpAddr = `${protocol}://${ip.address()}:${bold(env.port)}`; + let serverAddr = `${protocol}://${host}:${bold(config.port)}`; + let localIpAddr = `${protocol}://${ip.address()}:${bold(config.port)}`; if (stats.hasErrors()) { process.stdout.write(red('Build failed!\n\n')); @@ -45,26 +48,28 @@ async function devBuild(env) { compiler.hooks.failed.tap('CliDevPlugin', rej); - let server = new DevServer(config.devServer, compiler); + let server = new DevServer(webpackConfig.devServer, compiler); server.start(); res(server); }); } -async function prodBuild(env) { - env = { ...env, isServer: false, dev: !env.production, ssr: false }; - let config = await clientConfig(env); - await transformConfig(env, config); +/** + * @param {import('../../../types').Env} env + */ +async function prodBuild(config, env) { + if (config.prerender) { + const serverEnv = { ...env, isServer: true }; - if (env.prerender) { - const serverEnv = Object.assign({}, env, { isServer: true, ssr: true }); - let ssrConfig = serverConfig(serverEnv); - await transformConfig(serverEnv, ssrConfig); - let serverCompiler = webpack(ssrConfig); + const serverWebpackConfig = serverConfig(config, serverEnv); + await transformConfig(serverWebpackConfig, config, serverEnv); + const serverCompiler = webpack(serverWebpackConfig); await runCompiler(serverCompiler); } - let clientCompiler = webpack(config); + const clientWebpackConfig = await clientConfig(config, env); + await transformConfig(clientWebpackConfig, config, env); + const clientCompiler = webpack(clientWebpackConfig); try { let stats = await runCompiler(clientCompiler); @@ -220,20 +225,26 @@ function stripLoaderFromModuleNames(m) { return m; } -module.exports = function (env, watch = false) { - env.isProd = env.production; // shorthand - env.isWatch = !!watch; // use HMR? - env.cwd = resolve(env.cwd || process.cwd()); - - // env.src='src' via `build` default - let src = resolve(env.cwd, env.src); - env.src = isDir(src) ? src : env.cwd; +/** + * @param {boolean} isProd + */ +module.exports = function (argv, isProd) { + const env = { + isProd, + isWatch: !isProd, + isServer: false, + }; + const config = argv; + config.cwd = resolve(argv.cwd || process.cwd()); + + // config.src='src' via `build` default + const src = resolve(config.cwd, argv.src); + config.src = isDir(src) ? src : config.cwd; // attach sourcing helper - env.source = dir => resolve(env.src, dir); + config.source = dir => resolve(config.src, dir); - // determine build-type to run - return (watch ? devBuild : prodBuild)(env); + return (isProd ? prodBuild : devBuild)(config, env); }; module.exports.writeJsonStats = writeJsonStats; diff --git a/packages/cli/src/lib/webpack/transform-config.js b/packages/cli/src/lib/webpack/transform-config.js index 07e915239..a88a4738d 100644 --- a/packages/cli/src/lib/webpack/transform-config.js +++ b/packages/cli/src/lib/webpack/transform-config.js @@ -6,14 +6,14 @@ const { error, esmImport, tryResolveConfig, warn } = require('../../util'); const FILE = 'preact.config'; const EXTENSIONS = ['js', 'json']; -async function findConfig(env) { +async function findConfig(cwd) { let idx = 0; for (idx; idx < EXTENSIONS.length; idx++) { - let config = `${FILE}.${EXTENSIONS[idx]}`; - let path = resolve(env.cwd, config); + let configFile = `${FILE}.${EXTENSIONS[idx]}`; + let path = resolve(cwd, configFile); try { await stat(path); - return { configFile: config, isDefault: true }; + return { configFile, isDefault: true }; } catch (e) {} } @@ -90,17 +90,20 @@ function parseConfig(config) { return transformers; } -module.exports = async function (env, webpackConfig) { +/** + * @param {import('../../../types').Env} env + */ +module.exports = async function (webpackConfig, config, env) { const { configFile, isDefault } = - env.config !== 'preact.config.js' - ? { configFile: env.config, isDefault: false } - : await findConfig(env); + config.config !== 'preact.config.js' + ? { configFile: config.config, isDefault: false } + : await findConfig(config.cwd); const cliConfig = tryResolveConfig( - env.cwd, + config.cwd, configFile, isDefault, - env.verbose + config.verbose ); if (!cliConfig) return; @@ -111,7 +114,7 @@ module.exports = async function (env, webpackConfig) { } catch (error) { warn( `Failed to load preact-cli config file, using default!\n${ - env.verbose ? error.stack : error.message + config.verbose ? error.stack : error.message }` ); return; @@ -119,7 +122,7 @@ module.exports = async function (env, webpackConfig) { const transformers = parseConfig((m && m.default) || m); - const helpers = new WebpackConfigHelpers(env.cwd); + const helpers = new WebpackConfigHelpers(config.cwd); for (let [transformer, options] of transformers) { try { await transformer(webpackConfig, env, helpers, options); diff --git a/packages/cli/src/lib/webpack/webpack-base-config.js b/packages/cli/src/lib/webpack/webpack-base-config.js index bdcbe3464..a485d7303 100644 --- a/packages/cli/src/lib/webpack/webpack-base-config.js +++ b/packages/cli/src/lib/webpack/webpack-base-config.js @@ -59,14 +59,17 @@ function getSassConfiguration(...includePaths) { } /** + * @param {import('../../../types').Env} env * @returns {import('webpack').Configuration} */ -module.exports = function createBaseConfig(env) { - const { cwd, isProd, src, source } = env; +module.exports = function createBaseConfig(config, env) { + const { cwd, src, source } = config; + const { isProd, isServer } = env; + // 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')) || {}; + config.dest = resolve(cwd, config.dest || 'build'); + config.manifest = readJson(source('manifest.json')) || {}; + config.pkg = readJson(resolve(cwd, 'package.json')) || {}; // use browserslist config environment, config default, or default browsers // default browsers are '> 0.5%, last 2 versions, Firefox ESR, not dead' @@ -150,7 +153,7 @@ module.exports = function createBaseConfig(env) { use: [ { loader: require.resolve('babel-loader'), - options: createBabelConfig(env), + options: createBabelConfig(config, isProd), }, require.resolve('source-map-loader'), ], @@ -296,7 +299,7 @@ module.exports = function createBaseConfig(env) { new RemoveEmptyScriptsPlugin(), new MiniCssExtractPlugin({ filename: - isProd && !env.isServer ? '[name].[contenthash:5].css' : '[name].css', + isProd && !isServer ? '[name].[contenthash:5].css' : '[name].css', chunkFilename: isProd ? '[name].chunk.[contenthash:5].css' : '[name].chunk.css', diff --git a/packages/cli/src/lib/webpack/webpack-client-config.js b/packages/cli/src/lib/webpack/webpack-client-config.js index df41a6d80..1668cb708 100644 --- a/packages/cli/src/lib/webpack/webpack-client-config.js +++ b/packages/cli/src/lib/webpack/webpack-client-config.js @@ -24,10 +24,12 @@ const cleanFilename = name => ); /** + * @param {import('../../../types').Env} env * @returns {Promise} */ -async function clientConfig(env) { - const { source, src } = env; +async function clientConfig(config, env) { + const { source, src } = config; + const { isProd } = env; const asyncLoader = require.resolve('@preact/async-loader'); let entry = { @@ -36,7 +38,7 @@ async function clientConfig(env) { }; let swInjectManifest = []; - if (env.sw) { + if (config.sw) { let swPath = join(__dirname, '..', '..', '..', 'sw', 'sw.js'); const userSwPath = join(src, 'sw.js'); if (existsSync(userSwPath)) { @@ -63,7 +65,7 @@ async function clientConfig(env) { // copy any static files existsSync(source('assets')) && { from: 'assets', to: 'assets' }, // copy sw-debug - !env.isProd && { + !isProd && { from: resolve(__dirname, '../../resources/sw-debug.js'), to: 'sw-debug.js', }, @@ -77,15 +79,13 @@ async function clientConfig(env) { return { entry, output: { - path: env.dest, + path: config.dest, publicPath: '/', filename: pathData => { if (pathData.chunk.name === 'dom-polyfills') { - return env.isProd - ? '[name].[chunkhash:5].legacy.js' - : '[name].legacy.js'; + return isProd ? '[name].[chunkhash:5].legacy.js' : '[name].legacy.js'; } - return env.isProd ? '[name].[chunkhash:5].js' : '[name].js'; + return isProd ? '[name].[chunkhash:5].js' : '[name].js'; }, chunkFilename: '[name].chunk.[chunkhash:5].js', }, @@ -128,10 +128,10 @@ async function clientConfig(env) { plugins: [ new webpack.DefinePlugin({ - 'process.env.ADD_SW': env.sw, - 'process.env.PRERENDER': env.prerender, + 'process.env.ADD_SW': config.sw, + 'process.env.PRERENDER': config.prerender, }), - ...(await renderHTMLPlugin(env)), + ...(await renderHTMLPlugin(config, env)), copyPatterns.length !== 0 && new CopyWebpackPlugin({ patterns: copyPatterns, @@ -144,7 +144,7 @@ async function clientConfig(env) { /** * @returns {import('webpack').Configuration} */ -function isProd(env) { +function prodBuild(config) { let limit = 200 * 1000; // 200kb const prodConfig = { performance: Object.assign( @@ -153,7 +153,7 @@ function isProd(env) { maxAssetSize: limit, maxEntrypointSize: limit, }, - env.pkg.performance + config.pkg.performance ), plugins: [ @@ -198,7 +198,7 @@ function isProd(env) { }, }; - if (env['inline-css']) { + if (config['inline-css']) { prodConfig.plugins.push( new CrittersPlugin({ preload: 'media', @@ -209,11 +209,11 @@ function isProd(env) { ); } - if (env.analyze) { + if (config.analyze) { prodConfig.plugins.push(new BundleAnalyzerPlugin()); } - if (env.brotli) { + if (config.brotli) { prodConfig.plugins.push( new CompressionPlugin({ filename: '[path].br[query]', @@ -266,22 +266,22 @@ function setupProxy(target) { /** * @returns {import('webpack').Configuration} */ -function isDev(env) { - const { cwd, src } = env; +function devBuild(config) { + const { cwd, src } = config; return { infrastructureLogging: { level: 'info', }, - plugins: [env.refresh && new RefreshPlugin()].filter(Boolean), + plugins: [config.refresh && new RefreshPlugin()].filter(Boolean), optimization: { moduleIds: 'named', }, devServer: { - hot: env.refresh, - liveReload: !env.refresh, + hot: config.refresh, + liveReload: !config.refresh, compress: true, devMiddleware: { publicPath: '/', @@ -293,24 +293,27 @@ function isDev(env) { ignored: [resolve(cwd, 'build'), resolve(cwd, 'node_modules')], }, }, - https: env.https, - port: env.port, - host: process.env.HOST || env.host || '0.0.0.0', + https: config.https, + port: config.port, + host: process.env.HOST || config.host || '0.0.0.0', allowedHosts: 'all', historyApiFallback: true, client: { logging: 'none', overlay: false, }, - proxy: setupProxy(env.pkg.proxy), + proxy: setupProxy(config.pkg.proxy), }, }; } -module.exports = async function createClientConfig(env) { +/** + * @param {import('../../../types').Env} env + */ +module.exports = async function createClientConfig(config, env) { return merge( - baseConfig(env), - await clientConfig(env), - (env.isProd ? isProd : isDev)(env) + baseConfig(config, env), + await clientConfig(config, env), + (env.isProd ? prodBuild : devBuild)(config) ); }; diff --git a/packages/cli/src/lib/webpack/webpack-server-config.js b/packages/cli/src/lib/webpack/webpack-server-config.js index cba7badb7..943f8bacf 100644 --- a/packages/cli/src/lib/webpack/webpack-server-config.js +++ b/packages/cli/src/lib/webpack/webpack-server-config.js @@ -5,15 +5,15 @@ const baseConfig = require('./webpack-base-config'); /** * @returns {import('webpack').Configuration} */ -function serverConfig(env) { +function serverConfig(config) { return { entry: { - 'ssr-bundle': env.source('index'), + 'ssr-bundle': config.source('index'), }, output: { publicPath: '/', filename: 'ssr-bundle.js', - path: resolve(env.dest, 'ssr-build'), + path: resolve(config.dest, 'ssr-build'), libraryTarget: 'commonjs2', }, externals: { @@ -28,6 +28,9 @@ function serverConfig(env) { }; } -module.exports = function createServerConfig(env) { - return merge(baseConfig(env), serverConfig(env)); +/** + * @param {import('../../../types').Env} env + */ +module.exports = function createServerConfig(config, env) { + return merge(baseConfig(config, env), serverConfig(config)); }; diff --git a/packages/cli/tests/subjects/custom-template/template.html b/packages/cli/tests/subjects/custom-template/template.html index 1e611a086..8ad6c331f 100644 --- a/packages/cli/tests/subjects/custom-template/template.html +++ b/packages/cli/tests/subjects/custom-template/template.html @@ -3,7 +3,7 @@ <% preact.title %> - <% if (cli.config.isProd) { %> + <% if (cli.env.isProd) { %> <% } else { %>