refactor: update compiler-cli babel linker to be ESM only (#48521)

Since the linker is no longer being tested with CommonJS, we can remove
most of the CJS/ESM interop trickery.

PR Close #48521
This commit is contained in:
Paul Gschwendtner
2022-12-13 17:31:50 +00:00
parent a2b9c5c778
commit 661134fd21
12 changed files with 62 additions and 107 deletions
@@ -5,9 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {types as t} from '@babel/core';
import {assert} from '../../../../linker';
import {AstFactory, BinaryOperator, LeadingComment, ObjectLiteralProperty, SourceMapRange, TemplateLiteral, VariableDeclarationType} from '../../../../src/ngtsc/translator';
import {types as t} from '../babel_core';
/**
* A Babel flavored implementation of the AstFactory.
@@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {types as t} from '@babel/core';
import {assert, AstHost, FatalLinkerError, Range} from '../../../../linker';
import {types as t} from '../babel_core';
/**
* This implementation of `AstHost` is able to get information from Babel AST nodes.
@@ -1,58 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* This is an interop file allowing for `@babel/core` to be imported in both CommonJS or
* ES module files. The `@babel/core` package needs some special treatment because:
*
* Using a default import does not with CommonJS because the `@babel/core` package does not
* expose a `default` export at runtime (because it sets the `_esModule` property that causes
* TS to not create the necessary interop `default` export). On the other side, when loaded
* as part of an ESM, NodeJS will make all of the exports available as default export.
*
* Using named import bindings (i.e. namespace import or actual named bindings) is not
* working well for ESM because as said before, NodeJS will make all of the exports available
* as the `default` export. Hence ESM that imports CJS, always should use the default import.
*
* There is no solution that would work for both CJS and ESM, so we need to use a custom interop
* that switches between the named exports or the default exports depending on what is available.
* This allows the code to run in both ESM (for production) and CJS (for development).
*
* TODO(devversion): remove this once devmode uses ESM as well.
*/
// tslint:disable-next-line
import * as _babelNamespace from '@babel/core';
// tslint:disable-next-line
import _babelDefault from '@babel/core';
const babel: typeof _babelNamespace = _babelDefault ?? _babelNamespace;
// We create an alias of the `types` namespace so that we can re-export the
// types namespace. Preserving the namespace is important so that types
// can still be referenced using a qualified name.
import _typesNamespace = _babelNamespace.types;
// If the default export is available, we use its `types` runtime value
// for the type namespace we re-export. This is a trick we use to preserve
// the namespace types, while changing the runtime value of the namespace.
// TS complains about us assigning to a namespace but this is legal at runtime.
if (_babelDefault !== undefined) {
// @ts-ignore
_typesNamespace = _babelDefault.types;
}
export import types = _typesNamespace;
export type BabelFile = _babelNamespace.BabelFile;
export type PluginObj = _babelNamespace.PluginObj;
export type ConfigAPI = _babelNamespace.ConfigAPI;
export type NodePath<T = _babelNamespace.Node> = _babelNamespace.NodePath<T>;
export const NodePath: typeof _babelNamespace.NodePath = babel.NodePath;
export const transformSync: typeof _babelNamespace.transformSync = babel.transformSync;
export const parse: typeof _babelNamespace.parse = babel.parse;
@@ -5,12 +5,11 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {types as t} from '@babel/core';
import {NodePath, Scope} from '@babel/traverse';
import {DeclarationScope} from '../../../linker';
import {types as t} from './babel_core';
export type ConstantScopePath =
NodePath<t.FunctionDeclaration>|NodePath<t.FunctionExpression>|NodePath<t.Program>;
@@ -5,11 +5,12 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ConfigAPI, PluginObj} from '@babel/core';
import {NodeJSFileSystem} from '../../../src/ngtsc/file_system';
import {ConsoleLogger, LogLevel} from '../../../src/ngtsc/logging';
import {LinkerOptions} from '../../src/file_linker/linker_options';
import {ConfigAPI, PluginObj} from './babel_core';
import {createEs2015LinkerPlugin} from './es2015_linker_plugin';
/**
@@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {BabelFile, PluginObj, types as t} from '@babel/core';
import {NodePath} from '@babel/traverse';
import {FileLinker, isFatalLinkerError, LinkerEnvironment} from '../../../linker';
import {BabelAstFactory} from './ast/babel_ast_factory';
import {BabelAstHost} from './ast/babel_ast_host';
import {BabelFile, PluginObj, types as t} from './babel_core';
import {BabelDeclarationScope, ConstantScopePath} from './babel_declaration_scope';
import {LinkerPluginOptions} from './linker_plugin_options';
@@ -6,16 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/
import {leadingComment} from '@angular/compiler';
import generate from '@babel/generator';
import template from '@babel/template';
import {types as t} from '../../src/babel_core';
import {types as t} from '@babel/core';
import _generate from '@babel/generator';
import _template from '@babel/template';
import {BabelAstFactory} from '../../src/ast/babel_ast_factory';
// Babel is a CJS package and misuses the `default` named binding:
// https://github.com/babel/babel/issues/15269.
const generate = (_generate as any)['default'] as typeof _generate;
// Exposes shorthands for the `expression` and `statement`
// methods exposed by `@babel/template`.
const expression = template.expression;
const statement = template.statement;
const expression = _template.expression;
const statement = _template.statement;
describe('BabelAstFactory', () => {
let factory: BabelAstFactory;
@@ -5,11 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {types as t} from '../../src/babel_core';
import template from '@babel/template';
import {parse} from '@babel/parser';
import {types as t} from '@babel/core';
import parser from '@babel/parser';
import _template from '@babel/template';
import {BabelAstHost} from '../../src/ast/babel_ast_host';
// Babel is a CJS package and misuses the `default` named binding:
// https://github.com/babel/babel/issues/15269.
const template = (_template as any)['default'] as typeof _template;
describe('BabelAstHost', () => {
let host: BabelAstHost;
beforeEach(() => host = new BabelAstHost());
@@ -326,7 +331,7 @@ describe('BabelAstHost', () => {
describe('getRange()', () => {
it('should extract the range from the expression', () => {
const file = parse('// preamble\nx = \'moo\';');
const file = parser.parse('// preamble\nx = \'moo\';');
const stmt = file.program.body[0] as t.Statement;
assertExpressionStatement(stmt);
assertAssignmentExpression(stmt.expression);
@@ -6,16 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/
import {parse} from '@babel/parser';
import traverse, {NodePath} from '@babel/traverse';
import {types as t} from '@babel/core';
import parser from '@babel/parser';
import _traverse, {NodePath} from '@babel/traverse';
import {types as t} from '../src/babel_core';
import {BabelDeclarationScope} from '../src/babel_declaration_scope';
// Babel is a CJS package and misuses the `default` named binding:
// https://github.com/babel/babel/issues/15269.
const traverse = (_traverse as any)['default'] as typeof _traverse;
describe('BabelDeclarationScope', () => {
describe('getConstantScopeRef()', () => {
it('should return a path to the ES module where the expression was imported', () => {
const ast = parse(
const ast = parser.parse(
[
'import * as core from \'@angular/core\';',
'function foo() {',
@@ -31,7 +35,7 @@ describe('BabelDeclarationScope', () => {
});
it('should return a path to the ES Module where the expression is declared', () => {
const ast = parse(
const ast = parser.parse(
[
'var core;',
'export function foo() {',
@@ -47,7 +51,7 @@ describe('BabelDeclarationScope', () => {
});
it('should return null if the file is not an ES module', () => {
const ast = parse(
const ast = parser.parse(
[
'var core;',
'function foo() {',
@@ -62,7 +66,7 @@ describe('BabelDeclarationScope', () => {
});
it('should return the IIFE factory function where the expression is a parameter', () => {
const ast = parse(
const ast = parser.parse(
[
'var core;',
'(function(core) {',
@@ -5,11 +5,11 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {transformSync} from '../src/babel_core';
import babel from '@babel/core';
describe('default babel plugin entry-point', () => {
it('should work as a Babel plugin using the module specifier', () => {
const result = transformSync(
it('should work as a Babel plugin using the module specifier', async () => {
const result = (await babel.transformAsync(
`
import * as i0 from "@angular/core";
@@ -20,10 +20,10 @@ describe('default babel plugin entry-point', () => {
`,
{
plugins: [
'@angular/compiler-cli/linker/babel',
'@angular/compiler-cli/linker/babel/index.mjs',
],
filename: 'test.js',
})!;
}))!;
expect(result).not.toBeNull();
expect(result.code).not.toContain('ɵɵngDeclareNgModule');
@@ -31,8 +31,8 @@ describe('default babel plugin entry-point', () => {
expect(result.code).not.toMatch(/declarations:\s*\[MyComponent]/);
});
it('should be configurable', () => {
const result = transformSync(
it('should be configurable', async () => {
const result = (await babel.transformAsync(
`
import * as i0 from "@angular/core";
@@ -43,10 +43,10 @@ describe('default babel plugin entry-point', () => {
`,
{
plugins: [
['@angular/compiler-cli/linker/babel', {linkerJitMode: true}],
['@angular/compiler-cli/linker/babel/index.mjs', {linkerJitMode: true}],
],
filename: 'test.js',
})!;
}))!;
expect(result).not.toBeNull();
expect(result.code).not.toContain('ɵɵngDeclareNgModule');
@@ -6,13 +6,17 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as o from '@angular/compiler/src/output/output_ast';
import generate from '@babel/generator';
import babel, {NodePath, PluginObj, types as t} from '@babel/core';
import _generate from '@babel/generator';
// Babel is a CJS package and misuses the `default` named binding:
// https://github.com/babel/babel/issues/15269.
const generate = (_generate as any)['default'] as typeof _generate;
import {FileLinker} from '../../../linker';
import {MockFileSystemNative} from '../../../src/ngtsc/file_system/testing';
import {MockLogger} from '../../../src/ngtsc/logging/testing';
import {PartialDirectiveLinkerVersion1} from '../../src/file_linker/partial_linkers/partial_directive_linker_1';
import {NodePath, PluginObj, transformSync, types as t} from '../src/babel_core';
import {createEs2015LinkerPlugin} from '../src/es2015_linker_plugin';
describe('createEs2015LinkerPlugin()', () => {
@@ -37,7 +41,7 @@ describe('createEs2015LinkerPlugin()', () => {
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
transformSync(
babel.transformSync(
[
'var core;', `fn1()`, 'fn2({prop: () => fn3({})});', `x.method(() => fn4());`,
'spread(...x);'
@@ -65,7 +69,7 @@ describe('createEs2015LinkerPlugin()', () => {
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
transformSync(
babel.transformSync(
[
'var core;',
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core, x: 1});`,
@@ -109,7 +113,7 @@ describe('createEs2015LinkerPlugin()', () => {
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
const result = transformSync(
const result = babel.transformSync(
[
'var core;',
'ɵɵngDeclareDirective({version: \'0.0.0-PLACEHOLDER\', ngImport: core});',
@@ -131,7 +135,7 @@ describe('createEs2015LinkerPlugin()', () => {
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
const result = transformSync(
const result = babel.transformSync(
[
'import * as core from \'some-module\';',
'import {id} from \'other-module\';',
@@ -156,7 +160,7 @@ describe('createEs2015LinkerPlugin()', () => {
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
const result = transformSync(
const result = babel.transformSync(
[
'var core;',
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
@@ -181,7 +185,7 @@ describe('createEs2015LinkerPlugin()', () => {
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
const result = transformSync(
const result = babel.transformSync(
[
'function run(core) {',
` ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
@@ -206,7 +210,7 @@ describe('createEs2015LinkerPlugin()', () => {
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
const result = transformSync(
const result = babel.transformSync(
[
'function run() {',
` ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
@@ -235,7 +239,7 @@ describe('createEs2015LinkerPlugin()', () => {
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
const result = transformSync(
const result = babel.transformSync(
[
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core}); FOO;`,
].join('\n'),
@@ -278,7 +282,7 @@ describe('createEs2015LinkerPlugin()', () => {
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
const result = transformSync(
const result = babel.transformSync(
[
'import * as core from \'some-module\';',
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
+1 -7
View File
@@ -36,13 +36,7 @@
// `module.exports` as `export default`. Instead, named exports should be used for compat with CJS/ESM.
"noDefaultExport": [],
// List of modules which are incompatible and should never be imported at all.
"incompatibleModules": {
// `@babel/core` and `@babel/types` suggest named exports which do not exist at runtime within ESM
// (as these named exports are not statically discoverable by NodeJS). At the same time, these modules
// set `__esModule` and the default import does not exist for CJS at runtime (e.g. breaking tests).
"@babel/core": "This module is incompatible with the ESM/CJS interop. Use the custom interop file.",
"@babel/types": "This module is incompatible with the ESM/CJS interop. Use the custom interop file and import the `types` namespace."
}
"incompatibleModules": {}
}
],
"eofline": true,