generated from alecodes/base-template
205 lines
4.5 KiB
TypeScript
205 lines
4.5 KiB
TypeScript
import type { BuildConfig, BunPlugin, PluginBuilder } from "bun";
|
|
import type { FileImporter } from "sass";
|
|
import { type Config, type Entrypoint, EntrypointType, LogType } from "./types";
|
|
|
|
import { basename, join, normalize } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { rm } from "node:fs/promises";
|
|
import slug from "slug";
|
|
|
|
// This is used to prevent creating folders with external packages names
|
|
slug.extend({ "/": "-" });
|
|
|
|
const nodeModuleImporter: FileImporter<"async"> = {
|
|
findFileUrl(url) {
|
|
if (url.startsWith("@")) {
|
|
return new URL(import.meta.resolve(url));
|
|
}
|
|
|
|
return null;
|
|
},
|
|
};
|
|
|
|
const sassPlugin: BunPlugin = {
|
|
name: "Sass Loader",
|
|
async setup(build: PluginBuilder) {
|
|
const sass = await import("sass");
|
|
|
|
build.onLoad({ filter: /\.scss$/ }, async ({ path }) => {
|
|
const result = await sass.compileAsync(path, {
|
|
importers: [nodeModuleImporter],
|
|
});
|
|
|
|
return {
|
|
loader: "css",
|
|
contents: result.css,
|
|
};
|
|
});
|
|
},
|
|
};
|
|
|
|
function log(logtype: LogType, msg: string) {
|
|
const reset = "\x1b[0m";
|
|
let color = Bun.color("white", "ansi");
|
|
|
|
switch (logtype) {
|
|
case LogType.Success:
|
|
color = Bun.color("green", "ansi");
|
|
break;
|
|
case LogType.Error:
|
|
color = Bun.color("#f24444", "ansi");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!color) {
|
|
color = reset;
|
|
}
|
|
|
|
console.log(color + msg + reset);
|
|
}
|
|
|
|
// Packages needs to exist in the package.json file for this to work
|
|
function resolvePackage(pkg: string): Entrypoint {
|
|
const path = normalize(fileURLToPath(import.meta.resolve(pkg)));
|
|
const file = Bun.file(import.meta.resolve(pkg));
|
|
const mimetype = file.type.split(";").at(0);
|
|
|
|
let type: EntrypointType;
|
|
|
|
switch (mimetype) {
|
|
case "text/x-scss":
|
|
type = EntrypointType.Sass;
|
|
break;
|
|
case "text/javascript":
|
|
type = EntrypointType.Js;
|
|
break;
|
|
default:
|
|
throw new Error(`No loader found for type ${mimetype} at path ${path}`);
|
|
}
|
|
|
|
return {
|
|
path,
|
|
type,
|
|
};
|
|
}
|
|
|
|
export default {
|
|
log,
|
|
|
|
build: async (config: Config, entrypoints: Entrypoint[]) => {
|
|
// Resolve external packages
|
|
|
|
const external_cache: string[] = [];
|
|
const resolved_entrypoints = entrypoints.map((item) => {
|
|
if (item.type !== EntrypointType.Package) {
|
|
return item;
|
|
}
|
|
|
|
external_cache.push(item.path);
|
|
return resolvePackage(item.path);
|
|
});
|
|
|
|
const baseConfig = {
|
|
minify: config.production,
|
|
outdir: `${config.outdir}/js`,
|
|
packages: "external",
|
|
root: config.root,
|
|
splitting: config.production,
|
|
};
|
|
|
|
// Apply build config per type
|
|
const loaders = {
|
|
assetLoader: {
|
|
entrypoints: resolved_entrypoints
|
|
.filter((entry) => entry.type === EntrypointType.Asset)
|
|
.map((entry) => entry.path),
|
|
outdir: `${config.outdir}/asset`,
|
|
},
|
|
stylesLoader: {
|
|
...baseConfig,
|
|
entrypoints: resolved_entrypoints
|
|
.filter((entry) =>
|
|
[EntrypointType.Css, EntrypointType.Sass].includes(entry.type),
|
|
)
|
|
.map((entry) => entry.path),
|
|
outdir: `${config.outdir}/css`,
|
|
plugins: [sassPlugin],
|
|
},
|
|
jsLoader: {
|
|
...baseConfig,
|
|
entrypoints: resolved_entrypoints
|
|
.filter((entry) => entry.type === EntrypointType.Js)
|
|
.map((entry) => entry.path),
|
|
target: "browser",
|
|
},
|
|
};
|
|
|
|
// Transform into a list to later use Promise.all
|
|
const assets = Object.values(loaders).filter(
|
|
(item) => item.entrypoints.length !== 0,
|
|
);
|
|
|
|
log(LogType.Info, "Building assets...");
|
|
|
|
const out = await Promise.all(
|
|
assets.map(async (item) => {
|
|
const result = await Bun.build(item as BuildConfig);
|
|
|
|
if (!result.success) {
|
|
throw new AggregateError(result.logs, "Build failed");
|
|
}
|
|
|
|
// Normalize external packages folder structure
|
|
for (const out of result.outputs) {
|
|
if (!out.path.includes("node_modules")) {
|
|
continue;
|
|
}
|
|
|
|
let package_name = external_cache.find((pkg) =>
|
|
out.path.includes(pkg),
|
|
);
|
|
|
|
if (!package_name) {
|
|
throw new Error(
|
|
`Could not normilize path for external package: ${out.path}`,
|
|
);
|
|
}
|
|
|
|
package_name = slug(package_name);
|
|
|
|
if (!package_name) {
|
|
throw new Error(
|
|
`Could not normilize path for external package: ${out.path}`,
|
|
);
|
|
}
|
|
|
|
const new_path = join(
|
|
config.outdir,
|
|
"pkgs",
|
|
package_name,
|
|
basename(out.path),
|
|
);
|
|
|
|
await Bun.write(new_path, out);
|
|
}
|
|
|
|
return result;
|
|
}),
|
|
);
|
|
|
|
if (out.some((item) => !item.success)) {
|
|
throw new Error(`Some entrypoint failed to build: ${out}`);
|
|
}
|
|
|
|
await rm(join(config.outdir, "node_modules"), {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
|
|
log(LogType.Success, "Complete!");
|
|
},
|
|
};
|