esbuild 插件系统 #
插件概述 #
esbuild 插件系统允许你自定义构建过程,处理特殊文件类型,添加自定义转换逻辑等。
text
┌─────────────────────────────────────────────────────────────┐
│ esbuild 插件系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 构建流程 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 解析 │->│ 加载 │->│ 转换 │->│ 输出 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ ↑ ↑ ↑ │ │
│ │ │ │ │ │ │
│ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ │
│ │ │ onResolve│ │ onLoad │ │ onEnd │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
插件基础结构 #
基本格式 #
javascript
const myPlugin = {
name: 'my-plugin',
setup(build) {
// 插件逻辑
},
};
使用插件 #
javascript
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
plugins: [myPlugin],
});
完整示例 #
javascript
const esbuild = require('esbuild');
const myPlugin = {
name: 'my-plugin',
setup(build) {
console.log('Plugin setup started');
build.onEnd((result) => {
console.log('Build completed with', result.errors.length, 'errors');
});
},
};
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/bundle.js',
plugins: [myPlugin],
});
插件钩子 #
onResolve - 模块解析 #
自定义模块路径解析:
javascript
const pathPlugin = {
name: 'path-alias',
setup(build) {
build.onResolve({ filter: /^@\// }, (args) => {
return {
path: args.path.replace('@', './src'),
namespace: 'file',
};
});
},
};
onResolve 参数 #
javascript
build.onResolve(
{
filter: /\.txt$/, // 正则匹配
namespace: 'file', // 命名空间过滤
},
(args) => {
// args 包含:
// - path: 导入路径
// - importer: 导入者路径
// - namespace: 命名空间
// - resolveDir: 解析目录
// - kind: 导入类型 (import-statement, require-call, etc.)
return {
path: 'resolved-path', // 解析后的路径
namespace: 'custom', // 命名空间
external: false, // 是否外部模块
sideEffects: true, // 是否有副作用
pluginData: {}, // 传递给 onLoad 的数据
};
}
);
示例:URL 导入 #
javascript
const urlImportPlugin = {
name: 'url-import',
setup(build) {
build.onResolve({ filter: /^https?:\/\// }, (args) => ({
path: args.path,
namespace: 'http-url',
}));
build.onLoad({ filter: /.*/, namespace: 'http-url' }, async (args) => {
const response = await fetch(args.path);
const contents = await response.text();
return { contents, loader: 'js' };
});
},
};
onLoad - 文件加载 #
自定义文件加载和转换:
javascript
const txtPlugin = {
name: 'txt-loader',
setup(build) {
build.onLoad({ filter: /\.txt$/ }, async (args) => {
const fs = require('fs').promises;
const contents = await fs.readFile(args.path, 'utf8');
return {
contents: `export default ${JSON.stringify(contents)}`,
loader: 'js',
};
});
},
};
onLoad 参数 #
javascript
build.onLoad(
{
filter: /\.custom$/, // 正则匹配
namespace: 'file', // 命名空间过滤
},
async (args) => {
// args 包含:
// - path: 文件路径
// - namespace: 命名空间
// - suffix: 查询字符串
// - pluginData: onResolve 传递的数据
return {
contents: 'export default "hello"', // 文件内容
loader: 'js', // 加载器类型
resolveDir: '/path/to/dir', // 解析目录
pluginData: {}, // 传递给后续钩子的数据
};
}
);
示例:YAML 加载器 #
javascript
const yaml = require('yaml');
const yamlPlugin = {
name: 'yaml-loader',
setup(build) {
build.onLoad({ filter: /\.(yaml|yml)$/ }, async (args) => {
const fs = require('fs').promises;
const source = await fs.readFile(args.path, 'utf8');
const data = yaml.parse(source);
return {
contents: `export default ${JSON.stringify(data)}`,
loader: 'js',
};
});
},
};
onEnd - 构建结束 #
构建完成后执行:
javascript
const notifyPlugin = {
name: 'notify',
setup(build) {
build.onEnd((result) => {
if (result.errors.length > 0) {
console.error('❌ Build failed with errors');
} else {
console.log('✅ Build completed successfully');
console.log(` Output files: ${result.metafile?.outputs ? Object.keys(result.metafile.outputs).length : 0}`);
}
});
},
};
onStart - 构建开始 #
构建开始前执行:
javascript
const timerPlugin = {
name: 'timer',
setup(build) {
let startTime;
build.onStart(() => {
startTime = Date.now();
console.log('🚀 Build started...');
});
build.onEnd(() => {
const duration = Date.now() - startTime;
console.log(`⏱️ Build completed in ${duration}ms`);
});
},
};
onDispose - 清理资源 #
插件清理时执行:
javascript
const cleanupPlugin = {
name: 'cleanup',
setup(build) {
const watcher = startWatcher();
build.onDispose(() => {
watcher.close();
console.log('Resources cleaned up');
});
},
};
实用插件示例 #
1. 环境变量插件 #
javascript
const envPlugin = {
name: 'env',
setup(build) {
const env = process.env;
build.onResolve({ filter: /^env$/ }, (args) => ({
path: args.path,
namespace: 'env-ns',
}));
build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
contents: JSON.stringify(env),
loader: 'json',
}));
},
};
// 使用
// import env from 'env';
// console.log(env.NODE_ENV);
2. HTML 模板插件 #
javascript
const htmlPlugin = {
name: 'html-template',
setup(build) {
build.onLoad({ filter: /\.html$/ }, async (args) => {
const fs = require('fs').promises;
const html = await fs.readFile(args.path, 'utf8');
const code = `
const template = ${JSON.stringify(html)};
export default template;
export function render(data) {
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] || '');
}
`;
return { contents: code, loader: 'js' };
});
},
};
3. Markdown 插件 #
javascript
const markdownPlugin = {
name: 'markdown',
setup(build) {
build.onLoad({ filter: /\.md$/ }, async (args) => {
const fs = require('fs').promises;
const markdown = await fs.readFile(args.path, 'utf8');
const html = markdownToHtml(markdown);
return {
contents: `
export const html = ${JSON.stringify(html)};
export const raw = ${JSON.stringify(markdown)};
export default html;
`,
loader: 'js',
};
});
},
};
function markdownToHtml(md) {
return md
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
.replace(/\*\*(.*)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*)\*/g, '<em>$1</em>')
.replace(/\n/g, '<br>');
}
4. GraphQL 插件 #
javascript
const graphqlPlugin = {
name: 'graphql',
setup(build) {
build.onLoad({ filter: /\.(graphql|gql)$/ }, async (args) => {
const fs = require('fs').promises;
const query = await fs.readFile(args.path, 'utf8');
return {
contents: `
export const query = ${JSON.stringify(query)};
export default query;
`,
loader: 'js',
};
});
},
};
5. 虚拟模块插件 #
javascript
const virtualModulePlugin = {
name: 'virtual',
setup(build) {
const virtualModules = {
'virtual:config': `
export const apiUrl = 'https://api.example.com';
export const version = '1.0.0';
`,
'virtual:features': `
export const features = ['auth', 'dashboard', 'settings'];
export default features;
`,
};
build.onResolve({ filter: /^virtual:/ }, (args) => ({
path: args.path,
namespace: 'virtual',
}));
build.onLoad({ filter: /.*/, namespace: 'virtual' }, (args) => ({
contents: virtualModules[args.path],
loader: 'js',
}));
},
};
// 使用
// import { apiUrl } from 'virtual:config';
// import features from 'virtual:features';
6. 复制文件插件 #
javascript
const copyPlugin = (options = {}) => {
const { from, to } = options;
return {
name: 'copy',
setup(build) {
const fs = require('fs').promises;
const path = require('path');
build.onEnd(async () => {
await fs.mkdir(to, { recursive: true });
const files = await fs.readdir(from);
for (const file of files) {
const src = path.join(from, file);
const dest = path.join(to, file);
await fs.copyFile(src, dest);
}
console.log(`Copied ${files.length} files to ${to}`);
});
},
};
};
// 使用
esbuild.build({
plugins: [
copyPlugin({ from: 'public', to: 'dist/public' }),
],
});
7. 代码替换插件 #
javascript
const replacePlugin = (replacements = {}) => {
return {
name: 'replace',
setup(build) {
build.onLoad({ filter: /\.(js|ts|jsx|tsx)$/ }, async (args) => {
const fs = require('fs').promises;
let contents = await fs.readFile(args.path, 'utf8');
for (const [from, to] of Object.entries(replacements)) {
contents = contents.replace(new RegExp(from, 'g'), to);
}
return { contents, loader: args.path.endsWith('.tsx') ? 'tsx' : args.path.endsWith('.ts') ? 'ts' : 'js' };
});
},
};
};
// 使用
esbuild.build({
plugins: [
replacePlugin({
'__VERSION__': '1.0.0',
'__BUILD_TIME__': new Date().toISOString(),
}),
],
});
8. 压缩报告插件 #
javascript
const sizeReportPlugin = {
name: 'size-report',
setup(build) {
build.onEnd(async (result) => {
if (!result.metafile) return;
const outputs = result.metafile.outputs;
console.log('\n📦 Build Size Report:');
console.log('─'.repeat(50));
for (const [file, info] of Object.entries(outputs)) {
const size = info.bytes;
const sizeKB = (size / 1024).toFixed(2);
console.log(` ${file}: ${sizeKB} KB`);
}
console.log('─'.repeat(50));
const total = Object.values(outputs).reduce((sum, o) => sum + o.bytes, 0);
console.log(` Total: ${(total / 1024).toFixed(2)} KB\n`);
});
},
};
社区插件 #
esbuild-plugin-copy #
复制文件和目录:
javascript
const { copy } = require('esbuild-plugin-copy');
esbuild.build({
plugins: [
copy({
assets: [
{ from: './public/images', to: './dist/images' },
{ from: './public/fonts', to: './dist/fonts' },
],
}),
],
});
esbuild-plugin-pnpm #
处理 pnpm 符号链接:
javascript
const { pnpmPlugin } = require('esbuild-plugin-pnpm');
esbuild.build({
plugins: [pnpmPlugin()],
});
esbuild-plugin-svgr #
将 SVG 转换为 React 组件:
javascript
const svgrPlugin = require('esbuild-plugin-svgr');
esbuild.build({
plugins: [svgrPlugin()],
});
// 使用
// import Logo from './logo.svg';
// <Logo />
esbuild-plugin-less #
处理 Less 文件:
javascript
const { lessLoader } = require('esbuild-plugin-less');
esbuild.build({
plugins: [lessLoader()],
});
esbuild-plugin-sass #
处理 Sass/SCSS 文件:
javascript
const { sassPlugin } = require('esbuild-sass-plugin');
esbuild.build({
plugins: [sassPlugin()],
});
esbuild-plugin-postcss #
使用 PostCSS 处理 CSS:
javascript
const postcss = require('esbuild-plugin-postcss');
esbuild.build({
plugins: [
postcss({
plugins: [
require('autoprefixer'),
require('postcss-preset-env'),
],
}),
],
});
esbuild-plugin-style-module #
CSS Modules 支持:
javascript
const styleModule = require('esbuild-plugin-style-module');
esbuild.build({
plugins: [styleModule()],
});
高级插件技巧 #
链式插件 #
javascript
function createChainPlugin(plugins) {
return {
name: 'chain-plugin',
setup(build) {
for (const plugin of plugins) {
plugin.setup(build);
}
},
};
}
esbuild.build({
plugins: [
createChainPlugin([
envPlugin,
yamlPlugin,
markdownPlugin,
]),
],
});
插件配置 #
javascript
function createConfigurablePlugin(options = {}) {
const {
include = /\.js$/,
exclude = /node_modules/,
transform = (code) => code,
} = options;
return {
name: 'configurable-plugin',
setup(build) {
build.onLoad({ filter: include }, async (args) => {
if (exclude.test(args.path)) return null;
const fs = require('fs').promises;
let contents = await fs.readFile(args.path, 'utf8');
contents = await transform(contents, args.path);
return { contents, loader: 'js' };
});
},
};
}
// 使用
esbuild.build({
plugins: [
createConfigurablePlugin({
include: /\.js$/,
exclude: /node_modules|vendor/,
transform: async (code, path) => {
console.log(`Transforming ${path}`);
return code.replace(/__VERSION__/g, '1.0.0');
},
}),
],
});
异步插件 #
javascript
const asyncPlugin = {
name: 'async-plugin',
setup(build) {
build.onLoad({ filter: /\.async$/ }, async (args) => {
const data = await fetchData(args.path);
return {
contents: `export default ${JSON.stringify(data)}`,
loader: 'js',
};
});
},
};
async function fetchData(path) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ path, loaded: true });
}, 100);
});
}
错误处理 #
javascript
const errorHandlingPlugin = {
name: 'error-handling',
setup(build) {
build.onLoad({ filter: /\.custom$/ }, async (args) => {
try {
const contents = await processFile(args.path);
return { contents, loader: 'js' };
} catch (error) {
return {
errors: [
{
text: error.message,
location: {
file: args.path,
line: error.line,
column: error.column,
},
},
],
};
}
});
},
};
警告处理 #
javascript
const warningPlugin = {
name: 'warning',
setup(build) {
build.onLoad({ filter: /\.js$/ }, async (args) => {
const fs = require('fs').promises;
const contents = await fs.readFile(args.path, 'utf8');
const warnings = [];
if (contents.includes('console.log')) {
warnings.push({
text: 'console.log found in production code',
location: { file: args.path },
});
}
return {
contents,
loader: 'js',
warnings,
};
});
},
};
插件调试 #
日志输出 #
javascript
const debugPlugin = {
name: 'debug',
setup(build) {
build.onStart(() => {
console.log('[DEBUG] Build started');
});
build.onResolve({ filter: /.*/ }, (args) => {
console.log(`[DEBUG] Resolving: ${args.path}`);
return null;
});
build.onLoad({ filter: /.*/ }, (args) => {
console.log(`[DEBUG] Loading: ${args.path}`);
return null;
});
build.onEnd((result) => {
console.log(`[DEBUG] Build ended with ${result.errors.length} errors`);
});
},
};
插件顺序 #
javascript
esbuild.build({
plugins: [
// 1. 首先处理解析
pathAliasPlugin,
// 2. 然后加载文件
yamlPlugin,
markdownPlugin,
// 3. 最后转换代码
transformPlugin,
// 4. 构建结束处理
reportPlugin,
],
});
下一步 #
现在你已经掌握了 esbuild 插件系统,接下来学习 高级特性 了解代码分割、Tree-shaking 等高级功能!
最后更新:2026-03-28