UNPKG

bocchi

Version:

A cli build tool for userscript

292 lines (284 loc) 7.88 kB
// 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 };