Page MenuHomeSealhub

build.ts
No OneTemporary

build.ts

import { build, BuildContext, BuildResult, context } from "esbuild";
import { promises as fs } from "fs";
import * as chokidar from "chokidar";
import { default as glob } from "tiny-glob";
import { generate_css_includes } from "./find-css-includes.js";
import { sleep } from "./utils/sleep.js";
import { generateCollections } from "./generate-collections.js";
import { generateRoutes } from "./generate-routes.js";
import { generateComponents } from "./generate-components.js";
import { make_notifier } from "./notifier.js";
import getPort from "get-port";
import { removeNamedImports } from "../remove-named-imports.js";
import md5 from "md5";
import _locreq from "locreq";
import path, { basename, extname } from "path";
import { copyFile, writeFile } from "fs/promises";
const target_locreq = _locreq(process.cwd());
async function get_notifier_port() {
return getPort({ port: 4000 });
}
async function write_notifier_config(watch: boolean, port?: number) {
const config = { watch } as Record<string, boolean | number>;
if (port) {
config.port = port;
}
await fs.writeFile(
target_locreq.resolve("public/dist/notifier.json"),
JSON.stringify(config)
);
}
function addSuffix(filename: string, suffix: string) {
return filename.replace(/(\.[a-zA-Z0-9]+)?$/, suffix + "$1");
}
async function makeResourceURL(file_path: string) {
const hash_base = file_path + (await fs.stat(file_path)).mtimeMs;
const hash = md5(hash_base);
const public_url =
"dist/" + addSuffix(basename(file_path), "-" + hash.slice(0, 8));
const public_dir = "public";
await copyFile(
file_path,
target_locreq.resolve(path.resolve(public_dir, public_url))
);
return "/" + public_url;
}
async function build_ts() {
await Promise.all([
generateCollections(),
generateRoutes(),
generateComponents(),
]);
const embeddable_file_extensions = ["svg", "png", "jpg", "jpeg", "webp"];
const entryPoints = (
await glob(
`./src/back/**/*.{ts,tsx,${embeddable_file_extensions.join(",")}}`
)
).filter((path) => !path.includes(".#"));
try {
await build({
entryPoints,
sourcemap: true,
bundle: false,
jsxFactory: "TempstreamJSX.createElement",
outdir: "./dist/back",
logLevel: "info",
platform: "node",
target: "es2022",
format: "esm",
loader: Object.fromEntries(
embeddable_file_extensions.map((ext) => ["." + ext, "js"])
),
plugins: [
{
name: "sealgen-rewrite-asset-imports",
setup(build) {
build.onLoad(
{ filter: /\.tsx?$/ },
async (args: any) => {
let contents = await fs.readFile(
args.path,
"utf8"
);
for (const ext of embeddable_file_extensions) {
contents = contents.replaceAll(
new RegExp(
`^import (\w+|{[^}]+}) from "([^"]+.${ext})";`,
"gm"
),
(line) =>
line.replace(`.${ext}"`, '.js"')
);
}
contents = removeNamedImports(contents);
return {
contents,
loader:
extname(args.path) === ".tsx"
? "tsx"
: "ts",
};
}
);
},
},
{
name: "sealgen-load-assets",
setup(build) {
build.onLoad(
{
filter: new RegExp(
`\\.(${embeddable_file_extensions.join(
"|"
)})$`
),
},
async (args: any) => {
const mode = "url";
let contents = `export const url = "${await makeResourceURL(
args.path
)}";
import fs from "fs";
let content_promise = null;
export const content = function(){
if(content_promise) return content_promise;
content_promise = fs.promises.readFile("${args.path}", "utf-8");
return content_promise;
}
`;
return {
contents,
};
}
);
},
},
],
});
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
console.error(e.message);
}
}
async function complex_build(watch: boolean): Promise<void> {
const loader = Object.fromEntries(
(<const>["png", "svg", "jpg", "gif", "jpeg", "otf", "ttf"]).map(
(ext) => <const>["." + ext, "file"]
)
);
let css_build: BuildContext = await context({
entryPoints: ["./src/main.css"],
sourcemap: true,
bundle: true,
outdir: "./public/dist",
logLevel: "info",
// plugins: [sassPlugin()],
loader,
});
let ongoing_ts = false;
let ongoing_css = false;
let ongoing_ts_promise: Promise<any> | null = null;
if (watch) {
const watcher = chokidar.watch("src", { ignoreInitial: true });
const port = await get_notifier_port();
const notifier = make_notifier(port);
await write_notifier_config(watch, port);
watcher.on("all", async (_, path) => {
if (!css_build) return;
if (path.includes(".#")) return;
if (path.endsWith(".css") && !path.endsWith("/includes.css")) {
if (ongoing_css) {
return;
}
ongoing_css = true;
// refresh the list of all css files in includes.css
await generate_css_includes();
try {
await css_build?.rebuild?.();
console.log(`Built main.css [on ${path}]`);
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
console.error(e.message);
await sleep(200);
css_build
?.rebuild?.()
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
.catch((e) => console.error(e?.message));
}
if (ongoing_ts_promise) {
await ongoing_ts_promise;
}
notifier("css");
ongoing_css = false;
}
if (
(path.endsWith(".ts") || path.endsWith(".tsx")) &&
!path.endsWith("src/back/collections/collections.ts") &&
!path.endsWith("src/back/routes/routes.ts") &&
!path.endsWith("src/back/routes/urls.ts")
) {
if (ongoing_ts) {
return;
}
ongoing_ts = true;
ongoing_ts_promise = build_ts();
await ongoing_ts_promise;
notifier("ts");
ongoing_ts_promise = null;
console.log(`Finished TS build [on ${path}]`);
ongoing_ts = false;
}
});
} else {
await write_notifier_config(watch);
}
await generate_css_includes();
await Promise.all([css_build.rebuild(), build_ts()]);
if (watch) {
css_build?.rebuild?.();
} else {
css_build.dispose();
}
}
export async function buildProject({
watch,
}: {
watch: boolean;
}): Promise<void> {
try {
await build({
entryPoints: ["./src/front/index.ts"],
sourcemap: true,
outfile: "./public/dist/bundle.js",
logLevel: "info",
bundle: true,
});
await complex_build(watch);
} catch (e) {
console.log("CAUGHT!");
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
console.error(e.message);
if (!watch) {
process.exit(1);
}
}
}

File Metadata

Mime Type
text/x-java
Expires
Sat, Sep 20, 14:43 (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
949291
Default Alt Text
build.ts (6 KB)

Event Timeline