Webpack

一. Webpack

1. 模块打包工具的由来

  • ES Module 存在环境兼容问题
  • 模块文件过多,网络请求频繁
  • 所有的前端资源都需要模块化

2. 模块打包工具概要

  • 主流的模块打包工具
    • Webpack
    • Parcel
    • Rollup
  • 打包工具解决的是前端整体的模块化,并不单指 JavaScript 模块化

3. Webpack 快速上手

  • 初始化 package.json 文件,yarn init
  • 安装 webpack,webpack-cli,yarn add webpack webpack-cli –dev
  • 查看 webpack 版本,yarn webpack –version
  • 使用 webpack 打包代码,yarn webpack

4. 配置文件

  • webpack 默认以 src 目录下的 index.js 作为打包入口,最终打包结果会存放到 dist 目录下的 main.js

  • 项目根目录新建 webpack.config.js 文件

    const path = require('path')
    module.exports = {
      // 入口文件
      entry: './src/main.js',
      // 输出文件
      output: {
        // 输出文件名
        filename: 'bundle.js',
        // 输出目录,注意:path 必须是一个绝对路径
        path: path.join(__dirname, 'output')
      }
    }
    复制代码

5. 工作模式

  • production 生产模式

  • development 开始模式

  • none 无模式

  • 可以通过命令指定参数 –mode development 的方式指定工作模式

  • 可以通过在配置文件中配置 type 属性指定工作模式

    const path = require('path')
    module.exports = {
      // 工作模式
      mode: 'development',
      // 入口文件
      entry: './src/main.js',
      // 输出文件
      output: {
        // 输出文件名
        filename: 'bundle.js',
        // 输出目录,注意:path 必须是一个绝对路径
        path: path.join(__dirname, 'dist')
      }
    }
    复制代码

6. 资源模块加载

  • webpack 内部只会处理 js 文件

  • 通过 loader 处理资源文件

  • 安装 css-loader,yarn add css-loader –dev,用来加载 css 资源

  • 安装 style-loader,yarn add style-loader –dev,用来将转换的 css 资源通过 style 标签的方式放到页面中

    const path = require('path')
    module.exports = {
      // 工作模式
      mode: 'development',
      // 入口文件
      entry: './src/main.css',
      // 输出文件
      output: {
        // 输出文件名
        filename: 'bundle.js',
        // 输出目录,注意:path 必须是一个绝对路径
        path: path.join(__dirname, 'dist')
      },
      module:{
        // 针对于其他的资源模块加载规则的配置
        rules:[
          {
            // 用于匹配打包过程中所遇到的文件路径
            test: /\.css$/,
            // 指定匹配到的文件需要使用的 loader, 如果配置多个 loader ,执行顺序是从后向前
            use: [
              'style-loader',
              'css-loader'
            ]
          }
        ]
      }
    }
    复制代码

7. 导入资源模块

  • 将所需的资源文件导入到对应的 js 文件中,在 js 代码中使用资源
  • 优势
    • 逻辑合理,JS 确实需要这些资源文件
    • 确保上线资源不缺失,都是必要的

8. 文件资源加载器

  • 安装 file-loader,yarn add file-loader –dev

  • 安装 url-loader,yarn add url-loader –dev

    const path = require('path')
    module.exports = {
      // 工作模式
      mode: 'development',
      // 入口文件
      entry: './src/main.js',
      // 输出文件
      output: {
        // 输出文件名
        filename: 'bundle.js',
        // 输出目录,注意:path 必须是一个绝对路径
        path: path.join(__dirname, 'dist'),
        // 网站的根目录
        publicPath: './dist/'
      },
      module: {
        // 针对于其他的资源模块加载规则的配置
        rules: [
          {
            // 用于匹配打包过程中所遇到的文件路径
            test: /\.css$/,
            // 指定匹配到的文件需要使用的 loader, 如果配置多个 loader ,执行顺序是从后向前
            use: [
              'style-loader',
              'css-loader'
            ]
          },
          {
            test: /\.png$/,
            use: {
              loader: 'url-loader',
              // 配置选项
              options: {
                limit: 10 * 1024 // 以字节为单位
              }
            }
          }
        ]
      }
    }
    复制代码

9. 常用加载器分类

  • 编译转换类,将模块转换为 js 代码,例如:css-loader
  • 文件操作类,将模块拷贝至输出目录,将模块导出,例如:file-loader
  • 代码检查类,检查模块代码,例如:esline-loader

10. webpack 与 ES 2015

  • webpack 不能转换 ES 2015 特性,只能够转换 import 和 export
  • 安装 babel-loader,yarn add babel-loader @babel/core @babel/preset-env –dev

11. webpack 加载资源的方式

  • 遵循 ES Module 标准的 import 声明

  • 遵循 CommonJS 标准的 require 函数

  • 通过 require 函数导入 ES Module 默认导出,需要 以 .default 的方式

  • 遵循 AMD 标准的 define 函数和 require 函数

  • 样例

    • 安装 html-loader,yarn add html-loader –dev

      const path = require('path')
      module.exports = {
        // 工作模式
        mode: 'development',
        // 入口文件
        entry: './src/main.js',
        // 输出文件
        output: {
          // 输出文件名
          filename: 'bundle.js',
          // 输出目录,注意:path 必须是一个绝对路径
          path: path.join(__dirname, 'dist'),
          // 网站的根目录
          publicPath: './dist/'
        },
        module: {
          // 针对于其他的资源模块加载规则的配置
          rules: [
            // 加载 css 资源
            {
              // 用于匹配打包过程中所遇到的文件路径
              test: /\.css$/,
              // 指定匹配到的文件需要使用的 loader, 如果配置多个 loader ,执行顺序是从后向前
              use: [
                'style-loader',
                'css-loader'
              ]
            },
            // 加载 png 资源
            {
              test: /\.png$/,
              use: {
                loader: 'url-loader',
                // 配置选项
                options: {
                  limit: 10 * 1024 // 以字节为单位
                }
              }
            },
            // 转译 es6 资源
            {
              test: /\.js$/,
              use: {
                loader: 'babel-loader',
                options: {
                  presets: ['@babel/preset-env']
                }
              }
            },
            // 加载 html 资源
            {
              test: /\.html$/,
              use: {
                // html-loader 默认只会处理 src 属性,如果其他属性也要参与打包,则需要相应配置
                loader: 'html-loader',
                options: {
                  attributes: {
                    list: [
                      {
                        tag: 'img',
                        attribute: 'src',
                        type: 'src',
                      },
                      {
                        tag: 'a',
                        attribute: 'href',
                        type: 'src',
                      }
                    ]
                  }
                }
              }
            }
          ]
        }
      }
      复制代码

12. webpack 核心工作原理

  • loader 机制是 webpack 的核心

13. 开发一个 loader

  • 初始化 package.json,yarn init

  • 安装 webpack,webpack-cli,yarn add webpack webpack-cli –dev

  • 项目根目录新建 loader 模块,例如:markdown-loader.js

    // 每个 webpack 的 loader 都需要导出一个函数
    // 需要一个参数 source 所加载到资源文件的内容
    // 通过返回值,输出加工过后的结果,返回值必须是一段 JavaScript 代码
    const marked = require('marked')
    module.exports = source => {
      const html = marked(source)
      // 使用 module.exports 导出
      // return `module.exports = ${JSON.stringify(html)}`
      // 使用 export default 导出
      // return `export default ${JSON.stringify(html)}`
      // 返回 html 字符串,交给下一个 loader (html-loader) 处理
      return html
    }
    复制代码
  • 配置 webpack.config.js

    module.exports = {
      entry: './src/main.js',
      output: {
        filename: 'bundle.js'
      },
      module: {
        rules: [
          {
            test: /\.md$/,
            use: {
              loader: ['html-loader', './markdown-loader.js']
            }
          }
        ]
      }
    }
    复制代码

14. 插件机制

  • 目的:增强 webpack 自动化能力

    • 拷贝静态文件至输出目录

      • 安装 copy-webpack-plugin,yarn add copy-webpack-plugin –dev

      • 配置 webpack.config.js

        const path = require('path')
        const { CleanWebpackPlugin } = require('clean-webpack-plugin')
        const HtmlWebpackPlugin = require('html-webpack-plugin')
        const CopyWebpackPlugin = require('copy-webpack-plugin')
        module.exports = {
          mode: 'none',
          entry: './src/main.js',
          output: {
            filename: 'bundle.js',
            path: path.join(__dirname, 'dist')
          },
          module: {
            rules: [
              {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
              },
              {
                test: /\.png$/,
                use: {
                  loader: 'url-loader',
                  options: {
                    limit: 10 * 1024
                  }
                }
              }
            ]
          },
          plugins: [
            new CleanWebpackPlugin(),
            // 指定一个对象参数
            new HtmlWebpackPlugin({
              // html 页面标题
              title: 'Webpack Plugin Sample',
              // html 中 meta
              meta: {
                viewport: 'width=device-width'
              },
              // 指定模板文件
              template: './src/index.html'
            }),
            // 创建额外的 html 文件
            new HtmlWebpackPlugin({
              filename: 'about.html'
            }),
            // 传入一个数组参数,指定拷贝的文件路径
            new CopyWebpackPlugin({
              // from 可以是目录,也可以是文件的相对路径
              patterns: [
                { from: "./public", to: "." }
              ]
            })
          ]
        }
        复制代码
    • 压缩输出的代码

    • 自动生成 html

      • 安装 html-webpack-plugin,yarn add html-webpack-plugin –dev

      • 在 src 目录新建 index.html 模板文件

        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Webpack</title>
        </head>
        <body>
          <div class="container">
            <!-- 通过 htmlWebpackPlugin.options 中拿到配置的参数 -->
            <h1><%= htmlWebpackPlugin.options.title%></h1>
          </div>
        </body>
        </html>
        复制代码
      • 配置 webpack.config.js

        const { CleanWebpackPlugin } = require('clean-webpack-plugin')
        const HtmlWebpackPlugin = require('html-webpack-plugin')
        module.exports = {
          mode: 'none',
          entry: './src/main.js',
          output: {
            filename: 'bundle.js'
          },
          module: {
            rules: [
              {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
              },
              {
                test: /\.png$/,
                use: {
                  loader: 'url-loader',
                  options: {
                    limit: 10 * 1024
                  }
                }
              }
            ]
          },
          plugins: [
            new CleanWebpackPlugin(),
            // 指定一个对象参数
            new HtmlWebpackPlugin({
              // html 页面标题
              title: 'Webpack Plugin Sample',
              // html 中 meta
              meta: {
                viewport: 'width=device-width'
              },
              // 指定模板文件
              template: './src/index.html'
            }),
            // 创建额外的 html 文件
            new HtmlWebpackPlugin({
              filename:'about.html'
            })
          ]
        }
        复制代码
    • 清除 dist 目录

      • 安装 clean-webpack-plugin,yarn add clean-webpack-plugin –dev

      • 配置 webpack.config.js

        const { CleanWebpackPlugin } = require('clean-webpack-plugin')
        module.exports = {
          entry: './src/main.js',
          output: {
            filename: 'bundle.js'
          },
          module: {
            rules: [
              {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
              },
              {
                test: /\.png$/,
                use: {
                  loader: 'url-loader',
                  options: {
                    limit: 10 * 1024
                  }
                }
              }
            ]
          },
          plugins: [
            new CleanWebpackPlugin()
          ]
        }
        复制代码

15. 开发一个插件

  • 实现机制:钩子机制

  • webpack 要求插件必须是一个函数或者是一个包含 apply 方法的对象

    const path = require('path')
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    
    // MyPlugin
    class MyPlugin {
      // 在 webpack 启动时自动被调用
      // 有一个 compiler 参数,这个参数可以接受到 webpack 实例
      apply(compiler) {
        console.log('MyPlugin 启动')
        // 通过 compiler.hooks.emit.tap() 注册一个函数
        // tap() 接收两个参数,第一个是插件的名称,第二个是一个接收一个参数为 compilation 的函数
        // compilation 为此次打包的上下文
        compiler.hooks.emit.tap('MyPlugin', compilation => {
          for (const name in compilation.assets) {
            // console.log(name)
            // 通过 source() 方法或者内容
            // console.log(compilation.assets[name].source())
            if (name.endsWith('.js')) {
              const content = compilation.assets[name].source()
              const withoutContent = content.replace(/\/\*\*+\*\//g, '')
              // 将替换的结果覆盖到原有的内容中
              compilation.assets[name] = {
                source: () => withoutContent,
                size: () => withoutContent.length
              }
            }
          }
        })
      }
    }
    
    module.exports = {
      mode: 'none',
      entry: './src/main.js',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
          },
          {
            test: /\.png$/,
            use: {
              loader: 'url-loader',
              options: {
                limit: 10 * 1024
              }
            }
          }
        ]
      },
      plugins: [
        new CleanWebpackPlugin(),
        // 指定一个对象参数
        new HtmlWebpackPlugin({
          // html 页面标题
          title: 'Webpack Plugin Sample',
          // html 中 meta
          meta: {
            viewport: 'width=device-width'
          },
          // 指定模板文件
          template: './src/index.html'
        }),
        // 创建额外的 html 文件
        new HtmlWebpackPlugin({
          filename: 'about.html'
        }),
        // 传入一个数组参数,指定拷贝的文件路径
        new CopyWebpackPlugin({
          // from 可以是目录,也可以是文件的相对路径
          patterns: [
            { from: "./public", to: "." }
          ]
        }),
        new MyPlugin()
      ]
    }
    复制代码

16. 自动编译

  • 采用 webpack 中的 watch 工作模式,监听文件变化,自动重新打包
  • 方式一
    • 在启动 webpack 命令时添加一个 –watch 的参数
    • yarn webpack –watch

17. 自动刷新浏览器

  • 安装 browser-sync,yarn add browser-sync –dev
  • 启动 browser-sync 并监听 dist 目录文件变化,yarn browser-sync dist –files “**/*”

18. webpack dev server

  • webpack 官方提供的开发工具

  • 提供用于开发的 http server

  • 集成了自动编译和自动刷新浏览器等功能

  • 安装 webpack-dev-server,yarn add webpack-dev-server –dev

  • 运行 webpack-dev-server,yarn webpack-dev-server

  • webpack-dev-server 并没有将打包结果存储于磁盘中,而是内存中

  • 可以加 –open 的参数,用于自动唤起浏览器

  • 静态资源访问

    • 使用 contentBase 指定目录

      const path = require('path')
      const { CleanWebpackPlugin } = require('clean-webpack-plugin')
      const HtmlWebpackPlugin = require('html-webpack-plugin')
      const CopyWebpackPlugin = require('copy-webpack-plugin')
      
      // MyPlugin
      class MyPlugin {
        // 在 webpack 启动时自动被调用
        // 有一个 compiler 参数,这个参数可以接受到 webpack 实例
        apply(compiler) {
          console.log('MyPlugin 启动')
          // 通过 compiler.hooks.emit.tap() 注册一个函数
          // tap() 接收两个参数,第一个是插件的名称,第二个是一个接收一个参数为 compilation 的函数
          // compilation 为此次打包的上下文
          compiler.hooks.emit.tap('MyPlugin', compilation => {
            for (const name in compilation.assets) {
              // console.log(name)
              // 通过 source() 方法或者内容
              // console.log(compilation.assets[name].source())
              if (name.endsWith('.js')) {
                const content = compilation.assets[name].source()
                const withoutContent = content.replace(/\/\*\*+\*\//g, '')
                // 将替换的结果覆盖到原有的内容中
                compilation.assets[name] = {
                  source: () => withoutContent,
                  size: () => withoutContent.length
                }
              }
            }
          })
        }
      }
      
      module.exports = {
        mode: 'none',
        entry: './src/main.js',
        output: {
          filename: 'bundle.js',
          path: path.join(__dirname, 'dist')
        },
        // 配置 webpack-dev-server
        devServer:{
          // 指定额外的静态资源路径
          // 可以是一个字符串,可以是一个数组
          contentBase: './public'
        },
        module: {
          rules: [
            {
              test: /\.css$/,
              use: ['style-loader', 'css-loader']
            },
            {
              test: /\.png$/,
              use: {
                loader: 'url-loader',
                options: {
                  limit: 10 * 1024
                }
              }
            }
          ]
        },
        plugins: [
          new CleanWebpackPlugin(),
          // 指定一个对象参数
          new HtmlWebpackPlugin({
            // html 页面标题
            title: 'Webpack Plugin Sample',
            // html 中 meta
            meta: {
              viewport: 'width=device-width'
            },
            // 指定模板文件
            template: './src/index.html'
          }),
          // 创建额外的 html 文件
          new HtmlWebpackPlugin({
            filename: 'about.html'
          }),
          // 传入一个数组参数,指定拷贝的文件路径
          // 开发过程中一般不会使用
          // new CopyWebpackPlugin({
          //   // from 可以是目录,也可以是文件的相对路径
          //   patterns: [
          //     { from: "./public", to: "." }
          //   ]
          // }),
          new MyPlugin()
        ]
      }
      复制代码
  • 代理 API

    const path = require('path')
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    
    // MyPlugin
    class MyPlugin {
      // 在 webpack 启动时自动被调用
      // 有一个 compiler 参数,这个参数可以接受到 webpack 实例
      apply(compiler) {
        console.log('MyPlugin 启动')
        // 通过 compiler.hooks.emit.tap() 注册一个函数
        // tap() 接收两个参数,第一个是插件的名称,第二个是一个接收一个参数为 compilation 的函数
        // compilation 为此次打包的上下文
        compiler.hooks.emit.tap('MyPlugin', compilation => {
          for (const name in compilation.assets) {
            // console.log(name)
            // 通过 source() 方法或者内容
            // console.log(compilation.assets[name].source())
            if (name.endsWith('.js')) {
              const content = compilation.assets[name].source()
              const withoutContent = content.replace(/\/\*\*+\*\//g, '')
              // 将替换的结果覆盖到原有的内容中
              compilation.assets[name] = {
                source: () => withoutContent,
                size: () => withoutContent.length
              }
            }
          }
        })
      }
    }
    
    module.exports = {
      mode: 'none',
      entry: './src/main.js',
      output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
      },
      // 配置 webpack-dev-server
      devServer: {
        // 指定额外的静态资源路径
        // 可以是一个字符串,可以是一个数组
        contentBase: './public',
        // 添加代理服务配置
        // key 为请求路径前缀
        // value 为前缀所匹配到的代理规则配置
        proxy: {
          '/api': {
            // 代理目标
            // http://localhost:8080/api/users === https://api.github.com/api/users
            target: 'https://api.github.com',
            // 重写代理路径
            // key 重写的目标
            // value 重写的值
            // http://localhost:8080/api/users === https://api.github.com/users
            pathRewrite: {
              '^/api': ''
            },
            // 不能使用 localhost:8080 作为请求 Github 的主机名
            changeOrigin: true
          }
        }
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
          },
          {
            test: /\.png$/,
            use: {
              loader: 'url-loader',
              options: {
                limit: 10 * 1024
              }
            }
          }
        ]
      },
      plugins: [
        new CleanWebpackPlugin(),
        // 指定一个对象参数
        new HtmlWebpackPlugin({
          // html 页面标题
          title: 'Webpack Plugin Sample',
          // html 中 meta
          meta: {
            viewport: 'width=device-width'
          },
          // 指定模板文件
          template: './src/index.html'
        }),
        // 创建额外的 html 文件
        new HtmlWebpackPlugin({
          filename: 'about.html'
        }),
        // 传入一个数组参数,指定拷贝的文件路径
        // 开发过程中一般不会使用
        // new CopyWebpackPlugin({
        //   // from 可以是目录,也可以是文件的相对路径
        //   patterns: [
        //     { from: "./public", to: "." }
        //   ]
        // }),
        new MyPlugin()
      ]
    }
    复制代码
  • Source Map

    • 作用:解决源代码与运行代码不一致所产生的调试问题

    • 通过在文件的最后一行添加 //# sourceMappingURL = <文件路径> 注释的形式引入 sourceMap 文件

    • webpack 配置 sourceMap

      • 在 webpack.config.js 中设置 devtool

        const path = require('path')
        const { CleanWebpackPlugin } = require('clean-webpack-plugin')
        const HtmlWebpackPlugin = require('html-webpack-plugin')
        const CopyWebpackPlugin = require('copy-webpack-plugin')
        
        // MyPlugin
        class MyPlugin {
          // 在 webpack 启动时自动被调用
          // 有一个 compiler 参数,这个参数可以接受到 webpack 实例
          apply(compiler) {
            console.log('MyPlugin 启动')
            // 通过 compiler.hooks.emit.tap() 注册一个函数
            // tap() 接收两个参数,第一个是插件的名称,第二个是一个接收一个参数为 compilation 的函数
            // compilation 为此次打包的上下文
            compiler.hooks.emit.tap('MyPlugin', compilation => {
              for (const name in compilation.assets) {
                // console.log(name)
                // 通过 source() 方法或者内容
                // console.log(compilation.assets[name].source())
                if (name.endsWith('.js')) {
                  const content = compilation.assets[name].source()
                  const withoutContent = content.replace(/\/\*\*+\*\//g, '')
                  // 将替换的结果覆盖到原有的内容中
                  compilation.assets[name] = {
                    source: () => withoutContent,
                    size: () => withoutContent.length
                  }
                }
              }
            })
          }
        }
        
        module.exports = {
          mode: 'none',
          entry: './src/main.js',
          output: {
            filename: 'bundle.js',
            path: path.join(__dirname, 'dist')
          },
          // 配置 webpack-dev-server
          devServer: {
            // 指定额外的静态资源路径
            // 可以是一个字符串,可以是一个数组
            contentBase: './public',
            // 添加代理服务配置
            // key 为请求路径前缀
            // value 为前缀所匹配到的代理规则配置
            proxy: {
              '/api': {
                // 代理目标
                // http://localhost:8080/api/users === https://api.github.com/api/users
                target: 'https://api.github.com',
                // 重写代理路径
                // key 重写的目标
                // value 重写的值
                // http://localhost:8080/api/users === https://api.github.com/users
                pathRewrite: {
                  '^/api': ''
                },
                // 不能使用 localhost:8080 作为请求 Github 的主机名
                changeOrigin: true
              }
            }
          },
          // 配置调试
          devtool: 'source-map',
          module: {
            rules: [
              {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
              },
              {
                test: /\.png$/,
                use: {
                  loader: 'url-loader',
                  options: {
                    limit: 10 * 1024
                  }
                }
              }
            ]
          },
          plugins: [
            new CleanWebpackPlugin(),
            // 指定一个对象参数
            new HtmlWebpackPlugin({
              // html 页面标题
              title: 'Webpack Plugin Sample',
              // html 中 meta
              meta: {
                viewport: 'width=device-width'
              },
              // 指定模板文件
              template: './src/index.html'
            }),
            // 创建额外的 html 文件
            new HtmlWebpackPlugin({
              filename: 'about.html'
            }),
            // 传入一个数组参数,指定拷贝的文件路径
            // 开发过程中一般不会使用
            // new CopyWebpackPlugin({
            //   // from 可以是目录,也可以是文件的相对路径
            //   patterns: [
            //     { from: "./public", to: "." }
            //   ]
            // }),
            new MyPlugin()
          ]
        }
        复制代码
      • webpack 中 sourceMap 风格

        • 每种风格的效率和效果各不相同
        • eval 模式
          • 不会生成 sourceMap 文件
          • 只能定位具体的源代码文件名称,不能定位具体的行列信息
          • 构建速度最快
        • cheap-eval-source-map 模式
          • 可以定位到源代码文件名称,具体的行信息
          • 定位的代码是 ES6 转换之后的结果
        • cheap-module-eval-source-map 模式
          • 可以定位到源代码文件名称,具体的行信息
          • 定位的代码和源代码一模一样
        • nosources-source-map 模式
          • 定位的信息点进去是没有源代码的
          • 可以定位具体的行列信息
        • 规律
          • eval – 是否使用 eval 执行模块代码
          • cheap – Source Map 是否包含行信息
          • module – 是否得到 loader 处理之前的代码

19. HMR 模块热替换

  • 全称:hotModuleReplacement

  • 应用程序运行的过程中实时替换某个模块,应用的运行状态不会受影响

  • 使用方式

    • 方式一

      • webpack-dev-server –hot
    • 方式二

      • webpack.config.js 配置文件中添加对应的配置

        const webpack = require('webpack')
        const HtmlWebpackPlugin = require('html-webpack-plugin')
        
        module.exports = {
          mode: 'development',
          entry: './src/main.js',
          output: {
            filename: 'js/bundle.js'
          },
          devServer:{
            // 开启热替换,如果热替换没有执行,会自动唤起页面刷新
            // hot: true,
            // 开启热替换,如果热替换没有执行,不会自动唤起页面刷新
            hotOnly: true
          },
          devtool: 'cheap-module-eval-source-map',
          module: {
            rules: [
              {
                test: /\.css$/,
                use: [
                  'style-loader',
                  'css-loader'
                ]
              },
              {
                test: /\.(png|jpe?g|gif)$/,
                use: 'file-loader'
              }
            ]
          },
          plugins: [
            new HtmlWebpackPlugin({
              title: 'Webpack Tutorial',
              template: './src/index.html'
            }),
            // 利用 webpack 的内置插件
            new webpack.HotModuleReplacementPlugin()
          ]
        }
        
        复制代码
  • webpack 中的 HMR 并不可以开箱即用

  • webpack 中的 HMR 需要手动处理模块热替换逻辑

  • HMR API

    import createEditor from './editor'
    import background from './better.png'
    import './global.css'
    
    const editor = createEditor()
    document.body.appendChild(editor)
    
    const img = new Image()
    img.src = background
    document.body.appendChild(img)
    
    // module.hot 中的 hot 属性是 HMR API 的核心对象
    // 提供了 accpet 用于注册某一个模块更新过后的处理函数
    // accept 函数接收两个参数,第一个参数:依赖模块的路径,第二个参数:依赖模块的处理函数
    // 处理 js 模块的热替换
    if (module.hot) {
      let lastEditor = editor
      module.hot.accept('./editor.js', () => {
        // console.log('editor been updated')
        const value = lastEditor.innerHTML
        document.body.removeChild(lastEditor)
        const newEditor = createEditor()
        newEditor.innerHTML = value
        document.body.appendChild(newEditor)
        lastEditor = newEditor
      })
      module.hot.acceps('./better.png', () => {
        img.src = background
      })
    }
    复制代码

20. 生产环境优化

  • 不同环境下的配置

    • 配置文件根据环境不同导出不同的配置

    • 适用于中小型项目

      const path = require('path')
      const { CleanWebpackPlugin } = require('clean-webpack-plugin')
      const HtmlWebpackPlugin = require('html-webpack-plugin')
      const CopyWebpackPlugin = require('copy-webpack-plugin')
      
      // MyPlugin
      class MyPlugin {
        // 在 webpack 启动时自动被调用
        // 有一个 compiler 参数,这个参数可以接受到 webpack 实例
        apply(compiler) {
          console.log('MyPlugin 启动')
          // 通过 compiler.hooks.emit.tap() 注册一个函数
          // tap() 接收两个参数,第一个是插件的名称,第二个是一个接收一个参数为 compilation 的函数
          // compilation 为此次打包的上下文
          compiler.hooks.emit.tap('MyPlugin', compilation => {
            for (const name in compilation.assets) {
              // console.log(name)
              // 通过 source() 方法或者内容
              // console.log(compilation.assets[name].source())
              if (name.endsWith('.js')) {
                const content = compilation.assets[name].source()
                const withoutContent = content.replace(/\/\*\*+\*\//g, '')
                // 将替换的结果覆盖到原有的内容中
                compilation.assets[name] = {
                  source: () => withoutContent,
                  size: () => withoutContent.length
                }
              }
            }
          })
        }
      }
      
      // webpack 还可以导出一个函数,这个函数接收两个参数,第一个参数:环境参数,第二个参数:运行命令时传递的所有参数
      module.exports = (env, argv) => {
        let config = {
          mode: 'none',
          entry: './src/main.js',
          output: {
            filename: 'bundle.js',
            path: path.join(__dirname, 'dist')
          },
          // 配置 webpack-dev-server
          devServer: {
            // 指定额外的静态资源路径
            // 可以是一个字符串,可以是一个数组
            contentBase: './public',
            // 添加代理服务配置
            // key 为请求路径前缀
            // value 为前缀所匹配到的代理规则配置
            proxy: {
              '/api': {
                // 代理目标
                // http://localhost:8080/api/users === https://api.github.com/api/users
                target: 'https://api.github.com',
                // 重写代理路径
                // key 重写的目标
                // value 重写的值
                // http://localhost:8080/api/users === https://api.github.com/users
                pathRewrite: {
                  '^/api': ''
                },
                // 不能使用 localhost:8080 作为请求 Github 的主机名
                changeOrigin: true
              }
            }
          },
          // 配置调试
          devtool: 'source-map',
          module: {
            rules: [
              {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
              },
              {
                test: /\.png$/,
                use: {
                  loader: 'url-loader',
                  options: {
                    limit: 10 * 1024
                  }
                }
              }
            ]
          },
          plugins: [
            // 指定一个对象参数
            new HtmlWebpackPlugin({
              // html 页面标题
              title: 'Webpack Plugin Sample',
              // html 中 meta
              meta: {
                viewport: 'width=device-width'
              },
              // 指定模板文件
              template: './src/index.html'
            }),
            // 创建额外的 html 文件
            new HtmlWebpackPlugin({
              filename: 'about.html'
            }),
            new MyPlugin()
          ]
        }
      
        if(env === 'production'){
          config.mode = 'production'
          config.devtool = false
          config.plugins = [
            ...config.plugins,
            new CleanWebpackPlugin(),
            new CopyWebpackPlugin({
              patterns: [
                { from: "./public", to: "." }
              ]
            }),
          ]
        }
      }
      复制代码
    • 一个环境对应一个配置文件

      • 项目根目录创建 webpack.common.js webpack.dev.js webpack.prod.js

      • webpack.common.js

        const path = require('path')
        const HtmlWebpackPlugin = require('html-webpack-plugin')
        
        module.exports = {
          entry: './src/main.js',
          output: {
            filename: 'bundle.js',
            path: path.join(__dirname, 'dist')
          },
          // 配置 webpack-dev-server
          devServer: {
            // 指定额外的静态资源路径
            // 可以是一个字符串,可以是一个数组
            contentBase: './public',
            // 添加代理服务配置
            // key 为请求路径前缀
            // value 为前缀所匹配到的代理规则配置
            proxy: {
              '/api': {
                // 代理目标
                // http://localhost:8080/api/users === https://api.github.com/api/users
                target: 'https://api.github.com',
                // 重写代理路径
                // key 重写的目标
                // value 重写的值
                // http://localhost:8080/api/users === https://api.github.com/users
                pathRewrite: {
                  '^/api': ''
                },
                // 不能使用 localhost:8080 作为请求 Github 的主机名
                changeOrigin: true
              }
            }
          },
          module: {
            rules: [
              {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
              },
              {
                test: /\.png$/,
                use: {
                  loader: 'url-loader',
                  options: {
                    limit: 10 * 1024
                  }
                }
              }
            ]
          },
          plugins: [
            // 指定一个对象参数
            new HtmlWebpackPlugin({
              // html 页面标题
              title: 'Webpack Plugin Sample',
              // html 中 meta
              meta: {
                viewport: 'width=device-width'
              },
              // 指定模板文件
              template: './src/index.html'
            }),
            // 创建额外的 html 文件
            new HtmlWebpackPlugin({
              filename: 'about.html'
            })
          ]
        }
        复制代码
      • webpack.dev.js

        const common = require('./webpack.common.js')
        // 用于合并公共配置
        const merge = require('webpack-merge')
        
        module.exports = merge(common, {
          mode: 'none',
          devtool: 'cheap-module-eval-source-map'
        })
        复制代码
      • webpack.prod.js

        const common = require('./webpack.common.js')
        // 用于合并公共配置
        const merge = require('webpack-merge')
        const { CleanWebpackPlugin } = require('clean-webpack-plugin')
        const CopyWebpackPlugin = require('copy-webpack-plugin')
        
        module.exports = merge(common, {
          mode: 'production',
          plugins: [
            new CleanWebpackPlugin(),
            new CopyWebpackPlugin({
              patterns: [
                { from: "./public", to: "." }
              ]
            })
          ]
        })
        复制代码
      • 运行打包

        • yarn webpack –config 配置文件

21. webpack DefinePlugin

  • 为代码注入全局成员

    const webpack = require('webpack')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      mode: 'development',
      entry: './src/main.js',
      output: {
        filename: 'js/bundle.js'
      },
      devServer:{
        // 开启热替换,如果热替换没有执行,会自动唤起页面刷新
        // hot: true,
        // 开启热替换,如果热替换没有执行,不会自动唤起页面刷新
        hotOnly: true
      },
      devtool: 'cheap-module-eval-source-map',
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              'style-loader',
              'css-loader'
            ]
          },
          {
            test: /\.(png|jpe?g|gif)$/,
            use: 'file-loader'
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/index.html'
        }),
        // 利用 webpack 的内置插件
        new webpack.HotModuleReplacementPlugin(),
        // 定义全局变量
        // 参数为一个对象
        new webpack.DefinePlugin({
          API_BASE_API: JSON.stringify('https://api.example.com')
        })
      ]
    }
    复制代码

22. Tree Shaking

  • 摇掉代码中未引用的部分

  • 在生产模式下自动执行

    const webpack = require('webpack')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      mode: 'development',
      entry: './src/main.js',
      output: {
        filename: 'js/bundle.js'
      },
      // 集中配置 webpack 的优化功能
      optimization:{
        // 只导出被使用的成员
        usedExports: true,
        // 压缩代码,并且去除无引用的代码
        minimize: true,
        // 合并模块
        // 作用:尽可能将所有的模块合并输出到一个函数中,即提升了运行效率,又减少了代码的体积
        concatenateModules: true
      },
      devServer:{
        // 开启热替换,如果热替换没有执行,会自动唤起页面刷新
        // hot: true,
        // 开启热替换,如果热替换没有执行,不会自动唤起页面刷新
        hotOnly: true
      },
      devtool: 'cheap-module-eval-source-map',
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              'style-loader',
              'css-loader'
            ]
          },
          {
            test: /\.(png|jpe?g|gif)$/,
            use: 'file-loader'
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/index.html'
        }),
        // 利用 webpack 的内置插件
        new webpack.HotModuleReplacementPlugin(),
        // 定义全局变量
        // 参数为一个对象
        new webpack.DefinePlugin({
          API_BASE_API: JSON.stringify('https://api.example.com')
        })
      ]
    }
    复制代码

23. webpack SideEffects

  • 副作用:模块执行时除了导出成员之外所作的事

  • 一般用于 npm 包标记是否有副作用

  • 生产模式下会自动开启

  • 会找到 package.json 中是否有 sideEffects 属性,通过配置值来确定项目中是否有副作用

  • package.json

    {
      "name": "my-webpack5",
      "version": "1.0.0",
      "description": "my-webpack5",
      "main": "index.js",
      "author": "康小源",
      "license": "MIT",
      "devDependencies": {
        "css-loader": "^5.0.1",
        "file-loader": "^6.2.0",
        "html-webpack-plugin": "^4.5.0",
        "style-loader": "^2.0.0",
        "webpack": "4.44.2",
        "webpack-cli": "3.3.12",
        "webpack-dev-server": "^3.11.0"
      },
      // 标识项目中所有的代码没有副作用
      // 没有用到的模块没有副作用会被移除
      "sideEffects": false
    }
    复制代码
  • webpack.config.js

    const webpack = require('webpack')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      mode: 'development',
      entry: './src/main.js',
      output: {
        filename: 'js/bundle.js'
      },
      // 集中配置 webpack 的优化功能
      optimization:{
        // 开启副作用
        sideEffects: true,
        // 只导出被使用的成员
        usedExports: true,
        // 压缩代码,并且去除无引用的代码
        minimize: true,
        // 合并模块
        // 作用:尽可能将所有的模块合并输出到一个函数中,即提升了运行效率,又减少了代码的体积
        concatenateModules: true
      },
      devServer:{
        // 开启热替换,如果热替换没有执行,会自动唤起页面刷新
        // hot: true,
        // 开启热替换,如果热替换没有执行,不会自动唤起页面刷新
        hotOnly: true
      },
      devtool: 'cheap-module-eval-source-map',
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              'style-loader',
              'css-loader'
            ]
          },
          {
            test: /\.(png|jpe?g|gif)$/,
            use: 'file-loader'
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/index.html'
        }),
        // 利用 webpack 的内置插件
        new webpack.HotModuleReplacementPlugin(),
        // 定义全局变量
        // 参数为一个对象
        new webpack.DefinePlugin({
          API_BASE_API: JSON.stringify('https://api.example.com')
        })
      ]
    }
    复制代码

24. 代码分割

  • 多入口打包

    const webpack = require('webpack')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      mode: 'development',
      // 配置一个打包入口
      // entry: './src/main.js',
      // 配置多个打包入口
      entry:{
        album: './src/album.js',
        index: './src/index.js'
      },
      output: {
        filename: 'js/[name].bundle.js'
      },
      // 集中配置 webpack 的优化功能
      optimization:{
        // 开启副作用
        sideEffects: true,
        // 只导出被使用的成员
        usedExports: true,
        // 压缩代码,并且去除无引用的代码
        minimize: true,
        // 合并模块
        // 作用:尽可能将所有的模块合并输出到一个函数中,即提升了运行效率,又减少了代码的体积
        concatenateModules: true,
        // 提取公共模块
        splitChunks: {
        	chunks: 'all'
        }
      },
      devServer:{
        // 开启热替换,如果热替换没有执行,会自动唤起页面刷新
        // hot: true,
        // 开启热替换,如果热替换没有执行,不会自动唤起页面刷新
        hotOnly: true
      },
      devtool: 'cheap-module-eval-source-map',
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              'style-loader',
              'css-loader'
            ]
          },
          {
            test: /\.(png|jpe?g|gif)$/,
            use: 'file-loader'
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/index.html',
          // 指定自己的 bundle.js
          chunk: ['index']
        }),
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/album.html',
          chunk: ['album']
        }),
        // 利用 webpack 的内置插件
        new webpack.HotModuleReplacementPlugin(),
        // 定义全局变量
        // 参数为一个对象
        new webpack.DefinePlugin({
          API_BASE_API: JSON.stringify('https://api.example.com')
        })
      ]
    }
    
    复制代码
  • 动态导入

    import(/* webpackChunkName:'posts' */./posts/poss').then(module => {
    	console.log(module)
    })
    复制代码

25. 提取 css 到单个文件

  • 安装 mini-css-extract-plugin,yarn add mini-css-extract-plugin –dev

    const webpack = require('webpack')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    
    module.exports = {
      mode: 'development',
      // 配置一个打包入口
      // entry: './src/main.js',
      // 配置多个打包入口
      entry:{
        album: './src/album.js',
        index: './src/index.js'
      },
      output: {
        filename: 'js/[name].bundle.js'
      },
      // 集中配置 webpack 的优化功能
      optimization:{
        // 开启副作用
        sideEffects: true,
        // 只导出被使用的成员
        usedExports: true,
        // 压缩代码,并且去除无引用的代码
        minimize: true,
        // 合并模块
        // 作用:尽可能将所有的模块合并输出到一个函数中,即提升了运行效率,又减少了代码的体积
        concatenateModules: true
      },
      devServer:{
        // 开启热替换,如果热替换没有执行,会自动唤起页面刷新
        // hot: true,
        // 开启热替换,如果热替换没有执行,不会自动唤起页面刷新
        hotOnly: true
      },
      devtool: 'cheap-module-eval-source-map',
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              // 'style-loader',
              MiniCssExtractPlugin.loader, // 通过 link 标签的方式注入
              'css-loader'
            ]
          },
          {
            test: /\.(png|jpe?g|gif)$/,
            use: 'file-loader'
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/index.html',
          // 指定自己的 bundle.js
          chunk: ['index']
        }),
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/album.html',
          chunk: ['album']
        }),
        // 利用 webpack 的内置插件
        new webpack.HotModuleReplacementPlugin(),
        // 定义全局变量
        // 参数为一个对象
        new webpack.DefinePlugin({
          API_BASE_API: JSON.stringify('https://api.example.com')
        }),
        new MiniCssExtractPlugin()
      ]
    }
    复制代码
  • 压缩提取的 css 文件

    • 安装 optimize-css-Assets-webpack-plugin,yarn add optimize-css-assets-webpack-plugin –dev

      const webpack = require('webpack')
      const HtmlWebpackPlugin = require('html-webpack-plugin')
      const MiniCssExtractPlugin = require('mini-css-extract-plugin')
      const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
      const TerserWebpackPlugin = require('terser-webpack-plugin')
      
      module.exports = {
        mode: 'development',
        // 配置一个打包入口
        // entry: './src/main.js',
        // 配置多个打包入口
        entry:{
          album: './src/album.js',
          index: './src/index.js'
        },
        output: {
          filename: 'js/[name].bundle.js'
        },
        // 集中配置 webpack 的优化功能
        optimization:{
          // 开启副作用
          sideEffects: true,
          // 只导出被使用的成员
          usedExports: true,
          // 压缩代码,并且去除无引用的代码
          minimize: true,
          minimizer: [
            // 压缩 css 文件
            new OptimizeCssAssetsWebpackPlugin(),
            // 压缩 js 文件
            new TerserWebpackPlugin()
          ],
          // 合并模块
          // 作用:尽可能将所有的模块合并输出到一个函数中,即提升了运行效率,又减少了代码的体积
          concatenateModules: true
        },
        devServer:{
          // 开启热替换,如果热替换没有执行,会自动唤起页面刷新
          // hot: true,
          // 开启热替换,如果热替换没有执行,不会自动唤起页面刷新
          hotOnly: true
        },
        devtool: 'cheap-module-eval-source-map',
        module: {
          rules: [
            {
              test: /\.css$/,
              use: [
                // 'style-loader',
                MiniCssExtractPlugin.loader, // 通过 link 标签的方式注入
                'css-loader'
              ]
            },
            {
              test: /\.(png|jpe?g|gif)$/,
              use: 'file-loader'
            }
          ]
        },
        plugins: [
          new HtmlWebpackPlugin({
            title: 'Webpack Tutorial',
            template: './src/index.html',
            // 指定自己的 bundle.js
            chunk: ['index']
          }),
          new HtmlWebpackPlugin({
            title: 'Webpack Tutorial',
            template: './src/album.html',
            chunk: ['album']
          }),
          // 利用 webpack 的内置插件
          new webpack.HotModuleReplacementPlugin(),
          // 定义全局变量
          // 参数为一个对象
          new webpack.DefinePlugin({
            API_BASE_API: JSON.stringify('https://api.example.com')
          }),
          new MiniCssExtractPlugin()
        ]
      }
      复制代码

26. 文件名 Hash

  • 生产模式下,文件名需要使用 Hash

    const webpack = require('webpack')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
    const TerserWebpackPlugin = require('terser-webpack-plugin')
    
    module.exports = {
      mode: 'development',
      // 配置一个打包入口
      // entry: './src/main.js',
      // 配置多个打包入口
      entry:{
        album: './src/album.js',
        index: './src/index.js'
      },
      output: {
        // 项目级别的 hash,一旦项目中有改动,hash 值改变
        // filename: 'js/[name]-[hash].bundle.js'
        // chunk 级别的 hash,一旦属于同一路 chunk 有改变,hash 值改变
        // filename: 'js/[name]-[chunkhash].bundle.js'
        // 文件级别的 hash,一旦文件改变,hash 值改变
        // :8 控制 hash 值的长度
        filename: 'js/[name]-[contenthash:8].bundle.js'
      },
      // 集中配置 webpack 的优化功能
      optimization:{
        // 开启副作用
        sideEffects: true,
        // 只导出被使用的成员
        usedExports: true,
        // 压缩代码,并且去除无引用的代码
        minimize: true,
        minimizer: [
          // 压缩 css 文件
          new OptimizeCssAssetsWebpackPlugin(),
          // 压缩 js 文件
          new TerserWebpackPlugin()
        ],
        // 合并模块
        // 作用:尽可能将所有的模块合并输出到一个函数中,即提升了运行效率,又减少了代码的体积
        concatenateModules: true
      },
      devServer:{
        // 开启热替换,如果热替换没有执行,会自动唤起页面刷新
        // hot: true,
        // 开启热替换,如果热替换没有执行,不会自动唤起页面刷新
        hotOnly: true
      },
      devtool: 'cheap-module-eval-source-map',
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              // 'style-loader',
              MiniCssExtractPlugin.loader, // 通过 link 标签的方式注入
              'css-loader'
            ]
          },
          {
            test: /\.(png|jpe?g|gif)$/,
            use: 'file-loader'
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/index.html',
          // 指定自己的 bundle.js
          chunk: ['index']
        }),
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/album.html',
          chunk: ['album']
        }),
        // 利用 webpack 的内置插件
        new webpack.HotModuleReplacementPlugin(),
        // 定义全局变量
        // 参数为一个对象
        new webpack.DefinePlugin({
          API_BASE_API: JSON.stringify('https://api.example.com')
        }),
        new MiniCssExtractPlugin({
          filename: '[name]-[contenthash:8].bundle.css'
        })
      ]
    }
    复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享