diff --git a/.changeset/frank-cougars-go.md b/.changeset/frank-cougars-go.md new file mode 100644 index 000000000..f4d2244c3 --- /dev/null +++ b/.changeset/frank-cougars-go.md @@ -0,0 +1,5 @@ +--- +"@callstack/repack": patch +--- + +Handle previous compiler errors and abort bundling in `RepackOutputPlugin` diff --git a/packages/repack/src/plugins/OutputPlugin/OutputPlugin.ts b/packages/repack/src/plugins/OutputPlugin/OutputPlugin.ts index 69a551c9e..c7270114f 100644 --- a/packages/repack/src/plugins/OutputPlugin/OutputPlugin.ts +++ b/packages/repack/src/plugins/OutputPlugin/OutputPlugin.ts @@ -184,6 +184,13 @@ export class OutputPlugin { ); compiler.hooks.done.tapPromise('RepackOutputPlugin', async (stats) => { + if (stats.hasErrors()) { + throw new Error( + '[RepackOutputPlugin] Compilation failed:\n' + + stats.toString('errors-only') + ); + } + const compilationStats = stats.toJson({ all: false, assets: true, diff --git a/packages/repack/src/plugins/__tests__/OutputPlugin.test.ts b/packages/repack/src/plugins/__tests__/OutputPlugin.test.ts index 15656d69b..90adedab0 100644 --- a/packages/repack/src/plugins/__tests__/OutputPlugin.test.ts +++ b/packages/repack/src/plugins/__tests__/OutputPlugin.test.ts @@ -1,13 +1,69 @@ +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; import { type EntryNormalized, ModuleFilenameHelpers, type StatsChunk, + rspack, } from '@rspack/core'; +import RspackVirtualModulePlugin from 'rspack-plugin-virtual-module'; import { OutputPlugin, type OutputPluginConfig, } from '../OutputPlugin/index.js'; +async function testCompile(code: string, expectThrow?: string) { + // Create a temporary directory to write files to. + const tempDir = await fs.mkdtemp( + path.join(os.tmpdir(), 'output-plugin-test-') + ); + + // And then run the compiler with the provided code. + try { + const compiler = rspack({ + context: tempDir, + mode: 'production', + devtool: false, + entry: 'index.js', + output: { + filename: 'index.bundle', + path: path.join(tempDir, 'out'), + }, + plugins: [ + new OutputPlugin({ + context: tempDir, + platform: 'ios', + output: {}, + }), + new RspackVirtualModulePlugin({ + 'index.js': code, + }), + ], + }); + + const promise = new Promise((resolve, reject) => + compiler.run((error, _stats) => { + if (error) { + reject(error); + } else { + resolve(); + } + }) + ); + + // Depending on whether we expect an error or not, assert accordingly. + if (expectThrow) { + await expect(promise).rejects.toThrow(expectThrow); + } else { + await expect(promise).resolves.not.toThrow(); + } + } finally { + // Delete the temporary directory after the test + await fs.rm(tempDir, { recursive: true, force: true }); + } +} + const makeChunk = ({ name, entry = false, @@ -204,4 +260,17 @@ describe('OutputPlugin', () => { }); }); }); + + describe('apply', () => { + it('should throw an error when compilation has errors', async () => { + await testCompile( + 'const x = {{{', + '[RepackOutputPlugin] Compilation failed:' + ); + }); + + it('should complete successfully when compilation succeeds', async () => { + await testCompile('const x = {};'); + }); + }); });