diff --git a/build.js b/build.js index df0858a..e16f745 100644 --- a/build.js +++ b/build.js @@ -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', @@ -25,7 +24,7 @@ buildContext.config({ }, }) -buildContext.config({ +buildContext.add('cjs', { ...common, outdir: './dist/cjs', format: 'cjs', @@ -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( @@ -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() diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..c97e9b3 --- /dev/null +++ b/src/hooks.ts @@ -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) + } + }, + } +} diff --git a/src/index.ts b/src/index.ts index d39c7b2..21fc48b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 @@ -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 { - 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 { + return glob(pattern, opts) } async #createContext() { @@ -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 } } @@ -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() { @@ -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` +}