Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 编码专题

  • 深入浅出 Vite

    • 00-目录大纲
    • 07-Vite预构建上手
    • 09-Esbuild 插件开发实战
      • 0.前言
      • 1.基础-概念
        • 问题1:为什么 Esbuild 性能极高?
        • 问题2:可以通过哪两种方式来使用 Esbuild ?
        • 问题3:ESBuild 一共提供哪儿三种基础模式?能简单介绍下具体的使用方式吗?
      • 2. 进阶-插件开发
        • 问题4:ESbuild 插件本质上是什么?有哪儿个钩子可供使用,请简要介绍?
        • 问题5:onResolve 和 onLoad 钩子的使用方式?
        • 问题6:onStart 和 onEnd 钩子的使用方式?
      • 3. 插件实践
        • 问题7:如何实现"env-ns"虚拟模块,获取构建时环境 process.env?
        • 问题8:如何基于 ESbuild 编写一个支持识别 http 模块插件,简述大致流程?
        • 问题9:如何基于 ESbuild 编写一个 HTML 构建插件,将上述的 js 插入对预制的 html 模板,请简述大概流程?
    • 10-Rollup 打包基本概念及使用
    • 11-Rollup打包机制及插件开发
    • 12-Vite插件开发实战
    • 13-Vite热更新
    • 16-Vite搭建 SSR 框架
  • 快速上手 API

  • 深入浅出Babel

  • 深入浅出 React

  • 百问掘金
  • 深入浅出 Vite
wangjiasheng
2023-03-29
目录

09-Esbuild 插件开发实战

本节课程代码的仓库 (opens new window):11-vite-esbuild

# 0.前言

本篇博客为《深入浅出 Vite 》掘金手册第九章的总结。

# 1.基础-概念

# 问题1:为什么 Esbuild 性能极高?

  • Go 语言开发,GO 语言的优势主要体现两处,其一体现在编译型语言的特性,直接将逻辑代码构建为原生机器码,而 js 先解析为字节码,在转化为机器码。其二为多线程共享内存共享。

  • 从零造轮子:几乎没有使用三方库,这种"闭源"特性不仅体现在性能上,也使得在频繁操作 AST 场景下,能共享数据,免去了不同 AST 树的频繁解析和传递数据(string-> ts-> js -> string)。

    此特性很像 Flutter 采用 dart 语言带来的性能提升,通过自渲染引擎,打通客户端与原生端沟通成本,dart 取代传统的js Bridge 。

# 问题2:可以通过哪两种方式来使用 Esbuild ?

有两种方式:命令行 和 代码调用。

  • 命令行:

    "command:npx": "npx esbuild src/index.jsx --bundle --outfile=dist/out.js",
    
    1
  • 代码调用:

    "command:build": "node ./scripts/command/build.js",
    "command:serve": "node ./scripts/command/serve.js",
    "command:watch": "node ./scripts/command/watch.js",
    
    1
    2
    3

    具体使用见下个问题。

# 问题3:ESBuild 一共提供哪儿三种基础模式?能简单介绍下具体的使用方式吗?

提供三种模式:build、serve、watch

  • build:在 esbuild 中此api 体现为异步,也同样提供了 buildSync 函数,但是不推荐使用。
  • serve:可提供一个高性能的静态文件服务。(特别注意的是:与 watch 不同的时,只有当请求到来的时候,才会触发构建)
  • watch:在 watch 下代码变动会触发重新打包。

基础使用:

const buildConfig = {};
const result = await esbuild.build(buildConfig);
1
2

详细代码见仓库 (opens new window),配置参数大致可分为以下四类:通用参数、esbuild 特有参数、开发环境参数、生产环境参数。

  • 通用:

    • 入口:entryPoints: [""] 是一个数组。
    • 出口:outdir: "dist"
    • 指定模块语法规范:iffe、esm、cjs
  • esbuild 特有参数:

    • metafile:true 开启会在结果中注入元信息。(处理依赖相关时需要)

    • loader: {} 与webpack 中的 loader 概念相同 ,针对不同格式的文件,调用不同的 loader 进行处理。

      内置的 loader 如下:

      export type Loader = 'base64' | 'binary' | 'copy' | 'css' | 'dataurl' | 'default' | 'empty' | 'file' | 'js' | 'json' | 'jsx' | 'text' | 'ts' | 'tsx'

      • {".png":"base64"} :将图片格式处理为 base64 格式。
      • {".jsx": "jsx"}:处理 jsx 格式。
  • 开发环境:支持sourcemap 生成 map 文件。

  • 生产环境:

    • 是否打包:bundle:true,此参数配置的区别在于是否处理第三方依赖。

      可以写一个插件实现下面的内容折叠。

      通过观察 metafile 可以观察输入输出。

      • bundle: false 时,

        $ npm run command:build
        
        > 11-vite-esbuild@1.0.0 command:build
        > node ./scripts/command/build.js
        
        result {
          errors: [],
          warnings: [],
          outputFiles: undefined,
          metafile: {
            inputs: { 'src/react-component.jsx': [Object] },
            outputs: {
              'dist/command/build/react-component.js.map': [Object],
              'dist/command/build/react-component.js': [Object]
            }
          },
          mangleCache: undefined
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
      • bundle: true 时,

        $ npm run command:build
        
        > 11-vite-esbuild@1.0.0 command:build
        > node ./scripts/command/build.js
        
        result {
          errors: [],
          warnings: [],
          outputFiles: undefined,
          metafile: {
            inputs: {
              '../../node_modules/.pnpm/react@18.2.0/node_modules/react/cjs/react.development.js': [Object],
              '../../node_modules/.pnpm/react@18.2.0/node_modules/react/index.js': [Object],
              '../../node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server-legacy.browser.development.js': [Object],
              '../../node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js': [Object],
              '../../node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/server.browser.js': [Object],
              'src/react-component.jsx': [Object]
            },
            outputs: {
              'dist/command/build/react-component.js.map': [Object],
              'dist/command/build/react-component.js': [Object]
            }
          },
          mangleCache: undefined
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
    • format: "esm" 下可开启 splitting:true 可进行分包操作。

    • bundle: true 属性下,可额外通过 external:[] 指定不需要打入的第三方依赖。

    • minify: true 开启压缩操作。

    • write: true 和 webpack-dev-server 一样默认是将产物输出到内存,开启后,可以将产物写进磁盘。

# 2. 进阶-插件开发

# 问题4:ESbuild 插件本质上是什么?有哪儿个钩子可供使用,请简要介绍?

ESbuild 插件本质上就是一个对象,两个属性 name 和 setup 和 四个钩子函数。

let esbuildPlugin = {
  name: "esbuild:plugin", /* 插件名称 */ 
  setup(build){
    // build 暴露四个钩子函数
    
    // 模块路径解析,例如 import xxx from "module-name"
    build.onResolve({filter:/module-name/},args => ({}))
    
    // 模块内容加载,即 xxx 返回的内容
    build.onLoad({filter:/module-name/},args => ({}))
    
    // 构建开始前触发
    build.onStart(()=>{ console.log("构建开始")});
    
    // 构建结束时触发
    build.onEnd((buildResult) => {})
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 问题5:onResolve 和 onLoad 钩子的使用方式?

onResolve 和 onLoad 是一对非常重要的钩子函数。

常用的组合使用方式为:使用 onResolve 筛选出目标模块,并通过 namespace 标识模块。onLoad 可根据 module-name 拦截模块的加载信息。 典型案例见:问题7。

简单的筛选模板通过 {filter: //} 正则语法就能实现,如果想要实现复杂的功能,还需要获取当前解析模块的上下文信息,如:

  • 导入的是哪儿个模块?(args.path),以及被谁导入的?(args.importer)

  • 导入的模块规范?由 import 还是 require 方式?(args.kind)

  • 除了需要返回标识 namespace 属性,是否可以标识其余内容信息?如一些额外的内容。

在 onResolve 钩子中函数参数和返回值梳理如下:

build.onResolve({ filter: /^env$/ }, (args: onResolveArgs): onResolveResult => {
  // 模块路径
  console.log(args.path)
  // 父模块路径
  console.log(args.importer)
  // namespace 标识
  console.log(args.namespace)
  // 基准路径
  console.log(args.resolveDir)
  // 导入方式,如 import、require
  console.log(args.kind)
  // 额外绑定的插件数据
  console.log(args.pluginData)
  
  return {
      // 错误信息
      errors: [],
      // 是否需要 external
      external: false;
      // namespace 标识
      namespace: 'env-ns';
      // 模块路径
      path: args.path,
      // 额外绑定的插件数据
      pluginData: null,
      // 插件名称
      pluginName: 'xxx',
      // 设置为 false,如果模块没有被用到,模块代码将会在产物中会删除。否则不会这么做
      sideEffects: false,
      // 添加一些路径后缀,如`?xxx`
      suffix: '?xxx',
      // 警告信息
      warnings: [],
      // 仅仅在 Esbuild 开启 watch 模式下生效
      // 告诉 Esbuild 需要额外监听哪些文件/目录的变化
      watchDirs: [],
      watchFiles: []
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

在 onLoad 钩子中函数参数和返回值梳理如下:

build.onLoad({ filter: /.*/, namespace: 'env-ns' }, (args: OnLoadArgs): OnLoadResult => {
  // 模块路径
  console.log(args.path);
  // namespace 标识
  console.log(args.namespace);
  // 后缀信息
  console.log(args.suffix);
  // 额外的插件数据
  console.log(args.pluginData);
  
  return {
      // 模块具体内容
      contents: '省略内容',
      // 错误信息
      errors: [],
      // 指定 loader,如`js`、`ts`、`jsx`、`tsx`、`json`等等
      loader: 'json',
      // 额外的插件数据
      pluginData: null,
      // 插件名称
      pluginName: 'xxx',
      // 基准路径
      resolveDir: './dir',
      // 警告信息
      warnings: [],
      // 同上
      watchDirs: [],
      watchFiles: []
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 问题6:onStart 和 onEnd 钩子的使用方式?

使用方式比较简单,如下例所示:

let examplePlugin = {
  name: 'example',
  setup(build) {
    /* 触发时间:在 watch 或者 serve 下重新构建*/
    build.onStart(() => {
      console.log('build started')
    });
    build.onEnd((buildResult) => {
      if (buildResult.errors.length) {
        return;
      }
      // 构建元信息
      // 获取元信息后做一些自定义的事情,比如生成 HTML
      console.log(buildResult.metafile)
    })
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意如下:

  1. onStart 的触发时机?

  2. onEnd 返回的结果,即为 esbuild.build({}) 的执行结果,同 build 的使用一样?metafile 属性需要配置后才可获取。

# 3. 插件实践

# 问题7:如何实现"env-ns"虚拟模块,获取构建时环境 process.env?

具体案例见仓库 (opens new window)

let envPlugin = {
  name: "env",
  setup(build) {
    /* 解析 `import "env"` 依赖,并将其标识为 namespace "env-ns" */
    build.onResolve({ filter: /^env$/ }, (args) => ({
      path: args.path,
      namespace: "env-ns",
    }));

    /* 通过 namespace 捕获模块,并导出对象 {contents: "", loader:"json"} */
    build.onLoad({ filter: /.*/, namespace: "env-ns" }, () => ({
      contents: JSON.stringify(process.env),
      loader: "json",
    }));
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 问题8:如何基于 ESbuild 编写一个支持识别 http 模块插件,简述大致流程?

具体案例见仓库 (opens new window)

目标:支持打包 http 格式的模块依赖,案例如下:

import { render } from "https://cdn.skypack.dev/react-dom";
import React from 'https://cdn.skypack.dev/react'

let Greet = () => <h1>Hello, juejin!</h1>;

render(<Greet />, document.getElementById("root"));
1
2
3
4
5
6

实现思路如下:

  1. 通过 onResolve 对 http(s) 开头的 直接依赖 模块,并打上 http-url 标识。
  2. 对/-/react-dom@v17.0.1-o 等开头的 间接依赖 模块,需要先拼接出完成的 http 路径后再返回。简介依赖会自动携带之前打上的 http-url 标识,可根据此特性过滤。
  3. 通过 onLoad 拦截具有 http-url 标识的模块。
    • 通过 http 或者 https 第三方依赖封装一个 fetch 函数,使用此 fetch 函数去请求该模块(通过 args.path 获取上下文 )
    • 返回请求结果 {contents: 内容}

实现方案如下:

const esbuilPlugin = {
  name: "esbuild:http",
  setup(build) {
    let https = require("https");
    let http = require("http");

    // 1. 拦截直接依赖
    build.onResolve({ filter: /^https?:\/\// }, (args) => ({
      path: args.path,
      namespace: "http-url",
    }));
    
    // 2. 拦截间接依赖
    build.onResolve({ filter: /.*/, namespace: "http-url" }, (args) => ({
      path: new URL(args.path, args.importer).toString(),
      namespace: "http-url",
    }));


    // 2. 通过 fetch 请求加载 CDN 资源,并返回 contents
    build.onLoad({ filter: /.*/, namespace: "http-url" }, async (args) => {
      let contents = await new Promise((resolve, reject) => {
        function fetch(url) {
          console.log(`Downloading: ${url}`);
          let lib = url.startsWith("https") ? https : http;
          let req = lib
            .get(url, (res) => {
              if ([301, 302, 307].includes(res.statusCode)) {
                // 重定向
                fetch(new URL(res.headers.location, url).toString());
                req.abort();
              } else if (res.statusCode === 200) {
                // 响应成功
                let chunks = [];
                res.on("data", (chunk) => chunks.push(chunk));
                res.on("end", () => resolve(Buffer.concat(chunks)));
              } else {
                reject(
                  new Error(`GET ${url} failed: status ${res.statusCode}`)
                );
              }
            })
            .on("error", reject);
        }
        fetch(args.path);
      });
      return { contents };
    });
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

根据案例仓库的代码,执行 npm run plugin:cdn 后会在 dist 目录下生成 plugins/cdn-module.js 文件。因此,可以额外开发一个 HTML 插件用于导入构建的 js 产物。

# 问题9:如何基于 ESbuild 编写一个 HTML 构建插件,将上述的 js 插入对预制的 html 模板,请简述大概流程?

具体案例见仓库 (opens new window)

实现思路:

  1. 通过 onEnd 钩子函数可以获取到 bundle 产物打包后的一些元信息。

  2. 需要提供三个辅助函数:createScripts/createLink/generateHtml

  3. 获取元信息结果

    const {metafile} = bundle;
    const {outputs} = metafile; // 拿到 metafile 后获取所有的 js 和 css 产物路径
    const assets = Object.keys(outputs); // 获取 assets 数组
    
    1
    2
    3
  4. 使用 fs.writeFile 将 html 字符传写入磁盘。

基础版-完整代码如下:

const esbuildHTML = {
  name: "esbuild:html",
  setup(build) {
    /* 此钩子主要放在 onEnd */
    build.onEnd(async (buildResult) => {
      if (buildResult.errors.length) {
        return;
      }

      /* 通过 esbuild 获取此结果 */
      const { metafile } = buildResult;

      /* 1. 拼接 html */
      const scripts = [];
      const cssLinks = [];
      if (metafile) {
        const { outputs } = metafile;
        const assets = Object.keys(outputs);

        assets.forEach((asset) => {
          const relativePath = path.relative("dist/plugins", asset);
          if (asset.endsWith(".js")) {
            scripts.push(createScript(relativePath));
          } else if (asset.endsWith(asset)) {
            cssLinks.push(createLink(relativePath));
          }
        });

        const templateContent = generateHTML(scripts, cssLinks);

        /* 写入磁盘 */
        const templatePath = path.join(
          __dirname,
          "../../dist/plugins",
          "index.html",
        );

        await fs.writeFile(templatePath, templateContent);
      }
    });
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

构建后,可通过 serve . 启动一个静态文件文件服务器。

强烈推荐:三元老师写的 esbuild代码 (opens new window),代码逻辑非常清晰值得学习。

编辑 (opens new window)
上次更新: 2023/04/13, 9:04:00
07-Vite预构建上手
10-Rollup 打包基本概念及使用

← 07-Vite预构建上手 10-Rollup 打包基本概念及使用→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式