webpack学习笔记

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

1. 五个核心概念

1.1. Entry

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

1.2. Output

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

1.3. Loader

让webpack能够去处理哪些非JavaScript文件,webpack自身只理解JavaScript

1.4. Plugins

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

1.5. Mode

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

选项 描述 特点
development 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin 能让代码本地调试运行的环境
production 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 production
启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin
能让代码优化上线 运行的环境

2. webpack 的初体验

2.1. 初始化配置

https://www.webpackjs.com/guides/installation/

  1. 新建文件夹 【webpack-demo】

    mkdir webpack-demo && cd webpack-demo

  2. 初始化 package.json ,输入指令:

    npm init -y

  3. 下载并安装 webpack,输入指令:

    npm install webpack webpack-cli --save-dev

    webpack-cli(此工具用于在命令行中运行 webpack)

    在安装一个要打包到生产环境的安装包时,使用 npm install --save

    如果是安装一个用于开发环境的安装包时,使用 npm install --save-dev

    请在 npm 文档 中查找更多信息。

  4. 建立目录结构

    1
    2
    3
    4
    5
      webpack-demo
    |- package.json
    + |- index.html
    + |- /src
    + |- index.js
  5. index.js

    1
    2
    3
    4
    const div = document.createElement("div");
    div.innerHTML = 'Hello Webpack';

    document.body.appendChild(div);
  6. index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!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>webpack-demo</title>
    </head>
    <body>
    <script src="./src/index.js"></script>
    </body>
    </html>
  7. package.json

    确保安装包是私有的(private),并且移除 main 入口。这可以防止意外发布代码

    1
    2
    +   "private": true,
    - "main": "index.js",
  8. 执行打包命令 npx webpack,会将脚本作为入口起点,然后 输出main.js

    1
    2
    3
    $ npx webpack
    asset main.js 105 bytes [emitted] [minimized] (name: main)
    ./src/index.js 108 bytes [built] [code generated]
  9. 修改index.html的script脚本

    1
    <script src="./dist/main.js"></script>
  10. 在浏览器中打开 index.html,能看到以下文本:Hello Webpack

2.2. 编译打包应用

  1. 运行指令

    开发环境指令:npx webpack --mode=development

    功能:webpack 能够编译打包 js 和 json 文件,并且能将 es6 的模块化语法转换成浏览器能识别的语法

​ 生产环境指令:npx webpack --mode=production

​ 功能:在开发配置功能上多一个功能,压缩代码

  1. 结论

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

    能将 es6 的模块化语法转换成浏览器能识别的语法

    能压缩代码

  2. 问题

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

    不能将 js 的 es6 基本语法转化为 es5 以下语法

3. webpack开发环境的基本配置

在 webpack 4 中,可以无须任何配置使用,然而大多数项目会需要很复杂的设置,这就是为什么 webpack 仍然要支持 配置文件

3.1. 创建配置文件

  1. 创建文件 webpack.config.js

    这是webpack的配置文件,指示webpack干哪些活

    所有构建工具都是基于nodejs平台运行的,模块化默认采用commonjs

  2. 配置内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // node 内置核心模块,用来处理路径问题
    const { resolve } = require('path');

    module.exports = {
    //入口文件
    entry: './src/js/index.js',
    //输出配置
    output: {
    // 输出文件名
    filename: 'index.js',
    // 输出文件路径配置
    path: resolve(_dirname,'build/js')
    },
    //开发环境
    mode: 'development'
    };
  3. 运行指令:

    1
    npx webpack --config webpack.config.js

    如果 webpack.config.js 存在,则 webpack命令将默认选择使用它。

    这里使用 --config 选项只是表明可以传递任何名称的配置文件

    这对于需要拆分成多个文件的复杂配置是非常有用

  4. 目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    webpack-demo
    |- package.json
    |- webpack.config.js
    |- /build
    |- index.html
    |- js
    |- index.js
    |- /src
    |- js
    |- index.js
  5. npm 脚本(npm script)

在 package.json 中添加 "build": "webpack"

1
2
3
4
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
}

现在,可以使用 npm run build 命令,来替代之前使用的 npx 命令

注意,使用 npm 的 scripts,我们可以像使用 npx 那样通过模块名引用本地安装的 npm 包

3.2. 打包样式资源

3.2.1. css文件

  1. 创建文件 index.css,放入 /src/css 文件夹下

    1
    2
    3
    4
    5
    div{
    width: 200px;
    height: 200px;
    background-color: pink;
    }
  2. 在index.js中引入样式资源

    1
    import '../css/style.css';
  3. 下载安装loader包

    1
    npm install --save-dev style-loader css-loader
  4. 修改配置文件

    webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader

    在这种情况下,以 .css 结尾的全部文件,都将被提供给 style-loadercss-loader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // resolve 用来拼接绝对路径的方法 
    const { resolve } = require("path");

    module.exports = {
    // webpack 配置
    // 入口起点
    entry: './src/js/index.js',
    // 输出
    output: {
    // 输出文件名
    filename: 'index.js',
    // 输出路径
    // __dirname nodejs 的变量,代表当前文件的目录绝对路径
    path: resolve(__dirname,'build/js')
    },
    // loader 的配置
    module: {
    // 详细 loader 配置
    // 不同文件必须配置不同 loader 处理
    rules: [
    {
    // 匹配哪些文件
    test: /\.css$/,
    // 使用哪些 loader 进行处理
    // use 数组中 loader 执行顺序:从右到左,从下到上 依次执行
    use: [
    // 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效
    'style-loader',
    // 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
    'css-loader'
    ]
    }
    ]
    },
    // 模式
    // 开发模式
    mode: 'development'
    }
  5. 运行指令: npm run build

  6. 再次在浏览器中打开 index.html,你应该看到 Hello Webpack 现在所在的div背景颜色是粉红色

    并查看页面的 head 标签,在 index.js 中导入了 style 块元素

3.2.2. less文件

  1. 新建 index.less,放入 /src/css 文件夹下

    1
    2
    3
    body{
    background-color: blue;
    }
  2. 下载安装包

    1
    npm i less-loader less --save-dev
  3. 修改配置文件

    添加解析less的loader配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // resolve 用来拼接绝对路径的方法 
    const { resolve } = require("path");

    module.exports = {
    // webpack 配置
    // 入口起点
    entry: './src/js/index.js',
    // 输出
    output: {
    // 输出文件名
    filename: 'index.js',
    // 输出路径
    // __dirname nodejs 的变量,代表当前文件的目录绝对路径
    path: resolve(__dirname,'build/js')
    },
    // loader 的配置
    module: {
    // 详细 loader 配置
    // 不同文件必须配置不同 loader 处理
    rules: [
    {
    // 匹配哪些文件
    test: /\.css$/,
    // 使用哪些 loader 进行处理
    // use 数组中 loader 执行顺序:从右到左,从下到上 依次执行
    use: [
    // 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效
    'style-loader',
    // 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
    'css-loader'
    ]
    },
    {
    test: /\.less$/,
    use: [
    'style-loader',
    'css-loader',
    // 将 less 文件编译成 css 文件
    // 需要下载 less-loader 和 less
    'less-loader'
    ]
    }
    ]
    },
    // 模式
    // 开发模式
    mode: 'development'
    }
  4. 运行命令

    1
    npm run build

3.3. 打包 HTML 资源

https://github.com/jaketrent/html-webpack-template

  1. 创建index.html,放于 src 文件夹下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!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>webpack-demo</title>
    </head>
    <body>
    <div class="hello">
    <h1>Hello Webpack</h1>
    </div>
    </body>
    </html>
  2. 下载安装 plugin 包

    1
    npm i --save-dev html-webpack-plugin
  3. 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const { resolve } = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');

    module.exports = {
    entry: './src/index.js',
    output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
    },
    module: {
    rules: [
    // loader 的配置
    ]
    },
    plugins: [
    // plugins 的配置
    // html-webpack-plugin
    // 功能:默认会创建一个空的 HTML,自动引入打包输出的所有资源(JS/CSS)
    // 需求:需要有结构的 HTML 文件
    new HtmlWebpackPlugin({
    // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
    template: './src/index.html'
    })
    ],
    mode: 'development'
    };
  4. 运行指令

    1
    npm run build

3.4. 打包图片资源

  1. 创建文件夹 imgs,放于 src 中,并放入两张照片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    webpack-demo
    |- package.json
    |- webpack.config.js
    |- /src
    |- css
    |- style.css
    |- index.less
    |- imgs
    |- 1.png
    |- 2.png
    |- js
    |- index.js
    |- index.html
  2. index.html 中添加内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!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>
    <div class="hello">
    <h1>Hello Webpack</h1>
    <img src="./imgs/1.png" alt="">
    </div>
    <div id="box1"></div>
    <div id="box2"></div>
    </body>
    </html>
  3. index.less 中添加内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #box1{
    width: 100px;
    height: 100px;
    background: url('../imgs/1.png') no-repeat center;
    }

    #box2{
    width: 100px;
    height: 100px;
    background: url('../imgs/2.png') no-repeat center;
    }
  4. index.js中引入 index.less

    1
    import './index.less';
  5. index.css 中添加内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .hello{
    width: 400px;
    height: 400px;
    margin: 0 auto;
    text-align: center;
    }
    .hello img{
    width: 100%;
    }
  6. index.js 中引入 index.css

    1
    2
    import '../css/style.css';
    import '../css/index.less';
  7. 下载安装 loader 包

    1
    npm install --save-dev html-loader url-loader file-loader
  8. 修改配置文件

    需要在options里添加一个属性 esModule:false 和在rules对象中添加 type:'javascript/auto',解决在打包css文件的背景图路径错误以及多生成了图片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    const { resolve } = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');

    module.exports = {
    entry: './src/index.js',
    output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
    },
    module: {
    rules: [
    {
    test: /\.less$/,
    // 要使用多个 loader 处理用 use
    use: ['style-loader', 'css-loader', 'less-loader']
    },
    {
    // 问题:默认处理不了 html 中 img 图片
    // 图片
    test: /\.(png|gif|svg|jpg)$/,
    loader: 'url-loader',
    options: {
    // 图片大小小于8kb,就会被base64处理
    // 优点: 减少请求数量(减轻服务器压力)
    // 缺点:图片体积会更大(文件请求速度更慢)
    limit: 8 * 1024,
    // 问题:因为url-loader默认使用es6模块化解析,
    // 而html-loader引入图片是commonjs
    // 解析时会出问题:[object Module]
    // 解决:关闭 url-loader 的 es6 模块化,使用 commonjs 解析
    esModule: false,
    // 给图片进行重命名
    // [hash:10]取图片的 hash 的前 10 位
    // [ext]取文件原来扩展名
    name: 'imgs/[name].[hash:10].[ext]'
    // ,outputPath: 'imgs'
    },
    //这个属性如果没有设置,则会生成两张图片(如果你的页面只引入了一张图片)
    type: 'javascript/auto'
    },
    {
    test: /\.html$/,
    // 处理 html 文件的 img 图片
    // (负责引入 img,从而能被 url-loader 进行处理)
    loader: 'html-loader'
    }
    ]
    },
    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html'
    })
    ],
    mode: 'development'
    };
  9. 运行指令:

    1
    npm run build
  10. 得到的build文件夹目录结构

1
2
3
4
5
6
7
8
webpack-demo
|- /build
|- imgs
|- 1.db787884f1.png
|- 2.0b4e4b4e64.png
|- js
|- index.js
|- index.html
  1. build/index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!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>webpack-demo</title>
    <script defer src="js/index.js"></script><link href="css/main.css" rel="stylesheet"></head>
    <body>
    <div class="hello">
    <h1>Hello Webpack</h1>
    <img src="imgs/1.db787884f1.png" alt="">
    </div>
    <div id="box1"></div>
    <div id="box2"></div>
    </body>
    </html>

3.5. 打包字体资源

  1. 下载字体图标库 fontawesome,放入 src/libs 中

  2. 在 src/js/index.js中添加 fontawesome.min.css

    1
    import '../libs/font-awesome-4.7.0/css/font-awesome.min.css';
  3. 在 src/index.html中添加一个图标

    1
    2
    3
    4
    5
    <div class="hello">
    <i class="fa fa-bath"></i>
    <h1>Hello Webpack</h1>
    <img src="./imgs/1.png" alt="">
    </div>
  4. 修改配置文件

    loader中添加针对字体的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    // 处理字体
    test: /\.(eot|svg|ttf|woff|woff2)\w*/,
    loader: 'file-loader',
    options: {
    name: 'font/[name].[ext]',
    esModule: false
    },
    type: 'javascript/auto'
    }
  5. 整体配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    // resolve 用来拼接绝对路径的方法 
    const { resolve } = require("path");
    const htmlWebpackPlugin = require("html-webpack-plugin");

    module.exports = {
    // webpack 配置
    // 入口起点
    entry: './src/js/index.js',
    // 输出
    output: {
    // 输出文件名
    filename: 'js/index.js',
    // 输出路径
    // __dirname nodejs 的变量,代表当前文件的目录绝对路径
    path: resolve(__dirname,'build')
    },
    // loader 的配置
    module: {
    // 详细 loader 配置
    // 不同文件必须配置不同 loader 处理
    rules: [
    {
    // 匹配css文件
    test: /\.css$/,
    // 使用哪些 loader 进行处理
    // use 数组中 loader 执行顺序:从右到左,从下到上 依次执行
    use: [
    // 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效
    'style-loader',
    // 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
    'css-loader'
    ]
    },
    {
    // 匹配less文件
    test: /\.less$/,
    use: [
    'style-loader',
    'css-loader',
    // 将 less 文件编译成 css 文件
    // 需要下载 less-loader 和 less
    'less-loader'
    ]
    },
    {
    // 问题:默认处理不了 html 中 img 图片
    // 图片
    test: /\.(png|gif|svg|jpg|jpeg)$/,
    loader: 'url-loader',
    options: {
    // 图片大小小于8kb,就会被base64处理
    // 优点: 减少请求数量(减轻服务器压力)
    // 缺点:图片体积会更大(文件请求速度更慢)
    limit: 8 * 1024,
    // 问题:因为url-loader默认使用es6模块化解析,
    // 而html-loader引入图片是commonjs
    // 解析时会出问题:[object Module]
    // 解决:关闭 url-loader 的 es6 模块化,使用 commonjs 解析
    esModule: false,
    // 给图片进行重命名
    // [hash:10]取图片的 hash 的前 10 位
    // [ext]取文件原来扩展名
    name: 'imgs/[name].[hash:10].[ext]'
    // ,outputPath: 'imgs'
    },
    //这个属性如果没有设置,则会生成两张图片(如果你的页面只引入了一张图片)
    type: 'javascript/auto'
    },
    {
    // 处理 html 文件的 图片
    test: /\.html$/,
    // (负责引入 img,从而能被 url-loader 进行处理)
    loader: 'html-loader'
    },
    {
    // 处理字体
    test: /\.(eot|svg|ttf|woff|woff2)\w*/,
    loader: 'file-loader',
    options: {
    name: 'font/[name].[ext]',
    esModule: false
    },
    type: 'javascript/auto'
    }
    ]
    },
    plugins: [
    new htmlWebpackPlugin({
    template: './src/index.html'
    })
    ],
    // 模式
    // 开发模式
    mode: 'development'
    }

3.6. 打包其他资源

1
2
3
4
5
6
7
8
9
10
11
{
// 处理其他资源
// 排除所写资源
exclude: /\.(css|html|less|png|gif|jpg|jpeg|js)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]',
esModule: false
},
type: 'javascript/auto'
}

3.7. 清除文件

由于过去的代码示例遗留下来,webpack 会生成文件,然后将这些文件放置在 /build文件夹中,但是 webpack 无法追踪到哪些文件是实际在项目中用到的。通常,在每次构建前清理文件夹,是比较推荐的做法,因此只会生成用到的文件。

  1. 安装插件

    1
    npm install clean-webpack-plugin --save-dev
  2. 修改配置

    1
    2
    3
    4
    5
    const { CleanWebpackPlugin } = require("clean-webpack-plugin");

    plugins: [
    new CleanWebpackPlugin()
    ]

3.8. devserver

https://www.webpackjs.com/configuration/dev-server/

开发服务器devserver,用来自动化,自动编译,自动打开浏览器,自动刷新浏览器

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

  1. 下载安装包

    1
    npm i webpack-dev-server --save-dev
  2. 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    const { resolve } = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
    entry: './src/index.js',
    output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
    },
    module: {
    rules: [
    {
    test: /\.css$/,
    use: ['style-loader', 'css-loader']
    },
    // 打包其他资源(除了html/js/css资源以外的资源)
    {
    // 排除 css/js/html 资源
    exclude: /\.(css|js|html|less)$/,
    loader: 'file-loader',
    options: {
    name: '[hash:10].[ext]'
    }
    }
    ]
    },
    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html'
    })
    ],
    mode: 'development',
    devServer:{
    // 查找文件
    static: resolve(__dirname, 'build'),
    // 启动 gzip 压缩
    compress: true,
    // 端口号
    port: 3000,
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    // 当修改了 webpack 配置,新配置要想生效,必须重新 webpack 服务
    hot: true

    }
    };
  3. 运行指令: npx webpack-dev-server

  4. 添加一个 script 脚本,可以直接运行开发服务器(dev server),在package.json 中

    1
    2
    3
    4
    5
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server",
    "build": "webpack"
    }

    现在,可以在命令行中运行 npm start,就会看到浏览器自动加载页面

3.9. 开发环境配置

  1. 创建文件

    src

    — css

    — imgs

    — js

    — media

    — index.html

    webpack.config.js

  2. 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    // 绝对路径拼接
    const {resolve} = require("path");
    // css单独文件
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    // 打包html资源
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    // 清除文件
    const {CleanWebpackPlugin} = require("clean-webpack-plugin");

    module.exports = {
    // 入口文件
    entry: './src/js/index.js',
    // 输出配置
    output: {
    // 输出文件名
    filename: 'js/index.js',
    // 输出路径
    // __dirname nodejs 的变量,代表当前文件的目录绝对路径
    path: resolve(__dirname,'build')
    },
    // loader 的配置
    module: {
    // 详细 loader 配置
    // 不同文件必须配置不同 loader 处理
    rules: [
    {
    // css文件
    test: /\.css$/,
    // 使用哪些 loader 进行处理
    // use 数组中 loader 执行顺序:从右到左,从下到上 依次执行
    use: [
    // 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效
    // 'style-loader',
    // 这个 loader 取代 style-loader。
    // 作用:提取 js 中的 css 成单独文件
    MiniCssExtractPlugin.loader,
    // 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
    'css-loader'
    ]
    },
    {
    // less文件
    test: /\.less/,
    use: [
    // 'style-loader',
    // 这个 loader 取代 style-loader。
    // 作用:提取 js 中的 css 成单独文件
    MiniCssExtractPlugin.loader,
    'css-loader',
    // 将 less 文件编译成 css 文件
    // 需要下载 less-loader 和 less
    'less-loader'
    ]
    },
    {
    // 图片
    // 问题:默认处理不了 html 中 img 图片
    test: /\.(png|gif|jpg|jpeg)$/,
    loader: 'url-loader',
    options: {
    // 给图片进行重命名
    // [hash:10]取图片的 hash 的前 6 位
    // [ext]取文件原来扩展名
    name: 'imgs/[name].[hash:6].[ext]',
    // 问题:因为url-loader默认使用es6模块化解析,
    // 而html-loader引入图片是commonjs
    // 解析时会出问题:[object Module]
    // 解决:关闭 url-loader 的 es6 模块化,使用 commonjs 解析
    esModule: false,
    // 图片大小小于8kb,就会被base64处理
    // 优点: 减少请求数量(减轻服务器压力)
    // 缺点:图片体积会更大(文件请求速度更慢)
    limit: 8*1024
    },
    //这个属性如果没有设置,则会生成两张图片(如果你的页面只引入了一张图片)
    type: 'javascript/auto'
    },
    {
    // 处理 html 文件的 图片
    test: /\.html$/,
    // (负责引入 img,从而能被 url-loader 进行处理)
    loader: 'html-loader'
    },
    {
    // 字体
    test: /\.(eot|svg|ttf|woff|woff2|otf)/,
    loader: 'file-loader',
    options: {
    name: 'font/[name].[ext]',
    esModule: false
    },
    type: 'javascript/auto'
    },
    {
    // 其他资源
    exclude: /\.(css|html|js|less|png|gif|jpg|jpeg|eot|svg|ttf|woff|woff2|otf)$/,
    loader: 'file-loader',
    options: {
    name: 'libs/[name].[ext]',
    esModule: false
    },
    type: 'javascript/auto'
    }
    ]
    },
    plugins:[
    new HtmlWebpackPlugin({
    template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
    filename: 'css/main.css'
    }),
    new CleanWebpackPlugin()
    ],
    // 模式
    // 开发模式
    mode: 'development',

    devServer: {
    // 项目构建后路径
    static: resolve(__dirname, 'build'),
    // 启动 gzip 压缩
    compress: true,
    // 端口号
    port: 3000,
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    // 当修改了 webpack 配置,新配置要想生效,必须重新 webpack 服务
    hot: true
    }

    }
  3. 运行指令: npm start

4. webpack 生产环境的基本配置

4.1. 提取 css 成单独文件

  1. 下载插件

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

  2. 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    const { resolve } = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');

    module.exports = {
    entry: './src/js/index.js',
    output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build') },
    module: {
    rules: [
    {
    test: /\.css$/,
    use: [
    // 创建 style 标签,将样式放入
    // 'style-loader',
    // 这个 loader 取代 style-loader。
    // 作用:提取 js 中的 css 成单独文件
    MiniCssExtractPlugin.loader,
    // 将css文件整合到js文件中
    'css-loader'
    ]
    }
    ]
    },
    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
    // 对输出的css文件进行重命名
    filename: 'css/built.css'
    })
    ],
    mode: 'development'
    };
  3. 运行指令:

    1
    npm run build

4.2. css 兼容性处理

https://webpack.docschina.org/loaders/postcss-loader/

  1. 在 src/css/style.css 中添加一些css3样式

    1
    2
    3
    4
    5
    6
    7
    8
    .hello{
    width: 400px;
    height: 400px;
    margin: 0 auto;
    text-align: center;
    display: flex;
    backface-visibility: hidden;
    }
  2. 下载 loader

    1
    npm install --save-dev postcss-loader postcss-preset-env
  3. 修改配置文件

    node默认是生产环境,需要 process.env.NODE_ENV 修改环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 设置 nodejs 环境变量
    process.env.NODE_ENV = 'development';

    module: {
    rules: [
    {
    test: /\.css$/,
    use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    /*
    css兼容性处理:postcss -> postcss-loader postcss-preset-env
    帮postcss找到pakcage.json中browerslist里面的配置
    通过配置加载指定的兼容性
    https://github.com/browserslist/browserslist
    */
    // 使用loader的默认配置
    // postcss-loader
    // 修改loader配置
    {
    loader: 'postcss-loader',
    options: {
    postcssOptions:{
    plugins:[
    'postcss-preset-env'
    ]
    }
    }
    }
    ]
    }
    ]
    }
  4. 修改 package.json

    https://github.com/browserslist/browserslist

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

    1
    npm run build
  6. 查看 build/css/main.css

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .hello{
    width: 400px;
    height: 400px;
    margin: 0 auto;
    text-align: center;
    display: flex;
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
    }

4.3. 压缩 css

https://webpack.docschina.org/plugins/css-minimizer-webpack-plugin/

  1. 下载安装包

    1
    npm install css-minimizer-webpack-plugin --save-dev
  2. 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 压缩css
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

    module.exports = {
    optimization: {
    minimizer: [
    new CssMinimizerPlugin()
    ],
    // 开发环境启用css优化
    minimize: true
    }
    }

4.4. js 语法检查

https://webpack.docschina.org/plugins/eslint-webpack-plugin/#root

https://www.npmjs.com/package/eslint-config-airbnb-base

  1. 下载安装包

    1
    npm install --save-dev eslint-webpack-plugin eslint eslint-config-airbnb-base eslint-plugin-import
  2. 在 src/js/index.js 中添加函数

    1
    2
    3
    4
    const sum = (a,b)=>{
    return a+b;
    }
    console.log(sum(1,2));
  3. 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const { resolve } = require('path');
    // js语法检查
    const EslintPlugin = require("eslint-webpack-plugin");

    module.exports = {

    plugins:[
    // js检查
    new EslintPlugin({
    // 指定文件根目录,类型为字符串
    context: resolve(__dirname,"src"),
    // 指定需要检查的扩展名
    extensions: "js",
    // ESLint 自动修复特性
    fix: true
    })

    ]
    };
  4. 配置 package.json

    https://github.com/airbnb/javascript

    1
    2
    3
    4
    5
    6
    7
    8
    9
    "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
    "browser": true,
    "es6": true,
    "node": true,
    "jquery": true
    }
    }
  5. 运行指令:

    1
    npm run build
  6. 终端出现 warning Unexpected console statement no-console

    修改 index.js,添加注释

    在【生产环境】中应该避免使用console

    1
    2
    3
    4
    5
    6
    const sum = (a,b)=>{
    return a+b;
    }
    // 下一行不进行eslint检查
    // eslint-disable-next-line
    console.log(sum(1,2));
  7. 再次打开 src/js/index.js

    1
    2
    const sum = (a, b) => a + b;
    console.log(sum(1, 2));

4.5. js 兼容性处理

webpack- babel-loader

babel文档

babel官网

babel进阶

corejs

  1. 修改 src/js/index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const sum = (a, b) => a + b;
    // 下一行不进行eslint检查
    // eslint-disable-next-line
    console.log(sum(1, 2));

    const p = new Promise((resolve) => {
    resolve('data');
    });

    p.then((data) => {
    // eslint-disable-next-line
    console.log(data);
    });
  2. 下载安装包

    1
    npm i --save-dev babel-loader @babel/core @babel/preset-env
    • 基本js兼容性处理 –> @babel/preset-env

      只能转换基本语法,promise就不能转换

    • 全部js兼容性处理 –> @babel/polyfill

      1
      npm i @babel/polyfill -S

      src/js/index.js 中引入该插件

      1
      import '@babel/polyfill';

      将所有兼容性代码全部引入,体积太大

    • 按需加载 –> corejs

      1
      2
      npm i core-js -S
      npm i regenerator-runtime -S
  3. 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    module.exports = {

    module: {
    rules: [
    {
    // js文件-兼容不同浏览器
    test: /\.js$/,
    // 排除模块文件夹
    exclude: /node-modules/,
    loader: 'babel-loader',
    // 预设:指示 babel 做怎么样的兼容性处理
    options: {
    presets: [
    [
    '@babel/preset-env',
    {
    // 会根据 target 属性配置的目标环境,找出需要的ES6API进行导入
    // 引入目标环境不支持的所有 API
    useBuiltIns: 'entry',
    // 指定 core-js 版本
    corejs: {
    version: 3
    },
    // 指定兼容性做到哪个版本浏览器
    targets: {
    chrome: '60',
    firefox: '60',
    ie: '6',
    safari: '10',
    edge: '17'
    }
    }
    ]
    ]
    }
    }

    ]
    }
    };
  4. 运行指令:

    1
    npm run build
  5. 打开 build/js/index.js,发现代码已经改变

    1
    var sum = function sum(a, b) {\n  return a + b;\n};

4.6. js 压缩

https://webpack.docschina.org/plugins/terser-webpack-plugin#uglify-js

该插件使用 terser 来压缩 JavaScript

  1. 下载安装

    1
    npm install terser-webpack-plugin --save-dev
  2. 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 压缩js
    const TerserPlugin = require("terser-webpack-plugin");

    module.exports = {
    optimization: {
    minimizer: [
    // 压缩js
    new TerserPlugin({
    // 用来匹配需要压缩的文件
    test: /\.js$/,
    // 匹配不需要压缩的文件
    exclude: /node_modules/,
    // 是否将注释剥离到单独的文件中
    extractComments: false,
    // 删除注释
    terserOptions: {
    format: {
    comments: false,
    }
    }
    })
    ]
    };

4.7. HTML 压缩

  1. 方式一:转换为生产模式

    https://webpack.docschina.org/guides/production/#minification

    mode改为生产模式production,生产环境下默认使用 TerserPlugin

  2. 方式二:修改参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    module.exports = {
    plugins:[
    // 打包html
    new HtmlWebpackPlugin({
    template: './src/index.html',
    minify: {
    // 移除空格
    collapseWhitespace: true,
    // 移除注释
    removeComments: true
    }
    })
    ],
    // 模式
    // 开发模式
    mode: 'development'

    };

4.8. 生产环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// 拼接文件绝对路径
const { resolve } = require("path");
// html打包
const HtmlWebpackPlugin = require("html-webpack-plugin");
// css单独成文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 压缩css
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 压缩js
const TerserPlugin = require("terser-webpack-plugin");
// 清除build后生成build
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
// js语法检查
const EslintPlugin = require("eslint-webpack-plugin");


// css/less通用
const commonCssLoader=[
// 单独成文件
MiniCssExtractPlugin.loader,
// css文件变成commonjs模块加载到js中,内容为样式字符串
'css-loader',
{
// css兼容
// 帮postcss找到browerslist里面的配置
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['postcss-preset-env']
}
}
}
];

module.exports = {
// 入口文件
entry: './src/js/index.js',
// 输出
output: {
// 文件名
filename: 'js/build.js',
// 文件根路径
path: resolve(__dirname,"build")
},
// loader配置
module:{
rules:[
{
// css
// 检查css文件
test: /\.css$/,
// 使用插件
use: [...commonCssLoader]
},
{
// less
test: /\.less$/,
use: [...commonCssLoader,'less-loader']
},
{
// 图片
test: /\.(png|gif|jpg|jpeg)$/,
loader: 'url-loader',
options: {
// 图片重命名
name: 'imgs/[name].[hash:6].[ext]',
// url-loader使用es6模块化解析
// html-loader使用commonjs
// 关闭url-loader的es6模块化,使用commonjs解析
esModule: false,
// 图片大小小于8kb,使用base64处理
limit: 8*1024
},
// 设置只生成一张图片
type: 'javascript/auto'
},
{
// html的img路径
test: /\.html$/,
loader: 'html-loader'
},
{
// 字体
test: /\.(eot|svg|ttf|woff|woff2|otf)$/,
loader: 'file-loader',
options: {
// 重命名
name: 'fonts/[name].[ext]',
// url-loader使用es6模块化解析
// html-loader使用commonjs
// 关闭url-loader的es6模块化,使用commonjs解析
esModule: false
},
// 设置只生成一份
type: 'javascript/auto'
},
{
// 其他资源
exclude: /\.(html|js|css|less|png|gif|jpg|jpeg|eot|svg|ttf|woff|woff2|otf)$/,
loader: 'file-loader',
options: {
// 重命名
name: 'libs/[name].[hash:10].[ext]',
// url-loader使用es6模块化解析
// html-loader使用commonjs
// 关闭url-loader的es6模块化,使用commonjs解析
esModule: false
},
// 设置只生成一份
type: 'javascript/auto'
},
{
// js兼容
test: /\.js$/,
// 排除模块文件夹
exclude: /node-modules/,
loader: 'babel-loader',
options: {
presets:[
[
'@babel/preset-env',
{
useBuiltIns:'entry',
corejs:{
version: 3
},
// 兼容浏览器
targets: {
"chrome": "60",
"firefox": "60",
"ie": "6",
"safari": "10",
"edge": "17"
}
}
]
]
}
}
]
},
optimization: {
minimizer: [
// 压缩css
new CssMinimizerPlugin(),
// 压缩js
new TerserPlugin({
// 匹配文件
test: /\.js$/,
// 排除不需要压缩的文件
exclude: /node_modules/,
// 剥离的注释需要独立成文件
extractComments: false,
// js中删除注释
terserOptions: {
format: {
comments: false
}
}
})
],
// 开发环境中启用优化
minimize: true
},
// 插件
plugins: [
// 打包html
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true
}
}),
// 打包css
new MiniCssExtractPlugin({
filename: 'css/main.css'
}),
// 每次清除build文件夹后生成新build文件夹
new CleanWebpackPlugin(),
// js语法检查
new EslintPlugin({
// 自动修复
fix: true,
// 文件根目录
context: resolve(__dirname,"src"),
// 需要检查的扩展名
extensions: 'js'
})

],
// 模式选择
mode: 'production'

};

5. webpack 优化配置

5.1. HMR

https://webpack.docschina.org/guides/hot-module-replacement/

Hot Module Replacement 热模块替换

在devServer工作时,一个模块发生变化,只会重新打包这个模块

在webpack.config.js中修改devServer的配置

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

  • js文件:默认不能使用HMR

    只能处理非入口文件

    添加支持 HMR的代码

    1
    2
    3
    4
    5
    6
    7
    //print.js
    function print() {
    // eslint-disable-next-line
    console.log('print.js');
    }

    export default print;

    src/js/index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import print from './print';

    if (module.hot) {
    // 一旦hot为true,说明开启了HMR功能,让代码生效
    // 方法会监听print.js文件的变化,一旦发生变化,其他默认不会重新打包构建
    // 执行回调函数
    module.hot.accept('./print.js', () => {
    print();
    });
    }
  • html文件:默认不能使用HMR,html文件不能更新了

    修改entry,引入html文件

    但还是不支持HMR

1
2
3
4
5
6
7
8
9
10
entry: ['./src/js/index.js','./src/index.html'],
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 开启HMR功能
// 当修改了 webpack 配置,新配置要想生效,必须重新 webpack 服务
hot: true
}

5.2. source-map

一种提供源代码到构建代码映射的技术

如果构建后的代码出错了,通过映射可以追踪源代码

https://webpack.docschina.org/configuration/devtool/#root

使用source-map

1
devtool: 'source-map'

模式可以是

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

参数 生成位置 错误提示
source-map 外部文件 错误代码准确信息
源代码的错误位置
inline-source-map 只生成一个内联在js中 错误代码准确信息
源代码的错误位置
hidden-source-map 外部文件 错误代码错误原因,没有错误位置
不能追踪源代码错误,只能提示到构建后代码的错误位置
eval-source-map 内联在js中,每个eval对应一个eval-source-map 错误代码准确信息
源代码的错误位置
nosources-source-map 外部文件 错误代码准确信息
但是没有任何源代码信息
cheap-source-map 外部文件 错误代码准确信息
源代码的错误位置
只能准确到行,其他的能准确到行列
cheap-module-source-map 外部文件 错误代码准确信息
源代码的错误位置
只能准确到行,其他的能准确到行列
会将loader的source map加入
  • 内联 vs 外部:外部生成了独立的文件,内联构建速度更快

  • 开发环境:速度+调试友好 eval-source-map / eval-cheap-module-source-map

    速度 eval>inline>cheap

    ​ eval-cheap-source-map

    ​ eval-source-map

    调试
    source-map
    ​ cheap-module-source-map

    ​ cheap-source-map

  • 生产环境:源代码隐藏+调试友好 source-map / cheap-module-source-map

    内联会让代码体积过大,不使用inline,eval

    源代码隐藏:

    ​ nosources-source-map 全部隐藏

    ​ hidden-source-map 只隐藏构建后的源代码,会提示构建后代码错误信息

5.3. oneOf

https://webpack.docschina.org/configuration/module#ruleoneof

提升生产环境构建速度

当规则匹配时,只使用第一个匹配规则

不能有两个配置处理同种类型文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
rules:[
{
oneOf: [
{
// css
// 检查css文件
test: /\.css$/,
// 使用插件
use: [...commonCssLoader]
},
{
// less
test: /\.less$/,
use: [...commonCssLoader,'less-loader']
},
{
// 图片
test: /\.(png|gif|jpg|jpeg)$/,
loader: 'url-loader',
options: {
// 图片重命名
name: 'imgs/[name].[hash:6].[ext]',
// url-loader使用es6模块化解析
// html-loader使用commonjs
// 关闭url-loader的es6模块化,使用commonjs解析
esModule: false,
// 图片大小小于8kb,使用base64处理
limit: 8*1024
},
// 设置只生成一张图片
type: 'javascript/auto'
},
{
// html的img路径
test: /\.html$/,
loader: 'html-loader'
},
{
// 字体
test: /\.(eot|svg|ttf|woff|woff2|otf)$/,
loader: 'file-loader',
options: {
// 重命名
name: 'fonts/[name].[ext]',
// url-loader使用es6模块化解析
// html-loader使用commonjs
// 关闭url-loader的es6模块化,使用commonjs解析
esModule: false
},
// 设置只生成一份
type: 'javascript/auto'
},
{
// 其他资源
exclude: /\.(html|js|css|less|png|gif|jpg|jpeg|eot|svg|ttf|woff|woff2|otf)$/,
loader: 'file-loader',
options: {
// 重命名
name: 'libs/[name].[hash:10].[ext]',
// url-loader使用es6模块化解析
// html-loader使用commonjs
// 关闭url-loader的es6模块化,使用commonjs解析
esModule: false
},
// 设置只生成一份
type: 'javascript/auto'
},
{
// js兼容
test: /\.js$/,
// 排除模块文件夹
exclude: /node-modules/,
loader: 'babel-loader',
options: {
presets:[
[
'@babel/preset-env',
{
useBuiltIns:'entry',
corejs:{
version: 3
},
// 兼容浏览器
targets: {
"chrome": "60",
"firefox": "60",
"ie": "6",
"safari": "10",
"edge": "17"
}
}
]
]
}
}
]
}
]

5.4. 缓存

  1. js - 对babel进行缓存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    // js兼容
    test: /\.js$/,
    // 排除模块文件夹
    exclude: /node-modules/,
    loader: 'babel-loader',
    options: {
    presets:[

    ],
    // 开启缓存
    // 第二次构建是,会读取之前的缓存
    cacheDirectory: true
    }
    }
  2. 文件资源缓存

    hash值

    每次webpack构建时会生成一个唯一的hash值

    js和css使用同一个hash值

    重新打包时会导致所有缓存失效,即使只改变了一个文件

    chunkhash值

    也是生成hash值。如果打包来源于同一个chunk,hash值一样

    js和css还是使用同一个hash值。因为css来源于js

    contenthash

    根据文件内容生成hash值

    不同文件不同hash值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    output: {
    // 文件名
    filename: 'js/build.[contenthash:10].js'
    },
    plugins: [
    // 打包css
    new MiniCssExtractPlugin({
    filename: 'css/main.[contenthash:10].css'
    })
    ]

5.5. tree shaking

去除无用代码

必须使用es6模块化,开启production环境

减少代码

在package.json中设置,

1
"sideEffects":false;

所有代码都没有副作用,可以进行tree shaking

但可能导致css 文件被删除

修改为

1
"sideEffects": ["*.css","*.less"];

5.6. code split

  1. 设置多入口

    1
    2
    3
    4
    5
    6
    7
    entry: {
    index: './src/js/index.js',
    test: './src/js/test.js'
    },
    output: {
    filename: 'js/[name].[contenthash:10].js'
    }
  2. optimization

    在webpack.config.js 中搭配多入口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /*
    1. 可以将 node_modules 中代码单独打包一个 chunk 最终输出
    2. 自动分析多入口 chunk 中,有没有公共的文件。如果有会打包成单独一个 chunk
    */
    optimization: {
    splitChunks: {
    chunks: 'all'
    }
    }
  3. 动态import

    在 webpack.config.js

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

    在 src/js/index.js 中添加代码

    1
    2
    3
    4
    5
    6
    7
    8
    import(/*webpackChunkName: 'print'*/"./print")
    .then(({ print, getNum }) => {
    print();
    console.log(getNum(3));
    })
    .catch(() => {
    console.log('文件加载失败');
    });

出现错误:ERROR in [eslint] error Parsing error: Unexpected token (

1
npm i @babel/eslint-parser --save-dev

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
"eslintConfig": {
"parser": "@babel/eslint-parser",
"parserOptions": {
"requireConfigFile": false
},
"extends": "airbnb-base",
"env": {
"browser": true,
"es6": true,
"node": true,
"jquery": true
}
}

重新运行

1
npm run build

生成单独的 print.071e5f2a63.js文件

5.7. lazy loading

js懒加载

进入页面不会加载print,当点击按钮才会加载print文件

  • 第一次加载后,第二次会直接执行,不会重复加载
  • 使用时才加载
1
2
3
4
5
6
7
8
// src/js/index.js
document.getElementById("btn").onClick=function(){
import(/*webpackChunkName: 'print'*/"./print")
.then(({ print, getNum }) => {
print();
console.log(getNum(3));
});
}

webpackPrefetch 预加载

  • 使用之前加载js文件
  • 正常加载可以认为是并行加载(同时加载多个文件),没有明确的顺序,在前的先加载,同时文件过多过大,影响顺序;而预加载是等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
  • 兼容性不好(PC端适用)
1
2
3
4
5
6
7
document.getElementById("btn").onClick=function(){
import(/*webpackChunkName: 'print', webpackPrefetch: true*/"./print")
.then(({ print, getNum }) => {
print();
console.log(getNum(3));
});
}

5.8. pwa

渐进式网络开发应用程序(离线可访问)

https://webpack.docschina.org/guides/progressive-web-application/#root

  1. 下载安装包

    1
    npm install --save-dev workbox-webpack-plugin
  2. 修改配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    plugins:[
    // 离线可访问
    new WorkboxWebpackPlugin.GenerateSW({
    /*
    帮助 serviceworker 快速启动
    删除旧的 serviceworker
    生成一个 serviceworker 配置文件~
    */
    clientsClaim: true,
    skipWaiting: true
    })
    ]
  3. eslint不认识window、navigator全局变量

    解决:需要修改package.json中eslintConfig配置

    1
    2
    3
    "env": {      
    "browser": true
    }
  4. serviceworker代码必须运行在服务器上

    启动服务器,将build目录下所有资源作为静态资源暴露出去

    1
    2
    npm i serve -g
    serve -s build

5.9. 多进程打包

开启多进程打包

https://webpack.docschina.org/loaders/thread-loader/#root

进程启动大概为600ms,进程通信也有开销

只有工作消耗时间比较长,才需要多进程打包

  1. 下载安装包

    1
    npm install --save-dev thread-loader
  2. 修改配置

    放在需要多进程打包的loader之前

    babel-loader 需要做兼容,消耗时间长

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    module:{
    rules:[
    {
    oneOf: [
    {
    // js兼容
    test: /\.js$/,
    // 排除模块文件夹
    exclude: /node-modules/,
    use: [
    {
    /*
    开启多进程打包。
    进程启动大概为 600ms,进程通信也有开销。
    只有工作消耗时间比较长,才需要多进程打包
    */
    loader: 'thread-loader',
    options:{
    // 进程2个
    work: 2
    }

    },
    {
    loader: 'babel-loader',
    options: {
    presets:[
    [
    '@babel/preset-env',
    {
    useBuiltIns:'entry',
    corejs:{
    version: 3
    },
    // 兼容浏览器
    targets: {
    "chrome": "60",
    "firefox": "60",
    "ie": "6",
    "safari": "10",
    "edge": "17"
    }
    }
    ]
    ],
    // 开启缓存
    // 第二次构建是,会读取之前的缓存
    cacheDirectory: true
    }
    }
    ]
    }
    ]
    }
    ]
    }

5.10. externals

拒绝打包,通过http链接引入 http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js

1
2
3
4
5
6
module.exports = {
externals: {
// 拒绝 jQuery 被打包进来
jquery: 'jQuery'
}
}

5.11. dll

优化重复打包第三方库,加快打包速度

使用dll技术,对某些库(第三方库:jquery、react、vue)进行单独打包

当你运行webpack时,默认查找webpack.config.js配置文件

  1. 需要运行webpack.dll.js文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    const {resolve} =  require("path");
    const webpack = require("webpack");
    /*
    利用dll技术,对某些库(jquery,vue,react)进行单独打包
    */

    module.exports={
    entry: {
    // 最终打包生成的[name]->jquery
    // ['jquery'] -> 要打包的库是jquery
    jquery: ['jquery']
    },
    output: {
    filename: '[name].js',
    path: resolve(__dirname,'dll'),
    // 打包的库里面向外暴露出去的内容叫什么名字
    library: '[name]_[hash]'
    },
    plugins: [
    // 打包生成一个manifest.json --> 提供和jquery映射
    new webpack.DllPlugin({
    name: '[name]_[hash]', // 映射库暴露的内容名称
    path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
    })
    ],
    mode: 'production'
    }
  2. 修改命令

    1
    npx webpack --config webpack.dll.js
  3. 生成文件

    dll

    |- jquery.js

    |- jquery.js.LICENSE

    |- manifest.json

  4. 安装插件

    1
    npm i add-asset-html-webpack-plugin --save-dev
  5. 修改配置

    https://github.com/SimenB/add-asset-html-webpack-plugin

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 引入dll
    const webpack = require('webpack');
    // 打包的dll文件在html中自动引入
    const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");

    plugins:[
    // 告诉webpack哪些代码不需要打包,同时引用的名称也改变
    new webpack.DllReferencePlugin({
    manifest: resolve(__dirname,'dll/manifest.json')
    }),
    // 将每个文件打包输出去,并在html中自动引入
    new AddAssetHtmlPlugin({
    filepath: resolve(__dirname,'dll/jquery.js'),
    outputPath: 'libs/dll',
    publicPath: 'libs/dll'
    })
    ]
  6. 引入资源

    在src/js/index,js中,引入jquery

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import $ from 'jquery';

    $('#btn').on('click', () => {
    import(/* webpackChunkName: 'Print',webpackPrefetch: true */'./print')
    .then(({ print, getNum }) => {
    print();
    // eslint-disable-next-line
    console.log(getNum(3));
    })
    .catch(() => {
    console.log('文件加载失败');
    });
    });
  7. 运行命令

    1
    npm run build

5.12. 性能优化总结

5.12.1. 开发环境性能优化

  • 优化打包构建速度
    • HMR
  • 优化代码调试
    • source-map

5.12.2. 生产环境性能优化

  • 优化打包构建事项

    • oneof
    • babel缓存
    • 多进程打包
    • externals
    • dll
  • 优化代码运行的性能

    • 缓存(hash-chunkhash-contenthash)
      • hash:webpack每次打包都会重新生成一个hash
      • chunkhash:同属于一个chunk,共用一个hash
      • contenthash:根据文件内容生成的hash,文件内容变化才会重新生成hash
    • tree shaking
      • es6 module
      • production环境
      • sideEffect参数
    • code split 代码分隔
      • 单入口
      • 多入口
    • 懒加载/预加载
    • pwa

6. webpack 配置详情

6.1. entry

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

单入口

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

filename: ‘[name].js’ 此时chunk的名称默认是main

  1. array–> [‘./src/index.js’, ‘./src/add.js’]

    多入口

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

    –> 只有在HMR功能中让html热更新生效~

  2. object –> { index: ‘./src/index.js’, add: ‘./src/add.js’ }

    多入口

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

    此时chunk的名称是key

    –> 特殊用法

    1
    2
    3
    4
    5
    6
    {
    // 所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件
    index: ['./src/index.js', './src/count.js'],
    // 形成一个chunk,输出一个bundle文件
    add: './src/add.js'
    }

6.2. output

1
2
3
4
5
6
7
8
9
10
11
12
13
output: {			
// 文件名称(指定名称+目录)
filename: '[name].js',
// 输出文件目录(将来所有资源输出的公共目录)
path: resolve(__dirname, 'build'),
// 所有资源引入公共路径的前缀 --> 'imgs/a.jpg' --> './imgs/a.jpg'
// publicPath: '/', // 一般用于生产环境
chunkFilename: '[name]_chunk.js', // 非入口chunk的名称
// 通常结合dll用
// library: '[name]', // 整个库向外暴露的变量名
// libraryTarget: 'window', // 变量名添加到哪个上 browser
// libraryTarget: 'global', // 变量名添加到哪个上 node
}

chunkFilename 用于代码分离时,动态import生成的外部js文件命名

6.3. resolve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {		
// 解析模块的规则
resolve: {
// 配置解析模块路径别名:简写路径 缺点路径没有提示
alias: {
// 在index.js中可以使用 import ‘@/index.css’
"@": resolve(__dirname, 'src/css')
},
// 配置省略文件路径的后缀名
extensions: ['.js', '.json', '.jsx','.css'],
// 告诉webpack解析模块是去找哪个目录
modules: [resolve(__dirname,'node_modules'),"node_modules"]
}
}

index.js

1
2
import '../css/index.css'
--> 可以写成: import '@/index.css'

出现错误❌:error Unable to resolve path to module '@/style.css' import/no-unresolved

Eslint 无法识别alias别名

https://www.jianshu.com/p/17a67afaf293

  1. 安装eslint配置文件 .eslintrc.js

    1
    2
    npm init @eslint/config
    npm i eslint-import-resolver-alias --save-dev
  2. 添加配置 .eslintrc.js

    https://github.com/johvin/eslint-import-resolver-alias

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const {resolve} = require("path");

    module.exports = {
    "env": {
    "browser": true,
    "es6": true,
    "node": true,
    "jquery": true
    },
    "parser": "@babel/eslint-parser",
    "parserOptions": {
    "requireConfigFile": false
    },
    settings: {
    "import/resolver": {
    alias: true
    }
    }
    }
  3. 运行命令

    1
    npm run build

6.4. dev-server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// devServer
devServer: {
// 项目构建后路径
static: {
// 告诉服务器从哪里提供内容
directory: resolve(__dirname, 'build'),
// 文件更改将触发整个页面重新加载
watch: true,
//
staticOptions:{
// 忽略文件
ignored: /node_modules/
}
},
// 启动 gzip 压缩
compress: true,
// 域名
host: 'localhost',
// 端口号
port: 3000,
// 自动打开浏览器
open: true,
// 开启HMR功能
// 当修改了 webpack 配置,新配置要想生效,必须重新 webpack 服务
hot: true,
client: {
// 允许在浏览器中设置日志级别
// 'log' | 'info' | 'warn' | 'error' | 'none' | 'verbose'
logging: 'none',
// 当出现编译错误或警告时,在浏览器中显示全屏覆盖
overlay: {
errors: true,
warnings: false
},
// 在浏览器中以百分比显示编译进度
progress: true

},
// 当监听到文件变化时 dev-server 将会重新加载或刷新页面
liveReload: true,
// 服务器代理 --> 解决开发环境跨域问题
proxy:{
// 一旦devServe(5000)服务器受到/api/xx的请求,就会把请求文件转到另外一服务器(3000)
'/api': {
target: 'http://localhost:5000',
// 发送请求时,请求路径重写:将/api/xxx --> /xxx (去掉/api)
pathRewirte: {
'^/api': ''
}
}
}
}

6.5. optimization

会根据你选择的 mode 来执行不同的优化, 不过所有的优化还是可以手动配置和重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
optimization: {
splitChunks: {
chunks: 'all',
// 以下是默认值,可以不写
/*
minSize: 30*1024, // 分割的chunk最小为30kb
maxSize: 0, // 最大没有限制
minChunks: 1, // 要提取的chunk最少被引用1次
maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
maxInitialRequests: 3, // 入中js文件最大并行请求数量
automaticNameDelimiter: '~', // 名称连接符 vendors~xxx.js 因为这个里是~
name: true, //可以使用命名规则
cacheGroups: { // 分割chunk的组
// node_modules文件会被打包到vendors --> vendors~xxx.js xxx是模块名称
// 满足上面的公共规则:如:大小超过30kb,至少被引用一次
vendors: { // vendors~xxx.js 因为这个里是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
}
}
}
本文结束  感谢您的阅读
  • 本文作者: Wang Ting
  • 本文链接: /zh-CN/2020/06/23/webpack学习笔记/
  • 发布时间: 2020-06-23 21:37
  • 更新时间: 2023-04-15 16:16
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!