diff --git a/packages/angular_devkit/build_optimizer/BUILD b/packages/angular_devkit/build_optimizer/BUILD index f4f48cadf65d..ea3c70af6570 100644 --- a/packages/angular_devkit/build_optimizer/BUILD +++ b/packages/angular_devkit/build_optimizer/BUILD @@ -28,6 +28,7 @@ ts_library( "@npm//@types/node", "@npm//@types/webpack", "@npm//source-map", + "@npm//tslib", "@npm//typescript", ], ) diff --git a/packages/angular_devkit/build_optimizer/package.json b/packages/angular_devkit/build_optimizer/package.json index 0bd3bdd2d5dd..05c80b97e3c2 100644 --- a/packages/angular_devkit/build_optimizer/package.json +++ b/packages/angular_devkit/build_optimizer/package.json @@ -11,6 +11,7 @@ "dependencies": { "loader-utils": "1.2.3", "source-map": "0.7.3", + "tslib": "1.10.0", "typescript": "3.5.3", "webpack-sources": "1.4.3" } diff --git a/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts b/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts index c9e8402dca91..412fecb0225c 100644 --- a/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts @@ -100,6 +100,44 @@ describe('build-optimizer', () => { expect(boOutput.emitSkipped).toEqual(false); }); + it(`doesn't add pure comments to tslib helpers`, () => { + const input = tags.stripIndent` + class LanguageState { + } + + LanguageState.ctorParameters = () => [ + { type: TranslateService }, + { type: undefined, decorators: [{ type: Inject, args: [LANGUAGE_CONFIG,] }] } + ]; + + __decorate([ + Action(CheckLanguage), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) + ], LanguageState.prototype, "checkLanguage", null); + `; + + const output = tags.oneLine` + let LanguageState = /*@__PURE__*/ (() => { + class LanguageState { + } + + __decorate([ + Action(CheckLanguage), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) + ], LanguageState.prototype, "checkLanguage", null); + return LanguageState; + })(); + `; + + const boOutput = buildOptimizer({ content: input, isSideEffectFree: true }); + expect(tags.oneLine`${boOutput.content}`).toEqual(output); + expect(boOutput.emitSkipped).toEqual(false); + }); + it('should not wrap classes which had all static properties dropped in IIFE', () => { const classDeclaration = tags.oneLine` import { Injectable } from '@angular/core'; diff --git a/packages/angular_devkit/build_optimizer/src/helpers/ast-utils.ts b/packages/angular_devkit/build_optimizer/src/helpers/ast-utils.ts index 8c34506d3283..296b5d145ba5 100644 --- a/packages/angular_devkit/build_optimizer/src/helpers/ast-utils.ts +++ b/packages/angular_devkit/build_optimizer/src/helpers/ast-utils.ts @@ -5,10 +5,15 @@ * 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 * as tslib from 'tslib'; import * as ts from 'typescript'; const pureFunctionComment = '@__PURE__'; +// We include only exports that start with '__' because tslib helpers +// all start with a suffix of two underscores. +const tslibHelpers = new Set(Object.keys(tslib).filter(h => h.startsWith('__'))); + // Find all nodes from the AST in the subtree of node of SyntaxKind kind. export function collectDeepNodes(node: ts.Node, kind: ts.SyntaxKind): T[] { const nodes: T[] = []; @@ -41,3 +46,7 @@ export function hasPureComment(node: ts.Node): boolean { return !!leadingComment && leadingComment.some(comment => comment.text === pureFunctionComment); } + +export function isHelperName(name: string): boolean { + return tslibHelpers.has(name); +} diff --git a/packages/angular_devkit/build_optimizer/src/transforms/import-tslib.ts b/packages/angular_devkit/build_optimizer/src/transforms/import-tslib.ts index 7251aac5e2ad..4b57fc9e039e 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/import-tslib.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/import-tslib.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript'; +import { isHelperName } from '../helpers/ast-utils'; /** * @deprecated From 0.9.0 @@ -89,15 +90,3 @@ function createTslibImport( return newNode; } } - -function isHelperName(name: string): boolean { - // TODO: there are more helpers than these, should we replace them all? - const tsHelpers = [ - '__extends', - '__decorate', - '__metadata', - '__param', - ]; - - return tsHelpers.indexOf(name) !== -1; -} diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts index a692862a87bb..03be253a49e0 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript'; -import { addPureComment, hasPureComment } from '../helpers/ast-utils'; +import { addPureComment, hasPureComment, isHelperName } from '../helpers/ast-utils'; export function getPrefixFunctionsTransformer(): ts.TransformerFactory { return (context: ts.TransformationContext): ts.Transformer => { @@ -44,10 +44,8 @@ export function findTopLevelFunctions(parentNode: ts.Node): Set { // need to mark function calls inside them as pure. // Class static initializers in ES2015 are an exception we don't cover. They would need similar // processing as enums to prevent property setting from causing the class to be retained. - if (ts.isFunctionDeclaration(node) - || ts.isFunctionExpression(node) - || ts.isClassDeclaration(node) - || ts.isClassExpression(node) + if (ts.isFunctionLike(node) + || ts.isClassLike(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) ) { @@ -78,9 +76,15 @@ export function findTopLevelFunctions(parentNode: ts.Node): Set { topLevelFunctions.add(node); } else if (ts.isCallExpression(innerNode)) { let expression: ts.Expression = innerNode.expression; + + if (ts.isIdentifier(expression) && isHelperName(expression.text)) { + return; + } + while (expression && ts.isParenthesizedExpression(expression)) { expression = expression.expression; } + if (expression) { if (ts.isFunctionExpression(expression)) { // Skip IIFE's with arguments diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts index 10c444afb057..ec7bb8fd0fdd 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts @@ -186,4 +186,28 @@ describe('prefix-functions', () => { expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); + + it(`doesn't add pure comments to tslib helpers`, () => { + const input = tags.stripIndent` + class LanguageState { + + } + + LanguageState.ctorParameters = () => [ + { type: TranslateService }, + { type: undefined, decorators: [{ type: Inject, args: [LANGUAGE_CONFIG,] }] } + ]; + + __decorate([ + Action(CheckLanguage), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) + ], LanguageState.prototype, "checkLanguage", null); + `; + + const output = input; + + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); + }); });