Skip to content

Commit

Permalink
accepts compiler.command on plugin options
Browse files Browse the repository at this point in the history
  • Loading branch information
hmsk committed Apr 17, 2024
1 parent a2f1a62 commit 2cc5bb4
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 22 deletions.
24 changes: 19 additions & 5 deletions src/compiler.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { beforeEach, expect, it, vi } from 'vitest'
import { expect, it, vi } from 'vitest'
//@ts-expect-error typing isn't provided
import nodeElmCompiler from 'node-elm-compiler'

import { compile } from './compiler.js'

const compiled = `_Platform_export({'Hello':{'init':$author$project$Hello$main($elm$json$Json$Decode$string)({"versions":{"elm":"0.19.1"},"types":{"message":"Hello.Msg","aliases":{},"unions":{"Hello.Msg":{"args":[],"tags":{"Name":["String.String"]}},"String.String":{"args":[],"tags":{"String":[]}}}}})}});}(this));`
const compiled = `_Platform_export({'Hello':{'init':$author$project$Hello$main($elm$json$Json$Decode$string)({'versions':{'elm':'0.19.1'},'types':{'message':'Hello.Msg','aliases':{},'unions':{'Hello.Msg':{'args':[],'tags':{'Name':['String.String']}},'String.String':{'args':[],'tags':{'String':[]}}}}})}});}(this));`

beforeEach(() => {
it('calls node-elm-compiler', async () => {
vi.spyOn(nodeElmCompiler, 'compileToString').mockImplementation(() => compiled)
})

it('calls node-elm-compiler', async () => {
expect(await compile(['a', 'b', 'c'], { debug: false, optimize: true, verbose: true })).toMatch(
/export const Elm = {'Hello':/,
)
Expand All @@ -22,3 +20,19 @@ it('calls node-elm-compiler', async () => {
verbose: true,
})
})

it('executes manual command', async () => {
expect(
await compile(['a', 'b', 'c'], {
command: () => `echo "${compiled.replace('$', '\\$')}"`,
}),
).toMatch(/export const Elm = {'Hello':/)
})

it('executes manual command which fails', async () => {
expect(
compile(['a', 'b', 'c'], {
command: () => `whatever`,
}),
).rejects.toBe('Failed to run command')
})
33 changes: 27 additions & 6 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { toESModule } from 'elm-esm'
import nodeElmCompiler from 'node-elm-compiler'
import { findUpSync } from 'find-up'
import { dirname } from 'path'
import { exec } from 'node:child_process'
import { promisify } from 'node:util'

import { injectAssets } from './assetsInjector.js'

Expand All @@ -18,17 +20,36 @@ export interface NodeElmCompilerOptions {
verbose: boolean
}

export interface ManualCommand {
command: (targets: string[]) => string
}

const findClosestElmJson = (pathname: string) => {
const elmJson = findUpSync('elm.json', { cwd: dirname(pathname) })
return elmJson ? dirname(elmJson) : undefined
}

export const compile = async (targets: string[], options: Partial<NodeElmCompilerOptions>): Promise<string> => {
const compiled = await nodeElmCompiler.compileToString(targets, {
output: '.js',
cwd: findClosestElmJson(targets[0]),
...options,
})
export const compile = async (
targets: string[],
options: Partial<NodeElmCompilerOptions> | ManualCommand,
): Promise<string> => {
const compiled =
'command' in options
? await runCommandAndCapture(options.command(targets))
: await nodeElmCompiler.compileToString(targets, {
output: '.js',
cwd: findClosestElmJson(targets[0]),
...options,
})

return injectAssets(toESModule(compiled))
}

const runCommandAndCapture = async (command: string): Promise<string> => {
try {
const { stdout } = await promisify(exec)(command)
return stdout
} catch (e) {
throw 'Failed to run command'
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const plugin = (userOptions: Parameters<typeof parseOptions>[0] = {}): Pl

const releaseLock = await acquireLock()
try {
const compiled = await compile(targets, options.nodeElmCompilerOptions)
const compiled = await compile(targets, options.compilerOptions)

// Apparently `addWatchFile` may not exist: https://github.com/hmsk/vite-plugin-elm/pull/36
if (this.addWatchFile) {
Expand Down
26 changes: 20 additions & 6 deletions src/pluginOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('in dev build', () => {
it('returns default in dev', () => {
expect(parseOptions({})).toEqual<Result>({
isBuild: false,
nodeElmCompilerOptions: {
compilerOptions: {
debug: true,
optimize: false,
verbose: false,
Expand All @@ -18,7 +18,7 @@ describe('in dev build', () => {
it('overwrites debug and optimize as it given', () => {
expect(parseOptions({ debug: false, optimize: true })).toEqual<Result>({
isBuild: false,
nodeElmCompilerOptions: {
compilerOptions: {
debug: false,
optimize: true,
verbose: false,
Expand All @@ -33,14 +33,28 @@ describe('in dev build', () => {
}),
).toEqual<Result>({
isBuild: false,
nodeElmCompilerOptions: {
compilerOptions: {
debug: true,
optimize: false,
verbose: false,
pathToElm: 'wherever',
},
})
})

it('accepts manual command which omits options for node-elm-compiler', () => {
expect(
parseOptions({
nodeElmCompilerOptions: { pathToElm: 'wherever' },
compiler: { command: () => 'special command' },
}),
).toEqual<Result>({
isBuild: false,
compilerOptions: {
command: expect.any(Function),
},
})
})
})

describe('in production build', () => {
Expand All @@ -51,7 +65,7 @@ describe('in production build', () => {
it('returns default for prod build', () => {
expect(parseOptions({})).toEqual<Result>({
isBuild: true,
nodeElmCompilerOptions: {
compilerOptions: {
debug: false,
optimize: true,
verbose: true,
Expand All @@ -62,7 +76,7 @@ describe('in production build', () => {
it('overwrites debug and optimize per just only debug', () => {
expect(parseOptions({ debug: true })).toEqual<Result>({
isBuild: true,
nodeElmCompilerOptions: {
compilerOptions: {
debug: true,
optimize: false,
verbose: true,
Expand All @@ -73,7 +87,7 @@ describe('in production build', () => {
it('overwrites debug and optimize', () => {
expect(parseOptions({ debug: true, optimize: false })).toEqual<Result>({
isBuild: true,
nodeElmCompilerOptions: {
compilerOptions: {
debug: true,
optimize: false,
verbose: true,
Expand Down
12 changes: 8 additions & 4 deletions src/pluginOptions.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { NodeElmCompilerOptions } from './compiler.js'
import { ManualCommand, NodeElmCompilerOptions } from './compiler.js'

interface InputOptions {
debug?: boolean
optimize?: boolean
nodeElmCompilerOptions?: Partial<NodeElmCompilerOptions>
compiler?: {
command: (targets: string[]) => string
}
}

type DeterminedKeys = 'debug' | 'optimize' | 'verbose'

interface ParsedOptions {
isBuild: boolean
nodeElmCompilerOptions: Pick<NodeElmCompilerOptions, DeterminedKeys> &
Partial<Omit<NodeElmCompilerOptions, DeterminedKeys>>
compilerOptions:
| (Pick<NodeElmCompilerOptions, DeterminedKeys> & Partial<Omit<NodeElmCompilerOptions, DeterminedKeys>>)
| ManualCommand
}

export const parseOptions = (inputOptions: InputOptions): ParsedOptions => {
const isBuild = process.env.NODE_ENV === 'production'

return {
isBuild,
nodeElmCompilerOptions: {
compilerOptions: inputOptions.compiler ?? {
debug: inputOptions.debug ?? !isBuild,
optimize: typeof inputOptions.optimize === 'boolean' ? inputOptions.optimize : !inputOptions.debug && isBuild,
verbose: isBuild,
Expand Down

0 comments on commit 2cc5bb4

Please sign in to comment.