CommonsChunkPlugin 详解,webpack4 改成optimization.splitChunks

CommonsChunkPlugin:
1. name和names:将符合条件的模块提取到指定name的chunk中. 如果name为不存在的chunk则创建新chunk,如果name指定的chunk存在,则继续往chunk中添加被提取的模块内容所有chunk的公共代码
2 filename:指定提取出的公共代码的文件名称可以带路径,不指定 则使用output配置项中文件名的占位符。未定义时使用name作为文件名。
3 chunks:可以指定要提取公共模块的源chunks,指定的chunk必须是公共chunk的子模块,如果没有指定则使用所有entry中定义的入口chunk。
4 minChunks:在一个模块被提取到公共chunk之前,它必须被最少minChunks个chunk所包含。(通俗的说就是一个模块至少要被minChunks个模块所引用,才能被提取到公共模块。)
该数字必须不小于2或者不大于chunks的个数。默认值等于chunks的个数
如果指定了Infinity,则创建一个公共chunk,但是不包含任何模块,内部是一些webpack生成的runtime代码和chunk自身包含的模块(如果chunk存在的话)。
5 children:children和chunks不能同时设置,因为它们都是指定source chunks的
6 async

注意:vender是我们提取出来的公共chunk,通常不会被修改,所以理应在每次编译后文件名保持一致
所以 抽离第三方库和自定义公共模块

[cc lang=”javascript”]
// 第一种方法minChunks设为Infinity
//Infinity:只有当入口文件(entry chunks) >= 3 才生效,用来在第三方库中分离自定义的公共模块
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: [‘vendor’,’runtime’], // 在vendor上继续抽离 webpack运行文件
filename: ‘[name].js’,
minChunks: Infinity // 抽离自定义公共模块
}),
new webpack.optimize.CommonsChunkPlugin({
name: ‘common’,
filename: ‘[name].js’,
chunks: [‘first’,’second’]
//从first.js和second.js中抽取commons chunk, 也可以用 children和async 异步加载公共模块
}),
][/cc]

[cc lang=”javascript”]
//module:当前chunk及其包含的模块
//count:当前chunk及其包含的模块被引用的次数
//minChunks作为函数会遍历每一个入口文件及其依赖的模块,返回一个布尔值,为true代表当前正在处理的文件(module.resource)合并到commons chunk中,为false则不合并。
const config = {
entry: {
first: ‘./src/first.js’,
second: ‘./src/second.js’,
//vendor: Object.keys(packagejson.dependencies)//获取生产环境依赖的库
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ‘vendor’,
filename: ‘[name].js’,
minChunks: function (module,count) {
console.log(module.resource,`引用次数${count}`);
//”有正在处理文件” + “这个文件是 .js 后缀” + “这个文件是在 node_modules 中”
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, ‘./node_modules’)) === 0
)
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: ‘runtime’,
filename: ‘[name].js’,
chunks: [‘vendor’]
}),
]
}[/cc]

[cc lang=”javascript”]

new webpack.optimize.CommonsChunkPlugin({
children: true,
async: ‘children-async’
})[/cc]

你设置之后,也得看children的脸色怎么来划分:
children为true,共同引用的模块就会被打包合并到名为children-async的公共模块,当你懒加载first或者second的时候并行加载这和children-async公共模块
children为false,共同引用的模块就会被打包到首屏加载的app.bundle当中,这就会导致首屏加载过长了,而且也不要用到,所以最好还是设为true

webpack 4

splitChunks

[cc lang="js"]

// b.js
import vue from "vue" 

// 入口js
import _ from "lodash"
import "./a.js" //同步加载
import ('./b.js') //异步加载
[/cc]

chunks:抽取公共模块的来源,只有三个值

 async:(动态加载模块) 只分割异步打包的代码,b.js 会打包出b和vue两个chunk, lodash 拆不出来

 initial:(入口模块),只从入口进行拆分,但是异步内部的引入将不再考虑,直接打包一起,会将vue和b的内容打在一起

 all:(全部模块入口和动态的) 分割异步同步代码(需要定义新规则,将同步的代码打包) 

[cc lang=”js”]
//同步代码分割配置
splitChunks: {
chunks: ‘all’,
cacheGroups: {
a : {
name: ‘a’,
chunks: ‘all’,
}
}
}
// 会生成 一个同步代码a.js 和 分离出的b.js 和 vue 两个 chunk
[/cc]

Function(chunk): chunk对象包含chunk本身的许多信息,有用的也就是chunk.name,可以通过name选择特定的模块来源,返回true则选中,否则不选中。

cacheGroups 自定义配置决定生成的文件,缓存策略

  • test : 限制范围,正则匹配文件夹或文件
  • name : 打包的chunks的名字
  • priority : 优先级,多个分组冲突时决定把代码放在哪块
  • enforce: 强制生成

maxAsyncRequests:  异步的按需加载模块最大的并行请求数,通过import()或者require.ensure()方式引入的模块,分离出来的包是异步加载的。default:5
maxInitialRequests: entry文件最大的并行请求数, 默认3
minSize: 生成新的chunk的最小体积,默认30000B
maxSize: 打包出来的新的chunk最大的文件大小,与minSize相对,超过这个值,将新的chunk再进一步拆分成更小的chunk,单位B,默认0。
automaticNameDelimiter: name连接符,分离出来的新chunk的名字,默认基于cacheGroupsKey,chunks来源的name来取,例:缓存组的cacheGroupsKey是vendor,来源chunk是的name是adminA,adminB,那么连接符是~,分离出的chunk名字是vendor~adminA~adminB.js。

name: Boolean | string | function (module, chunks, cacheGroupKey)

分离出来的chunk的取名规则。不能和入口文件同名,如果和入口文件同名,那么入口文件将会被移除,不会被打包

          Boolean :如果是true,基于cacheGroupsKey,chunks来源的name来取。如果是false,按照数字0排序。
         string : string作为name, string.js。
         function (module, chunks, cacheGroupKey)
              cacheGroupKey: 缓存组的键名;
              chunks: Array类型,splitChunks.chunks的数组集合,每一个chunk对象有很多属性,这里有用也就         只有chunks[i].name,即打包来源chunk的键名;
              module: 可能有用的值module.context,module.resource,module.type;module.context公共模块来源chunk的所在文件夹,例如通过 npm 下载的包chunks.context == path.resolve(__dirname , ‘node_modules’);,module.resource公共模块来源chunk的完整路径,module.type文件类型javascript/auto,css/auto。
            返回值: 返回值作为文件名,同样不能和入口文件同名,否则入口文件会被移除

!!@  如果你要做精细化的代码分离,需要配置缓存组cacheGroups,

cacheGroups拥有上面所有的属性,除此之外还有priority、reuseExistingChunk、test、enforce四个属性,并且缓存组能够覆盖上面所有的属性。

priority :优先级,default:0;如果两个缓存组都需要将某一公共模块打包,为了不重复打包,肯定只能打包进入其中之一,那么优先考虑priority 高的。
reuseExistingChunk:是否重用已经存在的模块,default:true;例:如果在当前缓存组需要抽离出 jquery.js,但是 jquery.js已经被其它缓存组抽取出来了,那么将会重用已经抽取出来的 jquery.js。
test:function (module, chunks) | RegExp | string 在chunks的基础上,精确的选择那些公共模块应该被打包。
enforce:忽略minSize、maxSize、maxAsyncRequests、maxInitalRequests等限制条件直接打包。

缓存组也有默认配置 default ,default的配置会覆盖splitChunks中的默认配置,并且其它缓存组的minSize的优先级低于default缓存组的优先级,所以默认配置最好还是在splitChunks配置,不要在缓存组配置:

[cc lang=”js”]
splitChunks: {
chunks: ‘all’, // initial、async和all
minSize: 30000, // 形成一个新代码块最小的体积
maxAsyncRequests: 5, // 按需加载时候最大的并行请求数
maxInitialRequests: 3, // 最大初始化请求数
automaticNameDelimiter: ‘~’, // 打包分割符
name: true,
cacheGroups: {
vendors: { // 项目基本框架等
chunks: ‘all’,
test: /[\\/]node_modules[\\/](vue|vue-resource|vuex|underscore|mint-ui|jsonp|axios|babel-polyfill)[\\/]/, // 需要加node_modules,不然打包不纯
priority: 100,
name: ‘vendors’,
},
‘async-commons’: { // 异步加载公共包、组件等
chunks: ‘async’,
minChunks: 2,
name: ‘async-commons’,
priority: 90,
},
commons: { // 其他同步加载公共包
chunks: ‘all’,
minChunks: 2,
name: ‘commons’,
priority: 80,
},
}
}
// 同样用到react 单priority 等级高, 只会打包到vendors 里
// 如果我们这里将commons的配置去掉,将会生成一个topic~home的包,我们配置了async-common提取出异步加载的公共包后,将会默认将同步加载的公共包打包生成以automaticNameDelimiter连接符‘~’生成的名字’topic~home’包,内容其实和commons包内容完全一样
[/cc]

最后
hash 是 build-specific ,即每次编译都不同——适用于开发阶段
chunkhash 是 chunk-specific,是根据每个 chunk 的内容计算出的 hash——适用于生产

问题
chunk 不能打包到html 里

vue css 作用到子组件

1. 如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符:
[cc lang=”html”]

[/cc]

上述代码将会编译成:
[cc]
.a[data-v-f3f3eg9] .b { /* … */ }[/cc]

有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/ 操作符取而代之——这是一个 >>> 的别名,同样可以正常工作

webpack 2 升级到 webpack 4

1 安装 webpck 4, 根据提示安装新的 版本,
涉及插件

[cc lang=”json”]”vue-loader”: “^15.0.10”,
“vue-style-loader”: “^4.1.0”,
“vue-template-compiler”: “^2.5.16”,
“copy-webpack-plugin”: “^4.0.1”,
“extract-text-webpack-plugin”: “^4.0.0-beta.0”,
“html-webpack-plugin”: “^3.1.0”,
“optimize-css-assets-webpack-plugin”: “^4.0.0”,
“webpack-bundle-analyzer”: “^2.11.1”,
“webpack-dev-middleware”: “^3.1.2”,
“webpack-dev-server”: “^3.1.3”,
“webpack-merge”: “^4.1.0”
[/cc]

2 更新各种loader
mini-css-extract-plugin
这个插件将css按模块化解压到单独的文件中,为每个包含css的js文件创键一个css文件。
style-loader 是将css文件以行内样式style的标签写入打包后的html页面,mini-css-extract-plugin是直接link方式引入进去
extract-text-webpack-plugin
根据创键实例、或者配置多个入口chunk来的,比如配置了一个入口文件,最终所有的css都会提取在一个样式文件中;如果n个入口(即创键了n个实例),则会生成多个css文件
3 webpack.optimize.CommonsChunkPlugin 提取公共代码 改为 optimization.splitChunks

4 vue-loader
*需要把 VueLoaderPlugin = require(‘vue-loader/lib/plugin’) 添加到plugin 中
*v15版本之后(使用了不同的策略 推导.vue文件中各个语言块使用的loader,将各个语言块视为独立的文件,使用webpack中配置了规则的loader处理,由此带来的配置变化就是针对样式处理,webpack.rules中必须显示提供对应loader处理的规则),所以原有vue-loader配置中内联传入的样式相关的loader可以去除

[cc lang=”js”]test: /\.vue$/,
loader: ‘vue-loader’,
options: {
// v15+版本不再需要内联的cssloader配置
// 如果不去掉会报错??(我的项目未去掉也没报错,emmm…?)
}
}[/cc]

*鉴于第2点的推导变化,<script><script/>标签内的js代码将被视为独立的js文件并根据webpack配置使用babel-loader转译;因此,如果项目配置babel-loader时使用exclude:/node_modules/排除了依赖包中代码的转译,并且导入了/node_modules/中的.vue文件,<script>部分将不会被转译,故需要将.vue文件加入排除名单中

[cc lang=”js”]{
test: /\.js$/,
exclude: file => (
/node_modules/.test(file) &&
!/\.vue\.js/.test(file)
),
loader:’babel-loader’
},[/cc]

5 package.json
// window下直接set会不支持,需要npm i cross-env
“dev”: “cross-env NODE_ENV=development …”
// webpack4
webpack运行时还会根据mode设置一个全局变量process.env.NODE_ENV,这里的process.env.NODE_ENV不是node中的环境变量,而是webpack.DefinePlugin中定义的全局变量,允许你根据不同的环境执行不同的代码.

“dev”: “webpack –mode development”
“build”: “webpack –mode production”

6. minimize
默认为true,效果就是压缩js代码(代替以前的webpack.optimize.UglifyJsPlugin)

minimizer
可以自定义UglifyJsPlugin和一些配置,默认的压缩为uglifyjs-webpack-plugin 【当使用了OptimizeCSSAssetsPlugin插件,内置的uglifyjs-webpack-plugin插件会失效,这边需要重新配置下UglifyJsPlugin】

// 新增css的压缩
minimizer:
mode === “development”
? []
: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: mode === “development”
}),
new OptimizeCSSAssetsPlugin()
]

7 CSS文件提取插件
mini-css-extract-plugin 替代 extract-text-webpack-plugin

[cc lang=”js”]
//webpack 文件配置
const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’);
const devMode = process.env.NODE_ENV !== ‘production’;

module.exports = {
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? ‘[name].css’ : ‘[name].[hash].css’,
chunkFilename: devMode ? ‘[id].css’ : ‘[id].[hash].css’,
}),
],
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === ‘development’,
// if hmr does not work, this is a forceful method.
reloadAll: true,
},
},
‘css-loader’,
‘postcss-loader’,
‘sass-loader’,
],
},
],
},
};[/cc]

[cc lang=”js”]
//Extracting all CSS in a single file 生成css 到一个文件
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: ‘styles’,
test: /\.css$/,
chunks: ‘all’,
enforce: true,
},
},
},
},[/cc]

根据 entry point 或 chunk 打包不同css 文件 参考 https://www.npmjs.com/package/mini-css-extract-plugin

!!!优化css的处理器

[cc lang=”js”] new OptimizeCSSAssetsPlugin ({
// 默认是全部的CSS都压缩,该字段可以指定某些要处理的文件
assetNameRegExp: /\.(sa|sc|c)ss$/g,
// 指定一个优化css的处理器,默认cssnano
cssProcessor: require(‘cssnano’),

cssProcessorPluginOptions: {
preset: [ ‘default’, {
discardComments: { removeAll: true}, //对注释的处理
normalizeUnicode: false // 建议false,否则在使用unicode-range的时候会产生乱码
}]
},
canPrint: true // 是否打印编译过程中的日志
})[/cc]

!! 消除未使用的CSS
1、安装
npm i purify-webpack purify-css -D

2、引入及配置

[cc lang=”js”]const glob = require(‘glob’)
const PurifyCssPlugin = require(‘purifycss-webpack’)
plugins: [
new PurifyCssPlugin ({
paths: glob.sync(path.join(__dirname, ‘/*.html’))
})
][/cc]

!! !1. 开发模式要用到 new webpack.HotModuleReplacementPlugin()
2. 开发模式 配置里不能用 contentHash, chunkHash, 直接用hash 或者不用
3. 大量输出日志处理

[cc lang=”js”]stats: {
children: false
},[/cc]

4

react code splitting

Add the following to src/components/AsyncComponent.js.
高级组件

[cc lang=”javascript”]
import React, { Component } from “react”;

export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);

this.state = {
component: null
};
}

async componentDidMount() {
const { default: component } = await importComponent();

this.setState({
component: component
});
}

render() {
const C = this.state.component;

return C ? : null;
}
}

return AsyncComponent;
}[/cc]

Use it

[cc lang=”javascript”]const AsyncHome = asyncComponent(() => import(“./containers/Home”));[/cc]

in your rooter
[cc lang=”javascript”]
import React from “react”;
import { Route, Switch } from “react-router-dom”;
import asyncComponent from “./components/AsyncComponent”;
import AppliedRoute from “./components/AppliedRoute”;
import AuthenticatedRoute from “./components/AuthenticatedRoute”;
import UnauthenticatedRoute from “./components/UnauthenticatedRoute”;

const AsyncHome = asyncComponent(() => import(“./containers/Home”));
const AsyncLogin = asyncComponent(() => import(“./containers/Login”));
const AsyncNotes = asyncComponent(() => import(“./containers/Notes”));
const AsyncSignup = asyncComponent(() => import(“./containers/Signup”));
const AsyncNewNote = asyncComponent(() => import(“./containers/NewNote”));
const AsyncNotFound = asyncComponent(() => import(“./containers/NotFound”));

export default ({ childProps }) =>






{/* Finally, catch all unmatched routes */}


;[/cc]

可以用 react-loadable 代替
[cc lang=”javascript”]const AsyncHome = Loadable({
loader: () => import(“./containers/Home”),
loading: MyLoadingComponent
});

const MyLoadingComponent = ({isLoading, error}) => {
// Handle the loading state
if (isLoading) {
return

Loading…

;
}
// Handle the error state
else if (error) {
return

Sorry, there was a problem loading the page.

;
}
else {
return null;
}
};[/cc]