vite-plugin-erb
Version:
Use ERB files in Vite.js projects with a Ruby backend
80 lines (79 loc) • 2.87 kB
JavaScript
// src/index.ts
import { parse as parseQuery } from "querystring";
import { join, dirname, relative } from "path";
import { fileURLToPath } from "url";
import { accessSync } from "fs";
import execa from "execa";
import createDebugger from "debug";
var debug = createDebugger("vite-plugin-erb");
var _dirname = typeof __dirname === "undefined" ? dirname(fileURLToPath(import.meta.url)) : __dirname;
var outputDelimiter = "__VITE_ERB_RESULT__";
var renderedOutputRegex = new RegExp(`${outputDelimiter}([\\s\\S]*?)${outputDelimiter}`, "m");
var rendererPath = join(_dirname, "../src/renderer.rb");
function detectRunner(root) {
try {
accessSync(join(root, "bin/rails"));
return "ruby bin/rails runner";
} catch {
return "ruby";
}
}
async function renderErbFile(cwd, filename, code, options) {
const { engine = "", runner = "", env, ...execOptions } = options;
const path = relative(cwd, filename);
try {
const [cmd, ...cmdArgs] = runner.split(" ");
const args = [...cmdArgs, rendererPath, outputDelimiter, engine].filter((x) => x);
debug(`rendering ${path}`);
const { stdout } = await execa(cmd, args, { input: code, cwd, env: { DISABLE_SPRING: "1", ...env }, ...execOptions });
const matches = stdout.match(renderedOutputRegex);
if (!matches)
throw new Error(`No output when rendering ${filename}. Is the file valid?`);
debug(`rendered ${path}`);
return matches[1];
} catch (error) {
debug(`failed to render ERB file ${path}`, error);
throw error;
}
}
function parseId(id) {
const [filename, rawQuery] = id.split("?", 2);
const query = parseQuery(rawQuery);
if (query.erb !== void 0 || filename.endsWith(".erb"))
query.erb = true;
return { filename: filename.split(".erb")[0], query };
}
function ErbPlugin(options = {}) {
let root;
let plugins;
return {
name: "erb-plugin",
enforce: "pre",
configResolved(config) {
plugins = config.plugins.filter((p) => p.name !== "vite:import-analysis");
root = process.env.VITE_RUBY_ROOT || config.root;
if (!options.runner)
options.runner = detectRunner(root);
debug(`running renderer with '${options.runner}'`);
},
async transform(code, id, ssr) {
const { filename, query } = parseId(id);
if (!query.erb)
return;
code = await renderErbFile(root, `${filename}.erb`, code, options);
for (const plugin of plugins) {
const transform = plugin.transform;
const transformFn = typeof transform === "object" ? transform.handler : transform;
if (typeof transformFn === "function") {
const result = await transformFn.call(this, code, filename, ssr);
if (result)
code = typeof result === "object" ? result.code || "" : result;
}
}
return code;
}
};
}
export {
ErbPlugin as default
};