/** @file esbuild plugin for Civet language Simple zero config example @example ```javascript import esbuild from 'esbuild' import civetPlugin from '@danielx/civet/esbuild-plugin' esbuild.build({ ..., plugins: [ civetPlugin ] }).catch(() => process.exit(1)) ``` Chain civet output into another esbuild plugin, solid for example @example ```javascript import esbuild from 'esbuild' const { solidPlugin } = require('esbuild-plugin-solid'); import civetPlugin from '@danielx/civet/esbuild-plugin' esbuild.build({ ..., plugins: [ civetPlugin( next: solidPlugin() ) ] }).catch(() => process.exit(1)) ``` */ import type { Plugin, OnLoadArgs, OnLoadResult } from 'esbuild' interface Options { filter?: RegExp inlineMap?: boolean js?: boolean next?: Plugin } { readFile, writeFile, mkdtemp, rmdir } from 'fs/promises' path from 'path' { compile } from "./main.civet" // NOTE: this function is named civet so esbuild gets "civet" as the name of the plugin civet := (options: Options = {}): Plugin -> { filter=/\.civet$/ inlineMap=true js=true next } := options let nextTransform: (args: OnLoadArgs) => OnLoadResult | Promise | null | undefined let tmpPath: null | string if next next.setup { onEnd(); onStart(); resolve(); onResolve(); initialOptions(); esbuild(); onLoad(_, handler) nextTransform = handler } return { name: "civet" setup(build) build.onStart -> if next { tmpdir } := require 'os' tmpPath = await mkdtemp path.join tmpdir(), "civet-" return build.onEnd -> if tmpPath await rmdir tmpPath, { recursive: true } return build.onLoad { filter }, (args) -> try source := await readFile args.path filename := path.relative(process.cwd(), args.path) compiled := await compile source, { filename inlineMap js } if next and tmpPath outputFileName := filename + js ? '.jsx' : '.tsx' outputFilePath := path.join tmpPath, outputFileName // I'd prefer not to use temp files but I can't find a way to pass a stream to fs.readFile which is what // most esbuild plugins use await writeFile outputFilePath, compiled return await nextTransform { ...args path: outputFilePath } return contents: compiled catch e return errors: [{ text: e.message location: file: args.path namespace: args.namespace line: e.line column: e.column detail: e }] } defaultPlugin := civet() // Default zero-config plugin civet.setup = defaultPlugin.setup // This gets rewritten to roughly `module.exports = civet` in `build/esbuild.civet`. // It assumes that there are no named exports, only a default. export default civet