From 27896fc89cef72f4eaed30e3ce8bcd0c4f767907 Mon Sep 17 00:00:00 2001 From: barak igal Date: Sun, 11 Feb 2024 11:51:48 +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 | 38 ++++++++++++++++++- .../esbuild/test/e2e/rebuild/rebuild.spec.ts | 24 +++++++++++- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/packages/esbuild/src/stylable-esbuild-plugin.ts b/packages/esbuild/src/stylable-esbuild-plugin.ts index b2fc64348..99c397736 100644 --- a/packages/esbuild/src/stylable-esbuild-plugin.ts +++ b/packages/esbuild/src/stylable-esbuild-plugin.ts @@ -187,13 +187,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 = 1, deepDependencies } = res.meta.transformCssDepth!; @@ -315,7 +326,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(); @@ -325,6 +336,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); @@ -354,6 +368,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 c6ee5cea9..4f43bef2b 100644 --- a/packages/esbuild/test/e2e/esbuild-testkit.ts +++ b/packages/esbuild/test/e2e/esbuild-testkit.ts @@ -1,7 +1,7 @@ import { dirname, join } from 'node:path'; import { readFileSync, symlinkSync, writeFileSync } from 'node:fs'; import fs from '@file-services/node'; -import { BuildContext, BuildOptions, context } from 'esbuild'; +import { BuildContext, BuildOptions, context, Plugin } from 'esbuild'; import { createTempDirectorySync, runServer } from '@stylable/e2e-test-kit'; import playwright from 'playwright-core'; @@ -21,11 +21,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`); @@ -51,10 +53,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 +157,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 2d2f1e1f7..10958ea03 100644 --- a/packages/esbuild/test/e2e/rebuild/rebuild.spec.ts +++ b/packages/esbuild/test/e2e/rebuild/rebuild.spec.ts @@ -8,7 +8,7 @@ describe('Stylable ESBuild plugin rebuild on change', function () { afterEach(() => tk.dispose()); it('should pick up rebuild', async function () { - const { context, read, write } = await tk.build({ + const { context, read, write, act } = await tk.build({ project: 'rebuild', tmp: true, }); @@ -16,8 +16,28 @@ describe('Stylable ESBuild plugin rebuild on change', function () { 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'); });