webPack原理

简介

webpack是一个静态资源打包工具,会以某个文件为入口,将所有文件编译组合成一个或多个文件输出,让其能够在浏览器运行。

其本身功能非常局限,两种模式

1、开发模式:编译js

2、生产模式:编译js,压缩js

webpack五大核心概念

  1. 入口
  2. 输出
  3. 加载器
  4. 插件
  5. 模式
module.exports = {
  // 入口
  entry: "",
  // 输出
  output: {},
  // 加载器
  module: {
    rules: [],
  },
  // 插件
  plugins: [],
  // 模式
  mode: "",
};

基本概念

loader分为

  1. pre
  2. normal
  3. post
  4. inline

顺序为从下到上

  • 4 类 loader 的执行优级为:pre > normal > inline > post 。
  • 相同优先级的 loader 执行顺序为:从右到左,从下到上

实现原理

  • content 源文件的内容
  • map SourceMap 数据
  • meta 数据,可以是任何内容

loader本质是一个函数,把要处理的文件作为参数,进行处理之后再返回一个文件

最简单的loader

// loaders/loader1.js
module.exports = function loader1(content) {
  console.log("hello loader");
  return content;
};

loader的定义方式

同步loader

module.exports = function (content, map, meta) {
  return content;
};
module.exports = function (content, map, meta) {
  // 传递map,让source-map不中断
  // 传递meta,让下一个loader接收到其他参数
//参数1、错误信息 剩下同上
  this.callback(null, content, map, meta);
  return; // 当调用 callback() 函数时,总是返回 undefined
};

 this.callback是指调用下一个loader类似链表

异步loader

module.exports = function (content, map, meta) {
  const callback = this.async();
  // 进行异步操作
  setTimeout(() => {
    callback(null, result, map, meta);
  }, 1000);
};

 调用callback执行下一个loader,此时是异步操作。

Raw Loader

默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw 为 true,loader 可以接收原始的 Buffer

module.exports = function (content) {
  // content是一个Buffer数据
  return content;
};
module.exports.raw = true; // 开启 Raw Loader

Pitching Loader

webpack 会先从左到右执行 loader 链中的每个 loader 上的 pitch 方法(如果有),然后再从右到左执行 loader 链中的每个 loader 上的普通 loader 方法。webPack原理_第1张图片

 在这个过程中如果任何 pitch 有返回值,则 loader 链被阻断。webpack 会跳过后面所有的的 pitch 和 loader,直接进入上一个 loader 。webPack原理_第2张图片

module.exports = function (content) {
  return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  console.log("do somethings");
};

loader API

方法名 含义 用法
this.async 异步回调 loader。返回 this.callback const callback = this.async()
this.callback 可以同步或者异步调用的并返回多个结果的函数 this.callback(err, content, sourceMap?, meta?)
this.getOptions(schema) 获取 loader 的 options this.getOptions(schema)
this.emitFile 产生一个文件 this.emitFile(name, content, sourceMap)
this.utils.contextify 返回一个相对路径 this.utils.contextify(context, request)
this.utils.absolutify 返回一个绝对路径 this.utils.absolutify(context, request)


plugins

原理

Tapable 为 webpack 提供了统一的插件接口(钩子)类型定义,三个方法给插件,用于注入不同类型的自定义构建行为:

类似生命周期,在特定生命周期执行生命周期内的函数

  • tap:可以注册同步钩子和异步钩子。
  • tapAsync:回调方式注册异步钩子。
  • tapPromise:Promise 方式注册异步钩子

compiler和compilation

compiler

启动 webpack 构建时它都是一个独一无二唯一一个compiler对象,保存着完整的 Webpack 环境配置

主要有以下属性

  • compiler.options 可以访问本次启动 webpack 时候所有的配置文件,包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。
  • compiler.inputFileSystem 和 compiler.outputFileSystem 可以进行文件操作,相当于 Nodejs 中 fs。
  • compiler.hooks 可以注册 tapable 的不同种类 Hook,从而可以在 compiler 生命周期中植入不同的逻辑。

Compilation:

代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖

它有以下主要属性:

  • compilation.modules 可以访问所有模块,打包的每一个文件都是一个模块。
  • compilation.chunks chunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk,通过代码分割的模块又是另外的 chunk。
  • compilation.assets 可以访问本次打包生成所有文件的结果。
  • compilation.hooks 可以注册 tapable 的不同种类 Hook,用于在 compilation 编译模块阶段进行逻辑添加以及修改。

webPack原理_第3张图片

 简单插件

class TestPlugin {
  constructor() {
    console.log("TestPlugin constructor()");
  }
  // 1. webpack读取配置时,new TestPlugin() ,会执行插件 constructor 方法
  // 2. webpack创建 compiler 对象
  // 3. 遍历所有插件,调用插件的 apply 方法
  apply(compiler) {
    console.log("TestPlugin apply()");

    // 从文档可知, compile hook 是 SyncHook, 也就是同步钩子, 只能用tap注册
    compiler.hooks.compile.tap("TestPlugin", (compilationParams) => {
      console.log("compiler.compile()");
    });

    // 从文档可知, make 是 AsyncParallelHook, 也就是异步并行钩子, 特点就是异步任务同时执行
    // 可以使用 tap、tapAsync、tapPromise 注册。
    // 如果使用tap注册的话,进行异步操作是不会等待异步操作执行完成的。
    compiler.hooks.make.tap("TestPlugin", (compilation) => {
      setTimeout(() => {
        console.log("compiler.make() 111");
      }, 2000);
    });

    // 使用tapAsync、tapPromise注册,进行异步操作会等异步操作做完再继续往下执行
    compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {
      setTimeout(() => {
        console.log("compiler.make() 222");
        // 必须调用
        callback();
      }, 1000);
    });

    compiler.hooks.make.tapPromise("TestPlugin", (compilation) => {
      console.log("compiler.make() 333");
      // 必须返回promise
      return new Promise((resolve) => {
        resolve();
      });
    });

    // 从文档可知, emit 是 AsyncSeriesHook, 也就是异步串行钩子,特点就是异步任务顺序执行
    compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
      setTimeout(() => {
        console.log("compiler.emit() 111");
        callback();
      }, 3000);
    });

    compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
      setTimeout(() => {
        console.log("compiler.emit() 222");
        callback();
      }, 2000);
    });

    compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
      setTimeout(() => {
        console.log("compiler.emit() 333");
        callback();
      }, 1000);
    });
  }
}

module.exports = TestPlugin;

在这对文件修改需要compilation对象获取得到数据,修改数据操作。

 处理各种资源

  1. 样式资源:
    1. 加载
      1. css-loader,
      2. style-loader,
      3. less-loader,
      4. sass-loader
    2. 优化
      1. 提取css为单独文件:MiniCssExtractPlugin
      2. 兼容性处理:postcss-loader
      3. 压缩:CssMinizerPlugin
  2. 图片资源:file-loader,url-loader(现在用type:asset,其中加入parser{dataUrlConditon来压缩})
  3. 音频视频等:type:asset/resource
  4. js资源:
    1. Eslint插件(需要配置文件,直接extends属性用写好的)
    2. Babel-loader(需要配置文件,以及preset插件)
  5. html资源:HtmlWebpackPlugin(需要配置模板文件)

优化

  1. 提升开发体验
    1. webpack-dev-derver:开发服务器,自动化(需要在对象中加入devServer配置项)
    2. SourceMap:开启映射,
      1. 生产模式:devtool:“source-map”
      2. 开发模式:devetool:“cheap-module-source-map”
  2. 提升打包构建速度
    1. HMR:修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变
      1. css开启:devServer中添加hot
      2. js开启:vue-loader
    2. Cache:Eslint,Babel开启缓存

      1. babel:

         options: {
                      cacheDirectory: true, // 开启babel编译缓存
                      cacheCompression: false, // 缓存文件不要压缩
                    }
      2. Eslint:

        new ESLintWebpackPlugin({
              // 指定检查文件的根目录
              context: path.resolve(__dirname, "../src"),
              exclude: "node_modules", // 默认值
              cache: true, // 开启缓存
              // 缓存目录
              cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
              ),
            })

  3. 减少代码体积

    1. 压缩图片:ImageMinizerPlugin

    2. Babe按需引入:@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用

  4. 优化代码性能

    1. Code SPlit:在统一与拆分中取得均衡

      1. 功能

        1. 分割文件

        2. 按需加载

      2. 使用:对象中加入

          optimization: {
            // 代码分割配置
            splitChunks: {
              chunks: "all", // 对所有模块都进行分割
        
              },
          }
    2. Preload

      1. preload插件,允许预先加载资源(new PreloadWebpackPlugin)

    3. PWA

      1. 支持离线功能(通过service Woekers实现)

      2.  new WorkboxPlugin.GenerateSW({
              // 这些选项帮助快速启用 ServiceWorkers
              // 不允许遗留任何“旧的” ServiceWorkers
              clientsClaim: true,
              skipWaiting: true,
            }),

Webpack构建流程简单说一下

webPack原理_第4张图片

  • 通过 yargs 解析 configshell 中的配置项
  • webpack 初始化过程,首先会根据第一步的 options 生成 compiler 对象,然后初始化 webpack 的内置插件及 options 配置

  • run 代表编译的开始,会构建 compilation 对象,用于存储这一次编译过程的所有数据
  • make 执行真正的编译构建过程,从入口文件开始,构建模块,直到所有模块创建结束
    • 编译:入口文件开始递归解析依赖关系,构建整个模块依赖图。它会根据配置中的 Loader 对模块进行转换,并生成对应的 AST(抽象语法树)。然后,Webpack 会根据依赖图生成一个或多个 Chunk(代码块)。
    • 构建:将生成的 Chunk 进一步处理,包括合并、拆分、优化等。它会根据配置中的插件对 Chunk 进行处理,例如压缩代码、提取公共模块、生成资源文件等。
  • seal 生成 chunks,对 chunks 进行一系列的优化操作,并生成要输出的代码
  • emit 被触发之后,webpack 会遍历 compilation.assets, 生成所有文件,然后触发任务点 done,结束构建流程


聊一聊Babel原理吧

大多数JavaScript Parser遵循 estree 规范,Babel 最初基于 acorn 项目(轻量级现代 JavaScript 解析器) Babel大概分为三大部分:

  • 解析:将代码转换成 AST
    • 词法分析:将代码(字符串)分割为token流,即语法单元成的数组
    • 语法分析:分析token流(上面生成的数组)并生成 AST
  • 转换:访问 AST 的节点进行变换操作生产新的 AST
    • Taro就是利用 babel 完成的小程序语法转换
  • 生成:以新的 AST 为基础生成代码

Webpack中的Chunk和Bundle

chunk:打包过程中的代码块,一堆module的集合

bundle:是打包完成生成的代码

chunk 是 webpack 处理过程中的一组模块,bundle 是一个或多个 chunk 组成的集合。

  • module:不同文件类型的模块。Webpack 就是用来对模块进行打包的工具,这些模块各种各样,比如:js 模块、css 模块、sass 模块、vue 模块等等不同文件类型的模块。这些文件都会被 loader 转换为有效的模块,然后被应用所使用并且加入到依赖关系图中。相对于一个完整的程序代码,模块化的好处在于,模块化将程序分散成小的功能块,这就提供了可靠的抽象能力以及封装的边界,让设计更加连贯、目的更加明确。而不是将所有东西都揉在一块,既难以理解也难以管理。

  • chunk:数据块。

    a. 一种是非初始化的:例如在打包时,对于一些动态导入的异步代码,webpack 会帮你分割出共用的代码,可以是自己写的代码模块,也可以是第三方库(node_modules 文件夹里的),这些被分割的代码文件就可以理解为 chunk。

    b. 还有一种是初始化的就是写在入口文件处的各种文件,就是 chunk ,它们最终会被捆在一起打包成一个 main.js (当然输出文件名你可以自己指定),这个 main.js 可以理解为 bundle,当然它其实也是 chunk。

  • bundle:捆绑好的最终文件。如果说,chunk 是各种片段,那么 bundle 就是一堆 chunk 组成的“集大成者”,比如上面说的 main.js 就属于 bundle。当然它也类似于电路上原先是各种散乱的零件,最终组成一个集成块的感觉。它经历了加载和编译的过程,是源文件的最终版本。

在entry入口中配置的入口文件有三种类型 :

  1. 单个文件 : 只会产生一个chunk

  2. 数组格式 : 也只会产生一个chunk , 会把数组中所有的所有的源码都打包到一个chunk里 , 最终只生成一个bundle文件

  3. 对象格式 : 一个属性名对应一个入口文件 , 就会生成多个chunk , 在outputPath中 filename就需要写


你可能感兴趣的:(复习,webpack,前端,node.js)