From e7da6d84ce7304fb326feaea1d2b04fe9d248dbd Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 8 Jun 2026 10:27:42 -0400 Subject: [PATCH 1/2] fix(preview): support generating preview for envs without a compiler A compiler-less env (bundler/preview enabled but no compiler) failed during the GeneratePreview build task with "context.env.getCompiler is not a function" and, once that was guarded, with ENOENT when writing the preview link into a non-existent dist dir. - guard the getCompiler() call in the component bundling strategy - ensure the target dir exists before writing the preview link contents --- scopes/preview/preview/preview.main.runtime.ts | 3 +++ scopes/preview/preview/strategies/component-strategy.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/scopes/preview/preview/preview.main.runtime.ts b/scopes/preview/preview/preview.main.runtime.ts index 39251817576e..bdf2dc14b2e6 100644 --- a/scopes/preview/preview/preview.main.runtime.ts +++ b/scopes/preview/preview/preview.main.runtime.ts @@ -809,6 +809,9 @@ export class PreviewMain { const targetPath = join(targetDir, `${prefix}-${this.timestamp}.js`); // write only if link has changed (prevents triggering fs watches) if (this.writeHash.get(targetPath) !== hash) { + // the target dir is normally the compiler's dist dir, which already exists. for envs without a + // compiler (the dist dir was never created), make sure the dir exists before writing the link. + ensureDirSync(targetDir); writeFileSync(targetPath, contents); this.writeHash.set(targetPath, hash); } diff --git a/scopes/preview/preview/strategies/component-strategy.ts b/scopes/preview/preview/strategies/component-strategy.ts index 0dcf8600ff65..beefe512257c 100644 --- a/scopes/preview/preview/strategies/component-strategy.ts +++ b/scopes/preview/preview/strategies/component-strategy.ts @@ -382,8 +382,8 @@ export class ComponentBundlingStrategy implements BundlingStrategy { private getComponentOutputPath(capsule: Capsule, context: ComputeTargetsContext) { const capsulePath = resolve(`${capsule.path}`); - const compiler: Compiler = context.env.getCompiler(); - const distDir = compiler.getDistDir?.() || 'dist'; + const compiler: Compiler | undefined = context.env.getCompiler?.(); + const distDir = compiler?.getDistDir?.() || 'dist'; return join(capsulePath, distDir); } From 0a15090882d47407a7cf3109ca033fea7ec66ce2 Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 8 Jun 2026 11:06:44 -0400 Subject: [PATCH 2/2] test(preview): add e2e for building a preview on an env without a compiler reproduces the scenario fixed in the previous commit: a component on an env that has a preview/bundler but no compiler (like bitdev.general/envs/js-env). adds a react-based env fixture with the compiler and build pipe removed. --- .../extensions/react-no-compiler-env/index.ts | 4 +++ .../react-no-compiler-env.bit-env.ts | 25 +++++++++++++++++ e2e/harmony/custom-env.e2e.ts | 27 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 components/legacy/e2e-helper/excluded-fixtures/extensions/react-no-compiler-env/index.ts create mode 100644 components/legacy/e2e-helper/excluded-fixtures/extensions/react-no-compiler-env/react-no-compiler-env.bit-env.ts diff --git a/components/legacy/e2e-helper/excluded-fixtures/extensions/react-no-compiler-env/index.ts b/components/legacy/e2e-helper/excluded-fixtures/extensions/react-no-compiler-env/index.ts new file mode 100644 index 000000000000..f8ed1c9b7dde --- /dev/null +++ b/components/legacy/e2e-helper/excluded-fixtures/extensions/react-no-compiler-env/index.ts @@ -0,0 +1,4 @@ +import { ReactNoCompilerEnv } from './react-no-compiler-env.bit-env'; + +export default ReactNoCompilerEnv; +export { ReactNoCompilerEnv }; diff --git a/components/legacy/e2e-helper/excluded-fixtures/extensions/react-no-compiler-env/react-no-compiler-env.bit-env.ts b/components/legacy/e2e-helper/excluded-fixtures/extensions/react-no-compiler-env/react-no-compiler-env.bit-env.ts new file mode 100644 index 000000000000..27ff94922697 --- /dev/null +++ b/components/legacy/e2e-helper/excluded-fixtures/extensions/react-no-compiler-env/react-no-compiler-env.bit-env.ts @@ -0,0 +1,25 @@ +// @bit-no-check +// @ts-nocheck +import { ReactEnv } from '@teambit/react.react-env'; + +/** + * an env that has a preview/bundler but no compiler. it reproduces the (rare) scenario of an env + * without a compiler that still needs to generate a preview during `bit build` (e.g. + * bitdev.general/envs/js-env). + * + * it extends the react env (to inherit a working preview/bundler) and removes the compiler and the + * build pipeline: `compiler` is unset so no `getCompiler` is exposed on the env, and `build` is + * unset so the env contributes no build tasks (in particular no compiler task). the global preview + * tasks still run, so the preview is generated for a component on this compiler-less env. + */ +export class ReactNoCompilerEnv extends ReactEnv { + name = 'react-no-compiler-env'; + + icon = 'https://static.bit.dev/extensions-icons/react.svg'; + + compiler = undefined; + + build = undefined; +} + +export default new ReactNoCompilerEnv(); diff --git a/e2e/harmony/custom-env.e2e.ts b/e2e/harmony/custom-env.e2e.ts index 22f97dba631a..485206aaebd7 100644 --- a/e2e/harmony/custom-env.e2e.ts +++ b/e2e/harmony/custom-env.e2e.ts @@ -636,6 +636,33 @@ export default createMounter(MyReactProvider) as any;` expect(output).to.not.have.string('failed'); }); }); + describe('an env with a preview/bundler but without a compiler', () => { + let buildOutput: string; + before(() => { + // preview is disabled by default in e2e to speed up tagging. enable it so the GeneratePreview + // task actually runs - this is the path that used to fail for a compiler-less env. + helper.scopeHelper.setWorkspaceWithRemoteScope({ disablePreview: false }); + // a react-based env with the compiler (and the env build pipe) removed. so the env has a + // preview/bundler but no compiler, exactly like bitdev.general/envs/js-env. + const envName = helper.env.setCustomNewEnv('react-no-compiler-env'); + const envId = `${helper.scopes.remote}/${envName}`; + helper.fixtures.populateComponents(1, false); + helper.command.setEnv('comp1', envId); + helper.command.install(); + }); + it('bit build should not fail generating the preview', () => { + // before the fix it used to throw "context.env.getCompiler is not a function" and then + // ENOENT when writing the preview link into the (never created) dist dir. + // skip the TSCompiler task: comp1's env has no compiler anyway, and skipping it avoids + // compiling the env component itself (irrelevant to this scenario - the user's env was a + // resolved dependency, not built). the global GeneratePreview task still runs. + buildOutput = helper.command.build('comp1', '--skip-tasks TSCompiler'); + expect(buildOutput).to.have.string('build succeeded'); + }); + it('the GeneratePreview task should have run (preview was needed, not skipped)', () => { + expect(buildOutput).to.have.string('GeneratePreview'); + }); + }); describe('custom env with invalid env.jsonc', () => { before(() => { helper.scopeHelper.setWorkspaceWithRemoteScope();