MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1import * as esbuild from 'esbuild';
2import path from 'node:path';
3import { writeFileSync } from 'node:fs';
4
5function cString(input) {
6 return JSON.stringify(input);
7}
8
9function toSpecifier(rootDir, filePath) {
10 const relativePath = path.relative(rootDir, filePath).replaceAll('\\', '/');
11 const withoutExt = relativePath.replace(/\.(cts|mts|ts|cjs|mjs|js)$/u, '');
12
13 if (withoutExt.startsWith('node/')) {
14 return `node:${withoutExt.slice('node/'.length)}`;
15 }
16
17 if (withoutExt.startsWith('ant/')) {
18 return `ant:${withoutExt.slice('ant/'.length)}`;
19 }
20
21 throw new Error(`Unsupported builtin module path: ${relativePath}`);
22}
23
24function toAliasSpecifiers(specifier) {
25 if (specifier.startsWith('node:')) return [specifier.slice('node:'.length)];
26 return [];
27}
28
29function toFormat(filePath) {
30 if (/\.(cts|cjs)$/u.test(filePath)) return 'MODULE_EVAL_FORMAT_CJS';
31 if (/\.(mts|mjs)$/u.test(filePath)) return 'MODULE_EVAL_FORMAT_ESM';
32 return 'MODULE_EVAL_FORMAT_UNKNOWN';
33}
34
35async function bundleBuiltin(entryPath, format) {
36 const output = await esbuild.build({
37 entryPoints: [entryPath],
38 bundle: true,
39 write: false,
40 minify: true,
41 platform: 'neutral',
42 format: format === 'MODULE_EVAL_FORMAT_ESM' ? 'esm' : 'cjs',
43 target: ['es2020'],
44 plugins: [
45 {
46 name: 'builtin-externals',
47 setup(build) {
48 build.onResolve({ filter: /^(node:|ant:)/ }, args => ({
49 path: args.path,
50 external: true
51 }));
52 }
53 }
54 ]
55 });
56
57 if (output.outputFiles.length !== 1) {
58 throw new Error(`Expected exactly one bundled output for ${entryPath}`);
59 }
60
61 return output.outputFiles[0].contents;
62}
63
64function generateHeader(rootDir, bundles) {
65 const lines = [];
66
67 lines.push('/* Auto-generated builtin bundle data. DO NOT EDIT. */');
68 lines.push('');
69 lines.push('#ifndef ANT_BUILTIN_BUNDLE_DATA_H');
70 lines.push('#define ANT_BUILTIN_BUNDLE_DATA_H');
71 lines.push('');
72 lines.push('#include <stddef.h>');
73 lines.push('#include <stdint.h>');
74 lines.push('');
75
76 bundles.forEach((bundle, index) => {
77 const byteLines = [];
78 for (let i = 0; i < bundle.bytes.length; i += 16) {
79 byteLines.push(' ' + Array.from(bundle.bytes.slice(i, i + 16)).join(', '));
80 }
81
82 lines.push(`/* ${bundle.specifier} <- ${path.relative(rootDir, bundle.entryPath).replaceAll('\\', '/')} */`);
83 lines.push(`static const uint8_t ant_builtin_bundle_${index}[] = {`);
84 lines.push(byteLines.join(',\n'));
85 lines.push('};');
86 lines.push('');
87 });
88
89 lines.push('static const ant_builtin_bundle_module_t ant_builtin_bundle_modules[] = {');
90 bundles.forEach((bundle, index) => {
91 lines.push(` { ant_builtin_bundle_${index}, sizeof(ant_builtin_bundle_${index}), ${bundle.format} },`);
92 });
93 lines.push('};');
94 lines.push('');
95 lines.push('static const ant_builtin_bundle_alias_t ant_builtin_bundle_aliases[] = {');
96 bundles.forEach((bundle, index) => {
97 for (const specifier of bundle.specifiers) {
98 lines.push(` { ${cString(specifier)}, ${specifier.length}, ${cString(bundle.specifier)}, ${index} },`);
99 }
100 });
101 lines.push('};');
102 lines.push('');
103 lines.push('static const size_t ant_builtin_bundle_module_count =');
104 lines.push(' sizeof(ant_builtin_bundle_modules) / sizeof(ant_builtin_bundle_modules[0]);');
105 lines.push('');
106 lines.push('static const size_t ant_builtin_bundle_alias_count =');
107 lines.push(' sizeof(ant_builtin_bundle_aliases) / sizeof(ant_builtin_bundle_aliases[0]);');
108 lines.push('');
109 lines.push('#endif');
110
111 return lines.join('\n') + '\n';
112}
113
114async function main() {
115 const args = process.argv.slice(2);
116 if (args.length < 3) {
117 console.error(`Usage: ${process.argv[1]} <builtins-root> <output.h> <entry...>`);
118 process.exit(1);
119 }
120
121 const [builtinsRoot, outputFile, ...entryFiles] = args;
122 const bundles = [];
123
124 for (const entryPath of entryFiles) {
125 const specifier = toSpecifier(builtinsRoot, entryPath);
126 const format = toFormat(entryPath);
127 const bytes = await bundleBuiltin(entryPath, format);
128 const specifiers = [specifier, ...toAliasSpecifiers(specifier)];
129 bundles.push({ entryPath, specifier, specifiers, format, bytes });
130 }
131
132 const header = generateHeader(builtinsRoot, bundles);
133 const totalBundledBytes = bundles.reduce((sum, bundle) => sum + bundle.bytes.length, 0);
134
135 writeFileSync(outputFile, header);
136
137 console.log(`builtin bundle generated successfully: ${outputFile}`);
138 console.log(` bundled size: ${totalBundledBytes} bytes`);
139 console.log(` modules: ${bundles.length}`);
140}
141
142main().catch(error => {
143 console.error(error);
144 process.exit(1);
145});