查看原文
其他

解锁 Webpack,看这篇就够了

前端小贾 前端学苑 2021-07-15

关注“前端学苑” ,坚持每天进步一点点


「~解锁webpack篇~」


一、解锁webpack 基础篇


1、webpack 定义

webpack 是一个现代 JavaScript 应用程序的静态模块打包器,当 webpack 处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle。

2、webpack 的核心概念

1) 入口 (entry )指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。

2) 输出 (output) 属性告诉 webpack 在哪里输出它所创建的 bundles ,以及如何命名这些文件,默认值为 ./dist

3) 模块转换器(loader)让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)

4) 插件(plugins),插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量


3、初始化配置

新建一个文件夹,如: webpack-first (当然,你可以使用任意一个你喜欢的项目名)。推荐:掌握了webpack之后,根据自己的需求配置出来的,就是最佳配置。


mkdir webpack-demo    // 创建文件夹

cd webpack-demo        // 进入这个文件夹目录

npm init                         // 初始化项目,生成package.json

要使用 webpack,那么必然需要安装 webpack、webpack-cli:

npm install webpack webpack-cli -D

鉴于前端技术变更迅速,本篇文章基于 webpack 的版本号

├── webpack@4.41.5 └── webpack-cli@3.3.10

从 wepack V4.0.0 开始, webpack 是开箱即用的,在不引入任何配置文件的情况下就可以使用。


新建 src/index.js 文件,我们在文件中随便写点什么:

//index.jsclass Animal { constructor(name) { this.name = name; } getName() { return this.name; }}const dog = new Animal('dog');

使用 npx webpack --mode=development 进行构建,默认是 production 模式,我们为了更清楚查看打包后的代码,使用 development 模式。


webpack 有默认的配置,如默认的入口文件是 ./src,默认打包到dist/main.js。


查看 dist/main.js 文件,可以看到,src/index.js 并没有被转义为低版本的代码,这显然不是我们想要的。

{ "./src/index.js":        (function (module, exports) {            eval("class Animal {\n    constructor(name) {\n        this.name = name;\n    }\n    getName() {\n        return this.name;\n    }\n}\n\nconst dog = new Animal('dog');\n\n//# sourceURL=webpack:///./src/index.js?"); })}

4、将JS转义为低版本

将JS代码向低版本转换,我们需要使用 babel-loader。

首先安装一下 babel-loader

npm install babel-loader -D

此外,我们还需要配置 babel,为此我们安装一下以下依赖:

npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime -Dnpm install @babel/runtime @babel/runtime-corejs3

新建 webpack.config.js,如下:

//webpack.config.jsmodule.exports = { module: { rules: [ { test: /\.jsx?$/, use: ['babel-loader'], exclude: /node_modules/ //排除 node_modules 目录 } ] }}

建议给 loader 指定 include 或是 exclude,指定其中一个即可,因为 node_modules 目录通常不需要去编译,排除后,有效提升编译效率。

需要说明:

loader 需要配置在 module.rules 中,rules 是一个数组。

loader 的格式为:

{test: /\.jsx?$/,//匹配规则use: 'babel-loader'}

或者也可以像下面这样:

//适用于只有一个 loader 的情况{test: /\.jsx?$/,loader: 'babel-loader',options: {//...}}

test 字段是匹配规则,针对符合规则的文件进行处理。

use 字段有几种写法

1)可以是一个字符串,例如上面的 use: 'babel-loader'

2)use 字段可以是一个数组,例如处理CSS文件,use: ['style-loader', 'css-loader']

3)use 数组的每一项既可以是字符串也可以是一个对象,当我们需要在webpack 的配置文件中对 loader 进行配置,就需要将其编写为一个对象,并且在此对象的 options 字段中进行配置,如:

rules: [ { test: /\.jsx?$/, use: { loader: 'babel-loader', options: { presets: ["@babel/preset-env"] } }, exclude: /node_modules/ }]

5、mode

将 mode 增加到 webpack.config.js 中:

module.exports = { //.... mode: "development", module: { //... }}

mode 配置项,告知 webpack 使用相应模式的内置优化。

mode 配置项,支持以下两个配置:

1)development:将 process.env.NODE_ENV 的值设置为 development,启用 NamedChunksPlugin 和 NamedModulesPlugin

2)production:将 process.env.NODE_ENV 的值设置为 production,启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。


6、在浏览器中查看页面

我们可以使用 html-webpack-plugin 插件来帮助我们完成这些事情。

首先,安装一下插件:

npm install html-webpack-plugin -D

新建 public 目录,并在其中新建一个 index.html 文件 (vscode快捷 !+ tab)

修改 webpack.config.js 文件。

//首先引入插件const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { //... plugins: [ //数组 放着所有的webpack插件 new HtmlWebpackPlugin({ template: './public/index.html', filename: 'index.html', //打包后的文件名 minify: { removeAttributeQuotes: false, //是否删除属性的双引号 collapseWhitespace: false, //是否折叠空白 }, // hash: true //是否加上hash,默认是 false }) ]}

此时执行 npx webpack,可以看到 dist 目录下新增了 index.html 文件,并且其中自动插入了 <script> 脚本,引入的是我们打包之后的 js 文件。

更多 html-webpack-plugin配置项


如何在浏览器中实时展示效果

话不多说,先装依赖:

npm install webpack-dev-server -D

修改下咱们的 package.json 文件的 scripts:

"scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server", "build": "cross-env NODE_ENV=production webpack"},

在控制台执行 npm run dev,启动正常,页面上啥也没有,修改下我们的JS代码,往页面中增加点内容,正常刷新(也就是说不需要进行任何配置就可以使用了)。

不过呢,我们还是可以在 webpack.config.js 中进行 webpack-dev-server 的其它配置,例如指定端口号,设置浏览器控制台消息,是否压缩等等:

//webpack.config.jsmodule.exports = { //... devServer: { port: '3000', //默认是8080 quiet: false, //默认不启用 inline: true, //默认开启 inline 模式,如果设置为false,开启 iframe 模式 stats: "errors-only", //终端仅打印 error overlay: false, //默认不启用 clientLogLevel: "silent", //日志等级 compress: true //是否启用 gzip 压缩 }}

7、devtool

devtool 中的一些设置,可以帮助我们将编译后的代码映射回原始源代码。不同的值会明显影响到构建和重新构建的速度。

对我而言,能够定位到源码的行即可,因此,综合构建速度,在开发模式下,我设置的 devtool 的值是 cheap-module-eval-source-map。


//webpack.config.jsmodule.exports = { devtool: 'cheap-module-eval-source-map' //开发环境下使用}

生产环境可以使用 none 或者是 source-map,使用 source-map 最终会单独打包出一个 .map 文件,我们可以根据报错信息和此 map 文件,进行错误解析,定位到源代码。


source-map 和 hidden-source-map 都会打包生成单独的 .map 文件,区别在于,source-map 会在打包出的js文件中增加一个引用注释,以便开发工具知道在哪里可以找到它。hidden-source-map 则不会在打包的js中增加引用注释。


8、如何处理样式文件

webpack 不能直接处理 css,需要借助 loader。如果是 .css,我们需要的 loader

先安装一下需要使用的依赖:

npm install style-loader less-loader css-loader postcss-loader autoprefixer less -D

简单说一下的配置:

style-loader 动态创建 style 标签,将 css 插入到 head 中.

css-loader 负责处理 @import 等语句。

postcss-loader 和 autoprefixer,自动生成浏览器兼容性前缀了,应该没人去自己徒手去写浏览器前缀了吧

less-loader 负责处理编译 .less 文件,将其转为 css

我们之间在 webpack.config.js 写了 autoprefixer 需要兼容的浏览器,仅是为了方便展示。推荐大家在根目录下创建 .browserslistrc,将对应的规则写在此文件中,除了 autoprefixer 使用外。

注意:

loader 的执行顺序是从右向左执行的,也就是后面的 loader 先执行,上面 loader 的执行顺序为: less-loader ---> postcss-loader ---> css-loader ---> style-loader


9、图片/字体文件处理

我们可以使用 url-loader 或者 file-loader 来处理本地的资源文件。url-loader 和 file-loader 的功能类似,但是 url-loader 可以指定在文件大小小于指定的限制时,返回 DataURL,因此,个人会优先选择使用 url-loader。

首先安装依赖:

npm install url-loader -D

在 webpack.config.js 中进行配置:

//webpack.config.jsmodule.exports = { //... modules: { rules: [ { test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/, use: [ { loader: 'url-loader', options: { limit: 10240, //10K esModule: false } } ], exclude: /node_modules/ } ] }}

此处设置 limit 的值大小为 10240,即资源大小小于 10K 时,将资源转换为 base64,超过 10K,将图片拷贝到 dist 目录。esModule 设置为 false,否则,<img src={require('XXX.jpg')} /> 会出现 <img src=[Module Object] />


将资源转换为 base64 可以减少网络请求次数,但是 base64 数据较大,如果太多的资源是 base64,会导致加载变慢,因此设置 limit 值时,需要二者兼顾。


10、处理 html 中的本地图片

安装 html-withimg-loader 来解决咯。

npm install html-withimg-loader -D

修改 webpack.config.js:

module.exports = { //... module: { rules: [ { test: /.html$/, use: 'html-withimg-loader' } ] }}

11、入口配置

入口的字段为: entry

//webpack.config.jsmodule.exports = { entry: './src/index.js' //webpack的默认配置}

entry 的值可以是一个字符串,一个数组或是一个对象。


12、出口配置

配置 output 选项可以控制 webpack 如何输出编译文件。

const path = require('path');module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), //必须是绝对路径 filename: 'bundle.js', publicPath: '/' //通常是CDN地址 }}

13、每次打包前清空dist目录

我们需要插件: clean-webpack-plugin,安装依赖:

npm install clean-webpack-plugin -D

以前,clean-webpack-plugin 是默认导出的,现在不是,所以引用的时候,需要注意一下。另外,现在构造函数接受的参数是一个对象,可缺省。

//webpack.config.jsconst { CleanWebpackPlugin } = require('clean-webpack-plugin');module.exports = { //... plugins: [ //不需要传参数喔,它可以找到 outputPath new CleanWebpackPlugin() ]}

现在你再修改文件,重现构建,生成的hash值和之前dist中的不一样,但是因为每次 clean-webpack-plugin 都会帮我们先清空一波 dist 目录。


14、热更新

1)首先配置 devServer 的 hot 为 true

2)并且在 plugins 中增加 new webpack.HotModuleReplacementPlugin()

//webpack.config.jsconst webpack = require('webpack');module.exports = { //.... devServer: { hot: true }, plugins: [ new webpack.HotModuleReplacementPlugin() //热更新插件 ]}

15、多页应用打包
有时,我们的应用不一定是一个单页应用,而是一个多页应用,那么如何使用 webpack 进行打包呢。为了生成目录看起来清晰,不生成单独的 map 文件。

//webpack.config.jsconst path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { entry: { index: './src/index.js', login: './src/login.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[hash:6].js' }, //... plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', filename: 'index.html' //打包后的文件名 }), new HtmlWebpackPlugin({ template: './public/login.html', filename: 'login.html' //打包后的文件名 }), ]}

16、利用webpack解决跨域问题

假设前端在3000端口,服务端在4000端口,我们通过 webpack 配置的方式去实现跨域。

首先,我们在本地创建一个 server.js

let express = require('express');let app = express();app.get('/api/user', (req, res) => { res.json({name: '张三'});});app.listen(4000);

在 index.js 中请求 /api/user,修改 index.js 如下:

//需要将 localhost:3000 转发到 localhost:4000(服务端) 端口fetch("/api/user") .then(response => response.json()) .then(data => console.log(data)) .catch(err => console.log(err));

配置代理

修改 webpack 配置:

//webpack.config.jsmodule.exports = { //... devServer: { proxy: { "/api": "http://localhost:4000" } }}

重新执行 npm run dev,可以看到控制台打印出来了 {name: "张三"},实现了跨域。


二、解锁webpack 优化篇


1、量化

有时,我们以为的优化是负优化,这时,如果有一个量化的指标可以看出前后对比,那将会是再好不过的一件事。

speed-measure-webpack-plugin 插件可以测量各个插件和loader所花费的时间,使用之后,构建时,会得到类似下面这样的信息:


speed-measure-webpack-plugin 的使用很简单,可以直接用其来包裹 Webpack 的配置:
//webpack.config.jsconst SpeedMeasurePlugin = require("speed-measure-webpack-plugin");const smp = new SpeedMeasurePlugin();const config = { //...webpack配置}module.exports = smp.wrap(config);

2、exclude/include

我们可以通过 exclude、include 配置来确保转译尽可能少的文件。顾名思义,exclude 指定要排除的文件,include 指定要包含的文件。


exclude 的优先级高于 include,在 include 和 exclude 中使用绝对路径数组,尽量避免 exclude,更倾向于使用 include。

//webpack.config.jsconst path = require('path');module.exports = { //... module: { rules: [ { test: /\.js[x]?$/, use: ['babel-loader'], include: [path.resolve(__dirname, 'src')] } ] },}

3、cache-loader

在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下。

首先安装依赖:

npm install cache-loader -D

cache-loader 的配置很简单,放在其他 loader 之前即可。修改Webpack 的配置如下:

module.exports = { //...
module: { //我的项目中,babel-loader耗时比较长,所以我给它配置了`cache-loader` rules: [ { test: /\.jsx?$/, use: ['cache-loader','babel-loader'] } ] }}

4、抽离公共代码

抽离公共代码是对于多页应用来说的,如果多个页面引入了一些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要下载一次就缓存起来了,避免了重复下载。CommonsChunkPlugin已经被移除了

抽离公共代码对于单页应用和多页应该在配置上没有什么区别,都是配置在 optimization.splitChunks 中。

//webpack.config.jsmodule.exports = { optimization: { splitChunks: {//分割代码块 cacheGroups: { vendor: { //第三方依赖 priority: 1, //设置优先级,首先抽离第三方模块 name: 'vendor', test: /node_modules/, chunks: 'initial', minSize: 0, minChunks: 1 //最少引入了1次 }, //缓存组 common: { //公共模块 chunks: 'initial', name: 'common', minSize: 100, //大小超过100个字节 minChunks: 3 //最少引入了3次 } } } }}

5、CDN

对于静态资源的处理,放入CDN是一个很好的选择,webpack中配置CDN的方式如下:

output: { path: path.resolve(__dirname, 'dist'), filename: '[name]_[hash:8].js', publicPath: 'http://static.xxxx.com/'},

6、happypack

由于有大量文件需要解析和处理,构建是文件读写和计算密集型的操作,特别是当文件数量变多后,Webpack 构建慢的问题会显得严重。文件读写和计算操作是无法避免的,那能不能让 Webpack 同一时刻处理多个任务,发挥多核 CPU 电脑的威力,以提升构建速度呢?


HappyPack 就能让 Webpack 做到这点,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。

首先需要安装 happypack:

npm install happypack -D

修改配置文件:

const Happypack = require('happypack');module.exports = { //... module: { rules: [ { test: /\.js[x]?$/, use: 'Happypack/loader?id=js', include: [path.resolve(__dirname, 'src')] }, { test: /\.css$/, use: 'Happypack/loader?id=css', include: [ path.resolve(__dirname, 'src'), path.resolve(__dirname, 'node_modules', 'bootstrap', 'dist') ] } ] }, plugins: [ new Happypack({ id: 'js', //和rule中的id=js对应 //将之前 rule 中的 loader 在此配置 use: ['babel-loader'] //必须是数组 }), new Happypack({ id: 'css',//和rule中的id=css对应 use: ['style-loader', 'css-loader','postcss-loader'], }) ]}

说明:当 postcss-loader 配置在 Happypack 中,必须要在项目中创建 postcss.config.js

//postcss.config.jsmodule.exports = { plugins: [ require('autoprefixer')() ]}

另外,当你的项目不是很复杂时,不需要配置 happypack,因为进程的分配和管理也需要时间,并不能有效提升构建速度,甚至会变慢。


7、HardSourceWebpackPlugin

HardSourceWebpackPlugin 为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source

配置 hard-source-webpack-plugin,首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。

首先安装依赖:

npm install hard-source-webpack-plugin -D
修改 webpack 的配置:
//webpack.config.jsvar HardSourceWebpackPlugin = require('hard-source-webpack-plugin');module.exports = { //... plugins: [ new HardSourceWebpackPlugin() ]}
(要) 构建结果输出分析
 Webpack输出的代码可读性非常差而且文件非常大,让我们非常头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。接下来讲解vue项目中用到的分析工具:webpack-bundle-analyzer 。


项目中在webpack.prod.conf.js进行配置:
if (config.build.bundleAnalyzerReport) { const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin())}

执行 $ npm run build --report 后生成分析报告如下


觉得本文对你有帮助?请分享给更多人

关注「前端学苑」加星标,提升前端技能

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

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