Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): elide setClassMetadataAsync calls
Browse files Browse the repository at this point in the history
Updates the logic that elides `setClassMetadata` calls to also elide `setClassMetadataAsync`. The latter will be emitted when the component uses the new `defer` block syntax.
  • Loading branch information
crisbeto authored and dgp1130 committed Sep 5, 2023
1 parent 4fe0326 commit 188a00f
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 20 deletions.
Expand Up @@ -13,14 +13,19 @@ import { NodePath, PluginObj, types } from '@babel/core';
*/
const SET_CLASS_METADATA_NAME = 'ɵsetClassMetadata';

/**
* Name of the asynchronous Angular class metadata function created by the Angular compiler.
*/
const SET_CLASS_METADATA_ASYNC_NAME = 'ɵsetClassMetadataAsync';

/**
* Provides one or more keywords that if found within the content of a source file indicate
* that this plugin should be used with a source file.
*
* @returns An a string iterable containing one or more keywords.
*/
export function getKeywords(): Iterable<string> {
return [SET_CLASS_METADATA_NAME];
return [SET_CLASS_METADATA_NAME, SET_CLASS_METADATA_ASYNC_NAME];
}

/**
Expand All @@ -33,6 +38,7 @@ export default function (): PluginObj {
visitor: {
CallExpression(path: NodePath<types.CallExpression>) {
const callee = path.node.callee;
const callArguments = path.node.arguments;

// The function being called must be the metadata function name
let calleeName;
Expand All @@ -41,31 +47,58 @@ export default function (): PluginObj {
} else if (types.isIdentifier(callee)) {
calleeName = callee.name;
}
if (calleeName !== SET_CLASS_METADATA_NAME) {
return;
}

// There must be four arguments that meet the following criteria:
// * First must be an identifier
// * Second must be an array literal
const callArguments = path.node.arguments;
if (
callArguments.length !== 4 ||
!types.isIdentifier(callArguments[0]) ||
!types.isArrayExpression(callArguments[1])
calleeName !== undefined &&
(isRemoveClassMetadataCall(calleeName, callArguments) ||
isRemoveClassmetadataAsyncCall(calleeName, callArguments))
) {
return;
}
// The metadata function is always emitted inside a function expression
const parent = path.getFunctionParent();

// The metadata function is always emitted inside a function expression
const parent = path.getFunctionParent();

if (parent && (parent.isFunctionExpression() || parent.isArrowFunctionExpression())) {
// Replace the metadata function with `void 0` which is the equivalent return value
// of the metadata function.
path.replaceWith(path.scope.buildUndefinedNode());
if (parent && (parent.isFunctionExpression() || parent.isArrowFunctionExpression())) {
// Replace the metadata function with `void 0` which is the equivalent return value
// of the metadata function.
path.replaceWith(path.scope.buildUndefinedNode());
}
}
},
},
};
}

/** Determines if a function call is a call to `setClassMetadata`. */
function isRemoveClassMetadataCall(name: string, args: types.CallExpression['arguments']): boolean {
// `setClassMetadata` calls have to meet the following criteria:
// * First must be an identifier
// * Second must be an array literal
return (
name === SET_CLASS_METADATA_NAME &&
args.length === 4 &&
types.isIdentifier(args[0]) &&
types.isArrayExpression(args[1])
);
}

/** Determines if a function call is a call to `setClassMetadataAsync`. */
function isRemoveClassmetadataAsyncCall(
name: string,
args: types.CallExpression['arguments'],
): boolean {
// `setClassMetadataAsync` calls have to meet the following criteria:
// * First argument must be an identifier.
// * Second argument must be an inline function.
// * Third argument must be an inline function.
return (
name === SET_CLASS_METADATA_ASYNC_NAME &&
args.length === 3 &&
types.isIdentifier(args[0]) &&
isInlineFunction(args[1]) &&
isInlineFunction(args[2])
);
}

/** Determines if a node is an inline function expression. */
function isInlineFunction(node: types.Node): boolean {
return types.isFunctionExpression(node) || types.isArrowFunctionExpression(node);
}
Expand Up @@ -102,4 +102,88 @@ describe('elide-angular-metadata Babel plugin', () => {
`,
}),
);

it(
'elides pure annotated ɵsetClassMetadataAsync',
testCase({
input: `
import { Component } from '@angular/core';
export class SomeClass {}
/*@__PURE__*/ (function () {
i0.ɵsetClassMetadataAsync(SomeClass,
function () { return [import("./cmp-a").then(function (m) { return m.CmpA; })]; },
function (CmpA) { i0.ɵsetClassMetadata(SomeClass, [{
type: Component,
args: [{
selector: 'test-cmp',
standalone: true,
imports: [CmpA, LocalDep],
template: '{#defer}<cmp-a/>{/defer}',
}]
}], null, null); });
})();
`,
expected: `
import { Component } from '@angular/core';
export class SomeClass {}
/*@__PURE__*/ (function () { void 0 })();
`,
}),
);

it(
'elides JIT mode protected ɵsetClassMetadataAsync',
testCase({
input: `
import { Component } from '@angular/core';
export class SomeClass {}
(function () {
(typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵsetClassMetadataAsync(SomeClass,
function () { return [import("./cmp-a").then(function (m) { return m.CmpA; })]; },
function (CmpA) { i0.ɵsetClassMetadata(SomeClass, [{
type: Component,
args: [{
selector: 'test-cmp',
standalone: true,
imports: [CmpA, LocalDep],
template: '{#defer}<cmp-a/>{/defer}',
}]
}], null, null); });
})();
`,
expected: `
import { Component } from '@angular/core';
export class SomeClass {}
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && void 0 })();
`,
}),
);

it(
'elides arrow-function-based ɵsetClassMetadataAsync',
testCase({
input: `
import { Component } from '@angular/core';
export class SomeClass {}
/*@__PURE__*/ (() => {
i0.ɵsetClassMetadataAsync(SomeClass,
() => [import("./cmp-a").then(m => m.CmpA)],
(CmpA) => { i0.ɵsetClassMetadata(SomeClass, [{
type: Component,
args: [{
selector: 'test-cmp',
standalone: true,
imports: [CmpA, LocalDep],
template: '{#defer}<cmp-a/>{/defer}',
}]
}], null, null); });
})();
`,
expected: `
import { Component } from '@angular/core';
export class SomeClass {}
/*@__PURE__*/ (() => { void 0 })();
`,
}),
);
});

0 comments on commit 188a00f

Please sign in to comment.