vite(ESbuild)
为什么快
使用
pnpm i esbuild
使用 Esbuild 有 2 种方式,分别是 命令行调用和代码调用。
1.命令行调用
// src/index.jsx
import Server from "react-dom/server";
let Greet = () => <h1>Hello, juejin!</h1>;
console.log(Server.renderToString(<Greet />));
"scripts": {
"build": "./node_modules/.bin/esbuild src/index.jsx --bundle --outfile=dist/out.js"
},
2. 代码调用
项目打包——Build API
Build API
主要用来进行项目打包,包括build
、buildSync
和serve
三个方法。
- build,buildSync(同步版本)
const { build, buildSync, serve } = require("esbuild");
async function runBuild() {
// 异步方法,返回一个 Promise
const result = await build({
// ---- 如下是一些常见的配置 ---
// 当前项目根目录
absWorkingDir: process.cwd(),
// 入口文件列表,为一个数组
entryPoints: ["./src/index.jsx"],
// 打包产物目录
outdir: "dist",
// 是否需要打包,一般设为 true
bundle: true,
// 模块格式,包括`esm`、`commonjs`和`iife`
format: "esm",
// 需要排除打包的依赖列表
external: [],
// 是否开启自动拆包
splitting: true,
// 是否生成 SourceMap 文件
sourcemap: true,
// 是否生成打包的元信息文件
metafile: true,
// 是否进行代码压缩
minify: false,
// 是否开启 watch 模式,在 watch 模式下代码变动则会触发重新打包
watch: false,
// 是否将产物写入磁盘
write: true,
// Esbuild 内置了一系列的 loader,包括 base64、binary、css、dataurl、file、js(x)、ts(x)、text、json
// 针对一些特殊的文件,调用不同的 loader 进行加载
loader: {
'.png': 'base64',
}
});
console.log(result);
}
runBuild();
2.serve
// build.js
const { build, buildSync, serve } = require("esbuild");
function runBuild() {
serve(
{
port: 8000,
// 静态资源目录
servedir: './dist'
},
{
absWorkingDir: process.cwd(),
entryPoints: ["./src/index.jsx"],
bundle: true,
format: "esm",
splitting: true,
sourcemap: true,
ignoreAnnotations: true,
metafile: true,
}
).then((server) => {
console.log("HTTP Server starts at port", server.port);
});
}
runBuild();
就是说每次刷新浏览器,会重新打包
单文件转译——Transform API
transformSync
和transform
// transform.js
const { transform, transformSync } = require("esbuild");
async function runTransform() {
// 第一个参数是代码字符串,第二个参数为编译配置
const content = await transform(
"const isNull = (str: string): boolean => str.length > 0;",
{
sourcemap: true,
loader: "tsx",
}
);
console.log(content);
}
runTransform();
Esbuild 插件开发
let envPlugin = {
name: 'env',
setup(build) {
build.onResolve({ filter: /^env$/ }, args => ({
path: args.path,
namespace: 'env-ns',
}))
build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
contents: JSON.stringify(process.env),
loader: 'json',
}))
},
}
require('esbuild').build({
entryPoints: ['src/index.jsx'],
bundle: true,
outfile: 'out.js',
// 应用插件
plugins: [envPlugin],
}).catch(() => process.exit(1))
在不同的生命周期,选择某些文件,修改。 onResolve返回的namespace用来标记文件,onLoad可以使用来选择标记的文件。
- 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: []
}
}
- 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: []
}
});
- onStart,onEnd
let examplePlugin = {
name: 'example',
setup(build) {
build.onStart(() => {
console.log('build started')
});
build.onEnd((buildResult) => {
if (buildResult.errors.length) {
return;
}
// 构建元信息
// 获取元信息后做一些自定义的事情,比如生成 HTML
console.log(buildResult.metafile)
})
},
}
实战 1: CDN 依赖拉取插件
https://github.com/RunningLiLi/vite_learn/blob/master/packages/esbuild/plugin_cdnImport.mjs
js
import { build } from "esbuild";
import http from "http";
import https from "https";
const cdnPlugin = {
name: "cdnPlugin",
"setup":(build)=> {
build.onResolve({ filter: /^https?:\/\// }, (args) => ({
path: args.path,
namespace: "http-url",
}));
build.onResolve({ filter: /.*/, namespace: "http-url" }, (args) =>{
console.log(args)
return({
// 重写路径
path: new URL(args.path, args.importer).toString(),
namespace: "http-url",
})});
build.onLoad({ filter: /.*/, namespace: "http-url" }, (args) => {
return 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.destroy();
} else if (res.statusCode === 200) {
// 响应成功
let chunks = [];
res.on("data", (chunk) => chunks.push(chunk));
res.on("end", () =>
resolve({ contents: Buffer.concat(chunks) })
);
} else {
reject(
new Error(`GET ${url} failed: status ${res.statusCode}`)
);
}
})
.on("error", reject);
}
fetch(args.path);
});
});
},
};
build({
absWorkingDir: process.cwd(),
entryPoints: ["./src/index.jsx"],
outdir: "dist",
bundle: true,
format: "esm",
splitting: true,
sourcemap: true,
metafile: true,
plugins:[cdnPlugin]
}).then(()=>{});
实战 2: html自动生成
https://github.com/RunningLiLi/vite_learn/blob/master/packages/esbuild/plugins/plugin_htmlBuilder.ts
ts
import { build, Plugin } from "esbuild";
import { cdnPlugin } from "./plugin_cdnImport";
import * as fs from "fs";
import * as path from "path";
const htmlPlugin: Plugin = {
name: "htmlPlugin",
setup(build) {
build.onEnd((args) => {
console.log(args);
if (!args.metafile) throw new Error("请开启metafile选项");
let scripts = Object.keys(args.metafile.outputs).reduce((pre, cur) => {
return pre + createScript(cur.replace(/.*\//, "")) + "\n";
}, "\n");
let html = "";
fs.readFile(path.resolve(__dirname, "../index.html"), (err, data) => {
if (err) throw new Error(err.message);
else {
html += data.toString().replace("<body>", "<body>" + scripts);
fs.writeFile(
path.resolve(__dirname, "../dist/index.html"),
html,
() => {
console.log("html生成完成");
}
);
}
});
});
},
};
function createScript(src: string): string {
return `<script src="${src}" type="module"></script>`;
}
function createLink(href: string): string {
return `<link href="${href}">`;
}
build({
entryPoints: ["./src/index.jsx"],
outdir: "dist",
bundle: true,
format: "esm",
splitting: true,
plugins: [cdnPlugin, htmlPlugin],
metafile: true,
});