# Webpack 基础

# 为什么需要前端构建工具

  • 解决模块化的问题,由于部分浏览器并不支持 ES Module 的方式,我们需要将我们的源代码转换成浏览器认识的格式

  • 解决CSS、JS代码的浏览器兼容性问题

  • 对 HTML 代码、CSS 代码、JS 代码、图片等资源进行压缩

  • 对未使用的代码或运行不到的代码进行删除

  • 将较大的文件分离成多个较小的文件(代码分割)、将较小的文件进行合并

  • 对 .jsx、.tsx、.vue、.less、.sass等文件进行解析,转换成浏览器能识别的代码(loader解析)

  • 进行代码校验以及类型校验

  • 对第三方模块进行抽离

# Webpack 核心概念

Webpack 核心概念 (opens new window)

# webpack.config.js

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

module.exports = {
  // 入口文件
  entry: './src/index.js',

  // 输出配置
  output: {
    // 输出目录
    path: path.resolve(__dirname, 'dist'),
    // 输出文件名
    filename: 'bundle.js',
  },

  // 加载器配置
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },

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

  // 优化配置
  optimization: {
    splitChunks: {
      chunks: 'all',
    }
  }
};

# entry

指定 Webpack 打包⼊⼝⽂件: Webpack 执⾏构建的第⼀步将从 Entry 开始,可抽象成输⼊

//单⼊⼝ SPA,本质是个字符串
entry: {
  main: './src/index.js'
},

// 相当于简写
entry: './src/index.js',

//多⼊⼝ entry是个对象
entry: {
  index: './src/index.js',
  login: './src/login.js'
}

# output

打包转换后的⽂件输出到磁盘位置: 输出结果,在 Webpack 经过⼀系列处理并得出最终想要的代码后输出结果。

output: {
  filename: 'bundle.js', //输出⽂件的名称
  path: path.resolve(__dirname, 'dist') //输出⽂件到磁盘的⽬录,必须是绝对路径
},

//多⼊⼝的处理
output: {
  filename: '[name][chunkhash:8].js', //利⽤占位符,⽂件名称不要重复
  path: path.resolve(__dirname, 'dist') //输出⽂件到磁盘的⽬录,必须是绝对路径
}

# mode

Mode ⽤来指定当前的构建环境

  • production
  • development
  • none

设置 mode 可以⾃动触发 Webpack 内置的函数,达到优化的效果

开发阶段的开启会有利于热更新的处理,识别哪个模块变化

⽣产阶段的开启会有帮助模块压缩,处理副作⽤等⼀些功能

# loader

模块解析,模块转换器,⽤于把模块原内容按照需求转换成新内容。

Webpack 是模块打包⼯具,⽽模块不仅仅是 js,还可以是 css,图⽚或者其他格式 。

但是 Webpack 默认只知道如何处理 js 和 JSON 模块,那么其他格式的模块处理,和处理⽅式就需要 loader了。

常见的loader

style-loader
css-loader
less-loader
sass-loader
ts-loader     //将Ts转换成js
babel-loader  //转换ES6、7等js新特性语法
file-loader   //处理图⽚⼦图
eslint-loader

# 自己编写loader

//webpack.config.js
module: {
  rules: [{
    test: /\.js$/,
    use: [{
      loader: path.resolve(__dirname, "./loader/replaceLoader.js"),
      options: {
        name: "hello"
      }
    }]
  }]
}

// replaceLoader.js
// const loaderUtils = require("loader-utils");//官⽅推荐处理loader,query的⼯具

module.exports = function (source) {
  //this.query 通过 this.query 来接受配置⽂件传递进来的参数
  const options = this.getOptions();
  const result = source.replace('name', options.name);
  return source.replace('name', options.name);
};

异步调用

module.exports = function (source) {
  const options = this.getOptions();
  //定义⼀个异步处理,告诉webpack,这个loader⾥有异步事件,在⾥⾯调⽤下这个异步
  //callback 就是 this.callback 注意参数的使⽤
  const callback = this.async();
  setTimeout(() => {
    const result = source.replace('name', options.name);
    callback(null, result);
  }, 3000);
};

# 多个loader的使用

顺序,⾃下⽽上,⾃右到左

{
  test: /\.css$/,
  // loader 的执行顺序是:从后往前
  use: ['style-loader', 'css-loader', 'postcss-loader']
}

# module

模块,在 Webpack ⾥⼀切皆模块,⼀个模块对应着⼀个⽂件。Webpack 会从配置的 Entry 开始递归找 出所有依赖的模块。 当 Webpack 处理到不认识的模块时,需要在 Webpack 中的 module 处进⾏配置,当检测到是什么格式的 模块,使⽤什么loader来处理。

module: {
  rules: [{
    test: /\.xxx$/, //指定匹配规则
    use: {
      loader: 'xxx-load' //指定使⽤的loader
    }
  }]
}

loader: file-loader:处理静态资源模块

原理是把打包⼊⼝中识别出的资源模块,移动到输出⽬录,并且返回⼀个地址名称 所以我们什么时候⽤ file-loader 呢? 场景:就是当我们需要模块,仅仅是从源代码挪移到打包⽬录,就可以使⽤ file-loader来处理, txt,svg,csv,excel,图⽚资源等等

# Plugins

plugin 可以在webpack运⾏到某个阶段的时候,帮你做⼀些事情,类似于⽣命周期的概念 扩展插件,在 Webpack 构建流程中的特定时机注⼊扩展逻辑来改变构建结果或做你想要的事情。 作⽤于整个构建过程。

Plugin: 开始打包,在某个时刻,帮助我们处理⼀些什么事情的机制

plugin 要⽐ loader 稍微复杂⼀些,在 Webpack 的源码中,⽤ plugin 的机制还是占有⾮常⼤的场景,可以说 plugin 是webpack的灵魂

设计模式

  • 事件驱动
  • 发布订阅

plugin 是⼀个类,⾥⾯包含⼀个 apply 函数,接受⼀个参数,compiler

案例:

// webpack配置⽂件
const CopyrightWebpackPlugin = require("./plugin/copyright-webpack-plugin");
plugins: [
  new CopyrightWebpackPlugin({
    name: "hello"
  })
]

// copyright-webpack-plugin.js
class CopyrightWebpackPlugin {
  constructor(options) {
    //接受参数
    console.log(options);
  }
  apply(compiler) {}
}
module.exports = CopyrightWebpackPlugin;

# 配置 plugin 在什么时刻进⾏

Webpack 在编译代码过程中,会触发⼀系列 tapable 钩⼦事件,插件所做的,就是找到相应的钩⼦,往上⾯挂上⾃⼰的任务,也就是注册事件,这样,当 Webpack 构建的时候,插件注册的事件就会随着钩⼦的触发⽽执⾏了。

const compiler = Webpack(config);
Objet.keys(compiler.hooks).forEach(hookName => {
    compiler.hooks[hookName].tap(('xxx', () => {
        console.log(`run====> ${hookName}`)
    }))
})

class CopyrightWebpackPlugin {
  constructor(options) {
    // console.log(options);
  }
  apply(compiler) {
    //hooks.emit 定义在某个时刻
    compiler.hooks.emit.tapAsync(
      "CopyrightWebpackPlugin",
      (compilation, cb) => {
        compilation.assets["copyright.txt"] = {
          source: function () {
            return "hello copy";
          },
          size: function () {
            return 20;
          }
        };
        cb();
      }
    );

    //同步的写法
    //compiler.hooks.compile.tap("CopyrightWebpackPlugin", compilation => {
    // console.log("开始了");
    //});
  }
}
module.exports = CopyrightWebpackPlugin;

# HtmlWebpackPlugin

HtmlWebpackPlugin 会在打包结束后,⾃动⽣成⼀个 html ⽂件,并把打包⽣成的 js 模块引⼊到该 html 中。

配置:

title: ⽤来⽣成⻚⾯的 title 元素
filename: 输出的 HTML ⽂件名,默认是 index.html, 也可以直接配置带有⼦⽬录。
template: 模板⽂件路径,⽀持加载器,⽐如 html!./index.html
inject: true | 'head' | 'body' | false ,注⼊所有的资源到特定的 template 或者
templateContent 中,如果设置为 true 或者 body,所有的 javascript 资源将被放置到 body
元素的底部,'head' 将放置到 head 元素中。
favicon: 添加特定的 favicon 路径到输出的 HTML ⽂件中。
minify: {} | false , 传递 html-minifier 选项给 minify 输出
hash: true | false, 如果为 true, 将添加⼀个唯⼀的 Webpack 编译 hash 到所有包含的脚本和
CSS ⽂件,对于解除 cache 很有⽤。
cache: true | false,如果为 true, 这是默认值,仅仅在⽂件修改之后才会发布⽂件。
showErrors: true | false, 如果为 true, 这是默认值,错误信息会写⼊到 HTML ⻚⾯中
chunks: 允许只添加某些块 (⽐如,仅仅 unit test 块)
chunksSortMode: 允许控制块在添加到⻚⾯之前的排序⽅式,⽀持的值:'none' | 'default' |
{function}-default:'auto'
excludeChunks: 允许跳过某些块,(⽐如,跳过单元测试的块)

案例

const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  ...
  plugins: [
    new htmlWebpackPlugin({
      clean - webpack - plugin
      title: "My App",
      filename: "app.html",
      template: "./src/index.html"
    })
  ]
};
<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="UTF-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 <meta http-equiv="X-UA-Compatible" content="ie=edge" />
 <title><%= htmlWebpackPlugin.options.title %></title>
 </head>
 <body>
 <div id="root"></div>
 </body>
</html>

# clean-webpack-plugin

清除打包后的dist

# 打包流程

  1. 初始化 (Initialization)

Webpack 读取配置文件 webpack.config.js,并根据配置初始化打包环境。

  1. 解析 (Resolving)

Webpack 会解析入口文件 (entry),并递归地查找所有导入的模块和依赖项。解析过程包括以下几个步骤:

  • 加载模块:Webpack 会根据配置的规则(rules)加载不同的模块。

  • 解析模块:Webpack 会解析模块中的 import 和 require 语句,寻找其他依赖模块。

  • 加载依赖:Webpack 会递归地加载模块及其依赖项,并构建依赖图谱。

在这个过程中,Webpack 会使用不同的 loaders 来处理不同类型的文件。例如,使用 babel-loader 来转换 ES6 代码,使用 css-loader 来处理 CSS 文件等。

  1. 转换 (Transforming)

在解析完成后,Webpack 会对每个模块进行转换。转换通常涉及到以下步骤:

  • 加载器(Loaders):Webpack 使用配置的加载器来转换文件内容。加载器可以串联使用,以处理复杂的转换任务。

  • 插件(Plugins):Webpack 支持插件机制,插件可以在特定的时机执行特定的任务,比如清理旧的输出文件、生成 HTML 文件等。

  1. 输出 (Outputting)

完成转换后,Webpack 会生成最终的输出文件。输出配置通常包括:

  • 输出路径(Path):指定输出文件的位置。

  • 输出文件名(Filename):指定输出文件的命名规则。

  • 输出模块格式(Library Target):指定输出模块的格式,如 CommonJS、UMD、ES6 等。

  • 外部依赖(Externals):指定哪些模块不应该被打包进输出文件,而是作为外部依赖。

  1. 优化 (Optimizing)

在输出之前,Webpack 还可以执行一些优化操作:

  • 代码分割(Code Splitting):通过配置 splitChunks 插件来实现代码分割,生成按需加载的 chunk 文件。

  • 压缩(Minification):使用 TerserPlugin 或 UglifyJsPlugin 等插件来压缩输出的 JavaScript 文件。

  • Tree Shaking:去除未使用的代码,特别是在使用 ES6 模块时,Webpack 可以利用 Tree Shaking 技术来移除未导出的部分代码。

  1. 文件生成 (File Generation)

最终,Webpack 会生成输出文件,并将它们放置在指定的输出目录中。此时,所有模块和资源都已经按照配置的要求被打包好,并准备好在最终的应用环境中使用。

# 参考文章

深入前端工程化领域 (opens new window)

上次更新: 11/6/2024, 4:10:52 PM