import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import childProcess from "child_process"; import esbuild from "esbuild"; import glob from "glob"; import { createRequire } from "module"; import { stdoutColors } from "../util/terminal.js"; const require = createRequire(import.meta.url); const dirname = path.dirname(fileURLToPath(import.meta.url)); const watch = process.argv[2] === "--watch"; function prelude(name) { return [ "/**\n", " * @license\n", " * ", name, "\n", " * Copyright ", new Date().getFullYear().toString(), " Daniel Wirtz / The AssemblyScript Authors\n", " * SPDX-License-Identifier: Apache-2.0\n", " */" ].join(""); } // Report what's going on function time() { return new Date().toISOString(); } function reportPlugin(name) { return { name: "reporter", setup(build) { let startTime = 0; build.onStart(() => { console.log(`${time()} - ${name} - Starting new build ...`); startTime = Date.now(); }); build.onEnd(({ errors, warnings }) => { const duration = Date.now() - startTime; if (errors.length) { console.log(`${time()} - ${name} - ${stdoutColors.red("ERROR")} (${errors.length} errors, ${warnings.length} warnings, ${duration} ms)`); } else { console.log(`${time()} - ${name} - ${stdoutColors.green("SUCCESS")} (${warnings.length} warnings, ${duration} ms)`); } }); } }; } // Standard library integration function bundleFile(filename) { return fs.readFileSync(filename, { encoding: "utf8" }).replace(/\r\n/g, "\n"); } const stdlibPlugin = { name: "stdlib", setup(build) { build.onResolve({ filter: /\bindex\.generated\.js$/ }, args => { return { path: path.join(args.resolveDir, args.path), watchFiles: glob.sync(path.join(dirname, "..", "std", "assembly") + "/**/*.ts") .concat([ path.join(dirname, "..", "package.json"), path.join(dirname, "..", "cli", "options.json"), path.join(dirname, "..", "std", "portable", "index.d.ts") ]) }; }); build.onLoad({ filter: /\bindex\.generated\.js$/ }, args => { const out = [ `// GENERATED FILE. DO NOT EDIT.\n\n` ]; const version = require("../package.json").version; out.push( `export const version = ${JSON.stringify(version)};\n` ); const options = require("../cli/options.json"); out.push( `export const options = ${JSON.stringify(options, null, 2)};\n` ); out.push( `export const libraryPrefix = "~lib/";\n` ); const libraryDir = path.join(dirname, "..", "std", "assembly"); const libraryFiles = {}; for (const file of glob.sync("**/!(*.d).ts", { cwd: libraryDir })) { libraryFiles[file.replace(/\.ts$/, "")] = bundleFile(path.join(libraryDir, file)); } out.push( `export const libraryFiles = ${JSON.stringify(libraryFiles, null, 2)};\n` ); const definitionFiles = { assembly: bundleFile(path.join(dirname, "..", "std", "assembly", "index.d.ts")), portable: bundleFile(path.join(dirname, "..", "std", "portable", "index.d.ts")) }; out.push( `export const definitionFiles = ${JSON.stringify(definitionFiles, null, 2)};\n` ); const generated = out.join(""); fs.writeFileSync(path.join(dirname, "..", "cli", "index.generated.js"), generated); return { contents: generated, loader: "js" }; }); } }; // Diagnostic messages integration const diagnosticsPlugin = { name: "diagnostics", setup(build) { build.onResolve({ filter: /\bdiagnosticMessages\.generated$/ }, args => { return { path: path.join(args.resolveDir, args.path), watchFiles: [ path.join(dirname, "..", "src", "diagnosticMessages.json") ] }; }); build.onLoad({ filter: /\bdiagnosticMessages\.generated$/ }, args => { const out = [ `// GENERATED FILE. DO NOT EDIT.\n\n` ]; function makeKey(text) { return text.replace(/[^\w]+/g, "_").replace(/_+$/, ""); } out.push("/** Enum of available diagnostic codes. */\n"); out.push("export enum DiagnosticCode {\n"); let first = true; const messages = JSON.parse(fs.readFileSync(path.join(dirname, "..", "src", "diagnosticMessages.json"))); Object.keys(messages).forEach(text => { let key = makeKey(text); if (first) first = false; else { out.push(",\n"); } out.push(" " + key + " = " + messages[text]); }); out.push("\n}\n\n"); out.push("/** Translates a diagnostic code to its respective string. */\n"); out.push("export function diagnosticCodeToString(code: DiagnosticCode): string {\n switch (code) {\n"); Object.keys(messages).forEach(text => { out.push(" case " + messages[text] + ": return " + JSON.stringify(text) + ";\n"); }); out.push(" default: return \"\";\n }\n}\n"); const generated = out.join(""); fs.writeFileSync(path.join(dirname, "..", "src", "diagnosticMessages.generated.ts"), generated); return { contents: generated, loader: "ts" }; }); } }; // Web template integration const webPlugin = { name: "web", setup(build) { build.onEnd(() => { const startTime = Date.now(); const stdout = []; console.log(`${time()} - ${"web"} - Starting new build ...`); childProcess.spawn("node", [ "./build-web.js" ], { cwd: dirname, stdio: "pipe" }).on("data", data => { stdout.push(data.toString()); }).on("error", err => { const duration = Date.now() - startTime; console.log(stdout.join("")); console.log(`${time()} - ${"web"} - ${stdoutColors.red("ERROR")} (had errors, ${duration} ms)`); }).on("close", code => { if (code) return; const duration = Date.now() - startTime; console.log(`${time()} - ${"web"} - ${stdoutColors.green("SUCCESS")} (no errors, ${duration} ms)`); }); }); } }; // Build compiler and CLI const common = { target: "esnext", platform: "node", format: "esm", external: [ "assemblyscript", "binaryen", "long" ], legalComments: "none", bundle: true, sourcemap: true, treeShaking: true, minify: true, watch, incremental: watch }; const srcBuild = esbuild.build({ entryPoints: [ "./src/index.ts" ], tsconfig: "./src/tsconfig.json", outfile: "./dist/assemblyscript.js", banner: { js: prelude("The AssemblyScript compiler") }, plugins: [ diagnosticsPlugin, reportPlugin("src") ], ...common }); const cliBuild = esbuild.build({ entryPoints: [ "./cli/index.js" ], tsconfig: "./cli/tsconfig.json", outfile: "./dist/asc.js", banner: { js: prelude("The AssemblyScript frontend") }, plugins: [ stdlibPlugin, webPlugin, reportPlugin("cli") ], ...common }); // Optionally build definitions (takes a while) let buildingDefinitions = false; function buildDefinitions() { const startTime = Date.now(); const stdout = []; console.log(`${time()} - ${"dts"} - Starting new build ...`); buildingDefinitions = true; childProcess.spawn("node", [ "./build-dts.js" ], { cwd: dirname, stdio: "pipe" }).on("data", data => { stdout.push(data.toString()); }).on("error", err => { buildingDefinitions = false; const duration = Date.now() - startTime; console.log(stdout.join("")); console.log(`${time()} - ${"dts"} - ${stdoutColors.red("ERROR")} (had errors, ${duration} ms)`); }).on("close", code => { buildingDefinitions = false; if (code) return; const duration = Date.now() - startTime; console.log(`${time()} - ${"dts"} - ${stdoutColors.green("SUCCESS")} (no errors, ${duration} ms)`); }); } if (watch) { console.log("Watching for changes. Press RETURN to rebuild definitions.\n"); process.stdin.on("data", data => { if (data == "\r\n" || data == "\n") { process.stdout.write("\u001b[1A"); if (!buildingDefinitions) buildDefinitions(); } }); } console.log(`src : Compiler as a library cli : Compiler frontend asc dts : TS definition bundles web : Example web template\n`); await Promise.all([ srcBuild, cliBuild ]); buildDefinitions();