搭建和优化webpack的react项目
参考教程: https://www.jianshu.com/p/04e436cf75ba
遇到问题:
1、webpack-dev-server 成功启动无渲染
看看html内dom节点是否挂载JSX,是否引入了打包好的js资源。
使用HtmlWebpackPlugin会自动把生成的bundle.js文件引入html内。
通常来说,每个入口会生成本入口的依赖树图和chunk文件,一个入口对应一个chunk,而一个chunk对应一个Bundle,某个入口打包出来的bundle也只是一个js文件。(当然,也不是说chunk和bundle一一对应,比如开启了sourceMap,就会打包生成一个chunk对应两个Bundle)
webpack的使用目的:将所有非js文件打包成可以使用js表示的文件。
最后的最后,打包的目的都是为了让浏览器可以识别代码生成页面,所以(如果是打包一个完整项目)必须要把我们打包后生成的bundle.js文件引入html文件内,然后再用某些挂载方式展现在页面上。
html文件是我们自己先生成的,比如react内的html文件中有个< div id=‘app’>< /div>,就在js内获取这个dom节点生成真实Dom信息。
我以前经常搞不懂,为什么一个入口为什么能生成好几个文件出来,这几个文件又是怎么区分的之类的。
- bundle和chunk的区别
暂时先给过去的自己下一个结论吧:(没有使用代码分割等等方式的时候,单纯的打包。)
chunk是过程,bundle是结果。
webpack通过入口模块分析依赖图,然后打包依赖模块生成chunk,一个入口生成一个chunk,多入口生成多个chunk,chunk是打包过程中的Modules集合。
Bundle是打包结果最终输出的一个或者多个打包好的文件。
chunk在构建完成后便呈现为bundle。
2、vendors和externals
这两个都是可以打包外部拓展依赖包的。比如react、loadash、antd这种第三方通用依赖,可以使用这两个,但是有什么区别呢?
externals:从外部环境中获取,完全不会打包,只留一个引用接口,一般用在组件上,比如这个组件要放在react\antd环境中,就可以使用externals。像现在我们要搭建一个react项目,它本身就是一个完整的项目了,不可能从外部获取依赖,所以不能用externals。
vendors:
详细可见:https://webpack.docschina.org/configuration/optimization/
在webpack的Optimization(优化)属性中会使用到的概念。
比如SplitChunksPlugin,代码分割工具,可以使用它将vendors第三方依赖分割出去,单独生成一个文件。
再比如CommonsChunkPlugin(webpack3.x),提取公共第三方依赖打包。可以生成一个公共vendor。
vendor的体积优化可以使用uglfyJS压缩。
(具体如何使用看下方,哈哈哈我以前用过vendor结果这一次仔细学习发现以前用错了。orz)
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
chunks: ['index','list','about'], //提取哪些模块共有的部分
minChunks: 3 // 提取至少3个模块共有的部分
}),
3、优化webpack打包,关于文件过大等问题
优化webpack打包一是减少代码体积,二是加快打包速度。
减少代码体积:主要是将各种非首屏js代码、第三方包、样式文件等等从主文件中分离,主文件尽量只保留首屏加载时最需要的js代码,其他js文件分离出来懒加载,第三方包样式资源等作为静态资源也尽可能分出来,可以静态资源就可以放在CDN缓存、利用浏览器缓存加载,速度快很多。
两个比较有用的插件工具:
npm install -D clean-webpack-plugin webpack-bundle-analyzer
// 自动删除输出目录下的文件
const CleanWebpackPlugin = require(‘clean-webpack-plugin’);
// 可以优化打包体积,在打包结束的时候,会启动启动一个服务在浏览器查看打包的大小和包含的内容等
const BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’)
现在这里有一个足足有4M的main.js
1、路由懒加载,使用异步加载方式。
看看有哪些是不用首屏加载的,就异步加载了。具体原理什么的我另写了一篇懒加载相关的博客记录。
其实直接使用import(’’)就可以达到效果,但是也可以使用别人封装好的懒加载组件:react-loadable或React.lazy()
其实我们并不需要立马把全部模块加载,我们希望我们希望webpack打包的时候只打包我们会立马用到的代码,而不需要立马用到的模块,可以单独打包成一个js文件,当需要用到这个模块的时候才去加载这个模块的代码,这样可以减少我们第一次加载的bundle的文件提交,提高传输速度。
此时用ES6的模块懒加载就能实现我们的期望。
可能是我用的不熟,或者是因为打包的项目内容挺少的,我懒加载的那个测试文件才分出来2k,相比主文件的4M,有什么用啊orz。不过文件多的时候就要考虑这个了哈哈哈。
2、js处理:第三方依赖包过大,使用代码分割拆包。
antd和lodash的包怎么那么大?我明明是做了按需加载的啊,难不成失效了?。。。(后文找到解法了记录下下)
网上查的说法是可能多个文件依赖了相同的包,导致每一个文件都打包了多个重复的文件。
webpack3.x版本使用CommonsChunkPlugin合并包,现在使用代码分割SplitChunksPlugin,把共同引用的依赖合并成一个单独分出来。
1、首先,对于lodash, 原来是babel没有做按需加载处理,要知道我们的按需加载都是需要靠babel在背后含辛茹苦地识别转化的。所以,npm i babel-plugin-lodash -D,加上解析插件。
之后就可以了,我的main.js包减少了700多k,现在只有3.79M了。
2、代码分割
可以看webpack 的教程怎么使用:https://webpack.docschina.org/plugins/split-chunks-plugin/
optimization优化属性(webpack4.x的代码压缩和拆包都在这里处理,这是和webpack3.x的不同)
使用优化属性里的splitChunks。它的功能是合并打包第三方依赖生成一个额外的vendor,vendor里包含了ndoe_modules里的依赖,然后使用 HtmlWebpackPlugin将生成的vendor文件生成script标签插入html生效。
splitChunks就算什么配置都不做它也是生效的,源于webpack有一个默认配置,这也符合webpack4的开箱即用的特性。
默认配置:
splitChunks: {
// async表示只从异步加载得模块(动态加载import())里面进行拆分
// initial表示只从入口模块进行拆分
// all表示以上两者都包括
chunks: "async",
minSize: 30000, // 大于30k会被webpack进行拆包
minChunks: 1, // 被引用次数大于等于这个次数进行拆分
// import()文件本身算一个
// 只计算js,不算css
// 如果同时有两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
maxAsyncRequests: 5, // 最大的按需加载(异步)请求次数
// 最大的初始化加载请求次数,为了对请求数做限制,不至于拆分出来过多模块
// 入口文件算一个
// 如果这个模块有异步加载的不算
// 只算js,不算css
// 通过runtimeChunk拆分出来的runtime不算在内
// 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
maxInitialRequests: 3,
automaticNameDelimiter: '~', // 打包分隔符
name:true,
cacheGroups: {
// 默认的配置
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
// 默认的配置,vendors规则不命中的话,就会命中这里
default: {
minChunks: 2, // 引用超过两次的模块 -> default
priority: -20,
reuseExistingChunk: true
},
},
}
拆分策略 chunks: “all” | “async” | “initial”
需要对异步组件和同步组件同时拆分,采用chunks: "all"进行bundle的拆分。这意味着 chunk 可以在异步和非异步 chunk 之间共享。
如果项目中同步加载的组件chunk不大,可以不对同步加载组件进行拆分,使用chunks:async。
如果项目中异步加载的组件chunk不大,可以不对异步加载组件进行拆分,使用chunks:initial。
当然也可以混用,对于缓存组单独设置
我使用了"all"的拆分方式。哈哈哈哈令人吃惊,vendor竟有3.7M,原本的代码才70多Kb。
然后现在要对这整一个vendors再拆分,因为后面这个包会变得越来越大的。
- UI组件库(antd)
- 基础插件(react,react-dom,react-router-dom,mobx,axios等等)
这些都是更新频率非常低,公用率高,提及大,所以单独抽取。只要这些包不更新,拆包的chunk文件名就不会变。就一直缓存在浏览器
splitChunks:{
// ....
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
name: 'defaultVendors',
},
antdui: {
priority: 2,
name:"antdui",
test: /[\\/]node_modules[\\/](antd)[\\/]/, //(module) => (/antd/.test(module.context)),
},
// 拆分基础插件
basic: {
priority: 3,
name:"basic",
test: /[\\/]node_modules[\\/](moment|react|react-dom|react-router|react-router-dom|mobx|mobx-react|axios)[\\/]/,
},
// 默认的配置,vendors规则不命中的话,就会命中这里
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
}
把vendors主要分成了3个部分,第一个是default,第二个是antd,第三个是react等等基础包。拆出去之后可以用浏览器缓存,cdn缓存等方式加载静态资源,会比放在一个bundle请求快很多。
3、代码压缩
1、css使用mini-css-extract-plugin抽取css文件,再使用optimize-css-assets-webpack-plugin进行代码压缩。
关于antd的包大小。它确实是按需加载了,看看这里,它已经很努力地只引入了table和button这两个我按需加载的组件,占地方最大的竟然是样式文件。(拳头硬了)
yarn add mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
mini-css-extract-plugin
把js中import导入的样式文件,单独打包成一个css文件,结合html-webpack-plugin,以link的形式插入到html文件中。
注:
- 此插件不支持HMR,若修改了样式文件,是不能即时在浏览器中显示出来的,需要手动刷新页面。
- mini-css-extract-plugin 插件和style-loader冲突,使用之后把style-loader注释掉。
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
// 'style-loader',
'css-loader',
],
},
{
test: /\.less$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
// 'style-loader',
'css-loader',
'less-loader',
],
},
plugins:[new MiniCssExtractPlugin(), ......]
optimization: {
minimize: true,
minimizer: [
// ...
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
discardComments: {
removeAll: true }, // 移除注释
},
}),
],
}
打包结果:
antdui分离,成功把antd包减小500多K。
但是报了一个warning:export ‘default’ (imported as ‘styles’) was not found in ‘./index.less’ (module has no exports)。
妈耶。我引入的styles模块名识别不到?npm run dev的服务器启起来都是空白的。
查了一下是引用文件路径的问题,参考https://blog.csdn.net/qq_37431622/article/details/108045465
改成这样就好了。
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: false,
publicPath: '../',
},
},
// 'style-loader',
'css-loader',
],
},
{
test: /\.less$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: false,
publicPath: '../',
},
},
// 'style-loader',
'css-loader',
'less-loader',
],
},
2、js使用Uglifyjs,Uglifyjs还会进行Tree-shaking剔除无用代码。
npm i uglifyjs-webpack-plugin -D
// 在minimizer属性内添加
// 自定义js优化配置,将会覆盖默认配置
new UglifyJsPlugin({
parallel: true, //使用多进程并行运行来提高构建速度
sourceMap: false,
uglifyOptions: {
warnings: false,
compress: {
unused: true,
drop_debugger: true,
drop_console: true,
},
output: {
comments: false // 去掉注释
}
}
})
我的天啊orz这个压缩代码的效果好到逆天啊,一下子少了2M,现在只有1M了整个包。牛逼牛逼。
CDN引入方式
可以看这位博主是怎么做的:https://segmentfault.com/a/1190000038180453。
我还没试过,有兴趣的以后可能会再补充实践一下吧哈哈。
最后总结
搭建:
1、正常的入口、出口、常用loader配置,配置webpack-dev-server、热加载、sourceMap。
2、index.html,使用html-webpack-plugin插件。
优化总共经历的过程大概是:
1、路由懒加载、异步加载代码,减少主bundle代码体积。
2、对于js文件,代码拆分,把第三方依赖vendor单独拆出来。生成静态资源包。
3、压缩代码,抽取css、对css代码压缩,压缩js代码。
相关实验项目git地址,可以看到完整的webpack.config配置:
https://github.com/3sang/Dodo-webpack-practice
转载:https://blog.csdn.net/SaRAku/article/details/117261496