@fastly/js-compute
Version:
JavaScript SDK and CLI for building JavaScript applications on [Fastly Compute](https://www.fastly.com/products/edge-compute/serverless).
207 lines (206 loc) • 8.93 kB
JavaScript
import { dirname, sep, normalize } from 'node:path';
import { tmpdir, freemem } from 'node:os';
import { spawnSync, } from 'node:child_process';
import { mkdir, readFile, mkdtemp } from 'node:fs/promises';
import { rmSync } from 'node:fs';
import weval from '@bytecodealliance/weval';
import wizer from '@bytecodealliance/wizer';
import { isDirectory, isFile } from './files.js';
import { CompilerContext } from './compilerPipeline.js';
import { bundleStep } from './compiler-steps/bundle.js';
import { precompileRegexesStep } from './compiler-steps/precompileRegexes.js';
import { addStackMappingHelpersStep } from './compiler-steps/addStackMappingHelpers.js';
import { addFastlyHelpersStep } from './compiler-steps/addFastlyHelpers.js';
import { composeSourcemapsStep } from './compiler-steps/composeSourcemaps.js';
const maybeWindowsPath = process.platform === 'win32'
? (path) => {
return '//?/' + path.replace(/\\/g, '/');
}
: (path) => path;
async function getTmpDir() {
return await mkdtemp(normalize(tmpdir() + sep));
}
export async function compileApplicationToWasm(params) {
const { output, wasmEngine, enableHttpCache = false, enableExperimentalHighResolutionTimeMethods = false, enableAOT = false, aotCache = '', enableStackTraces, excludeSources, debugIntermediateFilesDir, wevalBin, moduleMode = false, doBundle = false, env, } = params;
let input = params.input;
try {
if (!(await isFile(input))) {
console.error(`Error: The \`input\` path does not point to a file: ${input}`);
process.exit(1);
}
}
catch {
console.error(`Error: The \`input\` path points to a non-existent file: ${input}`);
process.exit(1);
}
try {
await readFile(input, { encoding: 'utf-8' });
}
catch (maybeError) {
const error = maybeError instanceof Error ? maybeError : new Error(String(maybeError));
console.error(`Error: Failed to open the \`input\` (${input})`, error.message);
process.exit(1);
}
try {
if (!(await isFile(wasmEngine))) {
console.error(`Error: The \`wasmEngine\` path does not point to a file: ${wasmEngine}`);
process.exit(1);
}
}
catch {
console.error(`Error: The \`wasmEngine\` path points to a non-existent file: ${wasmEngine}`);
process.exit(1);
}
// If output exists already, make sure it's not a directory
// (we'll try to overwrite it if it's a file)
try {
if (await isDirectory(output)) {
console.error(`Error: The \`output\` path points to a directory: ${output}`);
process.exit(1);
}
}
catch {
// Output doesn't exist
}
try {
await mkdir(dirname(output), {
recursive: true,
});
}
catch (maybeError) {
const error = maybeError instanceof Error ? maybeError : new Error(String(maybeError));
console.error(`Error: Failed to create the \`output\` (${dirname(output)}) directory: ${output}`, error.message);
process.exit(1);
}
if (debugIntermediateFilesDir != null) {
try {
console.log(`Preparing \`debug-intermediate-files\` directory: ${debugIntermediateFilesDir}`);
await mkdir(debugIntermediateFilesDir, {
recursive: true,
});
}
catch (maybeError) {
const error = maybeError instanceof Error
? maybeError
: new Error(String(maybeError));
console.error(`Error: Failed to create the \`debug-intermediate-files\` (${debugIntermediateFilesDir}) directory`, error.message);
process.exit(1);
}
}
const tmpDir = await getTmpDir();
try {
if (doBundle) {
const ctx = new CompilerContext(input, tmpDir, debugIntermediateFilesDir, moduleMode, enableStackTraces, excludeSources);
// bundle input -> apply esbuild (bundle package imports, apply Fastly Plugin)
ctx.addCompilerPipelineStep(bundleStep);
// precompile regexes
ctx.addCompilerPipelineStep(precompileRegexesStep);
// add stack mapping helpers
if (enableStackTraces) {
ctx.addCompilerPipelineStep(addStackMappingHelpersStep);
}
// add Fastly helpers
ctx.addCompilerPipelineStep(addFastlyHelpersStep);
// compile sourcemaps up to this point and inject into bundle
if (enableStackTraces) {
ctx.addCompilerPipelineStep(composeSourcemapsStep);
}
await ctx.applyCompilerPipeline();
await ctx.maybeWriteDebugIntermediateFile('fastly_bundle.js');
// the output of the pipeline is the Wizer/Weval input
input = ctx.outFilepath;
}
const spawnOpts = {
stdio: [null, process.stdout, process.stderr],
input: maybeWindowsPath(input),
shell: true,
encoding: 'utf-8',
env: {
...env,
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS: enableExperimentalHighResolutionTimeMethods ? '1' : '0',
ENABLE_EXPERIMENTAL_HTTP_CACHE: enableHttpCache ? '1' : '0',
RUST_MIN_STACK: String(Math.max(8 * 1024 * 1024, Math.floor(freemem() * 0.1))),
},
};
try {
if (!doBundle) {
if (enableAOT) {
const wevalPath = wevalBin ?? (await weval());
const wevalProcess = spawnSync(`"${wevalPath}"`, [
'weval',
'-v',
...(aotCache ? [`--cache-ro ${aotCache}`] : []),
`--dir="${maybeWindowsPath(process.cwd())}"`,
'-w',
`-i "${wasmEngine}"`,
`-o "${output}"`,
], spawnOpts);
if (wevalProcess.status !== 0) {
throw new Error(`Weval initialization failure`);
}
process.exitCode = wevalProcess.status;
}
else {
const wizerProcess = spawnSync(`"${wizer}"`, [
'--allow-wasi',
`--wasm-bulk-memory=true`,
`--dir="${maybeWindowsPath(process.cwd())}"`,
'--inherit-env=true',
'-r _start=wizer.resume',
`-o="${output}"`,
`"${wasmEngine}"`,
], spawnOpts);
if (wizerProcess.status !== 0) {
throw new Error(`Wizer initialization failure`);
}
process.exitCode = wizerProcess.status;
}
}
else {
spawnOpts.input = `${maybeWindowsPath(input)}${moduleMode ? '' : ' --legacy-script'}`;
if (enableAOT) {
const wevalPath = wevalBin ?? (await weval());
const wevalProcess = spawnSync(`"${wevalPath}"`, [
'weval',
'-v',
...(aotCache ? [`--cache-ro ${aotCache}`] : []),
'--dir .',
`--dir ${maybeWindowsPath(dirname(input))}`,
'-w',
`-i "${wasmEngine}"`,
`-o "${output}"`,
], spawnOpts);
if (wevalProcess.status !== 0) {
throw new Error(`Weval initialization failure`);
}
process.exitCode = wevalProcess.status;
}
else {
const wizerProcess = spawnSync(`"${wizer}"`, [
'--inherit-env=true',
'--allow-wasi',
'--dir=.',
`--dir=${maybeWindowsPath(dirname(input))}`,
'-r _start=wizer.resume',
`--wasm-bulk-memory=true`,
`-o="${output}"`,
`"${wasmEngine}"`,
], spawnOpts);
if (wizerProcess.status !== 0) {
throw new Error(`Wizer initialization failure`);
}
process.exitCode = wizerProcess.status;
}
}
}
catch (maybeError) {
const error = maybeError instanceof Error
? maybeError
: new Error(String(maybeError));
throw new Error(`Error: Failed to compile JavaScript to Wasm:\n${error.message}`);
}
}
finally {
rmSync(tmpDir, { recursive: true });
}
}