webpack学习

4 阅读5分钟
  • 入口(entry)

声明项目入口文件。支持字符串,对象,函数,数组形式来配置。

module.exports = {
  entry: {
    // 字符串形态
    home: './home.js',
    // 数组形态
    shared: ['react', 'react-dom', 'redux', 'react-redux'],
    // 对象形态
    personal: {
      import: './personal.js',
      filename: 'pages/personal.js',
      dependOn: 'shared',
      chunkLoading: 'jsonp',
      asyncChunks: true
    },
    // 函数形态
    admin: function() {
      return './admin.js';
    }
  },
};

对象形式支持如下配置属性

import:声明入口文件,支持路径字符串或路径数组(多入口)

dependOn:当前入口所依赖的入口。它们必须在该入口被加载前被加载

runtime:设置该入口的 Runtime Chunk,若该属性不为空,Webpack 会将该入口的运行时代码抽离成单独的 Bundle

filename:效果与 output.filename 类同,用于声明该模块编译结果路径

library:声明该入口的 output.library 配置,一般在编译 NPM Library 时使用

publicPath:效果与 output.publicPath 相同,用于声明该入口文件的发布 URL

// 加载foo之前先加载main,可以将重复的模块代码、运行时代码等都放到main中
module.exports = {
  // ...
  entry: {
    main: "./src/index.js",
    foo: { import: "./src/foo.js", dependOn: "main" },
  },
};

// main与foo入口的运行时代码都抽取出来,放在common-runtime的Bundle中
const path = require("path");

module.exports = {
  mode: "development",
  devtool: false,
  entry: {
    main: { import: "./src/index.js", runtime: "common-runtime" },
    foo: { import: "./src/foo.js", runtime: "common-runtime" },
  },
  output: {
    clean: true,
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),
  },
};

  • 输出(output)

声明编译结果的存放位置。

output.path:声明编译结果放在什么文件目录下

output.filename:声明编译结果文件名规则,支持 [name]/[hash] 等占位符

output.publicPath:文件发布路径,在 Web 应用中使用率较高

output.clean:是否自动清除 path 目录下的内容,调试时特别好用

output.library:NPM Library 形态下的一些产物特性,例如:Library 名称、模块化(UMD/CMD 等)规范

output.chunkLoading:声明加载异步模块的技术方案,支持 false/jsonp/require 等方式

多数情况下只需要使用 path/filename/publicPath 即可满足需求。

  • 编译目标(target)

用于配置编译目标的运行环境。

虽然多数时候 Webpack 都被用于打包 Web 应用,但实际上 Webpack 还支持构建 Node、Electron、NW.js、WebWorker 等应用形态,这一特性主要通过 target 配置控制,支持如下数值:

web:构建为 Web 应用。

node[[X].Y]:编译为 Node 应用,此时将使用 Node 的 require 方法加载其它 Chunk,支持指定 Node 版本,如:node12.13。

  • 编译模式(mode)

支持 development/production/none 三种值

production:默认值,生产模式,使用该值时 Webpack 会自动帮我们开启一系列优化措施:Three-Shaking、Terser 压缩代码、SplitChunk 提起公共代码,通常用于生产环境构建。

development:开发模式,使用该值时 Webpack 会保留更语义化的 Module 与 Chunk 名称,更有助于调试,通常用于开发环境构建。

none:关闭所有内置优化规则。

  • 加载器(loader)

常用的加载器:

压缩图片:image-webpack-loader

使用 Babel:@babel/core @babel/preset-env babel-loader

使用 TypeScript:ts-loader

处理CSS:css-loader style-loader mini-css-extract-plugin

处理Less:css-loader style-loader less-loader

  • 插件(plugin)

常用的插件:

使用 ESLint:eslint-webpack-plugin

  • resolve

配置模块路径解析规则

  • externals

声明外部资源,Webpack 会直接忽略这部分资源,跳过这些资源的解析、打包操作。

  • optimization

控制如何优化产物包体积,内置 Dead Code Elimination、Scope Hoisting、代码混淆、代码压缩等功能。

  • 图片处理
  1. file-loader 等同于 type = "asset/resource"
module.exports = {
  module: {
    rules: [{
      test: /\.(png|jpg)$/,
      type: 'asset/resource'
    }],
  },
};

asset/resource 生成的文件会以 [hash][ext][query] 方式重命名并复制到编译目录下,同时在代码中插入文件 URL 地址。

  1. url-loader 等同于 type = "asset" 或 type = "asset/inline"
module.exports = {
  module: {
    rules: [{
      test: /\.(png|jpg)$/,
      type: "asset",
      parser: {
         dataUrlCondition: {
           maxSize: 1024 // 限定文件大小 1kb
         }
      }
    }],
  },
};

对于小于 parser.dataUrlCondition 的图像直接转化为 base64 编码,大于 parser.dataUrlCondition 的图像则调用 file-loader 进行加载。

  1. raw-loader 等同于 type = "asset/source"
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/i,
        type: "asset/source"
      },
    ],
  },
};

将文件内容复制到编译目录中,适用于 SVG 场景。

  1. 图像压缩
yarn add -D image-webpack-loader

module.exports = {
  module: {
    rules: [{
      test: /\.(gif|png|jpe?g|svg)$/i,
      type: "asset/resource",
      use: [{
        loader: 'image-webpack-loader',
        options: {
          // jpeg 压缩配置
          mozjpeg: {
            quality: 80
          },
        }
      }]
    }],
  },
};

mozjpeg:用于压缩 JPG(JPEG) 图片

optipng:用于压缩 PNG 图片

pngquant:同样用于压缩 PNG 图片

svgo:用于压缩 SVG 图片

gifsicle:用于压缩 Gif 图

webp:用于将 JPG/PNG 图压缩并转化为 WebP 图片格式

  • 使用 Babel

能将高版本如 ES6 代码等价转译为向后兼容,能直接在旧版 JavaScript 引擎运行的低版本代码。

npm i -D @babel/core @babel/preset-env babel-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        ],
      },
    ],
  },
};
  • 使用 TypeScript
npm i -D ts-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  }
};
  • 使用 ESLint
yarn add -D eslint-webpack-plugin

const ESLintPlugin = require('eslint-webpack-plugin')
module.exports = {
  plugins: [new ESLintPlugin()]
}
  • 处理CSS

css-loader:和处理 JS 代码一样解析 CSS 内容与资源依赖

style-loader:将 CSS 内容注入到页面的 style 标签,使得样式生效

mini-css-extract-plugin:将 CSS 代码抽离到单独的 .css 文件,并将文件通过 link 标签方式插入到页面中

yarn add -D style-loader css-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};


yarn add -D mini-css-extract-plugin

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HTMLWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    module: {
        rules: [{
            test: /\.css$/,
            use: [
                // 根据运行环境判断使用那个 loader
                // 生产环境用 mini-css-extract-plugin
                // 测试环境用 style-loader
                (process.env.NODE_ENV === 'development' ?
                    'style-loader' :
                    MiniCssExtractPlugin.loader),
                'css-loader'
            ]
        }]
    },
    plugins: [
        new MiniCssExtractPlugin(),
        new HTMLWebpackPlugin()
    ]
}
  • 使用 Less
yarn add -D less-loader

module.exports = {
    module: {
        rules: [{
            test: /\.less$/,
            use: [
                'style-loader',
                'css-loader',
                'less-loader'
            ]
        }]
    }
}
  • 配置结构
  1. 单个配置对象
module.exports = {
  entry: './src/index.js',
  // 其它配置...
};
  1. 配置对象数组

每个数组项都是一个完整的配置对象,每个对象都会触发一次单独的构建,通常用于需要为同一份代码构建多种产物的场景,例如在构建 Library 时,我们通常需要同时构建出 ESM/CMD/UMD 等模块方案的产物。

可以将公共配置抽取为 baseConfig 对象,之后配合 webpack-merge 创建不同目标数组项。

// yarn add -D webpack-merge

const { merge } = require("webpack-merge");

const baseConfig = {
  output: {
    path: "./dist"
  },
  name: "amd",
  entry: "./app.js",
  mode: "production",
};

module.exports = [
  merge(baseConfig, {
    output: {
      filename: "[name]-amd.js",
      libraryTarget: "amd",
    },
  }),
  merge(baseConfig, {
    output: {
      filename: "./[name]-commonjs.js",
      libraryTarget: "commonjs",
    },
  }),
];

若是"多份代码打包多份产物"的场景,则建议使用 entry 配置多个应用入口。

  1. 函数

函数根据环境参数动态调整配置对象,Webpack 启动时会执行该函数获取参数,函数返回 Webpack 配置对象,或配置数组,或 Promise 对象。

运行时 Webpack 会传入两个环境参数对象env和argv:

  • env:通过 --env 传递的命令行参数,适用于自定义参数
命令env 参数值
npx webpack --env prod{ prod: true }
npx webpack --env platform=app --env production{ platform: "app", production: true }
npx webpack --env app.platform="staging" --env app.name="test"{ app: { platform: "staging", name: "test" }
  • argv:命令行 Flags 参数,支持 entry/output-path/mode/merge 等。
// 通过命令行传入 env.app.type 与 argv.mode 值
// npx webpack --env app.type=miniapp --mode=production

module.exports = function (env, argv) {
  return {
    mode: argv.mode ? "production" : "development",
    devtool: argv.mode ? "source-map" : "eval",
    output: {
      path: path.join(__dirname, `./dist/${env.app.type}`,
      filename: '[name].js'
    },
    plugins: [
      new TerserPlugin({
        terserOptions: {
          compress: argv.mode === "production", 
        },
      }),
    ],
  };
};

用户根据命令行参数动态创建配置对象,实现简单的多环境部署策略。

  • 环境部署策略

将同一个应用部署在不同环境(如生产环境、开发环境、测试环境)中。通常会将部分通用配置放在基础文件中,其它不同环境的配置文件引入该模块并使用 webpack-merge 合并配置对象。

// webpack.common.js
const path = require("path");
const HTMLWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: { main: "./src/index.js" },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ["babel-loader"],
      },
    ],
  },
  plugins: [new HTMLWebpackPlugin()],
};

// yarn add -D webpack-merge

// webpack.development.js
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.common");

// 使用 webpack-merge 合并配置对象
module.exports = merge(baseConfig, {
  mode: "development",
  devtool: "source-map",
  devServer: { hot: true },
});

// 执行构建命令
npx webpack --config=webpack.development.js