解锁 Webpack,看这篇就够了

452 阅读11分钟

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

                                                       「~解锁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.js
class 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 -D
npm install @babel/runtime @babel/runtime-corejs3

新建 webpack.config.js,如下:

//webpack.config.js
    module.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.js
module.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.js
module.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.js
module.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.js
module.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.js
const { 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.js
const webpack = require('webpack');
module.exports = {    
    //....    
    devServer: {        
        hot: true    
    },    
    plugins: [        
        new webpack.HotModuleReplacementPlugin() //热更新插件    
    ]
}

15、多页应用打包

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

//webpack.config.js
const 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.js
const 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.js
const 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.js
    module.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.js
module.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.js
    var 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 后生成分析报告如下

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

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