Skip to content

Commit

Permalink
simpler hook implementation
Browse files Browse the repository at this point in the history
instead of a full fledged event bus, it'll
be easier to avoid circular hooks and add more functionality over time
  • Loading branch information
barelyhuman committed Dec 3, 2023
1 parent 5c4506a commit a0d1af7
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 59 deletions.
53 changes: 23 additions & 30 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ import tsc from 'tsc-prog'

const isDev = process.argv.includes('--dev')

const buildContext = createContext({
bundle: true,
})

const common = {
external: ['tiny-glob', 'defu', 'esbuild'],
entryPoints: ['./src/index.js'],
platform: 'node',
target: 'node14',
format: 'esm',
bundle: true,
}

buildContext.config({
const buildContext = createContext()

buildContext.add('esm', {
...common,
outdir: './dist/esm',
format: 'esm',
Expand All @@ -25,7 +24,7 @@ buildContext.config({
},
})

buildContext.config({
buildContext.add('cjs', {
...common,
outdir: './dist/cjs',
format: 'cjs',
Expand All @@ -34,11 +33,24 @@ buildContext.config({
},
})

buildContext.on('error', errors => {
errors.map(x => process.stdout.write(x.reason.toString() + '\n'))
buildContext.hook('esm:complete', async () => {
process.stdout.write('[custom-builder] ESM Built\n')
})

buildContext.hook('cjs:complete', async () => {
process.stdout.write('[custom-builder] CJS Built\n')
})

buildContext.hook('cjs:error', async errors => {
process.stdout.write('[custom-builder] CJS Error:\n')
console.error(errors)
})

buildContext.on('build', async () => {
buildContext.hook('error', async error => {
console.error(error)
})

buildContext.hook('complete', async () => {
process.stdout.write('[custom-builder] Built\n')

await writeFile(
Expand Down Expand Up @@ -74,24 +86,5 @@ buildContext.on('build', async () => {
process.exit(0)
})

function createChain() {
let agg = Promise.resolve()
const _chainer = fn => {
agg = agg.then(fn)
}
_chainer.value = async () => {
await agg
return null
}
return _chainer
}

const chain = createChain()

if (isDev) {
chain(() => buildContext.watch())
}

chain(() => buildContext.build())

await chain.value
if (isDev) await buildContext.watch()
await buildContext.build()
21 changes: 21 additions & 0 deletions src/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const createHook = () => {
let hookContext = new Map()
return {
async emit(eventName, reference: unknown) {
const hooks = hookContext.get(eventName) || []
for (let hook of hooks) {
await hook(reference)
}
},
hook(eventName, handler) {
const hooks = hookContext.get(eventName) || []
hooks.push(handler)
hookContext.set(eventName, hooks)
return () => {
let _hooks = hookContext.get(eventName) || []
_hooks.filter(x => x != handler)
hookContext.set(eventName, _hooks)
}
},
}
}
70 changes: 41 additions & 29 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import esbuild from 'esbuild'
import { defu } from 'defu'
import glob from 'tiny-glob'
import { EventEmitter } from 'node:events'
import { createHook } from './hooks.js'

export type GlobOptions = {
cwd?: string
Expand All @@ -15,29 +15,27 @@ export type FilePath = string

class ContextManager {
initialConfig: esbuild.BuildOptions = {}
#contextConfigs: esbuild.BuildOptions[] = []
#contextConfigs: { name: string; config: esbuild.BuildOptions }[] = []
#contexts: esbuild.BuildContext[] = []
#eventBus = new EventEmitter()
#eventBus = createHook()

constructor(initial: esbuild.BuildOptions) {
this.initialConfig = initial
}

config(conf: esbuild.BuildOptions) {
this.#contextConfigs.push(defu(conf, this.initialConfig))
hook(eventName, handler) {
return this.#eventBus.hook(eventName, handler)
}

glob(pattern: string, opts: GlobOptions): Promise<FilePath[]> {
return glob(pattern, opts)
}

on(eventName: 'error' | 'build', listener: (...args: any[]) => void) {
this.#eventBus.addListener(eventName, listener)
return () => this.#eventBus.removeListener(eventName, listener)
add(name: string, conf: esbuild.BuildOptions) {
this.#contextConfigs.push({
name,
config: defu(conf, this.initialConfig),
})
}

offAll(eventName: string) {
this.#eventBus.removeAllListeners(eventName)
glob(pattern: string, opts: GlobOptions): Promise<FilePath[]> {
return glob(pattern, opts)
}

async #createContext() {
Expand All @@ -46,24 +44,17 @@ class ContextManager {

while ((cfg = this.#contextConfigs.shift())) {
try {
cfg.plugins ||= []
cfg.config.plugins ||= []

cfg.plugins.push({
name: 'esbuild-multicontext-handler',
setup(build) {
build.onEnd(result => {
eBus.emit(`built-context`, {
result,
})
})
},
})
cfg.config.plugins.push(generateReportingPlugin(eBus, cfg.name))

const context = await esbuild.context(defu(cfg, this.initialConfig))
const context = await esbuild.context(
defu(cfg.config, this.initialConfig)
)

this.#contexts.push(context)
} catch (err) {
this.#eventBus.emit('error', err)
await this.#eventBus.emit(getContextErrorName(cfg.name), [err])
break
}
}
Expand All @@ -72,8 +63,7 @@ class ContextManager {
async build() {
await this.#createContext()
await Promise.all(this.#contexts.map(x => x.rebuild()))

this.#eventBus.emit('build')
await this.#eventBus.emit('complete', null)
}

async watch() {
Expand All @@ -85,3 +75,25 @@ class ContextManager {
export function createContext(initial: esbuild.BuildOptions) {
return new ContextManager(initial)
}

function generateReportingPlugin(eBus, name): esbuild.Plugin {
return {
name: 'esbuild-multicontext-handler',
setup(build) {
build.onEnd(async result => {
if (result.errors.length > 0)
return await eBus.emit(getContextErrorName(name), result.errors)

await eBus.emit(getContextCompletionName(name), result)
})
},
}
}

function getContextCompletionName(name) {
return `${name}:complete`
}

function getContextErrorName(name) {
return `${name}:error`
}

0 comments on commit a0d1af7

Please sign in to comment.