kirbyup
Version:
Zero-config bundler for Kirby Panel plugins
175 lines (173 loc) • 6.09 kB
JavaScript
import { name, version } from "../package.js";
import { loadConfig, resolvePostCSSConfig } from "./config.js";
import { PrettyError, handleError } from "./errors.js";
import kirbyupBuildCleanupPlugin from "./plugins/build-cleanup.js";
import kirbyupGlobImportPlugin from "./plugins/glob-import.js";
import kirbyupHmrPlugin from "./plugins/hmr.js";
import { printFileInfo, toArray } from "./utils.js";
import * as fs from "node:fs";
import * as fsp from "node:fs/promises";
import vuePlugin from "@vitejs/plugin-vue2";
import vueJsxPlugin from "@vitejs/plugin-vue2-jsx";
import { consola } from "consola";
import { colors } from "consola/utils";
import { basename, dirname, resolve } from "pathe";
import { debounce } from "perfect-debounce";
import externalGlobals from "rollup-plugin-external-globals";
import { build as build$1, createLogger, createServer, mergeConfig } from "vite";
import fullReloadPlugin from "vite-plugin-full-reload";
import * as vueCompilerSfc from "vue/compiler-sfc";
//#region src/node/index.ts
let resolvedKirbyupConfig;
let resolvedPostCssConfig;
const logLevel = "warn";
const logger = createLogger(logLevel);
const loggerWarn = logger.warn;
logger.warn = (msg, options) => {
if (msg.includes("(!) build.outDir")) return;
loggerWarn(msg, options);
};
function getViteConfig(command, options) {
const aliasDir = resolve(options.cwd, dirname(options.entry));
const { alias = {}, vite, extendViteConfig } = resolvedKirbyupConfig;
const userConfig = vite ?? extendViteConfig ?? {};
const sharedConfig = {
resolve: { alias: {
"~/": `${aliasDir}/`,
"@/": `${aliasDir}/`,
...alias
} },
plugins: [
vuePlugin({ compiler: vueCompilerSfc }),
vueJsxPlugin(),
kirbyupGlobImportPlugin(),
{
...externalGlobals({ vue: "Vue" }),
enforce: "post"
}
],
build: { copyPublicDir: false },
...resolvedPostCssConfig && { css: { postcss: resolvedPostCssConfig } },
envDir: options.cwd,
envPrefix: ["VITE_", "KIRBYUP_"],
customLogger: logger,
logLevel
};
if (command === "serve") {
const { port, watch } = options;
const serveConfig = mergeConfig(sharedConfig, {
plugins: [kirbyupHmrPlugin(options), watch && fullReloadPlugin(watch)].filter(Boolean),
build: { rollupOptions: { input: resolve(options.cwd, options.entry) } },
server: {
port,
strictPort: true,
origin: `http://localhost:${port}`
}
});
return mergeConfig(serveConfig, userConfig);
}
const mode = options.watch ? "development" : "production";
const buildConfig = mergeConfig(sharedConfig, {
mode,
plugins: [kirbyupBuildCleanupPlugin(options)],
build: {
lib: {
entry: resolve(options.cwd, options.entry),
formats: ["iife"],
name: "kirbyupExport",
fileName: () => "index.js"
},
minify: mode === "production",
outDir: options.outDir,
emptyOutDir: false,
rollupOptions: { output: { assetFileNames: "index.[ext]" } }
}
});
return mergeConfig(buildConfig, userConfig);
}
async function generate(options) {
const config = getViteConfig("build", options);
let result;
try {
result = await build$1(config);
} catch (error) {
consola.error("Build failed");
if (config.mode === "production") throw error;
}
if (result && !options.watch) {
const { output } = toArray(result)[0];
let maxLength = 0;
for (const chunkFile in output) {
const fileNameLength = output[chunkFile].fileName.length;
if (fileNameLength > maxLength) maxLength = fileNameLength;
}
for (const { fileName, type, code } of output) {
const content = code || await fsp.readFile(resolve(options.outDir, fileName), "utf8");
await printFileInfo({
root: options.cwd,
outDir: options.outDir,
filePath: fileName,
content,
type,
maxLength
});
}
}
return result;
}
async function build(options) {
assertEntryExists(options);
const { cwd } = options;
const { config, configFile } = await loadConfig(cwd);
resolvedKirbyupConfig = config ?? {};
resolvedPostCssConfig = await resolvePostCSSConfig(cwd);
consola.log(colors.green(`${name} v${version}`));
consola.start(`Building ${colors.cyan(options.entry)}`);
if (options.watch) consola.info("Running in watch mode");
const debouncedBuild = debounce(async () => {
generate(options).catch(handleError);
}, 100);
const startWatcher = async () => {
if (!options.watch) return;
const { watch } = await import("chokidar");
const ignored = ["**/{.git,node_modules}/**", "index.{css,js}"];
const watchPaths = typeof options.watch === "boolean" ? dirname(options.entry) : Array.isArray(options.watch) ? options.watch.filter((path) => typeof path === "string") : options.watch;
consola.info(`Watching for changes in ${toArray(watchPaths).map((i) => colors.cyan(i)).join(", ")}`);
const watcher = watch(watchPaths, {
ignoreInitial: true,
ignorePermissionErrors: true,
ignored,
cwd
});
if (configFile) watcher.add(configFile);
watcher.on("all", async (type, file) => {
const absolutePath = resolve(cwd, file);
if (configFile === absolutePath) {
resolvedKirbyupConfig = (await loadConfig(cwd)).config ?? {};
consola.info(`${colors.cyan(basename(file))} changed, setting new config`);
} else consola.log(`${colors.green(type)} ${colors.white(colors.dim(file))}`);
debouncedBuild();
});
};
await generate(options);
consola.success("Build successful");
startWatcher();
}
async function serve(options) {
assertEntryExists(options);
const { cwd } = options;
const { config } = await loadConfig(cwd);
resolvedKirbyupConfig = config ?? {};
resolvedPostCssConfig = await resolvePostCSSConfig(cwd);
consola.log(colors.green(`${name} v${version}`));
consola.info("Starting development server...");
const server = await createServer(getViteConfig("serve", options));
await server.listen();
consola.success(`Server is listening on :${server.config.server.port}`);
return server;
}
function assertEntryExists(options) {
if (!fs.existsSync(resolve(options.cwd, options.entry))) throw new PrettyError(`Cannot find "${options.entry}"`);
}
//#endregion
export { build, serve };