import { brightBlue, deepMerge, emptyDir, fromFileUrl, green, join, outdent, relative, resolve, SEP, sprintf, underline, wait,} from "./lib/build/deps.ts";import { compileSources } from "./lib/build/compileSources.ts";import { copySource } from "./lib/build/copySource.ts";import { createGraph } from "./lib/build/createGraph.ts";import { patchDenoConfig } from "./lib/build/patchDenoConfig.ts";import { resolvePaths } from "./lib/build/resolvePaths.ts";import { vendorDependencies } from "./lib/build/vendorDependencies.ts";import { assert } from "./lib/deps.ts";import { cleanup } from "./lib/build/cleanup.ts";import type { BuildOptions, BuildPlugin, BuildResult,} from "./lib/build/types.ts";import { createBuildContext } from "./lib/build/context.ts";import { assetManifest } from "./lib/build/assetManifest.ts";import { writeJsonFile } from "./lib/utils/json.ts";import { ULTRA_STATIC_PATH } from "./lib/constants.ts";
export type { BuildPlugin };
const defaultOptions: Partial<BuildOptions> = { output: ".ultra", reload: false, sourceMaps: false, exclude: [ ".git", join("**", ".DS_Store"), ],};
const cwd = Deno.cwd();const BUILD_COMPLETE_MESSAGE = "Build complete!";
function cwdRelative(path: string) { return relative(cwd, path);}
export default async function build( options: BuildOptions,): Promise<BuildResult> { const resolvedOptions = deepMerge(defaultOptions, options);
await assertBuildOptions(resolvedOptions);
const { browserEntrypoint, serverEntrypoint, output, sourceMaps, reload, plugin, exclude, } = resolvedOptions as Required<BuildOptions>;
const spinner = wait("Building").start();
const paths = resolvePaths(output, { browserEntrypoint, serverEntrypoint, exclude: [...exclude, output], });
const buildContext = createBuildContext(paths);
spinner.text = sprintf( "Cleaning output directory: %s", cwdRelative(buildContext.paths.outputDir), );
await emptyDir(buildContext.paths.outputDir);
try { if (plugin && plugin.onPreBuild) { await plugin.onPreBuild(buildContext); } } catch (error) { console.error(error); }
spinner.text = sprintf( "Copying source from: %s to %s", buildContext.paths.rootDir, cwdRelative(buildContext.paths.outputDir), ); await copySource(buildContext);
spinner.text = "Building module graph"; const browserModuleGraph = buildContext.graph = await createGraph( buildContext, );
spinner.text = "Compiling and minifying sources"; const compiled = await compileSources(buildContext, { sourceMaps, hash: true, });
spinner.text = "Vendoring browser dependencies";
const [browserImportMap, serverImportMap] = await Promise.all([ vendorDependencies(buildContext, { target: "browser", reload, paths: [...Array.from(compiled.values())], }), vendorDependencies(buildContext, { target: "server", reload, }), ]);
function toRelativeSpecifier(from: string, specifier: string) { specifier = relative( from, specifier.startsWith("file://") ? fromFileUrl(specifier) : specifier, );
return `.${SEP}${specifier}`; }
for (const module of browserModuleGraph.modules) { const relativeSpecifier = toRelativeSpecifier( buildContext.paths.outputDir, module.specifier, );
const resolvedSpecifier = toRelativeSpecifier( buildContext.paths.outputDir, compiled.get( module.specifier, )!, );
serverImportMap.imports[relativeSpecifier] = resolvedSpecifier;
const importSpecifier = `${ULTRA_STATIC_PATH}/${ relativeSpecifier.replace("./", "") }`;
const importResolved = `${ULTRA_STATIC_PATH}/${ resolvedSpecifier.replace("./", "") }`;
browserImportMap.imports[importSpecifier] = importResolved }
browserImportMap.imports[ULTRA_STATIC_PATH];
await Promise.all([ writeJsonFile( resolve(paths.outputDir, "./importMap.browser.json"), browserImportMap, ), writeJsonFile( resolve(paths.outputDir, "./importMap.server.json"), serverImportMap, ), ]);
const assets = await assetManifest(buildContext);
const denoConfig = await patchDenoConfig(paths);
await cleanup(paths);
const buildResult: BuildResult = { options: resolvedOptions, paths, importMap: { browser: browserImportMap, server: serverImportMap, }, assetManifest: assets, denoConfig, files: buildContext.files, };
const finalBuildResult = buildResult;
if (plugin) { try { spinner.text = sprintf("Executing build plugin: %s:onBuild", plugin.name);
await plugin.onBuild(finalBuildResult);
if (plugin.onPostBuild) { spinner.text = sprintf( "Executing build plugin: %s:onPostBuild", plugin.name, );
await plugin.onPostBuild(finalBuildResult); }
spinner.succeed(BUILD_COMPLETE_MESSAGE); } catch (error) { spinner.fail(error.message); } } else { spinner.succeed(BUILD_COMPLETE_MESSAGE); console.log(outdent`\n You can now deploy the "${brightBlue(output)}" output directory to a platform of your choice. Instructions for common deployment platforms can be found at ${green('https://ultrajs.dev/docs#deploying')}.\n Alternatively, you can cd into "${brightBlue(output)}" and run: ${underline("deno task start")} `); }
buildContext.graph?.free();
return finalBuildResult;}
export function assertBuildOptions(options: BuildOptions) { try { assert( options.browserEntrypoint, `No "browserEntrypoint" was provided, received "${options.browserEntrypoint}"`, );
assert( options.serverEntrypoint, `No "serverEntrypoint" was provided, received "${options.serverEntrypoint}"`, );
assert( options.output, `No "output" was provided, received "${options.output}"`, ); } catch (error) { throw new Error(error.message); }}