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)

242 lines (238 loc) 8.75 kB
"use strict"; 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 __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { default: () => createReScriptPlugin }); module.exports = __toCommonJS(index_exports); var import_node_fs = require("fs"); var fs = __toESM(require("fs/promises"), 1); var path = __toESM(require("path"), 1); var import_chalk = __toESM(require("chalk"), 1); var import_execa = require("execa"); var import_npm_run_path = require("npm-run-path"); // src/parseCompilerLog.ts var import_node_os = require("os"); var ruler = "\u2014".repeat(80); var fileAndRangeRegex = /(.+):(\d+):(\d+)(-(\d+)(:(\d+))?)?$/; var codeRegex = /^ {2,}([0-9]+| +|\.) (│|┆)/; var 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; } function parseCompilerLog(log) { const lines = log.split(import_node_os.EOL).filter(Boolean); if (lines[lines.length - 1]?.startsWith("#Done(")) { let foundError = false; let path2; 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; path2 = lines?.[i + 1]?.trim(); const match = path2?.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("\u2506", "|").replace("\u2502", "|"); 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: path2 }; } } return null; } // src/index.ts var logPrefix = import_chalk.default.cyan("[@jihchi/vite-plugin-rescript]"); async function launchReScript(watch, silent) { const cmd = watch ? "rescript build -with-deps -w" : "rescript build -with-deps"; const result = (0, import_execa.execaCommand)(cmd, { env: (0, import_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(">>>> Finish compiling")) { compileOnce(true); } } const { stdout, stderr } = result; stdout?.on("data", dataListener); stderr?.on("data", dataListener); if (watch) { await new Promise((resolve2) => { compileOnce = resolve2; }); } 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; 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, import_node_fs.existsSync)(path.resolve("./.bsb.lock")); const watch = !isLocked && (command === "serve" || Boolean(build.watch)); if (needReScript) { childProcessReScript = await launchReScript(watch, silent); } }, config: (userConfig) => ({ build: { // If the build watcher is enabled (adding watch config would automatically enable it), // exclude rescript files since recompilation should be based on the generated JS files. watch: userConfig.build?.watch ? { exclude: ["**/*.res", "**/*.resi"] } : null }, server: { watch: { // Ignore rescript files when watching since they may occasionally trigger hot update ignored: ["**/*.res", "**/*.resi"] } } }), configureServer(server) { fs.readFile(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 }); } ); }, // Hook that resolves `.bs.js` imports to their `.res` counterpart 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 (path.isAbsolute(source)) return null; const dirname2 = path.dirname(importer); try { require.resolve(source, { paths: [dirname2] }); return null; } catch (err) { } const resFile = source.replace(suffixRegex, ".res"); const id = path.join(dirname2, 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 }; }, // Hook that loads the generated `.bs.js` file from `lib/es6` for ReScript files async load(id) { if (!id.endsWith(".res")) return null; const relativePath = path.relative(root, id); const filePath = path.resolve(output, relativePath).replace(/\.res$/, suffix); this.addWatchFile(filePath); return { code: await fs.readFile(filePath, "utf8") }; }, async handleHotUpdate({ file, read, server }) { if (usingLoader && file.endsWith(suffix)) { const lib = path.resolve(output); const relativePath = path.relative(lib, file); if (relativePath.startsWith("..")) return; const resFile = relativePath.replace(suffixRegex, ".res"); const id = 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; } }; }