|
| 1 | +import { spawn, SpawnOptions } from "child_process"; |
| 2 | +import type { Plugin as VitePlugin } from "vite"; |
| 3 | + |
| 4 | +// Utility to invoke a given sbt task and fetch its output |
| 5 | +function printSbtTask(task: string, cwd?: string): Promise<string> { |
| 6 | + const args = ["--batch", "-no-colors", "-Dsbt.supershell=false", `print ${task}`]; |
| 7 | + const options: SpawnOptions = { |
| 8 | + cwd: cwd, |
| 9 | + stdio: ['ignore', 'pipe', 'inherit'], |
| 10 | + }; |
| 11 | + const child = process.platform === 'win32' |
| 12 | + ? spawn("sbt.bat", args.map(x => `"${x}"`), {shell: true, ...options}) |
| 13 | + : spawn("sbt", args, options); |
| 14 | + |
| 15 | + let fullOutput: string = ''; |
| 16 | + |
| 17 | + child.stdout!.setEncoding('utf-8'); |
| 18 | + child.stdout!.on('data', data => { |
| 19 | + fullOutput += data; |
| 20 | + process.stdout.write(data); // tee on my own stdout |
| 21 | + }); |
| 22 | + |
| 23 | + return new Promise((resolve, reject) => { |
| 24 | + child.on('error', err => { |
| 25 | + reject(new Error(`sbt invocation for Scala.js compilation could not start. Is it installed?\n${err}`)); |
| 26 | + }); |
| 27 | + child.on('close', code => { |
| 28 | + if (code !== 0) |
| 29 | + reject(new Error(`sbt invocation for Scala.js compilation failed with exit code ${code}.`)); |
| 30 | + else |
| 31 | + resolve(fullOutput.trimEnd().split('\n').at(-1)!); |
| 32 | + }); |
| 33 | + }); |
| 34 | +} |
| 35 | + |
| 36 | +export interface ScalaJSPluginOptions { |
| 37 | + cwd?: string, |
| 38 | + projectID?: string, |
| 39 | + uriPrefix?: string, |
| 40 | +} |
| 41 | + |
| 42 | +export default function scalaJSPlugin(options: ScalaJSPluginOptions = {}): VitePlugin { |
| 43 | + const { cwd, projectID, uriPrefix } = options; |
| 44 | + |
| 45 | + const fullURIPrefix = uriPrefix ? (uriPrefix + ':') : 'scalajs:'; |
| 46 | + |
| 47 | + let isDev: boolean | undefined = undefined; |
| 48 | + let scalaJSOutputDir: string | undefined = undefined; |
| 49 | + |
| 50 | + return { |
| 51 | + name: "scalajs:sbt-scalajs-plugin", |
| 52 | + |
| 53 | + // Vite-specific |
| 54 | + configResolved(resolvedConfig) { |
| 55 | + isDev = resolvedConfig.mode === 'development'; |
| 56 | + }, |
| 57 | + |
| 58 | + // standard Rollup |
| 59 | + async buildStart(options) { |
| 60 | + if (isDev === undefined) |
| 61 | + throw new Error("configResolved must be called before buildStart"); |
| 62 | + |
| 63 | + const task = isDev ? "fastLinkJSOutput" : "fullLinkJSOutput"; |
| 64 | + const projectTask = projectID ? `${projectID}/${task}` : task; |
| 65 | + scalaJSOutputDir = await printSbtTask(projectTask, cwd); |
| 66 | + }, |
| 67 | + |
| 68 | + // standard Rollup |
| 69 | + resolveId(source, importer, options) { |
| 70 | + if (scalaJSOutputDir === undefined) |
| 71 | + throw new Error("buildStart must be called before resolveId"); |
| 72 | + |
| 73 | + if (!source.startsWith(fullURIPrefix)) |
| 74 | + return null; |
| 75 | + const path = source.substring(fullURIPrefix.length); |
| 76 | + |
| 77 | + return `${scalaJSOutputDir}/${path}`; |
| 78 | + }, |
| 79 | + }; |
| 80 | +} |
0 commit comments