UNPKG

@jihchi/vite-plugin-rescript

Version:

[![Workflows - CI][workflows-ci-shield]][workflows-ci-url] [![npm package][npm-package-shield]][npm-package-url] ![npm download per month][npm-download-shield] [![npm license][npm-licence-shield]](./LICENSE)

219 lines (215 loc) 7.79 kB
//#region rolldown:runtime var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion const node_fs = __toESM(require("node:fs")); const node_fs_promises = __toESM(require("node:fs/promises")); const node_path = __toESM(require("node:path")); const chalk = __toESM(require("chalk")); const execa = __toESM(require("execa")); const npm_run_path = __toESM(require("npm-run-path")); const node_os = __toESM(require("node:os")); //#region src/parseCompilerLog.ts const ruler = "—".repeat(80); const fileAndRangeRegex = /(.+):(\d+):(\d+)(-(\d+)(:(\d+))?)?$/; const codeRegex = /^ {2,}([0-9]+| +|\.) (│|┆)/; const warningErrorRegex = /Warning number \d+ \(configured as error\)/; function isErrorLine(line) { if (line?.startsWith(" We've found a bug for you!")) return true; if (line?.startsWith(" Syntax error!")) return true; if (line && warningErrorRegex.test(line)) return true; return false; } /** * Parses the .compiler.log, returning the first error or null if no errors were found. * @param log - The log file text. * @returns Error object to send to the client or null. */ function parseCompilerLog(log) { const lines = log.split(node_os.EOL).filter(Boolean); if (lines[lines.length - 1]?.startsWith("#Done(")) { let foundError = false; let path; let startLine = 0; const messages = []; const frame = [ruler]; for (let i = 0; i < lines.length; i += 1) { const line = lines[i]; if (isErrorLine(line)) { if (foundError) break; foundError = true; path = lines?.[i + 1]?.trim(); const match = path?.match(fileAndRangeRegex); if (match) startLine = Number(match[2]); i += 1; } else if (!foundError) {} else if (line?.startsWith(" Warning number ")) break; else if (line?.startsWith("#Done(")) break; else { const match = line?.match(codeRegex); if (match) { let codeFrameLine = line?.replace("┆", "|").replace("│", "|"); if (Number(match[1]) === startLine) codeFrameLine = `> ${codeFrameLine?.substring(2)}`; if (codeFrameLine) frame.push(codeFrameLine); } else if (line?.startsWith(" ")) messages.push(line.trim()); } } if (foundError) return { message: messages.join("\n"), frame: `${frame.join("\n")}`, stack: "", id: path }; } return null; } //#endregion //#region src/index.ts const logPrefix = chalk.default.cyan("[@jihchi/vite-plugin-rescript]"); async function launchReScript(watch, silent, rewatch, buildArgs) { let cmd; let finishSignal; if (rewatch) { cmd = watch ? "rewatch watch" : "rewatch build"; finishSignal = "Finished initial compilation"; } else { cmd = watch ? "rescript build -with-deps -w" : "rescript build -with-deps"; finishSignal = ">>>> Finish compiling"; } if (buildArgs) cmd += ` ${buildArgs}`; const result = (0, execa.execaCommand)(cmd, { env: (0, npm_run_path.npmRunPathEnv)(), extendEnv: true, shell: true, windowsHide: false, cwd: process.cwd() }); let compileOnce = (_value) => {}; function dataListener(chunk) { const output = chunk.toString().trimEnd(); if (!silent) console.log(logPrefix, output); if (watch && output.includes(finishSignal)) compileOnce(true); } const { stdout, stderr } = result; stdout?.on("data", dataListener); stderr?.on("data", dataListener); if (watch) await new Promise((resolve) => { compileOnce = resolve; }); else await result; return { shutdown() { if (!result.killed) result.kill(); } }; } function createReScriptPlugin(config) { let root; let usingLoader = false; let childProcessReScript; const output = config?.loader?.output ?? "./lib/es6"; const suffix = config?.loader?.suffix ?? ".bs.js"; const suffixRegex = new RegExp(`${suffix.replace(".", "\\.")}$`); const silent = config?.silent ?? false; const rewatch = config?.rewatch ?? false; const buildArgs = config?.buildArgs ?? ""; return { name: "@jihchi/vite-plugin-rescript", enforce: "pre", async configResolved(resolvedConfig) { root = resolvedConfig.root; const { build, command, inlineConfig } = resolvedConfig; const isOnlyDevServerLaunching = command === "serve" && !Object.hasOwn(inlineConfig, "preview"); const isBuildForProduction = command === "build"; const needReScript = isOnlyDevServerLaunching || isBuildForProduction; const isLocked = (0, node_fs.existsSync)(node_path.resolve("./.bsb.lock")); const watch = !isLocked && (command === "serve" || Boolean(build.watch)); if (needReScript) childProcessReScript = await launchReScript(watch, silent, rewatch, buildArgs); }, config: (userConfig) => ({ build: { watch: userConfig.build?.watch ? { exclude: ["**/*.res", "**/*.resi"] } : null }, server: { watch: { ignored: ["**/*.res", "**/*.resi"] } } }), configureServer(server) { node_fs_promises.readFile(node_path.resolve("./lib/bs/.compiler.log"), "utf8").then((data) => { const log = data.toString(); const err = parseCompilerLog(log); if (err) server.hot.send({ type: "error", err }); }); }, async resolveId(source, importer, options) { if (source.endsWith(".res")) usingLoader = true; if (options.isEntry || !importer) return null; if (!importer.endsWith(".res")) return null; if (!source.endsWith(suffix)) return null; if (node_path.isAbsolute(source)) return null; const dirname = node_path.dirname(importer); try { require.resolve(source, { paths: [dirname] }); return null; } catch {} const resFile = source.replace(suffixRegex, ".res"); const id = node_path.join(dirname, resFile); const resolution = await this.resolve(resFile, importer, { skipSelf: true, ...options }); if (!resolution || resolution.external) return resolution; if (resolution.id !== resFile) return resolution; return { ...resolution, id }; }, async load(id) { if (!id.endsWith(".res")) return null; const relativePath = node_path.relative(root, id); const filePath = node_path.resolve(output, relativePath).replace(/\.res$/, suffix); this.addWatchFile(filePath); return { code: await node_fs_promises.readFile(filePath, "utf8") }; }, async handleHotUpdate({ file, read, server }) { if (usingLoader && file.endsWith(suffix)) { const lib = node_path.resolve(output); const relativePath = node_path.relative(lib, file); if (relativePath.startsWith("..")) return; const resFile = relativePath.replace(suffixRegex, ".res"); const id = node_path.join(root, resFile); const moduleNode = server.moduleGraph.getModuleById(id); if (moduleNode) return [moduleNode]; } else if (file.endsWith(".compiler.log")) { const log = await read(); const err = parseCompilerLog(log); if (err) server.hot.send({ type: "error", err }); } return; }, async closeBundle() { childProcessReScript?.shutdown(); return; } }; } //#endregion module.exports = createReScriptPlugin;