From 6199c02464e736e9acd2de03d0d351945d0bf6df Mon Sep 17 00:00:00 2001 From: Barak Date: Sun, 11 Feb 2024 23:01:10 +0200 Subject: [PATCH] fix: esbuild plugin break after stylable error (#2938) --- .../esbuild/src/stylable-esbuild-plugin.ts | 23 ++++++++-- packages/esbuild/test/e2e/esbuild-testkit.ts | 43 ++++++++++++++++--- .../esbuild/test/e2e/rebuild/rebuild.spec.ts | 26 +++++++++-- 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/packages/esbuild/src/stylable-esbuild-plugin.ts b/packages/esbuild/src/stylable-esbuild-plugin.ts index f50378674..e2ae21e75 100644 --- a/packages/esbuild/src/stylable-esbuild-plugin.ts +++ b/packages/esbuild/src/stylable-esbuild-plugin.ts @@ -188,13 +188,24 @@ export const stylablePlugin = (initialPluginOptions: ESBuildOptions = {}): Plugi build.onLoad( { filter: /.*/, namespace: namespaces.jsModule }, wrapDebug('onLoad stylable module', (args) => { + let res: StylableResults; const cacheResults = checkCache(args.path); if (cacheResults) { return cacheResults; } onLoadCalled = true; - - const res = stylable.transform(args.path); + try { + res = stylable.transform(args.path); + } catch (e) { + return { + errors: [ + { + text: String(e), + }, + ], + watchFiles: [args.path], + }; + } const { errors, warnings } = esbuildEmitDiagnostics(res, diagnosticsMode); const { imports, collector } = importsCollector(res); const { cssDepth = 0, deepDependencies } = res.meta.transformCssDepth!; @@ -316,7 +327,7 @@ export const stylablePlugin = (initialPluginOptions: ESBuildOptions = {}): Plugi * process the generated bundle and optimize the css output */ build.onEnd( - wrapDebug(`onEnd generate cssInjection: ${cssInjection}`, ({ metafile }) => { + wrapDebug(`onEnd generate cssInjection: ${cssInjection}`, ({ metafile, errors }) => { transferBuildInfo(); if (!onLoadCalled) { lazyDebugPrint(); @@ -326,6 +337,9 @@ export const stylablePlugin = (initialPluginOptions: ESBuildOptions = {}): Plugi let mapping: OptimizationMapping; if (devTypes.enabled) { if (!metafile) { + if (errors.length) { + return; + } throw new Error('metafile is required for css injection'); } const absSrcDir = join(projectRoot, devTypes.srcDir); @@ -355,6 +369,9 @@ export const stylablePlugin = (initialPluginOptions: ESBuildOptions = {}): Plugi if (cssInjection === 'css') { if (!metafile) { + if (errors.length) { + return; + } throw new Error('metafile is required for css injection'); } mapping ??= buildUsageMapping(metafile, stylable); diff --git a/packages/esbuild/test/e2e/esbuild-testkit.ts b/packages/esbuild/test/e2e/esbuild-testkit.ts index bd1b2fc7a..f759e364f 100644 --- a/packages/esbuild/test/e2e/esbuild-testkit.ts +++ b/packages/esbuild/test/e2e/esbuild-testkit.ts @@ -1,7 +1,6 @@ import { dirname, join } from 'node:path'; -import { readFileSync, symlinkSync, writeFileSync } from 'node:fs'; -import { nodeFs } from '@file-services/node'; -import { BuildContext, BuildOptions, context } from 'esbuild'; +import { readFileSync, symlinkSync, writeFileSync, cpSync } from 'node:fs'; +import { BuildContext, BuildOptions, context, Plugin } from 'esbuild'; import { createTempDirectorySync, runServer } from '@stylable/e2e-test-kit'; import playwright from 'playwright-core'; @@ -21,11 +20,13 @@ export class ESBuildTestKit { buildExport, tmp = true, overrideOptions = {}, + extraPlugins = [], }: { project: string; buildExport?: string; tmp?: boolean; overrideOptions?: BuildOptions; + extraPlugins?: Array; }) { let openServerUrl: string | undefined; let buildFile = require.resolve(`@stylable/esbuild/test/e2e/${project}/build.js`); @@ -34,7 +35,7 @@ export class ESBuildTestKit { if (tmp) { const t = createTempDirectorySync('esbuild-testkit'); this.disposables.push(() => t.remove()); - nodeFs.copyDirectorySync(projectDir, t.path); + cpSync(projectDir, t.path, { recursive: true }); buildFile = join(t.path, 'build.js'); projectDir = t.path; @@ -51,10 +52,41 @@ export class ESBuildTestKit { if (!run) { throw new Error(`could not find ${buildExport || 'run'} export in ${buildFile}`); } + const onEnd = new Set<() => void>(); + function act(fn: () => T, timeout = 3000) { + return new Promise((resolve, reject) => { + let results = undefined as T | Promise; + const tm = setTimeout(reject, timeout); + const handler = () => { + clearTimeout(tm); + onEnd.delete(handler); + if (results instanceof Promise) { + results.then(resolve, reject); + } else { + resolve(results); + } + }; + onEnd.add(handler); + results = fn(); + }); + } const buildContext = await run(context, (options: BuildOptions) => ({ ...options, - plugins: [...(options.plugins ?? [])], + plugins: [ + ...(options.plugins ?? []), + ...extraPlugins, + { + name: 'build-end', + setup(build) { + build.onEnd(() => { + for (const fn of onEnd) { + fn(); + } + }); + }, + }, + ], absWorkingDir: projectDir, loader: { '.png': 'file', @@ -124,6 +156,7 @@ export class ESBuildTestKit { context: buildContext, serve, open, + act, write(pathInCwd: string, content: string) { writeFileSync(join(projectDir, pathInCwd), content, 'utf8'); }, diff --git a/packages/esbuild/test/e2e/rebuild/rebuild.spec.ts b/packages/esbuild/test/e2e/rebuild/rebuild.spec.ts index b5eb40600..7500fbc6b 100644 --- a/packages/esbuild/test/e2e/rebuild/rebuild.spec.ts +++ b/packages/esbuild/test/e2e/rebuild/rebuild.spec.ts @@ -7,8 +7,8 @@ describe('Stylable ESBuild plugin rebuild on change', () => { afterEach(() => tk.dispose()); - it('should pick up rebuild', async () => { - const { context, read, write } = await tk.build({ + it('should pick up rebuild', async function () { + const { context, read, write, act } = await tk.build({ project: 'rebuild', tmp: true, }); @@ -16,8 +16,28 @@ describe('Stylable ESBuild plugin rebuild on change', () => { expect(css1, 'initial color').to.includes('color: red'); await context.watch(); await sleep(2222); - write('a.st.css', `.root{color: green}`); + await act(() => { + write('a.st.css', `.root{color: green}`); + }); + const css2 = read('dist/index.js'); + expect(css2, 'color after change').to.includes('color: green'); + }); + + it('should stay alive after error', async function () { + const { context, read, write, act } = await tk.build({ + project: 'rebuild', + tmp: true, + }); + const css1 = read('dist/index.js'); + expect(css1, 'initial color').to.includes('color: red'); + await context.watch(); await sleep(2222); + await act(() => { + write('a.st.css', `.root{}}}}}`); + }); + await act(() => { + write('a.st.css', `.root{color: green}`); + }); const css2 = read('dist/index.js'); expect(css2, 'color after change').to.includes('color: green'); });