# 代码调用
当我们需要传递很多参数的时候,更推荐使用 esbuild
的 JavaScript API
, 也就是代码调用的方式
esbuild
对外暴露了一系列的 API,主要包括两类: Build API
和 Transform API
,我们可以在 Nodejs
代码中通过调用这些 API 来使用 esbuild
的各种功能。
# Build API
Build API
主要用来进行项目打包,包括 build
、 buildSync
和 context
三个方法。
build
和 buildSync
都是用来执行构建操作的,它们的区别主要在于是否异步执行。build
适用于大多数场景, 尤其是那些需要异步处理或在并发任务中使用时。比如在 Node.js 中编写复杂的构建脚本时,你可能会希望异步处理多个任务,以提高效率。buildSync
适用于那些需要同步执行构建任务的场景,通常是在脚本简单、希望一步完成且不需要处理异步任务的情况下使用。在实际使用中,建议使用build
,通过异步提高性能。
在项目根目录下创建 esbuild.config.mjs
文件来进行处理
import esbuild from "esbuild"; esbuild.build({ // 入口文件列表,为一个数组 entryPoints: ['./src/App.tsx'], // 是否需要打包,一般设为 true bundle: true, // 是否进行代码压缩 minify: false, // 是否生成 SourceMap 文件 sourcemap: true, // 指定语言版本和目标环境 target: ['es2020', 'chrome58', 'firefox57', 'safari11'], //输出目录 outdir: './dist', // 指定loader loader: { ".svg": "dataurl", } })
成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
我们重新组织下之前的代码,增加其复杂度
// src/App.tsx import React from 'react'; import ReactDOM from 'react-dom/client' import Comp1 from './components/Comp1'; import Comp2 from './components/Comp2'; const App = () => (<div> <h1>Hello World!</h1> <Comp1 /> <Comp2 /> </div>); ReactDOM .createRoot(document.getElementById('root')!) .render(<App />)
成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// components/Comp1.tsx import React from 'react'; export default () => { return (<> <h1>Comp1</h1> <ul> <li>vite</li> <li>esbuild</li> <li>rollup</li> </ul> </>) }
成功
2
3
4
5
6
7
8
9
10
11
12
// components/Comp2.tsx import React from 'react'; import logo from "../assets/react.svg" export default () => { return (<> <h1>Comp2</h1> <img src={logo} /> </>) }
成功
2
3
4
5
6
7
8
9
10
然后我们利用node
进行构建
node esbuild.config.mjs
成功
构建完成后,可以看到新增了一个dist
文件夹,里面有两个构建好的文件
dist ├── App.js └── App.js.map
成功
2
3
为了验证构建好的文件,我们可以用 index.html
去调用对应的js文件即可
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="root"></div> <script src="dist/app.js"></script> </body> </html>
成功
2
3
4
5
6
7
8
9
10
11
12
利用http-server
运行一下
npx http-server -o -c-1 // -o 立即打开浏览器 // -c-1 清空http-server缓存
成功
2
3
# 引入css
基础构建功能完成后,我们尝试引入 css
,首先在目录中增加 style.css
文件
// src/style.css body{ color:red; font-size:26px; } .title{ color:blue; font-size:40px; }
成功
2
3
4
5
6
7
8
9
然后在 App.tsx
中引入
// App.tsx import "./style.css" const App = () => (<div> <h1 className="title">Hello World!!</h1> <Comp1 /> <Comp2 /> </div>);
成功
2
3
4
5
6
7
8
然后重新运行构建命令,查看构建后的文件
dist ├── App.css ├── App.css.map ├── App.js └── App.js.map
成功
2
3
4
5
最后打包出来的css文件名和app组件同名,所以最好css的名字不要和组件同名。
同样的,我们将 css
文件,挂载到 html
中
<link rel="stylesheet" href="dist/app.css">
成功
如果是在其他组件内引入的css,一样会打包到app.css中,比如在Comp1.tsx
中引入css
// components/comp.css ul{ list-style: none; } li{ border: 1px solid #ccc; } // components/Comp1.tsx import React from 'react'; import "./comp.css" export default () => { return (<> <h1>Comp1</h1> <ul> <li>vite</li> <li>esbuild</li> <li>rollup</li> </ul> </>) }
成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
我们也可以引入CSS modules预处理器,以便组件的css样式与全局css样式冲突
// components/comps.module.css .title{ color:yellow; } // components/Comp2.tsx import React from 'react'; import logo from "../assets/react.svg" import comps from "./comps.module.css"; export default () => { return (<> <h1 className={comps.title}>Comp2</h1> <img src={logo} /> </>) }
成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
需要在配置中加上css module
的loader
加载器
loader: { ".module.css": "local-css", },
成功
2
3
# 引入Html
之前都是直接将html写死在打包好的文件夹中,其实也可以直接打包已有的html文件
在src目录下创建index.html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link href="./App.css" rel="stylesheet"> </head> <body> <div id="root"></div> </body> <script src="./App.js"></script> </html>
成功
2
3
4
5
6
7
8
9
10
11
12
13
import esbuild from 'esbuild'; esbuild.build({ // 入口文件列表,为一个数组 entryPoints: ['src/App.tsx','src/index.html'], // 是否需要打包,一般设为 true bundle: true, // 是否进行代码压缩 minify: false, // 是否生成 SourceMap 文件 sourcemap: true, // 指定语言版本和目标环境 target: ['es2020', 'chrome58', 'firefox57', 'safari11'], // 是否生成打包的元信息文件 metafile: true, // 指定输出文件 outdir: './dist', // 指定loader loader: { ".html": "copy", ".module.css": "local-css", ".svg": "dataurl" } })
成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 插件 (opens new window)
Esbuild 的时候难免会遇到一些需要加上自定义插件的场景,并且 Vite 依赖预编译的实现中大量应用了 Esbuild 插件的逻辑,你可以到现有 esbuild 插件列表 (opens new window)中去查找已有的esbuild插件,比如,之前对于图片和css的处理。内联图像插件 (opens new window),css插件 (opens new window)
// 导入 npm install -D esbuild-plugin-inline-image npm install -D esbuild-plugin-class-modules // 使用 import inlineImage from "esbuild-plugin-inline-image"; import classModules from "esbuild-plugin-class-modules"; esbuild.build({ ... plugins: [ ... inlineImage(), classModules() ] ... });
成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 元数据分析
esbuild.build
是一个异步函数,我们还可以拿到这个函数的返回值,根据这个返回值,我们还能进行元数据的分析
import esbuild from "esbuild"; import inlineImage from "esbuild-plugin-inline-image"; (async () => { const result = await esbuild.build({ // 入口文件列表,为一个数组 entryPoints: ['src/app.tsx'], // 是否需要打包,一般设为 true bundle: true, // 是否进行代码压缩 minify: false, // 是否生成 SourceMap 文件 sourcemap: true, // 指定语言版本和目标环境 target: ['es2020', 'chrome58', 'firefox57', 'safari11'], // 是否生成打包的元信息文件 metafile: true, // 指定输出文件 outfile: './public/dist/app.js', // 指定loader // loader: { // ".svg": "dataurl", // }, plugins: [ inlineImage() ] }) console.log(result); // 打印详细的元信息 const text = await esbuild.analyzeMetafile(result.metafile, { verbose: true, }); console.log(text); })();
成功
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
# context
在项目打包方面,除了build
和buildSync
,Esbuild 还提供了另外一个比较强大的 API——context
context
为我们提供了三种可以增量构建的API,注意context
和下面的API都是异步的
- Watch mode (opens new window) 简单来说就是监听模式,当我们修改源文件的时候,会自动帮我们重建
- Serve mode (opens new window) 启动本地开发服务器,提供最新构建的结果。注意,Serve mode会自动帮我们构建打包源文件,但是并不支持热重载
- Rebuild mode (opens new window) 允许手动调用构建。当将 esbuild 与其他工具集成时这非常有用。
import esbuild from "esbuild"; import inlineImage from "esbuild-plugin-inline-image"; import classModules from "esbuild-plugin-class-modules"; (async () => { const ctx = await esbuild.context({ // 入口文件列表,为一个数组 entryPoints: ["src/app.tsx","src/index.html"], // 是否需要打包,一般设为 true bundle: true, // 是否进行代码压缩 minify: false, // 是否生成 SourceMap 文件 sourcemap: true, // 指定语言版本和目标环境 target: ["es2020", "chrome58", "firefox57", "safari11"], // 指定输出文件 // outfile: "./dist/app.js", outdir: "./dist", loader: { ".html": "copy", }, plugins: [inlineImage(),classModules()], }); // await ctx.watch(); ctx .serve({ servedir: "./dist", port: 8000, host: "localhost", }) .then((server) => { console.log(`server is running at ${server.host}:${server.port}`); }); })();
成功
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
# Live reload
实时重新加载是一种开发方法,您可以在浏览器与代码编辑器同时打开并可见。当您编辑并保存源代码时,浏览器会自动重新加载,并且重新加载的应用程序版本包含您的更改。这意味着您可以更快地迭代,因为您不必在每次更改后手动切换到浏览器、重新加载,然后切换回代码编辑器。
不过,esbuild并没有给我们提供实时重新加载的API,但是可以通过组合监视模式Watch mode (opens new window)(和服务模式Serve mode (opens new window)加上少量客户端 JavaScript 来构建实时重新加载仅在开发期间添加到应用程序的代码。
配置代码还是之前的不变,只是watch()
和serve()
函数同时打开,然后只需要在客户端html中加入下面的js代码即可
<script type="module"> new EventSource('/esbuild').addEventListener('change', () => location.reload()) </script>
成功
2
3
# Transform API
Transform
API 的核心是 transform
方法,它可以将一段代码从一种格式转换为另一种格式,或者应用特定的编译器选项。下面是如何使用它的基本示例。
const esbuild = require('esbuild'); const code = ` import { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; } `; esbuild.transform(code, { loader: 'jsx', // 指定目标 ECMAScript 版本 target: 'es2015', // 启用代码压缩 minify: true, }).then(result => { console.log(result.code); }).catch(() => process.exit(1));
成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
上述代码执行后,会输出转换和压缩后的代码
import{useState as o}from"react";function e(){const[t,n]=o(0);return React.createElement("button",{onClick:()=>n(t+1)},t)}
成功