From ec0d1bf6f337fc4f2c29cacaa6ca5b915eb4e561 Mon Sep 17 00:00:00 2001 From: JoostK Date: Thu, 13 Jun 2024 15:03:50 +0200 Subject: [PATCH] fix(compiler-cli): insert constant statements after the first group of imports (#56431) The linker inserts the constant statements that are needed to support compiled templates after the import statements of an ESM file, but it failed to account for import statements that are not at the top of the file. This is typically seen in FESM files where multiple individual ESMs have been concatenated into a single ESM file, with imports in various places. The linker would then find the very last import statement to insert the constant statements after, but this may result in TDZ errors for component templates that have been emitted earlier in the file. This commit updates the Babel linker plugin to insert constant statements after the last import of the first import group, therefore avoiding the TDZ error. Fixes #56403 PR Close #56431 --- .../linker/babel/src/es2015_linker_plugin.ts | 7 ++-- .../babel/test/es2015_linker_plugin_spec.ts | 33 ++++++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/compiler-cli/linker/babel/src/es2015_linker_plugin.ts b/packages/compiler-cli/linker/babel/src/es2015_linker_plugin.ts index 121f3ba7f39..9edd2294b3e 100644 --- a/packages/compiler-cli/linker/babel/src/es2015_linker_plugin.ts +++ b/packages/compiler-cli/linker/babel/src/es2015_linker_plugin.ts @@ -134,11 +134,12 @@ function insertIntoFunction( */ function insertIntoProgram(program: NodePath, statements: t.Statement[]): void { const body = program.get('body'); - const importStatements = body.filter((statement) => statement.isImportDeclaration()); - if (importStatements.length === 0) { + const insertBeforeIndex = body.findIndex((statement) => !statement.isImportDeclaration()); + + if (insertBeforeIndex === -1) { program.unshiftContainer('body', statements); } else { - importStatements[importStatements.length - 1].insertAfter(statements); + body[insertBeforeIndex].insertBefore(statements); } } diff --git a/packages/compiler-cli/linker/babel/test/es2015_linker_plugin_spec.ts b/packages/compiler-cli/linker/babel/test/es2015_linker_plugin_spec.ts index e1734320ac1..0ed0d811292 100644 --- a/packages/compiler-cli/linker/babel/test/es2015_linker_plugin_spec.ts +++ b/packages/compiler-cli/linker/babel/test/es2015_linker_plugin_spec.ts @@ -69,7 +69,6 @@ describe('createEs2015LinkerPlugin()', () => { ); const fileSystem = new MockFileSystemNative(); const logger = new MockLogger(); - const plugin = createEs2015LinkerPlugin({fileSystem, logger}); babel.transformSync( [ @@ -115,7 +114,6 @@ describe('createEs2015LinkerPlugin()', () => { ); const fileSystem = new MockFileSystemNative(); const logger = new MockLogger(); - const plugin = createEs2015LinkerPlugin({fileSystem, logger}); const result = babel.transformSync( [ 'var core;', @@ -138,7 +136,6 @@ describe('createEs2015LinkerPlugin()', () => { spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT')); const fileSystem = new MockFileSystemNative(); const logger = new MockLogger(); - const plugin = createEs2015LinkerPlugin({fileSystem, logger}); const result = babel.transformSync( [ "import * as core from 'some-module';", @@ -159,11 +156,35 @@ describe('createEs2015LinkerPlugin()', () => { ); }); + it('should return a Babel plugin that adds shared statements after the first group of imports', () => { + spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT')); + const fileSystem = new MockFileSystemNative(); + const logger = new MockLogger(); + const result = babel.transformSync( + [ + "import * as core from 'some-module';", + "import {id} from 'other-module';", + `ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`, + `ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`, + `ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`, + "import {second} from 'second-module';", + ].join('\n'), + { + plugins: [createEs2015LinkerPlugin({fileSystem, logger})], + filename: '/test.js', + parserOpts: {sourceType: 'unambiguous'}, + generatorOpts: {compact: true}, + }, + ); + expect(result!.code).toEqual( + 'import*as core from\'some-module\';import{id}from\'other-module\';const _c0=[1];const _c1=[2];const _c2=[3];"REPLACEMENT";"REPLACEMENT";"REPLACEMENT";import{second}from\'second-module\';', + ); + }); + it('should return a Babel plugin that adds shared statements at the start of the program if it is an ECMAScript Module and there are no imports', () => { spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT')); const fileSystem = new MockFileSystemNative(); const logger = new MockLogger(); - const plugin = createEs2015LinkerPlugin({fileSystem, logger}); const result = babel.transformSync( [ 'var core;', @@ -188,7 +209,6 @@ describe('createEs2015LinkerPlugin()', () => { spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT')); const fileSystem = new MockFileSystemNative(); const logger = new MockLogger(); - const plugin = createEs2015LinkerPlugin({fileSystem, logger}); const result = babel.transformSync( [ 'function run(core) {', @@ -213,7 +233,6 @@ describe('createEs2015LinkerPlugin()', () => { spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT')); const fileSystem = new MockFileSystemNative(); const logger = new MockLogger(); - const plugin = createEs2015LinkerPlugin({fileSystem, logger}); const result = babel.transformSync( [ 'function run() {', @@ -244,7 +263,6 @@ describe('createEs2015LinkerPlugin()', () => { spyOnLinkPartialDeclarationWithConstants(o.fn([], [], null, null, 'FOO')); const fileSystem = new MockFileSystemNative(); const logger = new MockLogger(); - const plugin = createEs2015LinkerPlugin({fileSystem, logger}); const result = babel.transformSync( [ `ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core}); FOO;`, @@ -298,7 +316,6 @@ describe('createEs2015LinkerPlugin()', () => { const fileSystem = new MockFileSystemNative(); const logger = new MockLogger(); - const plugin = createEs2015LinkerPlugin({fileSystem, logger}); const result = babel.transformSync( [ "import * as core from 'some-module';",