查看原文
其他

深入浅出webpack的最佳实践!

Jou 腾讯云开发者 2022-12-22


导语 | 本文推选自腾讯云开发者社区-【技思广益 · 腾讯技术人原创集】专栏。该专栏是腾讯云开发者社区为腾讯技术人与广泛开发者打造的分享交流窗口。栏目邀约腾讯技术人分享原创的技术积淀,与广泛开发者互启迪共成长。本文作者是腾讯云前端开发Jou。


自从加入腾讯,作者便一直在使用webpack,因此,本文主要整理一下webpack相关的知识点,由浅入深,后续会一直更新分享,以便对此方面感兴趣的开发者们提供一些经验和帮助。欢迎点击文末「阅读原文」访问腾讯云开发者社区,查看作者作品~



核心概念


(一)Module


模块:在webpack中,万物皆是模块,可以理解成是我们手写和引入的一个个文件。



(二)Chunk


代码库:一个chunk由十多个相关联模块组合而成,当我们写的module源文件传到webpack进行打包时,webpack会根据文件引用关系生成chunk文件,是打包过程的中间产物。



(三)bundle


webpack处理好chunk文件后,最后会输出bundle文件,这个bundle文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行。



(四)entry


打包的入口,在打包时,从entry开始寻找模块依赖。



(五)output


打包的出口,打包完成之后,各个资源文件输出的位置。



(六)mode


打包模式,有生产环境与测试环境两种,默认是测试环境


  • 测试环境,打包配置以方便调试监控代码为主。


  • 生产环境,打包配置以压缩体积,优化加载速度为主。



(七)loader


模块转换器,webpack打包的时候只能识别.js的文件,对于其他文件,则需要借助loader来处理,loader的作用是把模块原内容按照需求转换成新内容,如图片,css等文件,有三种配置方式:内联CLI config.js配置


注意:loader在处理数据的时候,其实是链式传递的,即所有loader形成一条流水线,当我处理不了时,交由下一个loader来处理。



(八)Plugin


扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情,用于增加webpack的功能,本质上是一个JavaScript的对象。



基础配置


首先,我们来创建一个空项目,运行:


npm initnpm install -D webpack webpack-cli


然后新建一个webpack.config.js文件,来编写我们的webpack配置。


(一)入口配置


我们先编写最基础的配置,入口和出口:


const path = require('path');
module.exports = { entry: path.resolve(__dirname, 'src/index.js'), output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', publicPath: './' }}


  • publicPath:指定基础路径,开发环境一般是项目的根路径,上线之后一般是CDN的路径。


  • __dirname:表示项目所在目录的根路径。


现在我们可以来试一下最简单的打包操作:


新建入口文件src/index.js,并且编写如下代码:


const a = 12const b = 12function add(x, y) { return x + y}const c = add(a,b)console.log(c)


然后在package.json中的script部分加入打包指令:


{ "scripts": { "build": "webpack --mode development" },}


执行打包:


npm run build


在项目根目录下,便出现了dist文件夹,以及打包的内容bundle.js,目前我们还没做任何优化,所以所有的代码都会被进去。



(二)使用模板html


使用html-webpack-plugin可以指定template模板文件,将会在output目录下,生成html文件,并引入打包后的js.


在配置文件中加入如下配置:


const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { //... plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, 'src/index.html'), filename:'webpack.html', }) ],};


使用html-webpack-plugin时,如果不传东西,它会生成一个默认的html模板,也可以传入一个配置对象。


  • 使用template指定用到的html模板。


  • filename,指定打包后的页面名称。


更多配置可以参阅: 

https://github.com/jantimon/html-webpack-plugin#options



(三)配置webpack-dev-server


webpack-dev-server可以帮我们启动一个本地的服务,并且有实时热更新的能力,之后我们只需要专注于代码的开发,无需在每次修改之后重新去打包,而webpack-dev-serve打包之后的代码不会放在dist文件夹中,只会放在一个缓存区中。


安装依赖:


npm install --save-dev webpack-dev-server


在webpack中加入配置:


{//...devServer: { contentBase: path.join(__dirname, 'dist'), port: '8080', host: 'localhost', },};



  • contentBase:代表打包后的访问资源的路径。


除此之外,还有几个常用的配置:


  • compress:传入一个boolean值,通知server是否启用gzip强烈建议使用 : 压缩后通常能帮我们减少响应70%左右的大小。


  • hot|hotOnly:传入一个boolean值,通知server是否启用HMR。


  • open:传入一个boolean值运行之后自动打开默认浏览器。


最后我们在package.json加入指令:


"scripts": { //.... "dev": "webpack-dev-server --mode development"}


我们就可以通过npm run dev,来启动服务,通过localhost:8080来访问打包的资源。


更多配置可以参阅: 

https://webpack.js.org/configuration/dev-server/



(四)配置模块热替换(HRM)


在使用webpack-dev-server之后,每次改完代码,页面会自动刷新,但有的时候,我们的改动其实很小,并不希望整个页面一起刷新,这个时候可以配置模块热替换。


配置热替换之后,当前端代码变动时,无需刷新整个页面,只把变化的部分替换掉,避免页面状态丢失。


在devServer中加入配置:


devServer: { //... hot:true};


更新packag.json命令:


"scripts": { //.... "dev": "webpack-dev-server --mode development --open --hot" },


  • --open表示启动后自动打开页面。


  • --hot表示启动模块热替换。



loader配置


接下来,准备编写loader的配置,来支持编写其他类型的文件,首先,我们在webpack.config.js中加入用于编写loader的模块,其中,rules就是放置loader的数组。


{ //... module: { rules: [], },}


(一)支持加载css


通过使用不同的style-loader和css-loader, 可以将css文件转换成JS文件类型。


首先安装相关依赖:


npm install --save-dev style-loader css-loader


然后在webpack.config.js文件中加入配置:


module: { rules: [ { test: /.css/, use: ['style-loader', 'css-loader'], exclude: /node_modules/, include: path.resolve(__dirname, 'src') } ],},


loader可以配置以下参数:


  • test: 匹配处理文件的扩展名的正则表达式。


  • use: loader名称。


  • include/exclude: 手动指定必须处理的文件夹或屏蔽不需要处理的文件夹。


  • query: 为loader提供额外的设置选项。


解释一下这里两个loader各自的作用:css-loader的作用是识别css文件中的@import语句,以及url链接等,并将css文件中的样式编译为js模块,并导出,而style-loader的作用就是将导出的样式模块以style标签的方式插入DOM树中。



(二)支持加载图片


  • file-loader: 解决CSS等文件中的引入图片路径问题,不使用的话,样式中的url路径打包后会参照html,而不是css文件。


  • url-loader: 当图片小于limit的时候会把图片Base64编码直接打包进项目,大于limit参数的时候还是使用file-loader进行拷贝,这样做可以减少运行时的http请求。


如果希望图片存放在单独的目录下,那么需要指定outputPath


安装依赖:


npm install --save-dev url-loader file-loader


在webpack.config.js中增加loader的配置(增加在module.rules的数组中) ,在webpack5之后不再需要手动安装图片的loader。


module: { rules: [ { test: /.(gif|jpg|png|bmp|eot|woff|woff2|ttf|svg)/, use: [ { loader: 'url-loader', options: { limit: 8192, outputPath: 'images' } } ] } ]}



(三)webpack5加载资源的配置


webpack5新增资源模块(asset module),允许使用资源文件(字体,图标等)而无需配置额外的loader。


资源模块支持以下四个配置:


  • asset/resource将资源分割为单独的文件,并导出url,类似之前的file-loader的功能。


  • asset/inline将资源导出为dataUrl的形式,类似之前的url-loader的小于limit参数时功能。


  • asset/source将资源导出为源码(source code)。类似的raw-loader功能。


  • asset会根据文件大小来选择使用哪种类型,当文件小于8KB(默认) 的时候会使用asset/inline,否则会使用asset/resource。


加载图片loader使用了use+loader的写法,这样写我们可以给loader传递参数,放入配置对象的options属性中,比如在上面的代码中,传入了文件大小和输出路径两个参数。


// ... module: { rules: [ // ... { test: /\.(jpe?g|png|gif)$/i, type: 'asset', generator: { // 输出文件位置以及文件名 // [ext] 自带 "." 这个与 url-loader 配置不同 filename: "[name][hash:8][ext]" }, parser: { dataUrlCondition: { maxSize: 50 * 1024 //超过50kb不转 base64 } } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, type: 'asset', generator: { // 输出文件位置以及文件名 filename: "[name][hash:8][ext]" }, parser: { dataUrlCondition: { maxSize: 10 * 1024 // 超过100kb不转 base64 } } }, ] },



(四)支持加载less


在实际项目中,使用less或者sass,会比直接编写css效率更高,这里以less为例。


安装对应的依赖:


npm install --save-dev less less-loader


在webpack.config.js中增加loader的配置(module.rules数组中)


module: { rules: [ { test: /.less/, use: ['style-loader', 'css-loader', 'less-loader'], exclude: /node_modules/, include: path.resolve(__dirname, 'src') }, ]}



(五)支持转义ES6/ES7/JSX


ES6/ES7/JSX转义需要Babel的依赖,支持装饰器。


npm install --save-dev @babel/core babel-loader @babel/preset-env @babel/preset-react @babel/plugin-proposal-decorators


稍微解释一下每个依赖的含义:


  • babel-loader相当于联系babel和webpack的桥梁,通过它获取到webpack打包流程中的数据。


  • @babel/core是bable的核心,babel各类转义方法的来源。


  • @babel/preset-env转义es6+。


  • @babel/preset-react转义react。


  • @babel/plugin-proposal-decorators babel处理装饰器语法的插件。


然后在webpack.config.js中增加loader的配置(module.rules数组中)。


module: { rules: [ { test: /.jsx?$/, use: [ { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/react'], plugins: [ [require("@babel/plugin-proposal-decorators"), { "legacy": true }] ] } } ], include: path.resolve(__dirname, 'src'), exclude: /node_modules/ }, ]}


这里的presets的字段代表着预设插件,babel可以转义很多种类型,它并不知道当前语句对应的是什么类型,所以我们需要预设几种类型,让它优先使用里面的插件来转义。


至此,我们的项目已经可以编写jsx,css,less等代码了。



打包优化


现在我们已经实现了一个react项目打包的流程,但是对打包过程中的代码,还没有进行具体的约束,效率不高,接下来,将对打包流程进行优化。


(一)压缩JS文件


安装依赖:


npm install --save-dev uglifyjs-webpack-pluginnpm install --save-dev optimize-css-assets-webpack-plugin


在webpack.config.js中增加optimization的配置,optimization意为优化,后续和优化相关的都会配置在这里:


const UglifyWebpackPlugin = require('uglifyjs-webpack-plugin');
module.exports = { //other code optimization: { minimizer: [ new UglifyWebpackPlugin({ parallel: 4, uglifyOptions: { compress: { drop_console: true, }, }) ] }}


  • parallel代表同时运行的线程数。


  • compress代表对代码类型的筛选,drop_console设置为true就代表删除所有的console语句。


webpack5已经不需要这么配置了,因为uglifyjs-webpack-plugin在webpack4之后已经不再维护了,webpack5内置了TerserWebpackPlugin插件,具有相同的压缩功能,和删除冗余代码功能。


new TerserWebpackPlugin({ // 配置移除的语句 terserOptions: { compress: { drop_console: true, //传true就是干掉所有的console.*这些函数的调用. drop_debugger: true, //干掉那些debugger; // pure_funcs: ['console.log'], // 如果你要干掉特定的函数比如console.info ,又想删掉后保留其参数中的副作用,那用pure_funcs来处理 } } }, }, }),



(二)分离CSS


因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,可以把CSS单独提取出来加载:


npm install --save-dev mini-css-extract-plugin


在webpack.config.js中增加plugins的配置,并且将'style-loader'修改为 {loader: MiniCssExtractPlugin.loader}。


CSS打包在单独目录,那么配置filename:


const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = { //other code module: { rules: [ { test: /.css/, use: [{ loader: MiniCssExtractPlugin.loader}, 'css-loader'], exclude: /node_modules/, include: path.resolve(__dirname, 'src') }, { test: /.less/, use: [{ loader: MiniCssExtractPlugin.loader }, 'css-loader', 'less-loader'], exclude: /node_modules/, include: path.resolve(__dirname, 'src') }, { test: /.scss/, use: [{ loader: MiniCssExtractPlugin.loader }, 'css-loader', 'sass-loader'], exclude: /node_modules/, include: path.resolve(__dirname, 'src') }, ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].css' }) ]}



(三)压缩CSS文件


安装依赖:


npm install --save-dev optimize-css-assets-webpack-plugin


在webpack.config.js中的optimization中增加配置:


const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = { //other code optimization: { minimizer: [ new OptimizeCssAssetsWebpackPlugin() ] }}


打包前先清空输出目录:


npm install --save-dev clean-webpack-plugin


在webpack.config.js中增加plugins的配置:


const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = { //other code plugins: [ new CleanWebpackPlugin() ]}


至此,已经完成了webpack的基本配置,能够满足项目的日常开发,完整的配置可以戳链接:

git@github.com:AdolescentJou/webpack4-base-demo.git



最后


感谢你能看到这里,实际上webpack4现在已经快过时了,但是不少的公司因为兼容性问题等原因,依旧还在使用,希望这篇文章对你有所帮助,后续会陆续更新其他webpack相关的文章,如果能留下你的一个赞,笔者将感激不尽。


参考资料:

1.【Webpack进阶】less-loader、css-loader、style-loader实现原理

2.从零开始配置webpack(基于webpack4和babel7版本)



 作者简介


Jou

腾讯云开发者社区【技思广益·腾讯技术人原创集】作者

腾讯云前端开发工程师,目前负责客服服务系统的前端开发工作,喜欢捣鼓各式各样的技术。



 推荐阅读


基于腾讯云自建高可用DNS方案实践

Kubernetes原理与架构初探

10分钟搞定!Golang分布式ID集合

C++异步:asio的coroutine实现!



👇点击「阅读原文」查看作者关于webpack的更多解读~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存