mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user