1.Tree Shaking部分
其实如果配置了 useBuiltIns 就不会需要再 import babel profill。 实际上目前的话虽然只是引入了某个模块的某个方法,在打 包还是会把模块的所有方法引入,最好是引入什么打包什么, 这就需要 tree shaking,摇掉不需要的内容。 注意 tree shaking 只支持 ES 模块的引入。 如果想开启,在开发环境下添加↓即可。如下:
optimization: { usedExports:true },
如此配置后引入如果并没有导出内容但是确实是有用的(如 profill)就会被直接忽略了,就会出错,所以才加上 sideEffects, 指定不需要 tree shaking 的文件,若是设置为 false,就全部 都要 tree shaking。 一般 css 也需要放进去。
"sideEffects":[ "*.css" ]
当然了在开发环境下,tree shaking 并不会把代码直接从打包 后的 js 中剔除掉,只是提示一下,因为直接改的话 source map 映射就不对了,改为线上环境则会生效(而且 tree shaking
一些默认配置甚至已经写好了,都不用写,直接改个环境就 行)。
2.Development和Production模式的区分打包
开发环境 source map 比较全,但是线上就简洁。 开发环境代码一般不压缩,线上则压缩。 如果切换环境要一直更改 webpack.config.js 文件则比较麻烦, 可以复制拆成 dev、prod 两个 js 文件
"script": { "dev":"webpack-dev-server --config webpack.dev.js", "bulid":"webpack -- config webpack.prod.js" },
当你开发完成并打包线上环境代码,形成文件,放到服务器,和后端相互结合就可以了;但是是这样子存在大量重复的代码,我们可以创建一个webpack.common.js文件存放共同的代码。
const HtmlWebpackPlugin = require('html-webpack-plugin ' ); const CleanwebpackPlugin = require( 'clean-webpack-plugin' ); module.exports = { entry: { main:'./src/ index.js·},
然后依靠一个第三方模块 webpack-merge,在导出的时候合 并配置即可,
const merge = require( 'webpack-merge'); const commonConfig = require( './webpack.common.js '); const prodConfig= { mode: 'production' , devtool: 'cheap-module-source-map'} module.exports = merge( commonConfig, prodConfig);
const webpack = require( 'webpack' ); const merge = require( 'webpack-merge ' ); const commonConfig = require( ' ./webpack.common.js '); const devConfig = { mode: 'development', devtool: 'cheap-module-eval-source-map ' ,devServer: i contentBase: './dist',open: true, port: 8080,hot: true3, plugins:[ new webpack.HotModuleReplacementPlugin(], optimization: { usedExports: true} } module.exports = merge( commonConfig,devConfig);
3.webpack和codesplitting
Code splitting 就是代码分割。 注意 clean 插件会以 webpack.config.js 所在目录为根目录,也 就只能清除根目录下面的内容,无法清除根目录外的内容, 不过我们可以添加一个 root 参数重新指定根路径。
plugins: [ new HtmlwebpackPlugin({ template: 'src/index.html'}), new cleanWebpackPlugin(['dist'l,{ root: path.resolve(_ dirname, '../')}) ], output: { filename: '[name] .js ', path: path.resolve(_dirname, '../dist ')}
当我们引入一个工具库,且在下面写了非常多业务逻辑,目 前最终也是打包到一个文件中,当然无异常,只是这个 js 文 件会非常大,用户需要等到这个文件加载完成;另外如果只 是简单的改变了一点业务逻辑,用户又需要重新加载这个文 件。可以通过在其它文件引入工具库,挂载到 window 就能在其 它文件中使用该库,然后其它文件就写业务逻辑,当然入口 要 2 个。
如此就换了一个打包方式(将单文件拆成几个文件),打包 后会生成两个 JS 文件,浏览器可以并行加载,可能会比单独 加载一个大文件要快一些,同时业务逻辑变更也只是需要加 载业务逻辑文件。 这种拆分公用部分代码的做法就是 code splitting,这个其实 与 webpack 没有任何关系,是我们自己拆分,当然了 webpack 现在内置有处理拆分代码的插件,就捆绑到一起了,拆分变 得更加简单。 目前简单配置如下↓,打包后会有 main.js(只有业务逻辑了) 和 vendors-main.js(提取了类库的代码),因为这是同步的模 块引入,可以分析提取。
entry :{ main:'./src/index.js' }
optimization: { splitChunks:{ chunks: 'all'} },
但是如果是异步加载呢(需要先安装处理异步语法的 babel, 因为还是实验性质语法),打包后依然只是index放业务逻辑,0.js 放类库。也就是说异步加载可以不写 optimization 配置, 也能实现代码分割。
function getcomponent( { return import( ' lodash' ).then(({ default: -})=>{ var element = document.createElement('div');.element.innerHTML = _.join(['Dell', ‘Lee'],'-');return elepent; }) } getcomponent( ).then(element>i document. body-appendChild(element);})
4.splitChunksPlugin配置参数
目前打包异步代码生成的文件名是以 id 命名,如果想自定义 名字可以采用魔法注释命名;
function getComponent() { return import(/* webpackCbunkName: "lodash"*/ 'lodash ") element.innerHTML = _.join( I'Dell',‘Lee']','-');return element; })
当然了之前安装的处理异步第三方插件不支持魔法注释,需要换一个官方的。
presets: [ "@babel/preset-env",{ targets: { chrome: "67",}, useBuiltIns: 'usage'} ], "@babel/preset-react"],.t,-1.. piugins: ["@babel/plugin-syntax-dynamic-import"]}
最终生成的文件名就是 vendors~lodash.js,当然了如果想去 掉 vendors 可以修改 splitChunks 的参数。
optimization: { splitChunks: { chunks: 'all',cacheGroups: { vendors: false,defadlt: false} }
不管是同步异步都最好使用配置,而且配置都对它们有效。
optimization: i splitChunks: { chunks: 'async'minSize: 3000o,|maxSize: 0, minChunks: 1, maxAsyncRequests: 5,maxInitialRequests: 3, automaticNameDelimiter: '~',name: true, cacheGroups: i vendors: false,default: false} }},
Chunks 参数代表是对同步/异步/全部代码生效,比如 async 就是只对异步代码有效
cacheGroups: i vendors: i test: /[N/]node_modules [V/]/,priority:-10 }, default: false}
CacheGroups 是确定需要处理的代码(chunks 属性)后就走 到这里,test 检验是否是在 node 模块目录下(类库)引入, 是就可以分割,并且归于 vendors 这个组(这就是代码会有 vendors 前缀),也可以通过 filename 属性自定义打包后的名 字。minSize 是引入的类库,大于这个值才做代码分割(这里就是 30kb)。
CatchGroups 中的 default 就是处理没有任何组接收的文件, 因此自定义的模块就是写到 default~main.js,当然也还是可 以 filename 自定义名字,可以看到不管怎么说这些配置最终 都和 catchGroups 有关。 MaxSize 是对比较超过它的值尝试对代码进行二次拆分,拆 分 n 个该值大小的文件,基本不行,可以不配置。 MinChunks 就是代码至少被引入多少次才做代码分割。 MaxAsyncRequests 是最大同时加载的请求,也就是最多拆分 多少个。
5.Lazy Loading懒加载
之前写成异步加载的模式就是为了能够懒加载(异步加载模 块,什么时候真正执行该模块才加载该模块,具体是和 ES6 的实验性方法 import()有关,与 webpack 无关,只不过是 webpack 可以识别需要懒加载的模块)。
function getcomponent(O { return import(/* webpackChunkName : "lodash"*/‘lodash') var element =document.createElement ( 'div');._.); return element; }) } document.addEventListener( 'click',O)=>{ getcomponent().then(element ->i document.body -appendChild(element);}); })
如此就是一开始不会加载 lodash,除非点击了页面才会去加 载这个模块。 每一个 JS 文件都是一个 Chunk。可以直接按照以下配置即可(有默认值)↓
6.打包分析、preloading、prefetching
打包分析是分析打包后的文件,看看是否合理。 首先需要生成一个打包过程描述文件↓(--profile...json,也 就是把描述文件放到 stats.json 中)。然后需要开 webpack.github.io/analyse 工具网站,上传刚刚 的 json 文件。
当然了也还有其它很多可视化分析工具,比如 webpack-chart ( 以 图 表 的 形 式 分 析 json 文 件 ) 等 等 , 一 般 使 用 webpack-bundle-analyzer 比较多。 为什么一开始 webpack 对 chunks 默认值是 async?因为 all 的话其实是让第二次加载页面无需重复加载类库性能更高 而已,而 webpack 希望第一次就很快,则异步代码我们最好 放到一个模块里面去,然后再到需要的地方引用,当触发操 作需要使用该模块才加载。 Command+shift+p 输入 code coverage 可以录制页面,查看文件的命令使用情况(百分比),就是目前加载的内容执行了 多少。
因此现在不再是去纠结缓存而是去看看文件的利用率,将一 开始没有用到的内容以异步的方式优化掉,性能才会更好, webpack 认为只有异步的代码(提高代码利用率)才能真正 提高性能,这就是为什么默认是 async。 比如可以把首页的登录弹窗留到点击登录才加载,不过这样 子容易有登录交互体验比较差,加载慢的问题,这就需要 prefetching 和 preloading 了,也就是等待页面基本加载完成 了,带宽空闲了,再偷偷加载这里的逻辑而非点击登录才下 载(其实还是要再下载,只不过下载过一次,有缓存了,非 常快)。 使用如下魔法注释即可实现;
7.CSS文件的代码分割
入口文件名会看 filename,而间接生成的文件名(就是在入 口文件中引入的文件)会看 ChunkFilename。 在默认情况下,我们打包并不会单独生成 CSS 文件,因为都 写在 js 中。 MiniCssExtractPlugin 插件(不支持 HMR,一般在线上使用) 可以对 CSS 进行代码分割然后单独生成 CSS 文件。 安装该插件,在配置文件引入插件,而且还需要更改之前处 理 CSS 的 loader(线上环境配置时)。
如此依然没有单独的 CSS,因为之前的 tree shaking 把 CSS 给 摇掉了,记得 sideEffect 整一下。 如果 CSS 文件直接被 html 引用就走 filename,如果被间接引
用才走 chunkFilename。 如果需要对 CSS 代码做压缩,还需要安装引入对应插件。
8.webpack与浏览器缓存
Performance:false 可以关闭有关性能问题的警告。 目前如果已经访问过有缓存的 JS 文件有所变动,普通刷新的 情况下还是读取原本缓存下的 JS 文件,因为文件名字没有变, 这就可以引入一个 contenthash 来解决缓存问题。
老版本的webpack可能出现即使没有改变文件也会出现hash 不一样的情况,因为 webpack 对业务逻辑与类库做了关联处 理,叫 manifest,既存在于业务逻辑也存在于类库,而可能 打包后它变得不一样,这里就用 runtime 抽离这些 manifest, 不影响我们的内容。
9.shimming的作用
实际上我们打包的内容还需要做不少兼容问题(变片),比 如 babel-profill 处理低版本浏览器的语法不存在问题。 Webpack 中引入的变量只能在当前模块使用,但是如果我想 在其它模块使用呢(就是当前模块没有引入),可以利用插 件。如下即代表如果我在一个模块使用了$就会自动引入 jquery 模块且命名为$(表面看起来就我当前模块没有引入变 量却可以使用变量,实际上已经偷偷的引入了)。
new webpack.ProvidePlugin({ $jquery' }),
我们可以在这里定义非常多的在各地使用的变量;一个模块的 this 默认就是指向自身,如果一定要指向 window?可以安装 imports-loader(修改 webpack 默认行为, 也就是变片的一种)。
10.环境变量的使用方法
module.exports = (env) →>{ if(env && env.production){ return merge( commonConfig,prodConfig);}else i return merge( commonConfig,devConfig);}
env 就是环境变量,在 scripts 命令中传递它对应的属性也就 代表开启什么模式,名字可以改。不过一般不会用这个,还是跟之前一样指定每个命令对应的 config 文件。