项目脚手架之webpack
一问?
module.exports = { entry: { app: ['./src/main/index'], // 企企项目入口配置 }, };
1.1 单入口打包
const HtmlWebpackPlugin = require('html-webpack-plugin'); plugins:[ new HtmlWebpackPlugin({ filename:'index.html', // 打包出来的文件名 template:path.resolve(__dirname,'./src/index.html'), hash:true, // 在引用资源的后面增加hash戳 }) ]
1.2 多入口打包
── src ├── entry-1.js └── entry-2.js
entry:{ jquery:['jquery'], // 打包jquery entry1:path.resolve(__dirname,'../src/entry-1.js'), entry2:path.resolve(__dirname,'../src/entry-2.js') }, output:{ filename:'[name].js', path:path.resolve(__dirname,'../dist') },
new HtmlWebpackPlugin({ filename:'index.html', template:path.resolve(__dirname,'../public/template.html'), hash:true, chunks:['jquery','entry1'], // 引入的chunk 有jquery,entry }), new HtmlWebpackPlugin({ filename:'login.html', template:path.resolve(__dirname,'../public/template.html'), hash:true, inject:false, // inject 为false表示不注入js文件 chunksSortMode:'manual', // 手动配置代码块顺序 chunks:['entry2','jquery'] }) inject有四个值: true body head false - true 默认值,script标签位于html文件的 body 底部 - body script标签位于html文件的 body 底部 - head script标签位于html文件的 head中 - false 不插入生成的js文件,这个几乎不会用到的
let htmlPlugins = [ { entry: "entry1", html: "index.html" }, { entry: "entry2", html: "login.html" } ].map( item => new HtmlWebpackPlugin({ filename: item.html, template: path.resolve(__dirname, "../public/template.html"), hash: true, chunks: ["jquery", item.entry] }) ); plugins: [...htmlPlugins]
module.exports = { const ROOT_PATH = path.resolve(__dirname); const entryPointName = 'app'; const version = require('./package.json').version; => "version": "1.0.0", output: { publicPath: '', filename: `[name].[chunkhash:8].min.js`, path: path.resolve(ROOT_PATH, dist), chunkFilename: `${entryPointName}-${version}-[name]-[chunkhash:8].min.js`, hashDigestLength: 8, // 散列摘要的前缀长度,默认为 20。 N.B: 缓存优化 chunkhash contenthash 不配置该属性的话,HashOutputWebpackPlugin无法生效;其需要用到hashDigestLength属性 }, };
filename
chunkFilename
https://www.cnblogs.com/skychx/p/webpack-filename-chunkFilename.html
// webpack.dev.config.js module.exports = env => { return resources.createServeConfig({ .... mode: 'development', .... }); }; //webpack.config.js module.exports = function (argv) { const configs = []; const prodConfig = resources.createServeConfig( .... mode: 'production', .... }, isProduction, ); configs.push(prodConfig); return configs; }; // webpack.dev.resource.js const path = require("path"); const merge = require("webpack-merge"); const argv = process.argv.slice(2); const needSourceMap = argv.includes('--sourcemap'); const CODE_MINIFY = dist === 'dist'; module.exports = { createServeConfig(customConfig, isProduction) { const ret = merge( { devServer:{....}, mode: 'development', output:{....}, resolveLoader:{....}, resolve:{....}, module: {....}, plugins: [....], },customConfig ) /** * N.B: sourcemap 开启和关闭 * 背景:web2 inte 等开发提测环境需要source-map,以方便调试。(souce-map开启后无法去除注释) * 线上环境需要关闭sourcemap以获得更好的压缩效果(会去除注释,减小更多的体积)。(等迭代或者产品稳定了,bug少了再去掉source-map) * * 设想1:用source-map模式,将.map文件独立出来,在线上的时候不上传.map文件,以保证测试环境和线上环境的代码是一致的。 * 后果:额,source-map模式只要上到外网,其它人可访问,就会导致源码泄漏(可通过manifest拿到所有的js文件,进而得到.map文件,就拿到源码了) * * 设想2:用eval模式 * 后果:代码体积大些,外网用户可看到被编译成es5的源码(应该是可以接受的) * * 所以只有内网环境才可以上source-map模式,外网环境要source-map的话,就只能用eval模式了(就是体积大些,等稳定了可以去掉eval)(不会泄漏源码,代码为es5的source-map) * * 结论: 只能选择用与不用eval模式。 */ let config = ret; if (needSourceMap) { config = Object.assign( { // 自动化测试使用eval模式 devtool: CODE_MINIFY ? 'source-map' : 'eval', }, ret, ); } /** * N.B: eval also belongs to cheap-source-map, 导致无法去掉注释 * issues => [Removing comments does not work when setting devtool to eval](https://github.com/webpack-contrib/uglifyjs-webpack-plugin/issues/180) */ config = merge( isProduction ? {}: {devtool: 'eval'}, config, ); console.log('config.devtool', config.devtool); return config; }, }; [package.json] "scripts":{ "dev":"webpack-dev-server --env.development --config ./build/webpack.base.js" "build":"webpack --env.production --config ./build/webpack.base.js" }
二问?
// loader module: { rules: [ { test: /\.css/, loader:['style-loader','css-loader'] } ] } // use module: { rules: [ { test: /\.css/, use:['style-loader','css-loader'] } ] } // use + loader module: { rules: [ { test: /\.css/, include: path.resolve(__dirname,'src'), exclude: /node_modules/, use: [{ loader: 'style-loader', options: { insert:'top' } },{ loader:'css-loader', options:{ // 如果css文件引入其他文件@import simportLoaders:2 } },'postcss-loader','sass-loader'] } ] }
样式相关Loaders
module.exports = { module: { rules: [ // css文件 { test: /\.css$/, use: [ 'cache-loader', 'thread-loader', 'css-hot-loader', MiniCssExtractPlugin.loader, //=> 抽离CSS为独立的文件 // 'style-loader', //=>把CSS插入到HEAD中 'css-loader', //=>编译解析@import/URL()这种语法 { loader: 'postcss-loader', options: { plugins: function () { return [ postcssPresetEnv({ stage: 0, }), require('autoprefixer'), ]; }, }, }, ], }, ], }, }
// index.css ::placeholder { color: red; } // postcss.config.js const autoprefixer = require('autoprefixer'); const mqpacker = require('css-mqpacker'); module.exports = (ctx) => ({ plugins: [ autoprefixer({ browsers: [ '>0.5%', 'last 2 versions', 'not dead', 'not op_mini all' ] }), mqpacker() ] }); // webpack.config.js { test:/\.css$/, use:[MiniCssExtractPlugin.loader,'css-loader','postcss-loader'], include:path.join(__dirname,'./src'), exclude:/node_modules/ }
module: { rules: [ { test: /\.less$/, use: [ 'style-loader', 'css-loader', 'less-loader' ] }, { test: /[^(.g)]+\.scss$/, enforce: 'pre', //=>强制在下面的loader之前执行 use: [ ..... { loader: 'sass-loader', }, { loader: 'sass-resources-loader', // 通过 sass-resources-loader 全局注册 Sass 变量 options: { resources: ['./src/theme/custom-variables.scss'], }, }, ], }, }
图片相关loaders
module: { //=>模块规则:使用加载器(默认从右向左执行) rules: [ { test: /\.(woff|woff2|eot|ttf)(\?.*$|$)/, use: ['url-loader'], }, { test: /\.(svg)$/i, use: ['svg-sprite-loader'], include: svgDirs, // 把 svgDirs 路径下的所有 svg 文件交给 svg-sprite-loader 插件处理 }, { test: /\.(png|jpg|gif|svg)$/, use: ['url-loader?limit=8192&name=images/[contenthash:8].[name].[ext]'], exclude: svgDirs, }, { test: /\.html$/, use: ['html-withimg-loader'] } ] }
https://blog.csdn.net/zc135565/article/details/104166781
脚本(JS、TS、TSX)相关loader
参考
{ test: /\.js$/, use: [ { loader: 'babel-loader', options: { babelrc: false, compact: false, sourceMap: false, comments: false, // 布尔类型,表示打包编译之后不产生注释内容 }, }, ], include: [/node_modules/, /\/packages\//], }, { test: [/\.tsx?$/], use: [ { loader: 'babel-loader', options: { babelrc: false, plugins: [require.resolve('@q7/build-scripts/lib/plugins/babel/babel-plugin-dynamic-router')] } }, { loader: 'esbuild-loader', options: { tsconfigRaw: require('./tsconfig.json'), //=> 如果您有一个tsconfig.json文件,esbuild loader将自动检测它。或者也可以通过tsconfigRaw选项直接传递: loader: 'tsx', minify: false, }, }, { loader: 'ts-loader', options: { happyPackMode: true, experimentalWatchApi: true, transpileOnly: true, // IMPORTANT! use transpileOnly mode to speed-up compilation }, }, ], include: [/router-defines(\/|\\)index/], exclude: [/node_modules/, /\.scss.ts$/, /\.test.tsx?$/], },
{ "presets": ["@babel/preset-typescript"], "plugins": [ "@babel/plugin-syntax-dynamic-import", "react-hot-loader/babel" ] }
{ "compilerOptions": { "experimentalDecorators": true, "inlineSources": true, "sourceRoot": "/", "target": "es5", "module": "esnext", "moduleResolution": "node", "downlevelIteration": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "removeComments": false, "baseUrl": ".", "outDir": "lib", "allowJs": true, "checkJs": false, "paths": { "@axios": ["./src/axios"], .... }, "plugins": [{ "transform": "ts-optchain/transform" }], "jsx": "react", "alwaysStrict": true, "noUnusedLocals": false, "importHelpers": true, "noImplicitThis": false, "emitDecoratorMetadata": true, "esModuleInterop": true, "lib": ["esnext", "es7", "es6", "es5", "es2015.promise", "es2015.generator", "dom"], "skipLibCheck": true, "typeRoots": ["node", "node_modules/@types", "global.d.ts"] }, "exclude": ["**/node_modules", "staticLib", ".cache", ".idea", "dist", "jest"] }
const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); module.exports = { entry: 'index.js', output: { path: path.resolve(__dirname, './dist'), filename: 'index_bundle.js' }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', //=> 指定自己的模板 filename: 'index.html', //=> 输出的文件名 hash: true, minify: {//=> 压缩HTMl文件 collapseWhitespace: true, //=> 删除空白符和换行符 removeComments: true, //=> 去掉注释 removeAttributeQuotes: true, //=> 去掉属性的双引号 removeEmptyAttributes: true //=> 去掉空属性 }, chunks:['common','index'], chunksSortMode:'manual'//对引入代码块进行排序的模式 }) ] };
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); const path = require('path'); module.exports = { plugins: [ new CleanWebpackPlugin(['build/dll'], { root: path.resolve() }) ], };
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const path = require('path'); module.exports = { plugins: [new MiniCssExtractPlugin()], module: { rules: [ { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, };
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); const TerserWebpackPlugin = require('terser-webpack-plugin'); const path = require('path'); module.exports = { mode: 'production', optimization: { minimizer: [ // 压缩JS资源的 new TerserWebpackPlugin({ parallel: true, }), new OptimizeCSSAssetsPlugin({ //压缩css资源的 assetNameRegExp:/\.css$/g, cssProcessor:require('cssnano') //cssnano是PostCSS的CSS优化和分解插件。cssnano采用格式很好的CSS,并通过许多优化,以确保最终的生产环境尽可能小。 }) ] }, },
const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { plugins: [ new RetryChunkLoadPlugin({ retryDelay: 10000, // 间隔多久重启一次 maxRetries: 5, // 最多重启次数 }), ] }
const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); module.exports = { plugins: [ new CopyWebpackPlugin([{ from: path.resolve(__dirname,'src/assets'), // 静态资源目录源地址 to:path.resolve(__dirname,'dist/assets') // 目标地址,相对于output的path目录 }]) ] }
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin({ analyzerMode: 'disabled', // 不启动展示打包报告的http服务器 generateStatsFile: true, // 是否生成stats.json文件 }) ] }
const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); module.exports = { plugins: [ new WebpackManifestPlugin({ fileName: 'manifest.json', }), ] }
const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); module.exports = { plugins: [ /** * HashOutputWebpackPlugin * N.B: 缓存优化:contentHash [chunkhash][contenthash] 用真实的基于内容的contenthash来替换webpack自带的contenthash(自带的在每次打包时,有几个chunk的contenthash会变化(尽管chunk里的内容没变)) * issues => [Webpack 4 chunkhash/contenthash can vary between builds](https://github.com/webpack/webpack/issues/7179) * HashOutput必须放在首位 * 必须设置 output.hashDigestLength 为[chunkhash:8]这里所保留的hash长度(8) */ new HashOutputWebpackPlugin(), ] }
const webpack = require('webpack'); module.exports = { plugins: [ new webpack.ProgressPlugin(), // 构建进度条 new webpack.WatchIgnorePlugin([/\.js$/, /\.d\.ts$/]), // 忽略掉 d.ts 文件,避免因为编译生成 d.ts 文件导致又重新检查。 new webpack.HashedModuleIdsPlugin(), // N.B: 缓存优化: moduleId 用hashId来替换数字id索引(用数字id来当作manifest里的索引,可能会因为顺序变化而变化,导致缓存失效) new webpack.DefinePlugin({ 'process.env.HMR': `"none"`, 'process.env.isProd': `"${isProduction}"`, 'process.env.publicPath': `"${publicPath}"`, 'process.env.sp_pro_path': `"${SP_PRO_PATH}"`, }), new webpack.IgnorePlugin(/^\.\/locale/,/moment$/) ] }
resolve: { alias: Object.assign({ 'react': path.resolve(path.join(__dirname, './node_modules/react')), 'react-dom': path.resolve(path.join(__dirname, './node_modules/react-dom')), 'react-hot-loader': path.resolve(path.join(__dirname, './node_modules/react-hot-loader')), ...... }, createKaledioAlias({ '@q7/athena': (project, p) => { Object.assign(p, { '@athena-ui': path.resolve(ROOT_PATH, './node_modules/@q7/athena/src'), '@grid': path.resolve(ROOT_PATH, './node_modules/@q7/athena/src/components/Grid'), '@ag-grid': path.resolve(ROOT_PATH, './node_modules/@q7/athena/src/components/ag-grid'), '@q7/athena/lib': path.resolve(ROOT_PATH, './node_modules/@q7/athena/src'), }) } }) ), plugins: [ new TsconfigPathsPlugin({ configFile: './tsconfig.json', logLevel: 'info', extensions: ['.ts', '.tsx'], mainFields: ['main', 'browser'], // baseUrl: "/foo" }), ], },
resolve: { extensions: [".js",".jsx",".json",".css"] },
resolve: { alias: { 'react': path.resolve(path.join(__dirname, './node_modules/react')), 'react-dom': path.resolve(path.join(__dirname, './node_modules/react-dom')), 'react-hot-loader': path.resolve(path.join(__dirname, './node_modules/react-hot-loader')), '@mobx': path.resolve(ROOT_PATH, 'node_modules/mobx'), '@q7/athena/src': path.join(__dirname, 'src'), '@q7/athena/lib': path.join(__dirname, 'lib'), ..... }, }
resolve: { mainFields: ['browser', 'module', 'main'], // 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是: mainFields: ["module", "main"], // target 的值为其他时,mainFields 默认值为: }
resolve.resolveLoader
module.exports = { resolveLoader: { modules: [ path.resolve(__dirname, '../node_modules'), path.resolve(process.cwd(), 'node_modules'), ], }, };
三问?
//=> 从 os.cpu 获取并行数量,如果取不到,默认按照4核(需要和运维确认) const cpuNumber = 1; optimization: { usedExports: true, //=> 开启,只要导出的模块都tree shaking(摇树),意思是把没有使用到的文件删除,去除无效的代码,减小文件体积 sideEffects: true, //=> 允许我们通过配置标识我们的代码是否有副作用,从而提供更大的压缩空间。(模块的副作用指的就是模块执行的时候除了导出成员,是否还做了其他的事情。) occurrenceOrder: false, //=> 标记模块的加载顺序,使初始包更小 removeEmptyChunks: false, //=> 检测或移除空的chunk,设置为 false 以禁用这项优化 removeAvailableModules: true, //=> 删除已可用模块 minimizer: [ //=> 来最小化包 new TerserWebpackPlugin({ //=> 该插件使用 terser 来压缩 JavaScript。 exclude: [/^manifest\..*\.js/], //=> 匹配不需要压缩的文件。 sourceMap: true, parallel: cpuNumber || true, //=> 使用多进程并发运行以提高构建速度。 并发运行的默认数量: os.cpus().length - 1 。 extractComments: true, //=> 是否将注释剥离到单独的文件中 }), new OptimizeCSSAssetsPlugin({ //=> 主要是用来压缩css文件 cssProcessor: require('cssnano'), //=> 用于优化\最小化CSS的CSS处理器,默认为cssnano cssProcessorPluginOptions: { //=> 传递给cssProcessor的插件选项 preset: ['advanced', { discardComments: { removeAll: true }, reduceIdents: { gridTemplate: false, }, }], }, }) ], runtimeChunk: { //=> 打包时生成一个体积很小runtime.xxx.js文件,用作映射其他chunk文件,目的是更新后,以较小的代价利用缓存,提升页面加载速度。 name: 'manifest', }, splitChunks: { /=> 代码分割 cacheGroups: { //=> 设置缓存组用来抽取满足不同规则的chunk css: { name: 'initial-all-vendor', test: /\.s?css$/, chunks: 'all', }, } }, },
const bodyParser = require('body-parser'); const devServerConfig = env => { const { DEPLOY_ENV = 'local' } = env; let isLocal = true; let host = DEPLOY_ENV; return { inline: true, //=> 在dev-server的两种不同模式之间切换。默认情况下,应用程序启用内联模式(inline mode) hot: true, //=> 启用webpack的模块热替换功能 host: '0.0.0.0', //=> 指定使用一个 host。默认是 localhost。 publicPath: '/', //=> 此路径下的打包文件可在浏览器中访问 disableHostCheck: true, //=>设置为true时,此选项绕过主机检查。不建议这样做,因为不检查主机的应用程序容易受到DNS重新连接攻击。 compress: false, //=> 启用gzip压缩 watch:true, //=> 开启监听 watchOptions: { //=> 监控的选项,可以监听文件变化,当它们修改后会重新编译 ignored: /node_modules\/(?!@q7)/, //=> 不需要进行监控哪个文件,对于某些系统,监听大量文件系统会导致大量的 CPU 或内存占用。这个选项可以排除一些巨大的文件夹 poll: 1233, //=> 每x秒检查一次变动, 通过传递true开启polling,或者指定毫秒为单位进行轮询。 aggregateTimeout:500, // 防抖 我一直输入代码 }, port: 3010, //=> 指定要监听请求的端口号 stats: { //=> 通过此选项,可以精确控制要显示的 bundle 信息。如果你想要显示一些打包信息,但又不是显示全部,这可能是一个不错的妥协 ...... }, }; }; module.exports = { devServerConfig: devServerConfig, };
const jQuery = require("jquery"); import jQuery from 'jquery'; // 通过下面externals配置,jQuery就不会被打包 <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> externals: { jquery: 'jQuery'//如果要在浏览器中运行,那么不用添加什么前缀,默认设置就是global }, module: {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { //... stats: 'verbose' //=> 全部输出 // stats: 'errors-only' //=> 只在错误时输出 }; stats:{ progress: true, // 显示打包进度 colors: true, // 打包日志显示颜色 assets: true, // 添加资源信息 version: true, // 添加 webpack 版本信息 hash: true, // 添加 compilation 的哈希值 timings: true, // 添加时间信息 chunks: false, // 添加 chunk 信息(设置为 `false` 能允许较少的冗长输出) chunkModules: false, // 将构建模块信息添加到 chunk 信息 warnings: false, // 添加警告 }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下次分享主题:source-map的使用以及如何调式打包后/线上的代码?
与webpack类似的工具还有哪些? 谈谈你为什么选择使用或放弃webpack?
1.grunt
2.gulp
3.webpack
4.rollup
5.parcel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); optimization: { minimize: true, minimizer: [ //压缩CSS new OptimizeCSSAssetsPlugin({}), ] },
const TerserWebpackPlugin = require('terser-webpack-plugin'); optimization: { minimize: true, minimizer: [ //压缩JS new TerserWebpackPlugin({}) ] }
{ test: /\.(png|svg|jpg|gif|jpeg|ico)$/, use: [ "file-loader", { loader: "image-webpack-loader", options: { mozjpeg: { progressive: true, quality: 65, }, optipng: { enabled: false, }, pngquant: { quality: "65-90", speed: 4, }, gifsicle: { interlaced: false, }, webp: { quality: 75, } } } ] }
const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const PurgecssPlugin = require("purgecss-webpack-plugin"); module.exports = { module: { rules: [ { test: /\.css$/, include: path.resolve(__dirname, "src"), exclude: /node_modules/, use: [ { loader: MiniCssExtractPlugin.loader, }, "css-loader", ], } ] }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].css", }), new PurgecssPlugin({ paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }), }) ] devServer: {}, };
module.exports = { mode:'production', devtool:false, module: { rules: [ { test: /\.js/, include: path.resolve(__dirname, "src"), use: [ { loader: "babel-loader", options: { presets: [["@babel/preset-env", { "modules": false }]], }, }, ], } ], } }
export default 'Hello';
import str from './hello.js'; console.log(str);
var hello = ('hello'); console.log(hello);
entry: { index: "./src/index.js", login: "./src/login.js" }
<link rel="preload" as="script" href="utils.js"> import( `./utils.js` /* webpackPreload: true */ /* webpackChunkName: "utils" */ )
<link rel="prefetch" href="utils.js" as="script"> button.addEventListener('click', () => { import( `./utils.js` /* webpackPrefetch: true */ /* webpackChunkName: "utils" */ ).then(result => { result.default.log('hello'); }) });
entry: { page1: "./src/page1.js", page2: "./src/page2.js", page3: "./src/page3.js", }, optimization: { splitChunks: { chunks: "all", //默认作用于异步chunk,值为all/initial/async minSize: 0, //默认值是30kb,代码块的最小尺寸 minChunks: 1, //被多少模块共享,在分割之前模块的被引用次数 maxAsyncRequests: 2, //限制异步模块内部的并行最大请求数的,说白了你可以理解为是每个import()它里面的最大并行请求数量 maxInitialRequests: 4, //限制入口的拆分数量 name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔开,如vendor~ automaticNameDelimiter: "~", //默认webpack将会使用入口名和代码块的名称生成命名,比如 'vendors~main.js' cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例 vendors: { chunks: "all", test: /node_modules/, //条件 priority: -10, ///优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值. }, commons: { chunks: "all", minSize: 0, //最小提取字节数 minChunks: 2, //最少被几个chunk引用 priority: -20 } } } }