1
0
silverbullet/plugos/compile.ts
2022-12-05 11:41:28 +01:00

143 lines
3.8 KiB
TypeScript

// import { esbuild } from "../../mod.ts";
import * as esbuildWasm from "https://deno.land/x/esbuild@v0.14.54/wasm.js";
import * as esbuildNative from "https://deno.land/x/esbuild@v0.14.54/mod.js";
export const esbuild: typeof esbuildWasm = Deno.run === undefined
? esbuildWasm
: esbuildNative;
import { path } from "./deps.ts";
import { denoPlugin } from "./forked/esbuild_deno_loader/mod.ts";
import { patchDenoLibJS } from "./hack.ts";
import { Manifest } from "./types.ts";
export type CompileOptions = {
debug?: boolean;
imports?: Manifest<any>[];
importMap?: URL;
// Reload plug import cache
reload?: boolean;
// Print info on bundle size
info?: boolean;
};
function esBuildExternals(imports?: Manifest<any>[]) {
if (!imports) {
return [];
}
const externals: string[] = [];
for (const manifest of imports) {
for (const dep of Object.keys(manifest.dependencies || {})) {
if (!externals.includes(dep)) {
externals.push(dep);
}
}
}
return externals;
}
export async function compile(
filePath: string,
functionName: string | undefined = undefined,
options: CompileOptions = {},
): Promise<string> {
const outFile = await Deno.makeTempFile({ suffix: ".js" });
let inFile = filePath;
if (functionName) {
// Generate a new file importing just this one function and exporting it
inFile = await Deno.makeTempFile({ suffix: ".ts" });
await Deno.writeTextFile(
inFile,
`import {${functionName}} from "${
path.resolve(filePath)
}";export default ${functionName};`,
);
}
// console.log("External modules", excludeModules);
try {
// TODO: Figure out how to make source maps work correctly with eval() code
const result = await esbuild.build({
entryPoints: [path.basename(inFile)],
bundle: true,
format: "iife",
globalName: "mod",
platform: "browser",
sourcemap: false, //debug ? "inline" : false,
minify: !options.debug,
outfile: outFile,
metafile: options.info,
external: esBuildExternals(options.imports),
treeShaking: true,
plugins: [
denoPlugin({
// TODO do this differently
importMapURL: options.importMap ||
new URL("./../import_map.json", import.meta.url),
loader: "native",
}),
],
absWorkingDir: path.resolve(path.dirname(inFile)),
});
if (options.info) {
const text = await esbuild.analyzeMetafile(result.metafile!);
console.log("Bundle info for", functionName, text);
}
let jsCode = await Deno.readTextFile(outFile);
jsCode = patchDenoLibJS(jsCode);
await Deno.remove(outFile);
return `(() => { ${jsCode} return mod;})()`;
} finally {
if (inFile !== filePath) {
await Deno.remove(inFile);
}
}
}
export async function compileModule(
cwd: string,
moduleName: string,
options: CompileOptions = {},
): Promise<string> {
const inFile = path.resolve(cwd, "_in.ts");
await Deno.writeTextFile(inFile, `export * from "${moduleName}";`);
const code = await compile(inFile, undefined, options);
await Deno.remove(inFile);
return code;
}
export async function sandboxCompile(
filename: string,
code: string,
functionName?: string,
options: CompileOptions = {},
): Promise<string> {
const tmpDir = await Deno.makeTempDir();
await Deno.writeTextFile(`${tmpDir}/${filename}`, code);
const jsCode = await compile(
`${tmpDir}/${filename}`,
functionName,
options,
);
await Deno.remove(tmpDir, { recursive: true });
return jsCode;
}
export async function sandboxCompileModule(
moduleUrl: string,
options: CompileOptions = {},
): Promise<string> {
await Deno.writeTextFile(
"_mod.ts",
`module.exports = require("${moduleUrl}");`,
);
const code = await compile("_mod.ts", undefined, options);
await Deno.remove("_mod.ts");
return code;
}