webpack 入门到精通(上)

566 阅读15分钟

本文章的讲解的基本技能内容图如下:

1、webpack基本使用

webapck 是什么

webpack 是一种前端资源构建的工具,一个静态模块打包器(module bundler)。在 webpack 看来,前端的所有资源文件(js / json / css / img / less/...)都会作为模块处理(chunk)。它根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

webpack 五个核心

entry

入口(entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。

output

输出(output)指示 webpack打包后的资源 bundles 输出到哪里去,以及如何命名。

loader

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只能够识别 JavaScript),比如css、less等,在这里可以理解为loader就是一个"翻译官"。

plugins

插件(plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量等,在这里可以理解为比loader的功能更加强大。

mode

模式(mode)指示 webpack 使用相应模式的配置,只有两种模式,分别为开发模式(development)和生产模式(production)。

webpack 初体验

初始化配置

初始化 package.json文件,输入指令为 npm init。

下载并安装 webpack,输入指令为 npm i webpack@4 webpack-cli@3 -D

编译打包

创建文件:

在src目录下创建一个入口文件index.js。

运行指令:

  1. 直接在终端运行 webpack,若想让运行环境为开发环境,需要将 mode 改为 development。
    功能:webpack 能够编译打包 js 和 json 文件,并且将 ES6 的模块化语法转换成浏览器能识别的语法

  2. 直接在终端运行 webpack,若想让运行环境为生产环境,需要将 mode 改为 production。
    功能:在开发配置功能上多一个功能,就是压缩代码,减小了构建后的代码体积。 结论:

  3. webpack 能够编译打包 js 和 json 文件。

  4. 能将 ES6 模块化语法转换成浏览器能识别的语法。

  5. 能压缩代码,减少构建后的代码体积,加快代码运行速度。 问题:

  6. 不能编译打包 css、img 等文件。

  7. 不能将 ES6 基本语法转化为 ES5 以下语法

开发环境 webpack.config.js 的相关代码如下

// webpack.config.js文件
// resolve 用来拼接绝对路径的方法
const { resolve } = require('path')
module.exports = {
    entry: './src/index.js',    // 入口文件 单入口
    output:{	//输出配置
        filename:'./build.js',// 输出文件名
        path: resolve(__dirname,'build') // 输出路径配置 使用绝对路径
    },
    // loader 
    module:{
        rules: []
    },
    plugins:[], // plugins 插件
    mode: 'development' // 生产环境
}

输出结果:生产环境下构建后的代码大小为:3.77KB。


将 mode 改为 production 后,构建后的代码大小为:930bytes。

从中可以看出,mode 的不同,构建后的代码大小也就不同,production 环境下内部会自动打包压缩 js 代码。从而提高在生产环境中打包速度。

2、webpack开发环境配置

打包样式资源

创建文件:

在index.js中引入相关的css、less

下载安装 loader 包:
npm i css-loader@3 style-loader@1 less-loader@5 less@4 -D

修改 webpack.config.js 配置文件:
const { resolve } = require('path')
module.exports = {
    entry: './src/index.js', // 入口文件 单入口
    output:{
        filename:'./build.js',  // 输出文件名
        path: resolve(__dirname,'build') // 输出路径 使用绝对路径
    },
    // loader 
    module:{
        rules: [
            //详细loader配置 不同文件必须配置不同的loader处理
            {   
                test: /\.css$/,//正则表达式匹配哪些文件
                use: [
                    // use数组中的执行loader顺序:下到上、右到左
            // style-loader作用:创建style标签,将js中的样式资源进行插入,添加daoheader中生效
                    'style-loader',
                 // css-loader作用:将css文件变成commonjs模块加载js中,里面内容时样式字符串
                    'css-loader'
                ]
            },
            {   //匹配less样式资源
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    // 将less文件编译成css文件
                    // 需要下载less-loader和less
                    'less-loader'
                ]
            }
        ]
    },
    // plugins 配置
    plugins:[
        //详细的plugins配置
    ],
    //模式
    mode: 'development'
}

运行指令:wepack

打包HTML资源

创建文件:
在src目录下创建 index.html。
下载安装 plugin 包:
npm i html-webpack-plugin@3 -D

修改 webpack.config.js 配置文件:
const HtmlWebpackPlugin = require('html-webpack-plugin')
....
	// plugins 配置
    plugins:[
        //html-webpack-plugin
        //功能:默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
        //需求:需要有结构的html文件
        new HtmlWebpackPlugin({
            //以这个为模板创建html文件,并自动引入输出的所有资源(JS/CSS)
            template: './src/index.html'
        })
    ],
....

打包图片资源

创建文件:
在src目录下放入图片
下载 loader 包:
npm i html-loader@0.5 url-loader@3 file-loader@5 -D

修改 webpack.config.js 配置文件:
....
module: {
  ....
  {   // 处理图片资源
                test: /\.(jpg|png|gif)$/,
                // 使用一个loader
                // 下载url-loader file-loader
                loader: 'url-loader',
                options: {//对loader进行相关配置
                    // 图片大小小于8kb,就会被base64进行处理 不会被打包输出
                    // 优点:减少请求数量(减轻服务器压力)
                    // 缺点:图片体积会更大(文件请求速度更慢) 
                    limit: 8 * 1024,
                    // 加载html页面中的图片出现的问题
                    // 因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
                    // 解析时会出现:[object Module]
                    // 解决:关闭url-loader的es6模块化,使用commonjs解析
                    esModule: false,
                    // 给图片重命名
                    // 取hash前十位 ext取扩展名(后缀名)
                    name: '[hash:10].[ext]'
                }
            },
            {   // 处理html中的图片
                test: /\.html$/,
                // 处理html文件中引入的img图片(负责引入img,从而能被url-loader进行处理)
                loader: 'html-loader'
            }
}
....

打包其他资源

创建文件:加入incon图标等文件

修改 webpack.config.js 配置文件:
module: {
	....
  { // 排除 css/js/html 资源 
   exclude: /\.(css|js|html|less)$/, 
   loader: 'file-loader', 
   options: { 
     name: '[hash:10].[ext]' 
   	} 
  }
	....
}

devServer

下载安装包:
npm i webpack-dev-server@3 -D

修改 webpack.config.js 配置文件:

...
    // 开发服务器devServer 用来自动化(自动编译、自动打开浏览器、自动刷新浏览器)
    // 特点:只会在内存中编译打包、不会有任何输出
 devServer: {
        contentBase: resolve(__dirname, 'build'),
        // 启动gzip压缩
        compress: true,
        // 端口号
        port: 3000,
        open: true,
    },
...

运行指令:npx webpack-dev-server
开启devServer 用来自动编译、自动打开浏览器、自动刷新浏览器。
特点:只在内存中编译打包、不会有任何的输出,提高在开发中的效率。

开发环境配置汇总

开发环境配置webpack.config.js的全部代码如下

const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    entry: './src/index.js', // 入口文件 单入口
    output:{
        filename:'./build.js',  // 输出文件名
        path: resolve(__dirname,'build') // 输出路径 使用绝对路径
    },
    // loader 
    module:{
        rules: [
            // 详细loader配置 不同文件必须配置不同的loader处理
            {   
                test: /\.css$/,//正则表达式匹配哪些文件
                use: [
                    // use数组中的执行loader顺序:下到上、右到左
                    // style-loader作用:创建style标签,将js中的样式资源进行插入,添加到header中生效
                    'style-loader',
                    // css-loader作用:将css文件变成commonjs模块加载js中,里面内容时样式字符串
                    'css-loader'
                ]
            },
            {   // 匹配less样式资源
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    // 将less文件编译成css文件
                    // 需要下载less-loader和less
                    'less-loader'
                ]
            },
            {   // 处理图片资源
                test: /\.(jpg|png|gif)$/,
                // 使用一个loader
                // 下载url-loader file-loader
                loader: 'url-loader',
                options: {//对loader进行相关配置
                    // 图片大小小于8kb,就会被base64进行处理 不会被打包输出
                    // 优点:减少请求数量(减轻服务器压力)
                    // 缺点:图片体积会更大(文件请求速度更慢) 
                    limit: 8 * 1024,
                    // 加载html页面中的图片出现的问题
                    // 因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
                    // 解析时会出现:[object Module]
                    // 解决:关闭url-loader的es6模块化,使用commonjs解析
                    esModule: false,
                    // 给图片重命名
                    // 取hash前十位 ext取扩展名(后缀名)
                    name: '[hash:10].[ext]'
                }
            },
            {   // 处理html中的图片
                test: /\.html$/,
                // 处理html文件中引入的img图片(负责引入img,从而能被url-loader进行处理)
                loader: 'html-loader'
            },
            {   // 处理其他资源
                // 排除css/js/html/less资源
                exclude: /\.(css|less|js|html)$/,
                loader: 'file-loader',
                options: {
                    name: '[hash:10].[ext]'
                }
            }
        ]
    },
    // plugins 配置
    plugins:[
        //html-webpack-plugin
        //功能:默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
        //需求:需要有结构的html文件
        new HtmlWebpackPlugin({
            //以这个为模板创建html文件,并自动引入输出的所有资源(JS/CSS)
            template: './src/index.html'
        })
    ],
    //模式
    mode: 'development',
    devServer: {
        // 项目构建后路径
        contentBase:resolve(__dirname,'build'),
        // 开启gzip压缩
        compress: true,
        // 端口号
        port: 5000,
        // 自动打开浏览器
        open: true
    }
}

3、webpack生产环境配置

提取 CSS 成单独文件

下载plugin插件:
npm install mini-css-extract-plugin@0.9 -D

修改 webpack.config.js 配置文件:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
    entry: './src/index.js', // 入口文件 单入口
    output:{
        filename:'./build.js',  // 输出文件名
        path: resolve(__dirname,'build') // 输出路径 使用绝对路径
    },
    // loader 
    module:{
        rules: [
            {   
                test: /\.css$/,//正则表达式匹配哪些文件
                use: [
                    // use数组中的执行loader顺序:下到上、右到左
                    // 这个 loader取代style-loader 作用:提取css中的css成单独文件
                    MiniCssExtractPlugin.loader,
                    // css-loader作用:将css文件变成commonjs模块加载js中,里面内容时样式字符串
                    'css-loader'
                ]
            }
        ]
    },
    plugins:[
        //功能:默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
        //需求:需要有结构的html文件
        new HtmlWebpackPlugin({
            //以这个为模板创建html文件,并自动引入输出的所有资源(JS/CSS)
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin({
            // 对输出的css文件进行重命名
            filename: 'css/main.css'
        })
    ],
    //模式
    mode: 'production',
}

运行指令:webpack

CSS 兼容性处理

下载loader:
npm install postcss-loader@3 postcss-preset-env@6 -D

修改 webpack.config.js 配置文件:  
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
    entry: './src/index.js', // 入口文件 单入口
    output:{
        filename:'./build.js',  // 输出文件名
        path: resolve(__dirname,'build') // 输出路径 使用绝对路径
    },
    // loader 
    module:{
        rules: [
            // 详细loader配置 不同文件必须配置不同的loader处理
            {   
                test: /\.css$/,//正则表达式匹配哪些文件
                use: [
                    // use数组中的执行loader顺序:下到上、右到左
                    // 这个 loader取代style-loader 作用:提取css中的css成单独文件
                    MiniCssExtractPlugin.loader,
                    // css-loader作用:将css文件变成commonjs模块加载js中,里面内容时样式字符串
                    'css-loader',
                   /* css兼容性处理 postcss --> postcss-loader  postcss-preset-env
                               postcss-preset-env 帮助postcss找到package.josn中的browserslist里面的配置
                               通过加载指定的css兼容性样式
                              "browserslist": {
                                  开发环境 设置node变量环境:process.env.NODE_ENV = 'development' 默认是production
                                "development": [
                                    "last 1 chrome version",
                                    "last 1 firefox version",
                                    "last 1 safari version"
                                ],
                                生产环境 :处理兼容性问题的时候默认是生产环境
                                "production": [
                                    ">0.2%",
                                    "not dead",
                                    "not op_mini all"
                                ]
                            } */
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            plugins: () => [
                                // postcss 的插件
                                require('postcss-preset-env')()
                            ]
                        }
                    }
                ]
            }
        ]
    },
    // plugins 配置
    plugins:[
        //html-webpack-plugin
        //功能:默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
        //需求:需要有结构的html文件
        new HtmlWebpackPlugin({
            //以这个为模板创建html文件,并自动引入输出的所有资源(JS/CSS)
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin({
            // 对输出的css文件进行重命名
            filename: 'css/main.css'
        })
    ],
    //模式
    mode: 'production',
}
修改 package.json 配置文件: 
"browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  }

运行指令:webpack

压缩CSS

下载 plugin 包:
npm install optimize-css-assets-webpack-plugin@5 -D

修改 webpack.config.js 配置文件: 
const OptimizeCssAssetsWebpackPlugin= require('optimize-css-assets-webpack-plugin')
....
    plugins:[
    	// 压缩 css代码
      new OptimizeCssAssetsWebpackPlugin()
    ]
....

运行指令:webpack

JS 语法检查

下载 loader 包
npm install eslint-loader@3 eslint@6 eslint-config-airbnb-base@15 eslint-plugin-import@2 -D

修改 webpack.config.js 配置文件:
module: {
		...
    {
                //js语法检查 eslint-loader eslint
                /*
                注意:只检测自己写的源代码,不需要检查第三方库
                设置检查规则:
                    package.json中eslintConfig中设置
                    "eslintConfig":{
                        "extends": "airbnb-base"
                    }
                    airbnb-> eslint-config-airbnb-base eslint-plugin-import eslint
                */
               test: /\.js$/,
               exclude: /node_modules/,
               loader: 'eslint-loader',
               options: {
                   // 自动修复eslint的错误
                   fix: true
               }
            }
		...
}
...
修改 package.json 配置文件:
"eslintConfig": {
    "extends": "airbnb-base"
  }

运行指令: webpack

JS 兼容性处理

下载 loader 包
npm install babel-loader@8 @babel/core@7 @babel/preset-env@7 @babel/polyfill@7 core-js@3 -D

修改 webpack.config.js 配置文件:
.....
module:{
	...
  {						
    			/*
                        js兼容性处理:babel-loader @babel/core 
          		1. 基本js兼容性处理 --> @babel/preset-env
            		问题:只能转换基本语法,如promise高级语法不能转换
          		2. 全部js兼容性处理 --> @babel/polyfill  
            		问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了~
          		3. 需要做兼容性处理的就做:按需加载  --> core-js
      		*/ 
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                    // 预设:指示babel做怎么样的兼容性处理
                    presets:[
                        [
                            '@babel/preset-env',
                            {
                                //按需加载
                                useBuiltIns: 'usage',
                                // 指定core-js版本
                                corejs: {
                                    version: 3
                                },
                                // 兼容哪些浏览器
                                targets: {
                                    chrome: '60',
                                    firefox: '60',
                                    ie: '9',
                                    safari: '10',
                                    edge: '17'
                                }
                            }
                        ]
                    ]
                }
            }
  ...
}
....

运行指令:webapck

JS 压缩

修改 webpack.config.js 配置文件:
....
mode:'production'
...

将 mode 的值改为 production,webpack 内部会通过插件自动会压缩 js 代码。

Html 压缩

修改 webpack.config.js 配置文件:
.....
plugins:[
        //html-webpack-plugin
        //功能:默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
        //需求:需要有结构的html文件
        new HtmlWebpackPlugin({
            //以这个为模板创建html文件,并自动引入输出的所有资源(JS/CSS)
            template: './src/index.html',
            // 压缩html
            minify: {
                // 移除空格
                collapseWhitespace: true,
                // 移除注释
                removeComments: true
            }
        }),
        // 从js中分离css代码
        new MiniCssExtractPlugin({
            // 对输出的css文件进行重命名
            filename: 'css/main.css'
        }),
        // 压缩 css代码
        new OptimizeCssAssetsWebpackPlugin()
    ],
.....

运行指令:webpack

生产环境配置汇总

生产环境配置webpack.config.js的全部代码如下:

const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin= require('optimize-css-assets-webpack-plugin')
module.exports = {
    entry: './src/index.js', // 入口文件 单入口
    output:{
        filename:'./build.js',  // 输出文件名
        path: resolve(__dirname,'build') // 输出路径 使用绝对路径
    },
    // loader 
    module:{
        rules: [
            // 详细loader配置 不同文件必须配置不同的loader处理
            {   
                test: /\.css$/,//正则表达式匹配哪些文件
                use: [
                    // use数组中的执行loader顺序:下到上、右到左
                    // 这个 loader取代style-loader 作用:提取css中的css成单独文件
                    MiniCssExtractPlugin.loader,
                    // css-loader作用:将css文件变成commonjs模块加载js中,里面内容时样式字符串
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            plugins: () => [
                                // postcss 的插件
                                require('postcss-preset-env')()
                            ]
                        }
                    }
                ]
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                    // 预设:指示babel做怎么样的兼容性处理
                    presets:[
                        [
                            '@babel/preset-env',
                            {
                                //按需加载
                                useBuiltIns: 'usage',
                                // 指定core-js版本
                                corejs: {
                                    version: 3
                                },
                                // 兼容哪些浏览器
                                targets: {
                                    chrome: '60',
                                    firefox: '60',
                                    ie: '9',
                                    safari: '10',
                                    edge: '17'
                                }
                            }
                        ]
                    ]
                }
            },
            {
                //js语法检查 eslint-loader eslint
                /*
                注意:只检测自己写的源代码,不需要检查第三方库
                设置检查规则:
                    package.json中eslintConfig中设置
                    "eslintConfig":{
                        "extends": "airbnb-base"
                    }
                    airbnb-> eslint-config-airbnb-base eslint-plugin-import eslint
                */
               test: /\.js$/,
               exclude: /node_modules/,
               loader: 'eslint-loader',
               options: {
                   // 自动修复eslint的错误
                   fix: true
               }
            }
        ]
    },
    // plugins 配置
    plugins:[
        //html-webpack-plugin
        //功能:默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
        //需求:需要有结构的html文件
        new HtmlWebpackPlugin({
            //以这个为模板创建html文件,并自动引入输出的所有资源(JS/CSS)
            template: './src/index.html',
            // 压缩html
            minify: {
                // 移除空格
                collapseWhitespace: true,
                // 移除注释
                removeComments: true
            }
        }),
        // 从js中分离css代码
        new MiniCssExtractPlugin({
            // 对输出的css文件进行重命名
            filename: 'css/main.css'
        }),
        // 压缩 css代码
        new OptimizeCssAssetsWebpackPlugin()
    ],
    //模式
    mode: 'production',
}

4、webpack优化环境配置

HMR

HMR(hot modules replacement)热模块替换功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。主要通过以下集中方式,来显著加快开发速度:

  1. 保留在完全重新加载页面期间丢失的应用程序状态
  2. 只更新变更内容,以节省宝贵的开放时间
  3. 在源代码中对 CSS/JS 进行修改,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。 HMR 有三种:样式文件、JS 文件、HTML 文件。

样式文件:

可以使用 HMR功能,因为style-loader内部实现了,所以在开发环境中一般使用 style-loader,而不使用 MiniCssExtractPlugin.loader 替代 style-loader 分离 CSS,从而加快了开发时的构建代码速度。

JS 文件:

默认没有 HMR 功能,需要修改 js 代码,添加支持 HMR 功能的代码

具体实现代码如下:
//在入口文件 index.js 配置
if (module.hot) {
    // 一旦module.hot为true 说明开启了 HMR 功能,HMR 功能生效
    module.hot.accept('./hot.js',function() {
        add()
    })
}
修改 webpack.config.js 配置
...
devServer: {
        // 项目构建后路径
        contentBase:resolve(__dirname,'build'),
        // 开启gzip压缩
        compress: true,
        // 端口号
        port: 5000,
        // 自动打开浏览器
        open: true,
        // 开启热替换
        hot: true
    }
...

运行指令:npx webpack-dev-server。

HTML文件:

默认是不能使用 HMR 功能,同时会导致 html 不能及时热更新。因为只有一个 html 文件,所以不用做性能优化。解决:修改 entry 入口 将html文件引入,整体会全部刷新。

修改 webpack.config.js 配置
....
 entry:['./src/index.js','.src/index.html']
..

source-map

是一种提供源代码到构建后代码映射技术(如果构建后代码出错了,通过映射关系可以追踪到源代码错误的位置)。

修改 webpack.config.js 配置
.....
devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    hot: true
  },
    // 添加devtool 即可
  devtool: 'eval-source-map'
....

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

有以下几种搭配:
source-map:map文件在外部
错误代码准确信息 和 源代码的错误位置
1、inline-source-map:map 文件在构建后js代码里面,属于内联
错误代码准确信息 和 源代码的错误位置
2、hidden-source-map:外部
错误代码错误原因,但是没有错误位置
不能追踪源代码,只能提示到构建后代码的错误位置
3、 eval-source-map:内联
每一个文件都生成对应的source-map,都在eval
错误代码准确信息 和源代码的错误位置
4、nosources-source-map:外部
错误代码准确信息,但是没有任何源代码信息
5、 cheap-source-map:外部
错误代码准确信息 和 源代码的错误位置
只能精确到行
6、cheap-module-source-map:外部
错误代码准确信息 和 源代码的错误位置
module会将loader的source map 加入

内联和外部的区别: 1外部生成了文件,内联没有,2、内联构建速度更快。

开发环境: 要求速度快、调试更加友好
速度快:(eval > inline > cheap....)
推荐使用: eval-source-map、eval-cheap-module-source-map

生产环境: 隐藏源代码,不用调试友好。
推荐使用: source-map、cheap-module-source-map

oneOf

一个 loader 只会匹配一个文件,加快了构建代码的速度,相当于一个小的性能优化。
注意:不能有两个 loader (配置)处理同一种类型文件。

修改 webpack.config.js 文件配置
module: {
        rules: [
            {
                   //js语法检查 eslint-loader eslint
                   /*
                   注意:只检测自己写的源代码,不需要检查第三方库
                   设置检查规则:
                       package.json中eslintConfig中设置
                       "eslintConfig":{
                           "extends": "airbnb-base"
                       }
                       airbnb-> eslint-config-airbnb-base eslint-plugin-import eslint
                   */
                  test: /\.js$/,
                  exclude: /node_modules/,
                  loader: 'eslint-loader',
                  onforce: 'pre' // 优先执行
                  options: {
                      // 自动修复eslint的错误
                      fix: true
                  }
            },
            {
                oneOf: [
                    // 详细loader配置 不同文件必须配置不同的loader处理
                    {
                        test: /\.css$/,//正则表达式匹配哪些文件
                        use: [
                            // use数组中的执行loader顺序:下到上、右到左
                            // 这个 loader取代style-loader 作用:提取css中的css成单独文件
                            MiniCssExtractPlugin.loader,
                            // css-loader作用:将css文件变成commonjs模块加载js中,里面内容时样式字符串
                            'css-loader',
                            {
                                loader: 'postcss-loader',
                                options: {
                                    ident: 'postcss',
                                    plugins: () => [
                                        // postcss 的插件
                                        require('postcss-preset-env')()
                                    ]
                                }
                            }
                        ]
                    },
                    {
                        test: /\.js$/,
                        exclude: /node_modules/,
                        loader: 'babel-loader',
                        options: {
                            // 预设:指示babel做怎么样的兼容性处理
                            presets: [
                                [
                                    '@babel/preset-env',
                                    {
                                        //按需加载
                                        useBuiltIns: 'usage',
                                        // 指定core-js版本
                                        corejs: {
                                            version: 3
                                        },
                                        // 兼容哪些浏览器
                                        targets: {
                                            chrome: '60',
                                            firefox: '60',
                                            ie: '9',
                                            safari: '10',
                                            edge: '17'
                                        }
                                    }
                                ]
                            ]
                        }
                    },
                ]
            }
        ]
    },

缓存

缓存分为两种:babel 缓存文件资源缓存。
JS babel 缓存:
当修改一个文件的时候不希望全部文件重新再次打包,类似于HMR。这个是在 production 环境中进行的,而 HMR 是在development 环境中进行的。
作用:第二次构建打包速度更快。
配置:直接在 js 的 loader 中添加 catchDirectory:true。

修改 webpack.config.js 文件:
....
 // js 兼容性处理 babel-loader @babel/core
                    {
                        /*
                        1、基本js兼容性处理--> @babel/preset-env
                           问题:只能转换基本的语法 不能转换高级语法 比如 primose 
                        2、 全部兼容js处理: 下载@babel/polyfill 只需要在入口文件引入即可
                         问题:全部都兼容 js体积变大
                        3、 按需兼容 -- core-js
                         */
                        test: /\.js$/,
                        exclude: /node_modules/,
                        use: [
                            // 开启多进程
                            // 'thread-loader',
                            {
                                loader: 'babel-loader',
                                options: {
                                    // 预设 通知babel做怎么样的兼容性处理
                                    presets: [
                                        [
                                            '@babel/preset-env',
                                            {
                                                //按需加载
                                                useBuiltIns: 'usage',
                                                // 指定core-js版本
                                                corejs: {
                                                    version: 3
                                                },
                                                // 兼容哪些浏览器
                                                targets: {
                                                    chrome: '60',
                                                    safari: '10',
                                                    ie: '9',
                                                    firefox: '60',
                                                    edge: '17'
                                                }
                                            }
                                        ]
                                    ],
                                    // 开启babel缓存 第二次构建的时候 会读取之前的缓存
                                    cacheDirectory: true
                                }
                            }
                        ]

                    }
....

文件资源缓存:

  1. hash 缓存: 每次 webpack 构建时会生成一个唯一的 hash 值。
    问题:因为 css 和 js 同时使用一个hash值,如果重新打包会导致所有缓存失效(可能我只修改一个文件)。
  2. chunkhash 缓存: 根据 chunk 生成的 hash 值。如果打包来源同一个 chunk ,那么 hash 值就一样。 问题:在以上的代码中,因为 css 是在 js 中被引进来的,所有属于同一个 chunk。
  3. contenthash 缓存: 根据文件内容的不同而生成一个唯一值,相当于一个指纹。

作用: 让线上代码运行缓存,从而提高性能。

修改 webpack.config.js 配置
...
entry: './src/js/index.js',
  output: {
    filename: 'js/built.[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
...
 plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.[contenthash:10].css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
...

tree-shaking

通常用于描述移除 JavaScript 上下文中的未引用代码。去除无用的代码。

使用前提: JS 代码必须使用 ES6 模块化,开启 production 模式。

作用: 减少代码体积。

修改 package.json 配置
"sideEffects":["*.css"] 
/*
"sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
 问题:构建的时候可能会把css/ @babel/polyfill (副作用)文件干掉
 */

code split

code split(代码分割):将代码分成不同的包/块,然后可以按需加载,而不是加载包含所有内容的单个包。

1、单入口和多入口: 单入口默认只有一个(bundle)输出文件。多入口有几个文件就输出几个 bundle。

修改 webpack.config.js 配置
....
  // 单入口
  // entry: './src/js/index.js',
  entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: './src/js/index.js',
    test: './src/js/test.js'
  },
....

2、多入口

修改 webpack.config.js 配置
....
  // 单入口
  // entry: './src/js/index.js',
  entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: './src/js/index.js',
    test: './src/js/test.js'
  },
	/*
    1. 可以将node_modules中代码单独打包一个chunk最终输出
    2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
  */
	optimization:{
        splitChunks: {
            chunks: 'all'
        }
    },
....

3、单入口: 在入口文件中配置,进行代码分割

index.js 入口文件配置
// 代码分割 单入口 
// 通过js代码,让某个文件单独被打包成一个chunk import 动态语法 能将某个文件单独打包 代码分割
// /*webpackChunkName: 'hot'*/ 给分割后的文件命名
import(/* webpackChunkName:'hot'*/'./hot.js')
.then(()=>{
    console.log('js文件分割成功');
})
.catch(()=>{
    console.log('分割失败');
})
修改 webpack.config.js 配置
...
 plugins: [
        //html-webpack-plugin
        //功能:默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
        //需求:需要有结构的html文件
        new HtmlWebpackPlugin({
            //以这个为模板创建html文件,并自动引入输出的所有资源(JS/CSS)
            template: './src/index.html',
            // 压缩html
            minify: {
                // 移除空格
                collapseWhitespace: true,
                // 移除注释
                removeComments: true
            }
        }),
        // 从js中分离css代码
        new MiniCssExtractPlugin({
            // 对输出的css文件进行重命名
            filename: 'css/main.css'
        }),
        // 压缩 css代码
        new OptimizeCssAssetsWebpackPlugin()
    ],
    //模式
    mode: 'production',
    // 从node_modules中打包第三方库
    optimization:{
        splitChunks: {
            chunks: 'all'
        }
    },
...

lazy loading

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或将引用另外一新的代码块。这样加快了应用的初始加载速度,减轻了它的总体积,因为某些代码块可能永远不会被加载。

正常加载: 可以认为是并行加载(同一时间加载多个文件)没有先后顺序,问题:没有用到的 js 也会加载就会浪费性能和构建速度。
JS 文件懒加载: 当需要的时候才加载
JS 文件预加载: 可以理解为等其他资源加载完毕,浏览器在空闲的时间偷偷的加载,问题就是兼容性比较差。

懒加载: 在html中 书写一个按钮 添加事件 当点击按钮的时候再加载对应的 js文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1 id="title">webpack</h1>
    <div id="box1"></div>
    <div id="box2"></div>
    <button id="btn">按钮</button>
</body>
</html>

index.js 文件配置

...
const btn = document.querySelector('#btn')
btn.addEventListener('click', () => {
    import(/* webpackChunkName: 'hot'*/'./hot.js').then(({ add }) => {
        console.log(add(2, 3))
    }).catch(() => {
        console.log('加载失败');
    })
})
...

预加载:

...
const btn = document.querySelector('#btn')
btn.addEventListener('click', () => {
    import(/* webpackChunkName: 'hot',webpackPrefetch: true*/'./hot.js').then(({ add }) => {
        console.log(add(2, 3))
    }).catch(() => {
        console.log('加载失败');
    })
})
...

pwa

渐进式网络开发应用程序(离线可访问)。问题:兼容性比较差。

下载plugin安装包
npm install workbox-webpack-plugin@5 -D

修改 webpack.config.js 配置
....
plugins: [
        //html-webpack-plugin
        //功能:默认会创建一个空的html,自动引入打包输出的所有资源(JS/CSS)
        //需求:需要有结构的html文件
        new HtmlWebpackPlugin({
            //以这个为模板创建html文件,并自动引入输出的所有资源(JS/CSS)
            template: './src/index.html',
            // 压缩html
            minify: {
                // 移除空格
                collapseWhitespace: true,
                // 移除注释
                removeComments: true
            }
        }),
        // 从js中分离css代码
        new MiniCssExtractPlugin({
            // 对输出的css文件进行重命名
            filename: 'css/main.css'
        }),
        // 压缩 css代码
        new OptimizeCssAssetsWebpackPlugin(),
        // 离线可访问
        new WorkboxWebpackPlugin.GenerateSW({
            /*
            1、帮助serviceworker 开启启动
            2、删除旧的 serviceworker

            生成一个serviceworker 配置文件
            */
           clientsClaim: true,
           skipWaiting: true
        })
    ],
....
在入口文件 index.js 进行配置
// 注册serviceworker 处理兼容性问题
/*  报eslint 错误
    "eslintConfig": {
    "extends": "airbnb-base",
    "browser": true  支持浏览器的全局变量
  }
*/ 

if ('serviceWorker' in navigator) {
    window.addEventListener('loade', () => {
        // 打包后自动生成一个service-worker.js文件 在这里注册引入即可
        navigator.serviceWorker.register('/service-worker.js')
        .then(() => {
            console.log('注册成功');
        })
        .catch(() => {
            console.log('注册失败');
        })
    })
}

多进程打包

下载loader安装包
npm install thread-loader@2 -D

webpack.config.js 进行配置
{
  			/*
                        1、基本js兼容性处理--> @babel/preset-env
                           问题:只能转换基本的语法 不能转换高级语法 比如 primose 
                        2、 全部兼容js处理: 下载@babel/polyfill 只需要在入口文件引入即可
                         问题:全部都兼容 js体积变大
                        3、 按需兼容 -- core-js
         */
                        test: /\.js$/,
                        exclude: /node_modules/,
                        use: [
                          // 开启多进程打包
                            'thread-loader',
                            {
                                loader: 'babel-loader',
                                options: {
                                    // 预设:指示babel做怎么样的兼容性处理
                                    presets: [
                                        [
                                            '@babel/preset-env',
                                            {
                                                //按需加载
                                                useBuiltIns: 'usage',
                                                // 指定core-js版本
                                                corejs: {
                                                    version: 3
                                                },
                                                // 兼容哪些浏览器
                                                targets: {
                                                    chrome: '60',
                                                    firefox: '60',
                                                    ie: '9',
                                                    safari: '10',
                                                    edge: '17'
                                                }
                                            }
                                        ]
                                    ]
                                }
                            }
                        ],
                    },

注意:
开启多进程打包,进程启动大概为600ms,进程通信也有开销,只有工作消耗时间比较长,才需要多进程打包。

externals

externals(外部扩展),防止将某些 import 的包(package)打包到bundle中,而是在运行时(runtime)再去从外部获取这些扩展依赖。通常通过CDN在构建后的html中进行引入。

在 webpack.config.js 配置
....
 externals: {
    // 拒绝jQuery被打包进来
    jquery: 'jQuery'
  }
};
....

dll

DllPlugin 和 DllReferencePlugin 用某种方法实现了拆分 bundles,对第三方库进行打包,同时还大幅度提升了构建的速度。

创建一个 webpack.dll.js 文件
const { resolve } = require("path");
const webpack = require('webpack')
// 使用dll技术 对第三方库进行打包 需要通过webpack --config webpack.dll.js运行这个文件
module.exports = {
    entry: {
        jquery: ['jquery']
    },
    output: {
        filenam: '[name].js',
        path: resolve(__dirname,'dll'),
        library:'[name]_[hash]'//打包的第三方库向外暴露的名字
    },
    plugins:[
        // 打包生成一个manifest.json文件 提供和jquery映射 
        // 利用manifest.json文件通知webpack不需要打包jquery了
        new webpack.DllPlugin({
            name: '[name].[hash]',// 映射暴露库的名称
            path: resolve(__dirname,'dll/manifest.json')
        })
    ],
    mode: 'production'
}

使用指令 webpack--config webpack.dll.js
下载 plugin 插件
npm i add-asset-html-webpack-plugin@3 -D

在 webpack.config.js 中再进行相关的配置
....
const AddAssetHtmlWebpackPlugin =  require('add-asset-html-webpack-plugin')
...
plugins: [
  ...
		new webpack.DllReferencePlugin({
            manifest:resolve(__dirname,'dll/manifest.json')
        }),
        // 将某个文件打包输出去,并在html中自动引入打包出去的资源
        new AddAssetHtmlWebpackPlugin({
            filepath: resolve(__dirname,'dll/jquery.js')
        })
	...
]
....

运行指令 webpack

可以看到在构建后的build文件夹中有 jquery.js 文件,同时 html 文件中自动引入了 jquery 文件。

开发环境性能优化

优化打包构建速度: HMR

优化代码调试: source-map

生产环境性能优化

优化打包构建速度: oneOf、bebel缓存、多进程打包、externals、dll

优化代码运行的性能: 缓存(hash-chunkhash-contenthash)、tree-shaking、code split、懒加载/预加载、pwa

5、webpack配置详解

entry配置

entry 入口起点有三种:string、array、object
1、string : './src/index.js'
单入口:打包形成一个chunk,输出一个bundle文件,此时chunk的名称默认是 main。
2、array: ['./src/index.js','./src/add.js']
多入口:所有入口文件 最终只会形成一个chunk,输出出去只有一个bundle文件。只有在 HMR 功能中让 html 热更新生效。
3、object
多入口:有几个入口文件就形成几个chunk,输出几个bundle文件,此时chunk的名称是key
特殊用法:

entry: {
	index:['./src/index.js','./src/count.js']
  add:'./src/add.js'
}

output配置

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    // 文件名称(指定名称+目录)
    filename: 'js/[name].js',
    // 输出文件目录(将来所有资源输出的公共目录)
    path: resolve(__dirname, 'build'),
    // 所有资源引入公共路径前缀 --> 'imgs/a.jpg' --> '/imgs/a.jpg'
    publicPath: '/', 
    chunkFilename: 'js/[name]_chunk.js', // 非入口chunk的名称 给代码分割重命名
    // library: '[name]', // 整个库向外暴露的变量名
    // libraryTarget: 'window' // 变量名添加到哪个上 browser
    // libraryTarget: 'global' // 变量名添加到哪个上 node
    // libraryTarget: 'commonjs'
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development'
};

module配置

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
      {
        test: /\.css$/,
        // 多个loader用use
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.js$/,
        // 排除node_modules下的js文件
        exclude: /node_modules/,
        // 只检查 src 下的js文件 小的性能优化
        include: resolve(__dirname, 'src'),
        // 优先执行
        enforce: 'pre',
        // 延后执行
        // enforce: 'post',
        // 单个loader用loader
        loader: 'eslint-loader',
        options: {}
      },
      {
        // 以下配置只会生效一个
        oneOf: []
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development'
};

resolve配置

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development',
  // 解析模块的规则
  resolve: {
    // 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
    alias: {
      $css: resolve(__dirname, 'src/css')
    },
    // 配置省略文件路径的后缀名
    extensions: ['.js', '.json', '.jsx', '.css'],
    // 告诉 webpack 解析模块是去找哪个目录
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  }
};

devServer配置

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/[name].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development',
  resolve: {
    alias: {
      $css: resolve(__dirname, 'src/css')
    },
    extensions: ['.js', '.json', '.jsx', '.css'],
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  },
  devServer: {
    // 运行代码的目录
    contentBase: resolve(__dirname, 'build'),
    // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
    watchContentBase: true,
    watchOptions: {
      // 忽略文件 性能优化
      ignored: /node_modules/
    },
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 5000,
    // 域名
    host: 'localhost',
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    hot: true,
    // 不要显示启动服务器日志信息
    clientLogLevel: 'none',
    // 除了一些基本启动信息以外,其他内容都不要显示
    quiet: true,
    // 如果出错了,不要全屏提示~
    overlay: false,
    // 服务器代理 --> 解决开发环境跨域问题
    proxy: {
      // 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
      '/api': {
        target: 'http://localhost:3000',
        // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
};

optimization配置

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin')

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build'),
    chunkFilename: 'js/[name].[contenthash:10]_chunk.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'production',
  resolve: {
    alias: {
      $css: resolve(__dirname, 'src/css')
    },
    extensions: ['.js', '.json', '.jsx', '.css'],
    modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
  },
  optimization: {
    splitChunks: {
      chunks: 'all'
      // 默认值,可以不写~
      /* minSize: 30 * 1024, // 分割的chunk最小为30kb
      maxSiza: 0, // 最大没有限制
      minChunks: 1, // 要提取的chunk最少被引用1次
      maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
      maxInitialRequests: 3, // 入口js文件最大并行请求数量
      automaticNameDelimiter: '~', // 名称连接符
      name: true, // 可以使用命名规则
      cacheGroups: {
        // 分割chunk的组
        // node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js
        // 满足上面的公共规则,如:大小超过30kb,至少被引用一次。
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          // 优先级
          priority: -10
        },
        default: {
          // 要提取的chunk最少被引用2次
          minChunks: 2,
          // 优先级
          priority: -20,
          // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
          reuseExistingChunk: true
        } 
      }*/
    },
    // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
    // 解决:修改a文件导致b文件的contenthash变化 导致缓存失效
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}`
    },
    minimizer: [
      // 配置生产环境的压缩方案:js和css
      new TerserWebpackPlugin({
        // 开启缓存
        cache: true,
        // 开启多进程打包
        parallel: true,
        // 启动source-map
        sourceMap: true
      })
    ]
  }
};

资料参考:尚硅谷

详细的视频教程可以去尚硅谷搜索webpack基础