Webpack 本质上是一个模块化打包工具,通过“万物皆模块”的设计思想,巧妙地实现了整个前端项目的模块化。

模块化

  • Node.js 中的 CommonJS:值拷贝
  • 浏览器中的 ES Modules:值引用

ES modules 模块系统存在的问题:

  1. 浏览器环境兼容问题
  2. 模块化划分出来的模块文件过多,每个文件需要单独从服务器请求回来,导致频繁地发送网络请求
  3. HTML 和 CSS 等资源文件的模块化问题

    模块打包工具

  4. 具备编译代码能力,解决环境兼容问题

  5. 将分散的模块再打包到一起,解决浏览器频繁请求模块文件问题
  6. 支持不同种类的前端模块类型,是的可以拥有一个统一的模块化方案

    核心特性

    Loader

    通过 Loader 实现特殊资源加载

  1. // ./webpack.config.js
  2. module.exports = {
  3. entry: './src/main.css',
  4. output: {
  5. filename: 'bundle.js'
  6. },
  7. module: {
  8. rules: [
  9. {
  10. test: /\.css$/, // 根据打包过程中所遇到文件路径匹配是否使用这个 loader
  11. use: 'css-loader' // 指定具体的 loader
  12. }
  13. ]
  14. }
  15. }

通过 JS 加载资源模块

所有资源的加载都是由 JS 代码去控制,使得 webpack 后期只需要维护 JS 代码这一条线即可

开发 Loader

每个 webpack 的 Loader 都需要导出一个函数,这个函数就是对资源的处理过程,输入参数是加载到的资源文件内容,输出就是加工后的结果。

  1. // ./webpack.config.js
  2. module.exports = {
  3. entry: './src/main.js',
  4. output: {
  5. filename: 'bundle.js'
  6. },
  7. module: {
  8. rules: [
  9. {
  10. test: /\.md$/,
  11. // 直接使用相对路径
  12. use: './markdown-loader'
  13. }
  14. ]
  15. }
  16. }
  1. <!-- ./src/about.md -->
  2. # About
  3. this is a markdown file.
  1. // ./src/main.js
  2. import about from './about.md'
  3. console.log(about)
  4. // 希望 about => '<h1>About</h1><p>this is a markdown file.</p>'
  1. // ./markdown-loader.js
  2. const marked = require('marked')
  3. module.exports = source => {
  4. // 加载到的模块内容 => '# About\n\nthis is a markdown file.'
  5. // 1. 将 markdown 转换为 html 字符串
  6. console.log(source)
  7. const html = marked(source)
  8. // html => '<h1>About</h1><p>this is a markdown file.</p>'
  9. // 2. 将 html 字符串拼接为一段导出字符串的 JS 代码
  10. const code = `module.exports = ${JSON.stringify(html)}`
  11. return code
  12. // code => 'export default "<h1>About</h1><p>this is a markdown file.</p>"'
  13. }

webpack 加载资源文件的过程类似于一个工作管道,依次使用多个 Loader,最终这个管道结束后的结果必须是一段标准的 JS 代码字符串

微信截图_20210809200725.png

插件机制

Webpack 插件机制的目的是为了增强 Webpack 在项目自动化构建方面的能力

常见的使用场景:

  • 实现自动在打包之前清除 dist 目录(上次的打包结果):clean-webpack-plugin
  • 自动生成应用所需要的 HTML 文件:html-webpack-plugin
  • 根据不同环境为代码注入类似 API 地址这种可能变化的部分;
  • 拷贝不需要参与打包的资源文件到输出目录:copy-webpack-plugin
  • 压缩 Webpack 打包完成后输出的文件;
  • 自动发布打包结果到服务器实现自动部署。

    开发插件

    钩子机制

  1. // ./remove-comments-plugin.js
  2. class RemoveCommentsPlugin {
  3. apply (compiler) {
  4. compiler.hooks.emit.tap('RemoveCommentsPlugin', compilation => {
  5. // compilation => 可以理解为此次打包的上下文
  6. for (const name in compilation.assets) {
  7. if (name.endsWith('.js')) {
  8. const contents = compilation.assets[name].source()
  9. const noComments = contents.replace(/\/\*{2,}\/\s?/g, '')
  10. compilation.assets[name] = {
  11. source: () => noComments,
  12. size: () => noComments.length
  13. }
  14. }
  15. }
  16. })
  17. }
  18. }

运行机制与核心工作原理

  • 通过 Loader 处理特殊类型资源的加载,例如加载样式、图片
  • 通过 Plugin 实现各种自动化的构建任务,例如自动压缩、自动发布

具体来看打包的过程,Webpack 启动后,会根据我们的配置,找到项目中的某个指定文件(一般这个文件都会是一个 JS 文件)作为入口。然后顺着入口文件中的代码,根据代码中出现的 import(ES Modules)或者是 require(CommonJS)之类的语句,解析推断出来这个文件所依赖的资源模块,然后再分别去解析每个资源模块的依赖,周而复始,最后形成整个项目中所有用到的文件之间的依赖关系树。
有了这个依赖关系树过后, Webpack 会遍历(递归)这个依赖树,找到每个节点对应的资源文件,然后根据配置选项中的 Loader 配置,交给对应的 Loader 去加载这个模块,最后将加载的结果放入 bundle.js(打包结果)中,从而实现整个项目的打包。

  1. Webpack CLI 启动打包流程;
  2. 载入 Webpack 核心模块,创建 Compiler 对象;
  3. 使用 Compiler 对象开始编译整个项目;
  4. 从入口文件开始,解析模块依赖,形成依赖关系树;
  5. 递归依赖树,将每个模块交给对应的 Loader 处理;
  6. 合并 Loader 处理完的结果,将打包结果输出到 dist 目录。