bocchi
Version:
A cli build tool for userscript
292 lines (284 loc) • 7.88 kB
JavaScript
// src/rollup.ts
import styles from "@ironkinoko/rollup-plugin-styles";
import commonjs from "@rollup/plugin-commonjs";
import image from "@rollup/plugin-image";
import nodeResolve from "@rollup/plugin-node-resolve";
import chalk from "chalk";
import fs4 from "fs-extra";
import { createRequire } from "module";
import prettyMilliseconds from "pretty-ms";
import * as rollup from "rollup";
import esbuild from "rollup-plugin-esbuild";
import slash2 from "slash";
// src/paths.ts
import path from "path";
import fs from "fs-extra";
import slash from "slash";
function resolveApp(...rest) {
return slash(path.resolve(process.cwd(), ...rest));
}
function resolveInput() {
const base = resolveApp("src", "index");
const index = [".ts", ".js"].map((extension) => base + extension).find((file) => fs.existsSync(file));
if (!index)
throw new Error("ensure src/index.(j|t)s exists");
return index;
}
var paths = {
resolveApp,
root: resolveApp(),
meta: resolveApp("meta.template"),
package: resolveApp("package.json"),
public: resolveApp("public"),
dist: resolveApp("dist"),
input: resolveInput(),
output: resolveApp("dist", "index.user.js"),
outputDev: resolveApp("dist", "index.dev.js")
};
// src/plugins/template.ts
import HTMLParser from "node-html-parser";
function die(message) {
throw new SyntaxError(`[html-template-plugin] ${message}`);
}
function parseHTML(code) {
const root = HTMLParser.parse(code);
const result = root.childNodes.reduce(
(res, node) => {
switch (node.nodeType) {
case HTMLParser.NodeType.ELEMENT_NODE: {
const el = node;
if (!el.id)
die(`<${el.rawTagName}/> need an \`id\` attribute
${el}`);
if (res[el.id])
die(`Duplicate \`id\`
- ${res[el.id]}
- ${el}`);
res[el.id] = el;
break;
}
}
return res;
},
{}
);
return Object.keys(result).reduce((res, key) => {
const el = result[key];
res[key] = el.toString();
return res;
}, {});
}
function template() {
return {
name: "template",
transform(code, id) {
if (!id.endsWith(".template.html"))
return null;
this.addWatchFile(id);
const res = parseHTML(code);
return { code: `export default ${JSON.stringify(res)}` };
}
};
}
// src/plugins/userscript.ts
import fs2 from "fs-extra";
function resolveMeta() {
let meta = fs2.readFileSync(paths.meta, "utf-8");
const pkg = fs2.readJsonSync(paths.package);
meta = meta.replace(/#version#/g, pkg.version).replace(/#description#/g, pkg.description).replace(/#homepage#/g, pkg.homepage);
return meta;
}
function genDevFile(meta) {
const devMeta = meta.replace(/(@name.*)/, "$1 - Dev").replace(
/(\/\/ ==\/UserScript==.*)/,
`// @require file://${paths.output}
$1`
);
fs2.ensureFileSync(paths.outputDev);
fs2.writeFileSync(paths.outputDev, devMeta);
}
function userscript(opts) {
if (!fs2.existsSync(paths.meta))
throw new Error("Missing meta.template file");
return {
name: "userscript",
options(opts2) {
const pkg = fs2.readJsonSync(paths.package);
opts2.external = Object.keys(pkg.globals || {});
},
buildStart() {
this.addWatchFile(paths.meta);
},
outputOptions(opts2) {
const meta = resolveMeta();
opts2.banner = meta;
const pkg = fs2.readJsonSync(paths.package);
opts2.globals = pkg.globals;
if (process.env.NODE_ENV === "development") {
genDevFile(meta);
}
return opts2;
}
};
}
// src/plugins/raw.ts
import fs3 from "fs-extra";
function raw() {
return {
name: "raw",
load(id) {
if (id.endsWith("?raw")) {
const filePath = id.slice(0, -4);
this.addWatchFile(filePath);
const content = fs3.readFileSync(filePath, "utf-8");
return `export default ${JSON.stringify(content)};`;
}
return null;
}
};
}
// src/rollup.ts
import copy from "rollup-plugin-copy";
function createRollupConfig() {
const pkg = fs4.readJsonSync(paths.package);
const require2 = createRequire(import.meta.url);
return rollup.defineConfig({
input: paths.input,
output: { file: paths.output, format: "iife" },
plugins: [
raw(),
styles({ sass: { impl: require2.resolve("sass") } }),
image(),
template(),
copy({
targets: [{ src: paths.resolveApp("public/*"), dest: paths.dist }]
}),
nodeResolve({ browser: true }),
commonjs(),
esbuild({
target: "es2017",
define: {
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
"process.env.APP_NAME": JSON.stringify(pkg.name),
"process.env.APP_VERSION": JSON.stringify(pkg.version)
}
}),
userscript()
]
});
}
function dateTime() {
let date = new Date();
date = new Date(date.getTime() - date.getTimezoneOffset() * 6e4);
return date.toISOString().replace(/T/, " ").replace(/\..+/, "");
}
function relativeId(source) {
return slash2(source).replace(slash2(paths.root) + "/", "");
}
function handleError(error) {
const name = error.name || error.cause?.name;
const nameSection = name ? `${name}: ` : "";
const pluginSection = error.plugin ? `(plugin ${error.plugin}) ` : "";
const message = `${pluginSection}${nameSection}${error.message}`;
const { bold, red, cyan, dim } = chalk;
console.log(bold(red(`[!] ${message.toString()}`)));
if (error.url) {
console.log(cyan(error.url));
}
if (error.loc) {
console.log(
`${relativeId(error.loc.file || error.id)} (${error.loc.line}:${error.loc.column})`
);
} else if (error.id) {
console.log(relativeId(error.id));
}
if (error.frame) {
console.log(dim(error.frame));
}
if (error.stack) {
console.log(dim(error.stack));
}
console.log("");
}
function watch2() {
const config = createRollupConfig();
const watcher = rollup.watch(config);
watcher.on("event", (e) => {
switch (e.code) {
case "START":
console.clear();
fs4.removeSync(paths.dist);
break;
case "BUNDLE_START":
let input = e.input;
if (typeof input !== "string") {
input = Array.isArray(input) ? input.join(", ") : Object.values(input).join(", ");
}
console.log(
chalk.cyan(
`bundles ${chalk.bold(relativeId(input))} \u2192 ${chalk.bold(
e.output.map(relativeId).join(", ")
)} ...`
)
);
break;
case "BUNDLE_END":
console.log(
chalk.green(
`created ${chalk.bold(
e.output.map(relativeId).join(", ")
)} in ${chalk.bold(prettyMilliseconds(e.duration))}`
)
);
break;
case "END":
console.log(`
[${dateTime()}] waiting for changes...`);
break;
case "ERROR":
handleError(e.error);
break;
}
});
}
async function build() {
const config = createRollupConfig();
console.log(
chalk.cyan(
`bundles ${chalk.bold(relativeId(paths.input))} \u2192 ${chalk.bold(
relativeId(paths.output)
)}...`
)
);
try {
await fs4.remove(paths.dist);
const start = Date.now();
const bundle = await rollup.rollup(config);
console.log(
chalk.green(
`created ${chalk.bold(relativeId(paths.output))} in ${chalk.bold(
prettyMilliseconds(Date.now() - start)
)}`
)
);
await bundle.write(config.output);
await bundle.close();
} catch (error) {
handleError(error);
}
}
// src/index.ts
var defaultOptions = {
mode: "development"
};
function bocchi(opts = defaultOptions) {
process.env.NODE_ENV = opts.mode;
const isDev = process.env.NODE_ENV === "development";
if (isDev)
watch2();
else
build();
}
export {
bocchi
};