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
This commit is contained in:
JoostK 2024-06-13 15:03:50 +02:00 committed by Andrew Kushnir
parent 571d06b6c3
commit ec0d1bf6f3
2 changed files with 29 additions and 11 deletions

View file

@ -134,11 +134,12 @@ function insertIntoFunction(
*/
function insertIntoProgram(program: NodePath<t.Program>, 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);
}
}

View file

@ -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';",