Preact-CLI项目Webpack生产构建Chunk加载报错求助
Let's break down what's going wrong here and fix it step by step.
Root Cause
When you add multiple CommonsChunkPlugin instances, Webpack expects the manifest chunk (the one with minChunks: Infinity) to be the last CommonsChunkPlugin you define. This is because it extracts the Webpack runtime code that needs to load all other chunks. Additionally, Preact-CLI's default CommonsChunkPlugin is conflicting with your custom splits, and your chunk sorting logic wasn't properly enforcing the correct load order in the HTML.
Step-by-Step Solution
1. Remove Preact-CLI's Default CommonsChunkPlugin
First, we need to get rid of the default plugin that comes with preact-cli, since it's conflicting with your custom splits:
// Find and remove the default CommonsChunkPlugin const defaultCommonsChunk = helpers.getPluginsByName(config, 'CommonsChunkPlugin')[0]; if (defaultCommonsChunk) { config.plugins.splice(config.plugins.indexOf(defaultCommonsChunk.plugin), 1); }
2. Reorder Your Custom CommonsChunkPlugins
Make sure your manifest chunk is the last one defined. This ensures Webpack correctly extracts the runtime after processing all other split chunks:
if(process.env.NODE_ENV === "production") { // Split vendor code (node_modules except specific libs) config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: ({ resource }) => ( resource !== undefined && resource.indexOf('node_modules') !== -1 && !/preact|preact-compat|redux|preact-redux|redux-form|lodash|sane-email-validation|moment|core-decorators|lodash-decorators|material-design-lite|preact-mdl/.test(resource) ), })); // Split Preact-related code config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'preact-chunk', filename: 'static/js/[name].[chunkhash:8].js', minChunks: (m) => m.resource && /preact|preact-compat/.test(m.resource) })); // Split other utility libs config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'others-chunk', filename: 'static/js/[name].[chunkhash:8].js', minChunks: (m) => m.resource && /redux|preact-redux|redux-form|lodash|sane-email-validation|moment|core-decorators|lodash-decorators/.test(m.resource) })); // Split Material-UI related code config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'material-ui-chunk', filename: 'static/js/[name].[chunkhash:8].js', minChunks: (m) => m.resource && /material-design-lite|preact-mdl/.test(m.resource) })); // Manifest chunk MUST be last config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', filename: 'static/js/[name].[chunkhash:8].js', minChunks: Infinity, })); }
3. Fix Chunk Sorting in HtmlWebpackPlugin
Your previous sorting function was too simplistic — it only prioritized the manifest but didn't handle the rest of the chunks correctly. Use this improved sorting logic to ensure manifest loads first, followed by your split chunks, then the main bundle:
htmlPlugin.options.chunksSortMode = (entry1, entry2) => { // Define the order we want chunks to load const chunkOrder = ['manifest', 'preact-chunk', 'material-ui-chunk', 'others-chunk', 'vendor', 'bundle']; return chunkOrder.indexOf(entry1.names[0]) - chunkOrder.indexOf(entry2.names[0]); };
4. Ensure ScriptExtHtmlWebpackPlugin Uses Defer Correctly
Keep defer as the default attribute (async can cause race conditions with chunk loading). We just need to make sure the plugin applies it consistently:
const scriptExtPlugin = helpers.getPluginsByName(config, 'ScriptExtHtmlWebpackPlugin')[0]; if (scriptExtPlugin) { scriptExtPlugin.plugin.options.defaultAttribute = 'defer'; }
Full Modified preact.config.js
Putting it all together, here's the complete config:
import webpack from 'webpack'; import path from 'path'; import { InterpolateHtmlPlugin } from 'react-dev-utils'; import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin'; export default (config, env, helpers) => { // Get and modify babel loader config let { rule } = helpers.getLoadersByName(config, 'babel-loader')[0]; let babelConfig = rule.options; babelConfig.plugins.push('transform-decorators-legacy'); babelConfig.presets.push('flow'); rule.options.plugins.push('transform-regenerator'); rule.options.plugins.push(["transform-runtime", { "helpers": false, "polyfill": false, "regenerator": true }]); // Add custom plugins const envs = env; // Assuming envs is available from your setup const paths = { appNodeModules: path.resolve(__dirname, 'node_modules') }; config.plugins.push(new InterpolateHtmlPlugin(envs.raw)); config.plugins.push(new webpack.DefinePlugin(envs.stringified)); config.plugins.push(new WatchMissingNodeModulesPlugin(paths.appNodeModules)); // Modify HtmlWebpackPlugin let htmlPlugin = helpers.getPluginsByName(config, 'HtmlWebpackPlugin')[0].plugin; htmlPlugin.options.template = `!!ejs-loader!${path.resolve(__dirname, './template.html')}`; htmlPlugin.options.title = "JApp"; htmlPlugin.options.manifest = Object.assign({}, { name: 'JH', short_name: 'JApp', theme_color: '#007DC5' }); // Remove default CommonsChunkPlugin const defaultCommonsChunk = helpers.getPluginsByName(config, 'CommonsChunkPlugin')[0]; if (defaultCommonsChunk) { config.plugins.splice(config.plugins.indexOf(defaultCommonsChunk.plugin), 1); } // Configure custom CommonsChunkPlugins (production only) if(process.env.NODE_ENV === "production") { config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: ({ resource }) => ( resource !== undefined && resource.indexOf('node_modules') !== -1 && !/preact|preact-compat|redux|preact-redux|redux-form|lodash|sane-email-validation|moment|core-decorators|lodash-decorators|material-design-lite|preact-mdl/.test(resource) ), })); config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'preact-chunk', filename: 'static/js/[name].[chunkhash:8].js', minChunks: (m) => m.resource && /preact|preact-compat/.test(m.resource) })); config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'others-chunk', filename: 'static/js/[name].[chunkhash:8].js', minChunks: (m) => m.resource && /redux|preact-redux|redux-form|lodash|sane-email-validation|moment|core-decorators|lodash-decorators/.test(m.resource) })); config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'material-ui-chunk', filename: 'static/js/[name].[chunkhash:8].js', minChunks: (m) => m.resource && /material-design-lite|preact-mdl/.test(m.resource) })); config.plugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', filename: 'static/js/[name].[chunkhash:8].js', minChunks: Infinity, })); } // Fix chunk sorting htmlPlugin.options.chunksSortMode = (entry1, entry2) => { const chunkOrder = ['manifest', 'preact-chunk', 'material-ui-chunk', 'others-chunk', 'vendor', 'bundle']; return chunkOrder.indexOf(entry1.names[0]) - chunkOrder.indexOf(entry2.names[0]); }; // Ensure ScriptExtHtmlWebpackPlugin uses defer const scriptExtPlugin = helpers.getPluginsByName(config, 'ScriptExtHtmlWebpackPlugin')[0]; if (scriptExtPlugin) { scriptExtPlugin.plugin.options.defaultAttribute = 'defer'; } };
Why This Works
- Removing the default CommonsChunkPlugin eliminates conflicts between preact-cli's default split and your custom splits.
- Ordering CommonsChunkPlugins correctly ensures the manifest chunk (which contains
webpackJsonp) is generated last, capturing the runtime needed to load all other chunks. - Explicit chunk sorting forces the manifest to load first in the HTML, so
webpackJsonpis defined before the main bundle tries to use it. - Using defer ensures scripts load in the order they appear in the HTML, preventing race conditions.
内容的提问来源于stack exchange,提问作者Vineet 'DEVIN' Dev

