webpack

99 阅读9分钟

Webpack 基础

项目初始化

npm init

安装依赖

npm install webpack webpack-cli -D

loader

  • webpack 默认支持处理 JS 与 JSON 文件,必须借助 Loader 来对不同类型的文件的进行处理
  • Loader 的执行顺序是固定从后往前

css-loader

  1. 安装
# 支持css文件的loade
npm install css-loader -D

style-loader

  • 单靠 css-loader 是没有办法将样式加载到页面上。这个时候,我们需要再安装一个 style-loader 来完成这个功能
  1. 安装
# 支持css文件的loade
npm install style-loader -D

1.引入 css

import './main.css'

postcss-loader

  • 使用 postcss-loader,自动添加 CSS3 部分属性的浏览器前缀
  1. 安装
npm install postcss-loader postcss -D

less-loader

  1. 安装
# 支持less文件的loade
npm install less-loader -D

sass-loader

-Sass 不光需要安装 sass-loader 还得搭配一个 node-sass,这里 node-sass 建议用淘宝镜像来安装

  1. 安装
npm install sass-loader -D
# 淘宝镜像
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

file-loader 处理图片的 loader

  • 识别图片的 loader
  • url-loader: 解依赖 file-loader,当图片小于 limit 值的时候,会将图片转为 base64 编码,大于 limit 值的时候依然是使用 file-loader 进行拷贝
  • img-loader: 压缩图片
  1. 安装
npm install file-loader -D

npm install url-loader -D

配置 loader

// webpack.config.js
// loader配置
module.exports = {
  mode: 'development', // 模式
  entry: './src/main.css', // 打包入口地址
  output: {
    filename: 'bundle.css', // 输出文件名
    path: path.join(__dirname, 'dist'), // 输出文件目录
  },
  module: {
    rules: [
      // 转换规则
      {
        test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
      },
      {
        test: /\.(jpe?g|png|gif)$/i, // 匹配图片文件
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name][hash:8].[ext]',
            },
          },
        ],
        // use:[
        //   {
        //     loader: 'url-loader',
        //     options: {
        //       name: '[name][hash:8].[ext]',
        //       // 文件小于 50k 会转换为 base64,大于则拷贝文件
        //       limit: 50 * 1024
        //     }
        //   }
        // ]
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 匹配字体文件
        use: [
          {
            loader: 'url-loader',
            options: {
              name: 'fonts/[name][hash:8].[ext]', // 体积大于 10KB 打包到 fonts 目录下
              limit: 10 * 1024,
            },
          },
        ],
      },
    ],
  },
}

webpack5 新增资源模块使用(无需资源文件相关 loader)

  1. asset/resource 将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能.
  2. asset/inline 将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能.
  3. asset/source 将资源导出为源码(source code). 类似的 raw-loader 功能.
  4. asset 会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource
  • 配置
const config = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.(jpe?g|png|gif)$/i,
        type: 'asset',
        generator: {
          // 输出文件位置以及文件名
          // [ext] 自带 "." 这个与 url-loader 配置不同
          filename: '[name][hash:8][ext]',
        },
        parser: {
          dataUrlCondition: {
            maxSize: 50 * 1024, //超过50kb不转 base64
          },
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        type: 'asset',
        generator: {
          // 输出文件位置以及文件名
          filename: '[name][hash:8][ext]',
        },
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 超过100kb不转 base64
          },
        },
      },
    ],
  },
  // ...
}

module.exports = (env, argv) => {
  console.log('argv.mode=', argv.mode) // 打印 mode(模式) 值
  // 这里可以通过不同的模式修改 config 配置
  return config
}

babel-loader

  • 在开发中我们想使用最新的 Js 特性,但是有些新特性的浏览器支持并不是很好,所以 Js 也需要做兼容处理,常见的就是将 ES6 语法转化为 ES5。
  1. 安装
npm install babel-loader @babel/core @babel/preset-env -D
#babel-loader 使用 Babel 加载 ES2015+ 代码并将其转换为 ES5
#@babel/core Babel 编译的核心包
#@babel/preset-env Babel 编译的预设,可以理解为 Babel 插件的超集

#ECMA 规范中的新特性(装饰器)
npm install babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
  1. 配置
// webpack.config.js
// ...
const config = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/i,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        ],
      },
      // ...
    ],
  },
  //...
}
// 或者单独配置
// .babelrc.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // useBuiltIns: false 默认值,无视浏览器兼容配置,引入所有 polyfill
        // useBuiltIns: entry 根据配置的浏览器兼容,引入浏览器不兼容的 polyfill
        // useBuiltIns: usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加
        useBuiltIns: 'entry',
        corejs: '3.9.1', // 是 core-js 版本号
        targets: {
          chrome: '58',
          ie: '11',
        },
      },
    ],
  ],
  // ECMA 规范中的新特性插件
  // plugins: [
  //   ['@babel/plugin-proposal-decorators', { legacy: true }],
  //   ['@babel/plugin-proposal-class-properties', { loose: true }],
  // ],
}

plugin(插件)

  • 插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务

html-webpack-plugin

1.安装

# js 或者 css 文件可以自动引入到 Html 中,就需要使用插件 html-webpack-plugin来帮助你完成这个操作
npm install html-webpack-plugin -D
  1. 配置
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  //...
  plugins: [
    // 配置插件
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
}

clean-webpack-plugin

  • 自动清空上次打包目录
  1. 安装
npm install clean-webpack-plugin -D
  1. 配置
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  // ...
  plugins: [
    new CleanWebpackPlugin(), // 引入插件
  ],
}

mini-css-extract-plugin 分离样式文件

  • 依赖 style-loader 将样式通过 style 标签的形式添加到页面上,更多时候,我们都希望可以通过 CSS 文件的形式引入到页面上
  1. 安装
npm install mini-css-extract-plugin -D
  1. 配置
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const config = {
  // ...
  // ...
  plugins: [
    // 配置插件
    // ...
    new MiniCssExtractPlugin({
      // 添加插件
      filename: '[name].[hash:8].css',
    }),
    // ...
  ],
}

环境配置 cross-env

  1. 安装
npm install cross-env -D
  1. 配置
// ./package.json
"scripts": {
    "dev": "cross-env NODE_ENV=dev webpack serve --mode development",
    "test": "cross-env NODE_ENV=test webpack --mode production",
    "build": "cross-env NODE_ENV=prod webpack --mode production"
  },
  1. 打印环境变量
// webpack.config.js
console.log('process.env.NODE_ENV=', process.env.NODE_ENV) // 打印环境变量 dev test prod

const config = {
  //...
}

module.exports = (env, argv) => {
  console.log('argv.mode=', argv.mode) // 打印 mode(模式)值 development production production
  // 这里可以通过不同的模式修改 config 配置
  return config
}

本地服务 webpack-dev-server

  1. 安装
npm intall webpack-dev-server@3.11.2 -D
  • ⚠️ 注意:本文使用的 webpack-dev-server 版本是 3.11.2,当版本 version >= 4.0.0 时,需要使用 devServer.static 进行配置,不再有 devServer.contentBase 配置项。
  1. 配置
// webpack.config.js
const config = {
  // ...
  devServer: {
    contentBase: path.resolve(__dirname, 'public'), // 静态文件目录
    compress: true, //是否启动压缩 gzip
    port: 8080, // 端口号
    // open:true  // 是否自动打开浏览器
  },
  // ...
}
module.exports = (env, argv) => {
  console.log('argv.mode=', argv.mode) // 打印 mode(模式) 值
  // 这里可以通过不同的模式修改 config 配置
  return config
}
  • 配置 contentBase 直接到对应的静态目录下面去读取文件,而不需对文件做任何移动,节省了时间和性能开销。

SourceMap 配置 devtool (本地开发查错工具)

  • SourceMap 是一种映射关系,当项目运行后,如果出现错误,我们可以利用 SourceMap 反向定位到源码位置
  1. 配置
// webpack.config.js
const config = {
  // ...
  devtool: 'source-map',
  // ...
}
  • 执行打包后,dist 目录下会生成以 .map 结尾的 SourceMap 文件

  • 不同配置不同效果 devtool | build | rebuild | 显示代码 | SourceMap 文件 | 描述 (none) | 很快 | 很快 | 无 | 无 | 无法定位错误 eval| 快| 很快(cache)| 编译后| 无 | 定位到文件 source-map| 很慢| 很慢| 源代码有| 定位到行列 eval-source-map| 很慢| 一般(cache)| 编译后有(dataUrl)| 定位到行列 eval-cheap-source-map| 一般| 快(cache)| 编译后有(dataUrl)| 定位到行 eval-cheap-module-source-map| 慢| 快(cache)| 源代码有(dataUrl)| 定位到行 inline-source-map| 很慢| 很慢| 源代码有(dataUrl)| 定位到行列 hidden-source-map| 很慢| 很慢| 源代码有| 无法定位错误 nosource-source-map| 很慢| 很慢| 源代码无| 定位到文件

  • 本地开发:

推荐:eval-cheap-module-source-map

理由: 本地开发首次打包慢点没关系,因为 eval 缓存的原因,rebuild 会很快 开发中,我们每行代码不会写的太长,只需要定位到行就行,所以加上 cheap 我们希望能够找到源代码的错误,而不是打包后的,所以需要加上 module

生产环境: 推荐:(none) 理由: 就是不想别人看到我的源代码

Webpack 进阶

优化构建速度

resolve.alias

  • 用的创建 import 或 require 的别名,用来简化模块引用,项目中基本都需要进行配置。
const path = require('path')
//...
// 路径处理方法
function resolve(dir) {
  return path.join(__dirname, dir)
}

const config = {
  //...
  resolve: {
    // 配置别名
    alias: {
      '~': resolve('src'),
      '@': resolve('src'),
      components: resolve('src/components'),
    },
  },
}

resolve.extensions

  • 引入模块时允许不带扩展名
const config = {
  //...
  resolve: {
    extensions: ['.js', '.json', '.wasm'],
  },
}

resolve.modules

  • 告诉 webpack 优先 src 目录下查找需要解析的文件,会大大节省查找时间
const path = require('path')

// 路径处理方法
function resolve(dir) {
  return path.join(__dirname, dir)
}

const config = {
  //...
  resolve: {
    modules: [resolve('src'), 'node_modules'],
  },
}

externals

  • externals 配置选项提供了「从输出的 bundle 中排除依赖,剥离不需要改动的一些依赖,大大节省打包构建的时间
// 使用时import即可
const config = {
  //...
  externals: {
    jquery: 'jQuery',
    vue: 'Vue',
    'element-ui': 'Element',
    'vue-router': 'VueRouter',
    axios: 'axios',
  },
}

缩小 loader 解析范围

  • 在配置 loader 的时候,我们需要更精确的去指定 loader 的作用目录或者需要排除的目录,通过使用 include 和 exclude 两个配置项,可以实现这个功能,常见的例如:

  • include:符合条件的模块进行解析

  • exclude:排除符合条件的模块,不解析

  • exclude 优先级更高

  • 例如在配置 babel 的时候

const path = require('path')

// 路径处理方法
function resolve(dir) {
  return path.join(__dirname, dir)
}

const config = {
  //...
  module: {
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      // ...
    ],
  },
}

多进程配置

  • thread-loader
  1. 安装
npm i -D  thread-loader
  1. 配置
const path = require('path')

// 路径处理方法
function resolve(dir) {
  return path.join(__dirname, dir)
}

const config = {
  //...
  module: {
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          {
            loader: 'thread-loader', // 开启多进程打包
            options: {
              worker: 3,
            },
          },
          'babel-loader',
        ],
      },
      // ...
    ],
  },
}
  • happypack (webpack5 已弃用)

babel-loader 开启缓存

  • babel 在转译 js 过程中时间开销比价大,将 babel-loader 的执行结果缓存起来,重新打包的时候,直接读取缓存
  1. 配置
const config = {
  module: {
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          // ...
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true, // 启用缓存
            },
          },
        ],
      },
      // ...
    ],
  },
}

cache-loader

  • 其他 loader 缓存
  1. 安装
npm i -D cache-loader
  1. 配置 cache-loader
const config = {
  module: {
    // ...
    rules: [
      {
        test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
        use: [
          // 'style-loader',
          MiniCssExtractPlugin.loader,
          'cache-loader', // 获取前面 loader 转换的结果
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },
      // ...
    ],
  },
}

cache 持久化缓存

  • 通过配置 cache 缓存生成的 webpack 模块和 chunk,来改善构建速度。
const config = {
  cache: {
    type: 'filesystem',
  },
}

优化构建结果

构建结果分析 webpack-bundle-analyzer

  • webpack-bundle-analyzer 可以直观的看到打包结果中,文件的体积大小、各模块依赖关系、文件是够重复等问题,极大的方便我们在进行项目优化的时候,进行问题诊断。
  1. 安装
npm i -D webpack-bundle-analyzer
  1. 配置
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin

const config = {
  // ...
  plugins: [
    // ...
    // 配置插件
    new BundleAnalyzerPlugin({
      // analyzerMode: 'disabled',  // 不启动展示打包报告的http服务器
      // generateStatsFile: true, // 是否生成stats.json文件
    }),
  ],
}
  1. 启动命令
"scripts": {
    // ...
    "analyzer": "cross-env NODE_ENV=prod webpack --progress --mode production"
  },
  1. npm run analyzer

压缩 CSS

  • optimize-css-assets-webpack-plugin
  1. 安装
npm install -D optimize-css-assets-webpack-plugin
  1. 配置
// 压缩css
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// ...

const config = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      // 添加 css 压缩配置
      new OptimizeCssAssetsPlugin({}),
    ],
  },
  // ...
}

压缩 JS terser-webpack-plugin

  • 因为 webpack5 内置了 terser-webpack-plugin 插件,所以我们不需重复安装,直接引用就可以了,具体配置如下
  1. 配置
const TerserPlugin = require('terser-webpack-plugin')

const config = {
  // ...
  optimization: {
    minimize: true, // 开启最小化
    minimizer: [
      // ...
      new TerserPlugin({}),
    ],
  },
  // ...
}

清除无用的 CSS

  • purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS
  1. 安装
npm i -D purgecss-webpack-plugin
  1. 添加配置
// ...
const PurgecssWebpackPlugin = require('purgecss-webpack-plugin')
const glob = require('glob') // 文件匹配模式
// ...

function resolve(dir) {
  return path.join(__dirname, dir)
}

const PATHS = {
  src: resolve('src'),
}

const config = {
  plugins: [
    // 配置插件
    // ...
    new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
    }),
  ],
}

Tree-shaking

Tree-shaking 作用是剔除没有使用的代码,以降低包的体积

  • webpack 默认支持,需要在 .bablerc 里面设置 model:false,即可在生产环境下默认开启

优化运行时体验

  1. 运行时优化的核心就是提升首屏的加载速度,主要的方式就是

降低首屏加载文件体积,首屏不需要的文件进行预加载或者按需加载

代码懒加载 prefetch 与 preload 上面我们使用异步加载的方式引入图片的描述,但是如果需要异步加载的文件比较大时,在点击的时候去加载也会影响到我们的体验,这个时候我们就可以考虑使用 prefetch 来进行预拉取 3.4.1 prefetch

prefetch (预获取):浏览器空闲的时候进行资源的拉取

复制代码 3.4.2 preload

preload (预加载):提前加载后面会用到的关键资源 ⚠️ 因为会提前拉取资源,如果不是特殊需要,谨慎使用

  1. 入口点分割

配置多个打包入口,多页打包