1. WebPack简介

WebPack是一种前端资源构建工具,一个静态模块打包器。

插一句:所有构建工具都是基于nodejs平台运行的,默认采用commonjs模块规范。

五大核心概念:

1.1 Entry

入口,指示Webpack以哪个文件为入口起点开始打包,构建内部依赖图。

1.2 Output

输出指示Webpack打包后的资源输出到哪里,以及如何命名。

1.3 Loader

Loader让Webpack能够去处理那些非Javascript文件(Webpack自身只理解Javascript, json),如css,图片等文件,类似翻译官。

1.4 Plugins

插件可以用于执行范围更广的任务。插件范围包括:从打包优化到压缩,重新定义环境中的变量等。

1.5 Mode

模式(Mode)指示Webpack使用相应模式的配置。

development:能让代码本地调试运行的环境。

production:能让代码优化上线运行的环境。

两者均会启用不同的插件(Plugins)

Loader和Plugins的区别

1. 从功能角度区分

Loader虽然扩展了Webpack,但它只专注于转化文件这一领域,完成压缩,打包,翻译。从字面意思上也能看出来,loader是用于加载文件的,将文件转化并加载进来。

如:

​ css-loader和style-loader模块是为了打包css的

​ babel-loader和babel-core模块时为了把ES6的代码转成ES5

​ url-loader和file-loader是把图片进行打包的。

Plugins也是扩展了Webpack功能,但是它是作用于Webpack本身的,功能要更加丰富,用于处理loader不能处理的事。从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。

2. 从运行时角度区分

loader运行在打包文件之前;

plugins在整个编译周期都起作用

2. Webpack初体验

2.1 运行指令

 1#初始化一个npm项目
 2	npm init
 3	
 4#本地安装
 5	npm i webpack webpack-cli -D
 6	# -D 会将包信息写入到package.json中的devDependencies中
 7	# devDependencies 里面的插件只用于开发环境,不用于生产环境,而 dependencies 是需要发布到生产环境的。
 8	
 9#开发环境打包
10	webpack --entry ./src/index.js -o ./build --output-filename built.js --mode=development
11#webpack会以./src/index.js为入口,开始打包,打包后输出到./build/built.js
12#整体打包环境是开发环境。
13
14#生产环境打包
15	webpack --entry ./src/index.js -o ./build --output-filename built.js --mode=production

小结:

  1. Webpack能处理js/json资源,不能处理css/img等其他资源
  2. 生产环境和开发环境将ES6模块编译成浏览器能识别的模块
  3. 生产环境比开发环境多一个压缩js代码

3. 打包样式资源

上一章实践可知,Webpack无法处理css/img等样式资源,因此需要借助Loader进行文件的转化与加载处理。

3.1 webpack配置文件

webpack.config.js : webpack的配置文件

作用:指示Webpack干哪些活(当运行Webpack指令时,会加载里面的配置)

 1// resolve 是nodejs path模块中用来拼接绝对路径的方法
 2const { resolve } = require('path');
 3
 4module.exports = {
 5    // Webpack配置
 6    // 入口起点
 7    entry: './src/index.js',
 8    // 输出
 9    output:{
10        // 输出文件名
11        filename: 'built.js',
12        // 输出路径
13        // __dirname是nodejs变量,代表当前文件所在目录
14        path: resolve(__dirname, 'build')
15    },
16    // loader的配置
17    module:{
18        rules:[
19            // 详细地loader配置
20            // *** 不同文件 需要配置不同loader ***
21            {
22                //匹配哪些文件
23                test: /\.css$/,
24                // 使用哪些loader去处理
25                use:[
26                    // use数组中loader执行顺序是从尾部到前面依次执行
27                    // 2. 创建style标签,将js中的样式资源插入,添加到页面head中生效
28                    'style-loader',
29                    // 1. 将css文件以字符串的形式编成commonjs模块加载到js中,里面内容是样式字符串
30                    'css-loader'
31                ]
32            },
33            {
34                test: /\.less$/,
35                use:[
36                    'style-loader',
37                    'css-loader',
38                    // 将less文件编译成css文件
39                    'less-loader'
40                ]
41            }
42        ]
43    },
44    // plugins的配置
45    plugins:[
46        // 详细plugins配置
47
48    ],
49    // 模式
50    mode: 'development', // 开发模式
51    // mode: 'production'
52}

4. 打包html资源

借助plugin实现打包, 这里使用html-webpack-plugin实现.

loader使用:1. 下载; 2. 使用(配置loader)

plugins使用:1. 下载; 2. 引入; 3. 使用

4.1 webpack配置

 1 const {resolve} = require('path');
 2 const HtmlWebpackPlugin = require('html-webpack-plugin');
 3 
 4 module.exports={
 5     entry: './src/index.js',
 6     output:{
 7         filename:'built.js',
 8         path: resolve(__dirname, 'build')
 9     },
10     module:{
11         rules:[
12            // loader配置
13            {
14
15            }
16         ]
17     },
18     plugins:[
19         // 功能: 默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
20         // 需要有自己写的结构的html文件
21         // template参数: 复制index.html文件, 并自动引入所有资源(js等)
22         new HtmlWebpackPlugin({
23             template: './src/index.html'
24         })
25     ],
26     mode:'development'
27 }

5. 打包图片资源

使用loader处理图片资源

1. 处理css中图片资源

在Webpack5中url-loader以及file-loader已经废弃,如果一定要使用需要加上一些参数,见下面代码:

*在Webpack5中使用asset来实现图片资源打包(type:‘asset/resource’)

2. 处理html中img标签内的图片

使用html-loader处理, 负责引入img,从而能被url-loader进行处理

 1 const {resolve} = require('path');
 2 const HtmlWebpackPlugin = require('html-webpack-plugin');
 3
 4 module.exports = {
 5     entry: './src/index.js',
 6     output: {
 7         filename: 'built.js',
 8         path: resolve(__dirname, 'build')
 9     },
10     module:{
11         rules:[
12             {
13                 test: /\.less$/,
14                 // 使用多个loader使用use, 从数组最后往前依次使用
15                 use:[
16                     'style-loader',
17                     'css-loader',
18                     'less-loader'
19                 ]
20             },
21             {
22                 test: /\.(jpg|png|gif)$/,
23                 // 只是用一个loader
24                 // 需要下载url-loader和file-loader
25                 loader: 'url-loader',
26                 options: {
27                     // 图片大小小于8kb, 就会被当成base64处理
28                     // 优点: 减少请求数量(减轻服务器压力)
29                     // 缺点: 图片体积更大
30                     limit: 30 * 1024,
31                     // webpack5使用要加
32                     // 因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
33                     // 需要关闭utl-loader的es6模块化,使用commonjs解析
34                     esModule: false,
35                     // 给图片重命名
36                     // [ext]取文件原扩展名
37                     name: '[hash:10].[ext]'
38                 },
39                 // Webpack5使用要加
40                 type: 'javascript/auto'
41                //  use: [
42                //      'url-loader'
43                //  ]
44             },
45             // Webpack5打包图片资源
46             // {
47             //     test: /\.(jpg|png|gif)$/,
48             //     type:'asset/resource'
49             // {
50                 test: /\.html$/,
51                 loader: 'html-loader'
52             }
53         ]
54     },
55     plugins:[
56         new HtmlWebpackPlugin({
57             template: './src/index.html'
58         })
59     ],
60     mode: 'development'
61 }

6. 打包其他资源

打包如字体等不需要做任何处理,原封不动处理的资源

使用file-loader处理除html/js/css以外的其他资源

Webpack5貌似不需要使用file-loader也可以直接打包

或者使用type:‘asset/resource’来处理字体文件

image-20220323154928880

 1 const {resolve} = require('path');
 2 const HtmlWebpaclPlugin = require('html-webpack-plugin');
 3 
 4 module.exports = {
 5     entry: './src/index.js',
 6     output: {
 7         filename: 'built.js',
 8         path: resolve(__dirname, 'build')
 9     },
10     module:{
11         rules:[
12             {
13                 test: /\.css$/,
14                 use: [
15                     'style-loader',
16                     'css-loader'
17                 ]
18             },
19             // 打包其他资源,除了html/js/css 以外的其他资源
20             {
21                 exclude: /\.(css|js|html)/,
22                 loader: 'file-loader'
23             }
24         ]
25     },
26     plugins:[
27         new HtmlWebpaclPlugin({
28             template: './src/index.html'
29         })
30     ],
31     mode: 'development'
32 }

7. devServer

开发服务器 devServer: (用来自动化编译,自动打开浏览器,自动刷新浏览器),即热更新

特点: 只在内存中编译打包, 而不会输出

启动指令: npx webpack-dev-Server

需要下载包webpack-dev-server

 1 const {resolve} = require('path');
 2 const HtmlWebpaclPlugin = require('html-webpack-plugin');
 3 
 4 module.exports = {
 5     entry: './src/index.js',
 6     output: {
 7         filename: 'built.js',
 8         path: resolve(__dirname, 'build')
 9     },
10     module:{
11         rules:[
12             {
13                 test: /\.css$/,
14                 use: [
15                     'style-loader',
16                     'css-loader'
17                 ]
18             },
19             // 打包其他资源,除了html/js/css 以外的其他资源
20             // Webpack5用法
21             {
22                 exclude: /\.(css|js|html)/,
23                 type: 'asset/resource'
24             }
25         ]
26     },
27     plugins:[
28         new HtmlWebpaclPlugin({
29             template: './src/index.html'
30         })
31     ],
32     mode: 'development',
33
34     // 开发服务器 devServer: (用来自动化编译,自动打开浏览器,自动刷新浏览器)
35     // 特点: 只会在内存中编译打包, 不会有任何输出
36     devServer:{
37         // Webpack4用法
38         // contentBase: resolve(__dirname, 'build'),
39         // Webpack5用法
40         static: resolve(__dirname, 'build'),
41         //启动gzip压缩, 使代码体积更小,启动更快
42         compress: true,
43         //端口号
44         port: 3000,
45         // 自动打开浏览器
46         open: true
47     }
48 }

8. 开发环境基本配置

整合前面几个章节学习内容, 集成为统一开发环境

输出资源到指定目录:

Webapck4中使用options.outputPath来实现

Webpack5中使用generator.outputPath来实现

 1 const { resolve } = require('path');
 2 const HtmlWebpackPlugin = require('html-webpack-plugin');
 3
 4 module.exports = {
 5     entry: './src/js/index.js',
 6     output: {
 7         filename: 'js/built.js',
 8         path: resolve(__dirname, 'build')
 9     },
10     module: {
11         rules: [
12             // loader配置
13             {
14                 // 处理less资源
15                 test: /\.less$/,
16                 use: [
17                     'style-loader',
18                     'css-loader',
19                     'less-loader'
20                 ]
21             },
22             {
23                // 处理css资源
24                test: /\.css$/,
25                use: [
26                    'style-loader',
27                    'css-loader'
28                ]
29            },
30            {
31                // 处理图片资源
32                test: /\.(jpg|png|gif)$/,
33                // 在Webpack5中借助内置asset-module来实现
34                type: 'asset/resource',
35                generator:{
36                    filename: 'asset/[hash:6][ext]',
37                }
38                // 或者借助url-loader来实现
39                // loader: 'url-loader',
40                // options: {
41                //     // 图片大小小于8kb, 就会被当成base64处理
42                //     // 优点: 减少请求数量(减轻服务器压力)
43                //     // 缺点: 图片体积更大
44                //     limit: 30 * 1024,
45                //     // webpack5使用要加
46                //     // 因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
47                //     // 需要关闭utl-loader的es6模块化,使用commonjs解析
48                //     esModule: false,
49                //     // 给图片重命名
50                //     // [ext]取文件原扩展名
51                //     name: '[hash:10].[ext]'
52                // },
53                // // Webpack5使用要加
54                // type: 'javascript/auto'
55            },
56            {
57                // 处理html中的img资源
58                test: /\.html$/,
59                loader: 'html-loader',
60            },
61            {
62                // 处理其他资源
63                exclude: /\.(html|js|css|less|jpg|png|jpg|gif)/,
64                // 在Webpack5中借助内置asset-module来实现
65                type: 'asset/resource',
66                generator:{
67                    filename: 'asset/[hash:6].[ext]'
68                }
69                // Webpack4用法
70                // loader: 'file-loader'
71            }
72         ]
73     },
74     plugins: [
75         new HtmlWebpackPlugin({
76             template: './src/index.html'
77         })
78     ],
79     mode: 'development',
80
81     devServer:{
82        // Webpack4用法
83        // contentBase: resolve(__dirname, 'build'),
84        // Webpack5用法
85        static: resolve(__dirname, 'build'),
86        // 启动gzip压缩, 使代码体积更小,启动更快
87        compress: true,
88        // 端口号
89        port: 3000,
90        // 自动打开浏览器
91        open: true
92    }
93 }

9. 提取CSS成单独文件

9.1. mini-css-extract-plugin插件提取CSS

通常Webpack会将CSS样式文件打包进js文件中, 这样会造成js文件体积过大, 加载缓慢, 不利于生产环境上线使用

使用mini-css-extract-plugin来进行提取

**注意:**css-loader将css处理整合进js文件中,style-loader创建style标签并插入到html中,因此处理css文件时,不能使用style-loader,

使用MiniCssExtractPlugin.loader取代style-loader,作用是提取js中的css成单独文件。

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 4
 5module.exports={
 6    entry: './src/js/index.js',
 7    output:{
 8        filename:'js/built.js',
 9        path:resolve(__dirname,'build')
10    },
11    module:{
12        rules:[
13            {
14                test: /\.css$/,
15                use:[
16                    // 'style-loader',
17                    // 这个loader取代style-loader,作用是提取js中的css成单独文件
18                    MiniCssExtractPlugin.loader,
19                    'css-loader'
20                ]
21            }
22        ]
23    },
24    plugins:[
25        new HtmlWebpackPlugin({
26            template: './src/index.html'
27        }),
28        // 提取css成单独文件
29        new MiniCssExtractPlugin({
30            // 对输出文件进行重命名并指定目录
31            filename: 'css/built.css'
32        })
33    ],
34    mode:'development'
35}

9.2. CSS兼容性处理

使用postcss-loader 以及 postcss-preset-env两个包

具体用法可以去github上看

在package.json中编写browserlist配置,postcss-preset-env插件帮助postcss找到对应browserlist配置,并进行css兼容性处理

 1"browserslist":{
 2    "development":[
 3      "last 1 chrome version",
 4      "last 1 firefox version",
 5      "last 1 safari version"
 6    ],
 7    "production":[
 8      ">0.2%",
 9      "no dead",
10      "not op_mini all"
11    ]
12  }

postcss.config.js编写

1module.exports={
2    plugins:[
3        require('postcss-preset-env')
4    ]
5}

webpack.config.js编写

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 4
 5// 设置node环境变量
 6process.env.NODE_ENV = 'production'
 7
 8module.exports={
 9    entry: './src/js/index.js',
10    output:{
11        filename:'js/built.js',
12        path:resolve(__dirname,'build')
13    },
14    module:{
15        rules:[
16            {
17                test: /\.css$/,
18                use:[
19                    // 'style-loader',
20                    // 这个loader取代style-loader,作用是提取js中的css成单独文件
21                    MiniCssExtractPlugin.loader,
22                    'css-loader',
23
24                    // css兼容性处理: postcss --> postcss-loader postcss-preset-env
25                    // 帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容样式
26                    // "browserslist":{
27                    // 开发环境  指node环境变量
28                    //     "development":[
29                    //         "last 1 chrome version",
30                    //         "last 1 firefox version",
31                    //         "last 1 safari version"
32                    //     ],
33                    // 默认生产环境
34                    //     "production":[
35                    //         ">0.2%",
36                    //         "no dead",
37                    //         "not op_mini all"
38                    //     ]
39                    // }
40
41                    // 方法一 使用loader的默认配置
42                    // 'postcss-loader',
43                    // 方法二 修改loader的配置
44                    {
45                        loader:'postcss-loader',
46                    }
47                ]
48            }
49        ]
50    },
51    plugins:[
52        new HtmlWebpackPlugin({
53            template: './src/index.html'
54        }),
55        new MiniCssExtractPlugin({
56            // 对输出文件进行重命名并指定目录
57            filename: 'css/built.css'
58        })
59    ],
60    mode:'development'
61}

也可以将postcss.config.js的内容写入webpack配置

 1 {
 2     	 loader: 'postcss-loader',
 3         options: {
 4             postcssOptions: {
 5                 plugins: [
 6                     require('postcss-preset-env')()
 7                 ]
 8             }
 9         }
10 }

9.3. 压缩CSS

一般类似于兼容性处理,文件转换都是靠loader来完成,而压缩等都是靠插件plugin来实现的

主要使用optimize-css-assets-webpack-plugin这一插件

 1plugins:[
 2    new HtmlWebpackPlugin({
 3        template: './src/index.html'
 4    }),
 5    // 提取css成单独文件
 6    new MiniCssExtractPlugin({
 7        // 对输出文件进行重命名并指定目录
 8        filename: 'css/built.css'
 9    }),
10    // 压缩css
11    new OptimizeCssAssetsWebpackPlugin()
12]

10. JS语法检查

下载eslint, eslint-webpack-plugin, eslint-config-airbnb-base, eslint-plugin-import四个包

eslint-webpack-plugin 用于替代eslint-loader(即将废弃)

在package.json中eslintConfig属性进行设置, eslint-plugin-import用于导入该设置, 利用airbnb-base用于规则设置

1"eslintConfig":{
2    "extends": "airbnb-base"
3}

webpack.config.js配置

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3const ESLintWebpackPlugin = require('eslint-webpack-plugin');
 4
 5module.exports={
 6    entry: './src/js/index.js',
 7    output: {
 8        filename: 'js/built.js',
 9        path: resolve(__dirname, 'build')
10    },
11    module:{
12        rules:[
13        ]
14    },
15    plugins:[
16        new HtmlWebpackPlugin({
17            template: './src/index.html'
18        }),
19        new ESLintWebpackPlugin({
20            // 自动修复
21            fix: true,
22            // 检查文件
23            extensions: ['js'],
24            // 排除文件夹
25            exclude: '/node_modules/'
26        })
27    ],
28    mode: 'development'
29}
1// eslint-disable-next-line
2#上述注释可以用于忽略eslint检查

问题: eslint不认识window, navigator等浏览器全局变量, 需要修改配置

1  "eslintConfig": {
2    "extends": "airbnb-base",
3    "env":{
4      "browser": true
5    }
6  }

11. JS兼容性处理

JS兼容性问题,例如:下述代码在IE浏览器会报错

1const add = (x, y) => x + y;
2
3// eslint-disable-next-line
4console.log(add(2, 3));

利用babel-loader进js的兼容性处理

11.1. 选用**@babel/preset-env进行基本**js兼容性处理

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3const ESLintWebpackPlugin = require('eslint-webpack-plugin');
 4
 5module.exports={
 6    entry: './src/js/index.js',
 7    output: {
 8        filename: 'js/built.js',
 9        path: resolve(__dirname, 'build')
10    },
11    module:{
12        rules:[
13            {
14                test: /\.js$/,
15                exclude: /node_modules/,
16                loader: 'babel-loader',
17                options: {
18                    // 预设,指示babel做怎样的兼容性处理
19                    presets: ['@babel/preset-env']
20                }
21            }
22        ]
23    },
24    plugins:[
25        new HtmlWebpackPlugin({
26            template: './src/index.html'
27        }),
28        new ESLintWebpackPlugin({
29            fix: true,
30            extensions: ['js'],
31            exclude: '/node_modules/'
32        })
33    ],
34    mode: 'development'
35}

11.2. 利用**@babel/polyfill进行全部**的js兼容性处理

在js文件内引入即可

1import '@babel/polyfill';

11.3. 按需加载兼容性处理core-js

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3const ESLintWebpackPlugin = require('eslint-webpack-plugin');
 4
 5module.exports={
 6    entry: './src/js/index.js',
 7    output: {
 8        filename: 'js/built.js',
 9        path: resolve(__dirname, 'build')
10    },
11    module:{
12        rules:[
13            {
14                test: /\.js$/,
15                exclude: /node_modules/,
16                loader: 'babel-loader',
17                options: {
18                    // 预设,指示babel做怎样的兼容性处理
19                    presets: [
20                        ['@babel/preset-env',
21                            {
22                                // 按需加载
23                                useBuiltIns: 'usage',
24                                //指定core-js版本
25                                corejs: {
26                                    version: 3
27                                },
28                                // 指定兼容性做到哪个浏览器
29                                targets: {
30                                    chrome: '60',
31                                    firefox: '60',
32                                    ie: '9',
33                                    safari: '10',
34                                    edge: '17'
35                                }
36                            }
37                        ]
38                    ]
39                }
40            }
41        ]
42    },
43    plugins:[
44        new HtmlWebpackPlugin({
45            template: './src/index.html'
46        }),
47        new ESLintWebpackPlugin({
48            fix: true,
49            extensions: ['js'],
50            exclude: '/node_modules/'
51        })
52    ],
53    mode: 'development'
54}

12. JS和html压缩

12.1. JS压缩

在生产环境下js自动压缩

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3
 4module.exports={
 5    entry: './src/js/index.js',
 6    output: {
 7        filename: 'js/built.js',
 8        path: resolve(__dirname, 'build')
 9    },
10    module:{
11        rules:[
12            
13        ]
14    },
15    plugins:[
16        new HtmlWebpackPlugin({
17            template: './src/index.html'
18        })
19    ],
20    // 生产环境下自动压缩js代码
21    mode: 'production'
22}

12.2. Html压缩

只需要配置HtmlWebpackPlugin

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3
 4module.exports={
 5    entry: './src/js/index.js',
 6    output: {
 7        filename: 'js/built.js',
 8        path: resolve(__dirname, 'build')
 9    },
10    module:{
11        rules:[
12            
13        ]
14    },
15    plugins:[
16        new HtmlWebpackPlugin({
17            template: './src/index.html',
18            minify: {
19                // 移除空格
20                collapseWhitespace: true,
21                // 移除注释
22                removeComments: true
23            }
24        })
25    ],
26    // 生产环境下自动压缩js代码
27    mode: 'production'
28}

13. 生产环境配置

包含了:

  1. CSS代码处理
  2. CSS兼容性配置
  3. CSS代码压缩
  4. JS语法检查
  5. js兼容性处理
  6. 图片处理
  7. js和html压缩

**注意:**正常来讲,一个文件只能被一个loader处理,当一个文件要被多个loader处理时,那么一定要指定loader的执行先后顺序

  1const {resolve} = require('path');
  2const HtmlWebpackPlugin = require('html-webpack-plugin');
  3const MiniCssExtractPlugin = require('mini-css-extract-plugin')
  4const commonCssLoader = {
  5    loader: 'postcss-loader',
  6    options: {
  7        postcssOptions: {
  8            plugins: [
  9                require('postcss-preset-env')()
 10            ]
 11        }
 12    }
 13}
 14const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
 15const ESLintWebpackPlugin = require('eslint-webpack-plugin');
 16const { Generator } = require('webpack');
 17
 18module.exports = {
 19    entry: './src/js/index.js',
 20    output:{
 21        filename: 'js/built.js',
 22        path: resolve(__dirname, 'build')
 23    },
 24    module: {
 25        rules:[
 26            {
 27                test: /\.css$/,
 28                use: [
 29                    MiniCssExtractPlugin.loader,
 30                    'css-loader',
 31                    // css兼容性处理
 32                    commonCssLoader
 33                ]
 34            },
 35            {
 36                test: /\.less$/,
 37                use: [
 38                    MiniCssExtractPlugin.loader,
 39                    'css-loader',
 40                    commonCssLoader,
 41                    'less-loader'
 42                ]
 43            },
 44            {
 45                test: /\.js$/,
 46                exclude: /node_module/,
 47                loader: 'babel-loader',
 48                options:{
 49                    presets: [
 50                        [
 51                            '@babel/preset-env',
 52                            {
 53                                // 按需加载
 54                                useBuiltIns: 'usage',
 55                                //指定core-js版本
 56                                corejs: {
 57                                    version: 3
 58                                },
 59                                // 指定兼容性做到哪个浏览器
 60                                targets: {
 61                                    chrome: '60',
 62                                    firefox: '60',
 63                                    ie: '9',
 64                                    safari: '10',
 65                                    edge: '17'
 66                                }
 67                            }
 68                        ]
 69                    ]
 70                }
 71            },
 72            {
 73                test: /\.(jpg|png|gif)/,
 74                type: 'asset/resource',
 75                generator: {
 76                    filename: 'img/[hash:6][ext]'
 77                }
 78            },
 79            // html中图片处理
 80            {
 81                test: /\.html/,
 82                loader: 'html-loader'
 83            },
 84            // 其他文件处理
 85            {
 86                exclude: /\.(js|css|less|html|jpg|jpg|gif)/,
 87                type: 'asset/resource',
 88                generator: {
 89                    filename: 'asset/[hash:6][ext]'
 90                }
 91            }
 92        ]
 93    },
 94    plugins:[
 95        new HtmlWebpackPlugin({
 96            template: './src/index.html',
 97            minify: {
 98                collapseWhitespace: true,
 99                removeComments: true
100            }
101        }),
102        new MiniCssExtractPlugin({
103            filename: 'css/built.css'
104        }),
105        new OptimizeCssAssetsWebpackPlugin(), // CSS压缩
106        new ESLintWebpackPlugin({
107            fix: true,
108            extensions: ['js'],
109            exclude: '/node_modules/'
110        })
111    ],
112    mode: 'production'
113}

14. Webpack性能优化

14.1. 开发环境性能优化

  1. 优化Webpack打包构建速度 (HMR)

    HMR: hot module replacement 热更新

    作用: 一个模块变化, 只会重新打包这一个模块,而不是打包所有

    Webpack5自动开启

    Webpack4开启:

     1devServer:{
     2        // Webpack4用法
     3        // contentBase: resolve(__dirname, 'build'),
     4        // Webpack5用法
     5        static: resolve(__dirname, 'build'),
     6        // 启动gzip压缩, 使代码体积更小,启动更快
     7        compress: true,
     8        // 端口号
     9        port: 3000,
    10        // 自动打开浏览器
    11        open: true,
    12        // HMR
    13        hot: true
    14    }
    

    样式文件: 可以使用HMR功能,因为’style-loader’内部实现了~

    js文件: js文件变化时devServer会重新编译所有文件,虽然也能实现更新,但是默认不能使用HMR功能,即无法实现局部打包– > 需要修改代码,添加支持 HMR功能

    修改:

    1 if(module.hot){
    2    // 为true说明开启了hmr功能
    3    module.hot.accept('./print.js', function(){
    4       // 方法会监听print.js文件变化,一但发生变化,其它默认不会重新打包构建
    5       // 会执行后面的回调函数
    6       print();
    7    })
    8 }
    

    html文件: 默认不能使用HMR功能,同时会导致问题:修改html文件后浏览器并未自动刷新热更新 (不用做HMR)

    修改:

    1// Entry更改为数组, 添加html文件
    2entry: ['./src/js/index.js', './src/index.html']
    
  2. 优化代码调试(source-map)

    source-map: 一种提供源代码到构建后代码映射的技术 (如果构建后代码出错, 通过构建关系可以追踪到源代码错误)

    配置:

     1module.exports = {
     2    ...
     3    devtool: 'source-map' // 能够提示错误代码准确信息 和 源代码错误位置
     4    ...
     5}
     6
     7// source-map 参数
     8// [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
     9// inline-source-map    // source-map内联嵌入到js文件中,内联速度更快    * 能够提示错误代码准确信息 和 源代码错误位置
    10// hidden-source-map    // 外部生成   * 能够提示错误代码原因 和 但是没有错误位置,只能提示到构建后代码错误位置
    11// eval-source-map      // 内联,每一个js文件后会追加一个sourceMapURL,在eval函数中   * 能够提示错误代码准确信息 和 源代码错误位置
    12// nosources-source-map // 外部生成   * 能够提示错误代码准确信息 和 但是没有任何源代码信息
    13// cheap-source-map     // 外部生成   * 能够提示错误代码准确信息 和 源代码错误位置(只能精确到行)
    14// module-source-map    // 外部生成   * 能够提示错误代码准确信息 和 源代码错误位置
    15
    16/*  --- 开发环境 ---
    17	速度快(eval>inline>cheap...)
    18		eval-cheap-source-map
    19		eval-source-map     一般用这个性价比最好
    20	调试更友好
    21		source-map
    22		cheap-module-source-map
    23		module会将loader的source-map加入
    24		cheap-source-map
    25
    26	--- 生产环境 ---
    27	考虑代码隐藏
    28	nosources-source-map
    29	hidden-source-map
    30
    31	不考虑inline 会使文件体积变大
    32*/
    

14.2. 生产环境性能优化

  1. 优化Webpack打包构建速度

    oneOf

    babel缓存

    多进程打包

    externals

    dll

  2. 优化代码运行性能

    缓存(hash - chunkhash - contenthash)

    tree shaking

    code split

    懒加载/预加载

    pwa

15. 缓存

当生产环境构建编译时,缓存文件,下次编译时不变的代码直接使用缓存即可,加快构建速度

15.1. babel缓存

babel构建代码时,避免重复构建,可以使用babel缓存, 下次构建时针对不变的部分直接使用缓存

cacheDirectory: true

 1{
 2    test: /\.js$/,
 3    exclude: /node_module/,
 4    loader: 'babel-loader',
 5    options:{
 6        presets: [
 7        	[
 8                '@babel/preset-env',
 9                {
10                    // 按需加载
11                    useBuiltIns: 'usage',
12                    //指定core-js版本
13                    corejs: {
14                        version: 3
15                    },
16                    // 指定兼容性做到哪个浏览器
17                    targets: {
18                        chrome: '60',
19                        firefox: '60',
20                        ie: '9',
21                        safari: '10',
22                        edge: '17'
23                    }
24                }
25        	]
26        ],
27        // 开启babel缓存
28        cacheDirectory: true
29    }
30}

15.2. 文件资源缓存

假如开启node server, 由于浏览器文件资源缓存, 重新打包后刷新浏览器并不会显示新打包的效果

解决办法:

  1. hash: webpack配置内文件名添加hash值

    1output:{
    2        filename: 'js/built.[hash:10].js',  // chunkhash ; contenthash
    3        path: resolve(__dirname, 'build')
    4    }
    

    问题: js和css同时使用一个hash值,如果重新打包, 所有缓存都将失效, 因为不管文件变没变, 都会重新生成hash值

  2. chunkhash: 根据chunk生成hash值, 如果打包来源于同一个chunk, 那么hash值一样

    存在问题: css和js文件hash值还是一样, 因为css被js引入, 属于同一个chunk, 因此两者hash值还是一样

  3. contenthash: 根据文件内容生成hash值, 不同文件hash值一定不一样, 内容不变, hash值不变

16. Tree Shaking

含义: 去除应用程序中没有使用到的代码

前提:

  1. 必须使用ES6模块化
  2. 开启production模式

则 webpack自动启用

在package.json中配置:

代表所有代码都没有副作用(都可以进行tree shaking)

可能会把css文件去除

1"sideEffects": false

改进:

1"sideEffects":["*.css"]

17. code split 代码分割

将代码打包输出分割为多个js文件

demo1: 多入口: 一个入口输出一个bundle

1entry: {
2    main: './src/js/index.js',
3    test: './src/js/test.js'
4},
5output:{
6    filename: 'js/[name].[contenthash:10].js', // 输出名字为entry指定的key
7    path: resolve(__dirname, 'build')
8}

demo2: 引入的node_modules代码单独打包输出, 多个库会合并打包成一个文件

自动分析多入口文件中有没有公共依赖, 如果有, 则打包成单独chunk

1optimization:{
2    splitChunks:{
3        chunks:'all'
4    }
5}

demo3: 通过js代码让某个文件单独打包成一个chunk

import 动态导入语法

1// 导入test文件内两个函数
2// webpackChunkName 指定打包后文件名
3import(/* webpackChunkName: 'test' */'./test').then(({mul, count}) => {
4  console.log(mul(2,3));
5  console.log(count(6,2));
6})

小结:

一般多入口不常用,通常使用单入口 + optimization + import动态导入

18. JS文件懒加载

需要时才加载

懒加载必然需要将js拆分为单独bundle, 因此可以使用import动态导入

1import(/* webpackChunkName: 'test' */ './test').then(({mul, count})=>{
2    console.log(mul(2, 3));
3})

预加载:

会在使用前, 提前加载js文件

会等其他资源加载完毕, 再加载

有兼容性问题, 慎用!

1import(/* webpackChunkName: 'test' , webpackPrefetch: true */'./test').then(({mul, count})=>{
2    console.log(mul(2, 3));
3})

19. PWA

渐进式网页开发应用程序

加载的网页离线也能访问

利用workbox-webpack-plugin插件

**注意: **serviceWorker必须运行在服务器端, 可以通过nodejs启动

webpack.config.js配置

1new WorkboxWebpackPlugin.GenerateSW({
2    // 帮助serviceWorker快速启动
3    // 删除旧的 serviceWorker
4    // 生成一个serviceWorker的配置文件
5    clientsClaim: true,
6    skipWaiting: true
7})

js代码注册serviceWorker代码运行

 1// 注册serviceWorker
 2// 处理兼容性问题
 3if ('serviceWorker' in navigator) {
 4  window.addEventListener('load', () => {
 5    navigator.serviceWorker.register('/service-worker.js').then(() => {
 6      console.log('注册成功!');
 7    }).catch(() => {
 8      console.log('注册失败');
 9    });
10  });
11}

20. 多进程打包

使用thread-loader进行, 需要给哪一类文件开启多进程打包, 就在对应loader下面使用

只有总体打包消耗工作时间比较长才需要使用多进程打包

一般可以在babel打包的时候使用

 1{
 2    test: /\.js$/,
 3    exclude: /node_module/,
 4    use: [
 5        // 开启多进程打包
 6        'thread-loader',
 7        {
 8            loader: 'babel-loader',
 9            options:{
10                presets: [
11                    [
12                        '@babel/preset-env',
13                        {
14                            // 按需加载
15                            useBuiltIns: 'usage',
16                            //指定core-js版本
17                            corejs: {
18                                version: 3
19                            },
20                            // 指定兼容性做到哪个浏览器
21                            targets: {
22                                chrome: '60',
23                                firefox: '60',
24                                ie: '9',
25                                safari: '10',
26                                edge: '17'
27                            }
28                        }
29                    ]
30                ],
31                cacheDirectory: true
32            }
33        }
34    ],
35}

指定进程数量:

1{
2    loader:'thread-loader',
3    options:{
4        workers: 2
5    }
6}

21. externals

阻止指定module打包进bundle中, 比如希望通过cdn引入时

例如: 防止js中引入的jquery被打包:

1externals: {
2    // 忽略库名 -- npm包名
3    jquery: 'jQuery'
4}

22. dll

将多个库分别打包为单独chunk

解决重复打包第三方库的问题

optimization虽然也分离第三方库, 但是仍需要重新打包

webpack.dll.js配置编写

作用: 将第三方库单独输出为js

 1const {resolve} = require('path');
 2const webpack = require('webpack');
 3
 4/*
 5    使用dll技术对某些库(第三方库)进行单独打包
 6    运行指令: webpack --config webpack.dll.js
 7*/
 8module.exports = {
 9    entry: {
10        // 最终打包生成的[name] --> jquery
11        // ['jquery'] --> 要打包的库是jquery
12        jquery: ['jquery']
13    },
14    output:{
15        filename: '[name].js',
16        path: resolve(__dirname, 'dll'),
17        library: '[name]_[hash]', // 打包的库里面向外暴露出去的内容叫什么名字
18    },
19    plugins:[
20        // 打包生成一个manifest.json文件,提供和jquery的映射
21        new webpack.DllPlugin({
22            name: '[name]_[hash]', // 映射库的暴露的内容
23            path: resolve(__dirname, 'dll/manifest.json')
24        })
25    ],
26    mode: 'production'
27}

webpack.config.js配置

作用:

  1. 告诉webpack哪些库不需要打包(之前已经导出为dll的库)
  2. 将第三方库输出到build下, 并在html中自动引入该第三方资源
1// 告诉webpack哪些库不参与打包,同时使用时名称也需要修改,但是最终并未引入包
2new webpack.DllReferencePlugin({
3    manifest: resolve(__dirname, 'dll/manifest.json')
4}),
5// 将某个文件打包输出出去,并在html中自动引入该资源
6new AddAssetHtmlWebpackPlugin({
7    filepath: resolve(__dirname, 'dll/jquery.js'),
8    outputPath: 'auto'
9})

23. entry详细配置

  1. string写法 –> ‘./src/index.js’

    打包形成一个chunk,输出一个bundle文件

     1const {resolve} = require('path');
     2const HtmlWebpackPlugin = require('html-webpack-plugin');
     3
     4module.exports = {
     5    entry: './src/index.js',
     6    output: {
     7        filename: 'built.js',
     8        path: resolve(__dirname, 'build')
     9    },
    10    plugins:[
    11        new HtmlWebpackPlugin()
    12    ],
    13    mode: 'development'
    14}
    
  2. Array写法

    多入口, 所有入口文件最终只会生成一个chunk, 输出只有一个bundle

    –>通常来说, 只有在HMR功能中, 通过多入口让html热更新生效

     1const {resolve} = require('path');
     2const HtmlWebpackPlugin = require('html-webpack-plugin');
     3
     4module.exports = {
     5    entry: ['./src/index.js' , './src/add.js'],
     6    output: {
     7        filename: '[name].js',
     8        path: resolve(__dirname, 'build')
     9    },
    10    plugins:[
    11        new HtmlWebpackPlugin()
    12    ],
    13    mode: 'development'
    14}
    
  3. object写法

    多入口, 有几个入口文件就形成几个chunk,输出几个bundle文件

    此时[name]为key值

    特殊用法: 与数组结合

     1const {resolve} = require('path');
     2const HtmlWebpackPlugin = require('html-webpack-plugin');
     3
     4module.exports = {
     5    entry: {
     6        index: ['./src/index.js', './src/count.js'],
     7        add: './src/add.js'
     8    },
     9    output: {
    10        filename: '[name].js',
    11        path: resolve(__dirname, 'build')
    12    },
    13    plugins:[
    14        new HtmlWebpackPlugin()
    15    ],
    16    mode: 'development'
    17}
    

24. output详细配置

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3
 4module.exports = {
 5    entry: './src/index.js',
 6    output: {
 7        // 文件名称(可以指定名称 + 目录)
 8        filename: 'js/[name].js',
 9        // 指定输出文件目录(将来所有资源输出的公共目录)
10        path: resolve(__dirname, 'build'),
11        // 所有资源引入的公共路径前缀 --> 补充到path的前面 --> 'imgs/a.jpg' --> '/imgs/a.jpg'
12        publicPath: '/',
13        // 非入口chunk的名称 如import动态导入或者optimization分割的chunk
14        chunkFilename: '[name]_chunk.js',
15        // js文件整个库向外暴露出去的变量名
16        library: '[name]',
17        libraryTarget: 'window' // 变量名添加到哪个上 browser
18    },
19    plugins:[
20        new HtmlWebpackPlugin()
21    ],
22    mode: 'development'
23}

25. module详细配置

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3
 4module.exports = {
 5    entry: './src/index.js',
 6    output: {
 7        // 文件名称(可以指定名称 + 目录)
 8        filename: 'js/[name].js',
 9        // 指定输出文件目录(将来所有资源输出的公共目录)
10        path: resolve(__dirname, 'build'),
11    },
12    module: {
13        rules: [
14            {
15                test:/\.css$/,
16                // 多个loader用use
17                use: ['style-loader', 'css-loader']
18            },
19            {
20                test: /\.js$/,
21                // 排除文件
22                exclude: /node_modules/,
23                // 只检查src下的js文件
24                include: resolve(__dirname, 'src'),
25                // 优先执行
26                enforce: 'pre',
27                // 延后执行
28                // enforce: 'post',
29                // 单个loader用loader
30                loader: 'eslint-loader'
31            },
32            {
33                // 通常每个不同类型的文件在loader转换时,都会被命中,遍历module中rules中所有loader
34                // oneOf 只要匹配即退出, 根据文件类型选择对应的loader
35                // 不能有两个loader处理同一种文件
36                oneOf:[]
37            }
38        ]
39    },
40    plugins:[
41        new HtmlWebpackPlugin()
42    ],
43    mode: 'development'
44}

26. resolve解析模块详细配置

解析模块的规则设置

 1const {resolve} = require('path');
 2const HtmlWebpackPlugin = require('html-webpack-plugin');
 3
 4module.exports = {
 5    entry: './src/js/index.js',
 6    output: {
 7        // 文件名称(可以指定名称 + 目录)
 8        filename: 'js/[name].js',
 9        // 指定输出文件目录(将来所有资源输出的公共目录)
10        path: resolve(__dirname, 'build'),
11    },
12    module: {
13        rules: [
14            {
15                test:/\.css$/,
16                use:['style-loader', 'css-loader']
17            }
18        ]
19    },
20    plugins:[
21        new HtmlWebpackPlugin()
22    ],
23    mode: 'development',
24    
25    // 解析模块的规则
26    resolve: {
27        // 配置解析模块路径别名
28        // 例如 将src/css 替换为 $css
29        // 优点: 简写路径 
30        // 缺点: 没有提示
31        alias: {
32            $css: resolve(__dirname, 'src/css')
33        },
34        
35        // 配置省略文件路径的后缀名 --> './css/index'
36        // 文件名一样则匹配第一个遇到的, 命名时需要注意
37        extensions: ['.js', '.json', '.css'],
38
39        // 告诉webpack解析模块时是去找哪个目录
40        modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
41    }
42}

27. devServer详细配置

 1     // 开发服务器 devServer: (用来自动化编译,自动打开浏览器,自动刷新浏览器)
 2     // 特点: 只会在内存中编译打包, 不会有任何输出
 3     devServer:{
 4         // --- 运行代码的目录 --- 
 5         // Webpack4用法
 6         // contentBase: resolve(__dirname, 'build'),
 7         // Webpack5用法
 8         static: resolve(__dirname, 'build'),
 9         // 监视运行目录下的所有文件
10         watchcontentBase: true,
11         watchOptions: {
12             // 忽略监视的文件
13             ignored: /node_modules/
14         },
15         // 启动gzip压缩, 使代码体积更小,启动更快
16         compress: true,
17         // 端口号
18         port: 3000,
19         // 域名
20         host: 'localhost',
21         // 自动打开浏览器
22         open: true,
23         // 开启HMR功能 webpack5自动开启
24         hot: true,
25         // 不需要显示启动服务器日志
26         clientLogLevel: 'none',
27         // 除了一些基本启动信息以外,其他内容都不要打印
28         quiet: 'true',
29         // 如果出现错误,不要全屏提示~
30         overlay: false,
31         // 服务器代理 --> 解决开发环境下跨域问题
32         proxy: {
33             // 一旦devServer(port:5000)服务器接收到/api/xxx的请求, 就会把请求转发到另外一个服务器(port:3000)
34             '/api': {
35                 target: 'http://localhost:3000',
36                 // 发送请求时,请求路径重写: 将api/xxx --> /xxx(去掉/api)
37                 pathRewrite: {
38                     '^/api' : ''
39                 }
40             }
41         }
42     }

28. optimization详细配置

优化配置选项

 1    optimization: {
 2        splitChunks: {
 3            chunks: 'all',
 4            // 下面所有的都默认就行, 通常不需要配置
 5            minSize: 30 * 1024, // 分割的chunk最小为30kb
 6            maxSize: 0, // 最大没有限制
 7            minChunks: 1, // 要提取的chunk最少要被引用1次
 8            maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
 9            maxInitialRequests: 3, // 入口js文件最大并行请求数量
10            automaticNameDelimiter: '~', // 名称连接符
11            name: true, // 可以使用命名规则
12            cacheGroup: { // 分割chunk的组
13                // node_modules中的文件会被打包到vendors组的chunk中 --> vecdors~0.js
14                // 满足上面的公共规则
15                vendors: {
16                    test: /[\\/]node_modules[\\/]/,
17                    priority: -10
18                },
19                default: {
20                    // 被提取的chunk最少要被引用2次
21                    minChunks: 2,
22                    priority: -20,
23                    // 如果当前要打包的模块和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
24                    reuseExistingChunk: true
25                }
26            }
27        }
28        // 解决: 修改a文件导致b文件的contenthash变化
29        runtimeChunk: {
30            name: entrypoint => `runtime-$(entrypoint.name)`
31        },
32        minimizer: [
33            // 配置生产环境的压缩方案: js和css
34            new TerserWebpackPluging({
35                // 开启缓存
36                cache: true,
37                // 并行打包
38                parallel: true,
39                // 启用source-map 否则会被压缩
40                sourceMap: true,
41            })
42        ]
43    }