Open wenerme opened 8 months ago
You are welcome to write a plugin to do that: https://esbuild.github.io/plugins/
Ok, let me try, this works, but I don't know how to filter by with type:embed, workaround by force a prefix embed:
import fs from 'node:fs/promises';
import path from 'node:path';
import * as esbuild from 'esbuild';
import type { Plugin } from 'esbuild';
let embedPlugin: Plugin = {
name: 'embed',
setup(build) {
build.onResolve({ filter: /^embed:.*/, namespace: 'file' }, (args) => {
switch (args.kind) {
case 'import-statement':
case 'dynamic-import':
break;
default:
return null;
}
let p = args.path.slice('embed:'.length);
if (p.startsWith('.')) {
p = path.resolve(args.resolveDir, p);
}
return {
path: p,
namespace: 'embed',
pluginData: {
resolveDir: args.resolveDir,
},
};
});
build.onLoad({ filter: /.*/, namespace: 'embed' }, async (args) => {
const resolveDir = args.pluginData.resolveDir;
if (args.with?.type !== 'embed') {
return null;
}
const cwd = args.with.cwd || '/app';
console.log(`embed ${path.relative(resolveDir, args.path)} to ${cwd}`);
const stat = await fs.stat(args.path);
if (!stat.isDirectory()) {
throw new Error(`Embed need a directory: ${args.path}`);
}
async function readDirectory(dir: string, basePath = dir, result: Record<string, any> = {}) {
const files = await fs.readdir(dir, { withFileTypes: true });
for (const file of files) {
const filePath = path.join(dir, file.name);
const relativePath = path.relative(basePath, filePath);
if (file.isDirectory()) {
readDirectory(filePath, basePath, result);
} else {
result[path.resolve(cwd, relativePath)] = await fs.readFile(filePath, 'utf8');
}
}
return result;
}
const o = await readDirectory(args.path);
return {
contents: `
import { memfs } from 'memfs';
const {fs:lfs,vol} = memfs(${JSON.stringify(o)}, '/app/');
const fs = lfs.promises
export {
fs,vol,lfs
}
`,
loader: 'js',
resolveDir: resolveDir,
};
});
},
};
await esbuild.build({
entryPoints: ['app.ts'],
bundle: true,
outfile: 'out.mjs',
plugins: [embedPlugin],
format: 'esm',
platform: 'node',
target: 'node20',
banner: {
js: `import { createRequire } from 'module';const require = createRequire(import.meta.url);var __filename;var __dirname;{const {fileURLToPath} = await import('url');const {dirname} = await import('path');var __filename = fileURLToPath(import.meta.url); __dirname = dirname(__filename)};`,
},
});
types.d.ts
declare module 'embed:*' {
const fs: typeof import('node:fs/promises');
export { fs };
}
app.ts
import { fs } from './src/utils' with { type: 'embed', cwd: '/app/' };
console.log(`FS`, await fs.readdir('/app'));
console.log(`Content`, await fs.readFile('/app/Closer.ts', 'utf-8'));
It's not just you. There isn't a way to declaratively filter by type: 'embed'
right now. I still need to add an additional API for that. Right now you have to write code that checks the with
map provided to the plugin. It should be the same thing, but just less efficient than it could be if there was a declarative API (since it would avoid some unnecessary IPC traffic).
I want do where to ask about this feature, but I do think this is possible for esbuild.
I want to deliver a single file which can run as a server, that serve the static or dynamic ssr content. I already use esbuild to bundle everything into a single
cli.mjs
, but esbuild left other content behind. I know I can do this by file loader likeimport content from './index.html'
, but I think maybeIf this can return a streamich/memfs, maybe a hono server can accept a fs object to use the embeded files, do we can deliver a single file that can provide a functional web app.
Just like
//go:embed
for golang or include_dir for Rust.