Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@angular-devkit/build-optimizer@0.1001.0 after prod build, only a black screen is displayed on the device #18682

Closed
1 of 15 tasks
eun-choi opened this issue Sep 3, 2020 · 19 comments · Fixed by #18711
Closed
1 of 15 tasks
Assignees
Labels
area: devkit/build-optimizer freq1: low Only reported by a handful of users who observe it rarely severity5: regression type: bug/fix
Milestone

Comments

@eun-choi
Copy link

eun-choi commented Sep 3, 2020

🐞 Bug report

Command (mark with an x)

  • new
  • build
  • serve
  • test
  • e2e
  • generate
  • add
  • update
  • lint
  • xi18n
  • run
  • config
  • help
  • version
  • doc

Description

Install @angular-devkit/build-angular@0.1001.0 or @angular-devkit/build-angular@0.1001.0-rc.0

After build, only a black screen is displayed on the device and the app cannot be used.

FYI, it works without problem until version @angular-devkit/build-angular@0.1000.8 or @angular-devkit/build-angular@0.1001.0-next.7

🔬 Minimal Reproduction

  1. npm install @angular-devkit/build-angular@0.1000.8 or @angular-devkit/build-angular@0.1001.0-next.7
  2. ng build --prod
  3. run on ios/android device
  4. it works fine
  5. npm install @angular-devkit/build-angular@0.1001.0 or @angular-devkit/build-angular@0.1001.0-rc.0
  6. ng build --prod
  7. run on ios/android device
  8. only a black screen is displayed on the device and the app cannot be used

🌍 Your Environment


Angular CLI: 10.1.0
Node: 12.18.3
OS: darwin x64

Angular: 10.1.0
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router, service-worker
Ivy Workspace: Yes

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.1001.0
@angular-devkit/build-angular      0.1001.0-next.7
@angular-devkit/build-optimizer    0.1001.0
@angular-devkit/build-webpack      0.1001.0
@angular-devkit/core               10.1.0
@angular-devkit/schematics         10.1.0
@angular/cdk                       10.2.0
@angular/fire                      6.0.2
@angular/material                  10.2.0
@angular/material-moment-adapter   10.2.0
@angular/pwa                       0.1001.0
@angular/youtube-player            10.2.0
@ngtools/webpack                   10.1.0-next.7
@schematics/angular                10.1.0
@schematics/update                 0.1001.0
rxjs                               6.6.2
typescript                         4.0.2
webpack                            4.44.1
@alan-agius4
Copy link
Collaborator

This seems like a bug but we'll need to look at a reproduction to find and fix the problem. Can you setup a minimal repro please?

You can read here why this is needed. A good way to make a minimal repro is to create a new app via ng new repro-app and adding the minimum possible code to show the problem. Then you can push this repository to github and link it here.

This might be related to your directory structure so its really important to get an accurate repro to diagnose this.

@alan-agius4 alan-agius4 added the needs: repro steps We cannot reproduce the issue with the information given label Sep 3, 2020
@appimpact
Copy link

appimpact commented Sep 3, 2020

We have the same issue with newest versions of angular and devkit.

Seems that SCSS styles configured in angular.json are not being included in the bundle properly:

image

However, src/styles.scss is included.

If we move SCSS references from angular.json to be imported through styles.scss, everything works correctly.

This started to happen after the update this morning to angular 10.1.0 / devkit 0.1001.0.

@eun-choi
Copy link
Author

eun-choi commented Sep 3, 2020

It wasn't just happening on devices, it was happening in production mode. It is reproduced with ng serve --prod and attaches the log.


polyfills.7b0614071d9e64dd3eff.js:1 Unhandled Promise rejection: Angular JIT compilation failed: '@angular/compiler' not loaded!
  - JIT compilation is discouraged for production use-cases! Consider AOT mode instead.
  - Did you bootstrap using '@angular/platform-browser-dynamic' or '@angular/platform-server'?
  - Alternatively provide the compiler with 'import "@angular/compiler";' before bootstrapping. ; Zone: <root> ; Task: Promise.then ; Value: Error: Angular JIT compilation failed: '@angular/compiler' not loaded!
  - JIT compilation is discouraged for production use-cases! Consider AOT mode instead.
  - Did you bootstrap using '@angular/platform-browser-dynamic' or '@angular/platform-server'?
  - Alternatively provide the compiler with 'import "@angular/compiler";' before bootstrapping.
    at X (main.d577a404cf692988b691.js:1)
    at Function.get (main.d577a404cf692988b691.js:1)
    at Fe (main.d577a404cf692988b691.js:1)
    at fs (main.d577a404cf692988b691.js:1)
    at main.d577a404cf692988b691.js:1
    at ds.processProvider (main.d577a404cf692988b691.js:1)
    at main.d577a404cf692988b691.js:1
    at main.d577a404cf692988b691.js:1
    at Array.forEach (<anonymous>)
    at ve (main.d577a404cf692988b691.js:1) Error: Angular JIT compilation failed: '@angular/compiler' not loaded!
  - JIT compilation is discouraged for production use-cases! Consider AOT mode instead.
  - Did you bootstrap using '@angular/platform-browser-dynamic' or '@angular/platform-server'?
  - Alternatively provide the compiler with 'import "@angular/compiler";' before bootstrapping.
    at X (http://localhost:8100/main.d577a404cf692988b691.js:1:1700902)
    at Function.get (http://localhost:8100/main.d577a404cf692988b691.js:1:1753555)
    at Fe (http://localhost:8100/main.d577a404cf692988b691.js:1:1706153)
    at fs (http://localhost:8100/main.d577a404cf692988b691.js:1:1758368)
    at http://localhost:8100/main.d577a404cf692988b691.js:1:1756645
    at ds.processProvider (http://localhost:8100/main.d577a404cf692988b691.js:1:1756659)
    at http://localhost:8100/main.d577a404cf692988b691.js:1:1756461
    at http://localhost:8100/main.d577a404cf692988b691.js:1:1703442
    at Array.forEach (<anonymous>)
    at ve (http://localhost:8100/main.d577a404cf692988b691.js:1:1703406)

스크린샷 2020-09-03 오후 5 26 18

@eun-choi eun-choi changed the title @angular-devkit/build-angular@0.1001.0 after build, only a black screen is displayed on the device @angular-devkit/build-angular@0.1001.0 after prod build, only a black screen is displayed on the device Sep 3, 2020
@alan-agius4
Copy link
Collaborator

@appimpact, you issue seems different, please file an new issue with a reproduction.

@eun-choi, unfortunately without a reproduction it’s hard to tell what’s happening.

@appimpact
Copy link

It's the same issue - our interfaces are not black, but are broken after the update as SCSS files are not being loaded.

@eun-choi
Copy link
Author

eun-choi commented Sep 3, 2020

It may be a similar issue. I'm also using several scss files.

The amount of code in the current app is large, so I will try to create a compact code that can be reproduced.

@eun-choi
Copy link
Author

eun-choi commented Sep 4, 2020

[Added Info.]
I found what is caused by @angular-devkit/build-optimizer
There were 2 commits in version rc.0, which was affected and did not run.

e49c926#diff-d35822fd50a0465d0a3f6ddfb943b127

ab62c7a#diff-d35822fd50a0465d0a3f6ddfb943b127

These two changes are not in the release notes.
When I changed scrub-file.js to next.7 version file, I confirmed that there was no problem.

@eun-choi eun-choi changed the title @angular-devkit/build-angular@0.1001.0 after prod build, only a black screen is displayed on the device @angular-devkit/build-optimizer@0.1001.0 after prod build, only a black screen is displayed on the device Sep 4, 2020
@alan-agius4
Copy link
Collaborator

Those 2 commits are internal refactors which shouldn't effect the applications.

I'll try to go through the refactor again, maybe i can spot something. A reproduction would definitely be helpful here or at least what's the difference in emitted code between the changes it would be hard to tell what's happening. You can run the production build without mangling, which should help diff the bundles easier.

NG_BUILD_DEBUG_OPTIMIZE=1 ng build --prod

@eun-choi
Copy link
Author

eun-choi commented Sep 4, 2020

@alan-agius4 I will copy the code by function unit in both versions of the scrub-file.js file and check which function causes the problem to me.

@eun-choi
Copy link
Author

eun-choi commented Sep 4, 2020

The problem occurs in function isTslibHelper()
Other functions are works fine with the latest code.

Existing code without problems:

// Check if an identifier is part of the known tslib identifiers.
function identifierIsTslib(id, tslibImports, checker) {
    const symbol = checker.getSymbolAtLocation(id);
    if (!symbol || !symbol.declarations || !symbol.declarations.length) {
        return false;
    }
    return symbol
        .declarations
        .some((spec) => tslibImports.indexOf(spec) !== -1);
}
// Check if a function call is a tslib helper.
function isTslibHelper(callExpr, helper, tslibImports, checker) {
    let name;
    if (ts.isIdentifier(callExpr.expression)) {
        name = callExpr.expression.text;
    }
    else if (ts.isPropertyAccessExpression(callExpr.expression)) {
        const left = callExpr.expression.expression;
        if (!ts.isIdentifier(left)) {
            return false;
        }
        if (!identifierIsTslib(left, tslibImports, checker)) {
            return false;
        }
        name = callExpr.expression.name.text;
    }
    else {
        return false;
    }
    // node.text on a name that starts with two underscores will return three instead.
    // Unless it's an expression like tslib.__decorate, in which case it's only 2.
    return name === `_${helper}` || name === helper;
}

Code causing the problem:

// Check if a function call is a tslib helper.
function isTslibHelper(callExpr, helper, tslibImports, checker) {
    var _a;
    if (!ts.isIdentifier(callExpr.expression) || callExpr.expression.text !== helper) {
        return false;
    }
    const symbol = checker.getSymbolAtLocation(callExpr.expression);
    if (!((_a = symbol === null || symbol === void 0 ? void 0 : symbol.declarations) === null || _a === void 0 ? void 0 : _a.length)) {
        return false;
    }
    for (const name of tslibImports) {
        for (const dec of symbol.declarations) {
            if (ts.isImportSpecifier(dec) && name.elements.includes(dec)) {
                return true;
            }
        }
    }
    return false;
}

@eun-choi
Copy link
Author

eun-choi commented Sep 4, 2020

FYI, typescript@4.0.2 and tslib@2.0.1 version.

The tsconfig.json configuration is as follows:

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "target": "es2015",
    "module": "es2020",
    "moduleResolution": "node",
    "lib": ["es2018","dom"],
    "sourceMap": true,
    "declaration": false,
    "experimentalDecorators": true,
    "downlevelIteration": true,
    "importHelpers": true,
    "removeComments": true,
    "strict": true,
    "alwaysStrict": true,
    "strictBindCallApply": true,
    "strictFunctionTypes": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "suppressImplicitAnyIndexErrors": true,
    "noFallthroughCasesInSwitch": true,
    "forceConsistentCasingInFileNames": true
  },
  "angularCompilerOptions": {
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

@alan-agius4
Copy link
Collaborator

I think what's is happening here is that one of your libraries was compiled using an older version of TypeScript, where it used namespace imports for tslib.

Can you try to update the scrub-file.js to the below:

scrub-file.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.expect = exports.getScrubFileTransformerForCore = exports.getScrubFileTransformer = exports.testScrubFile = void 0;
/**
 * @license
 * Copyright Google Inc. 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
 */
const ts = require("typescript");
const ast_utils_1 = require("../helpers/ast-utils");
function testScrubFile(content) {
    const markers = [
        'decorators',
        '__decorate',
        'propDecorators',
        'ctorParameters',
        'ɵsetClassMetadata',
    ];
    return markers.some((marker) => content.includes(marker));
}
exports.testScrubFile = testScrubFile;
function getScrubFileTransformer(program) {
    return scrubFileTransformer(program, false);
}
exports.getScrubFileTransformer = getScrubFileTransformer;
function getScrubFileTransformerForCore(program) {
    return scrubFileTransformer(program, true);
}
exports.getScrubFileTransformerForCore = getScrubFileTransformerForCore;
function scrubFileTransformer(program, isAngularCoreFile) {
    if (!program) {
        throw new Error('scrubFileTransformer requires a TypeScript Program.');
    }
    const checker = program.getTypeChecker();
    return (context) => {
        const transformer = (sf) => {
            const ngMetadata = findAngularMetadata(sf, isAngularCoreFile);
            const tslibImports = findTslibImports(sf);
            const nodes = [];
            ts.forEachChild(sf, checkNodeForDecorators);
            function checkNodeForDecorators(node) {
                if (!ts.isExpressionStatement(node)) {
                    return ts.forEachChild(node, checkNodeForDecorators);
                }
                const exprStmt = node;
                // Do checks that don't need the typechecker first and bail early.
                if (isIvyPrivateCallExpression(exprStmt) || isCtorParamsAssignmentExpression(exprStmt)) {
                    nodes.push(node);
                }
                else if (isDecoratorAssignmentExpression(exprStmt)) {
                    nodes.push(...pickDecorationNodesToRemove(exprStmt, ngMetadata, checker));
                }
                else if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker)
                    || isAngularDecoratorExpression(exprStmt, ngMetadata, tslibImports, checker)) {
                    nodes.push(...pickDecorateNodesToRemove(exprStmt, tslibImports, ngMetadata, checker));
                }
                else if (isPropDecoratorAssignmentExpression(exprStmt)) {
                    nodes.push(...pickPropDecorationNodesToRemove(exprStmt, ngMetadata, checker));
                }
            }
            const visitor = (node) => {
                // Check if node is a statement to be dropped.
                if (nodes.find((n) => n === node)) {
                    return undefined;
                }
                // Otherwise return node as is.
                return ts.visitEachChild(node, visitor, context);
            };
            return ts.visitNode(sf, visitor);
        };
        return transformer;
    };
}
function expect(node, kind) {
    if (node.kind !== kind) {
        throw new Error('Invalid node type.');
    }
    return node;
}
exports.expect = expect;
function findAngularMetadata(node, isAngularCoreFile) {
    let specs = [];
    // Find all specifiers from imports of `@angular/core`.
    ts.forEachChild(node, (child) => {
        if (child.kind === ts.SyntaxKind.ImportDeclaration) {
            const importDecl = child;
            if (isAngularCoreImport(importDecl, isAngularCoreFile)) {
                specs.push(...ast_utils_1.collectDeepNodes(importDecl, ts.SyntaxKind.ImportSpecifier));
            }
        }
    });
    // If the current module is a Angular core file, we also consider all declarations in it to
    // potentially be Angular metadata.
    if (isAngularCoreFile) {
        const localDecl = findAllDeclarations(node);
        specs = specs.concat(localDecl);
    }
    return specs;
}
function findAllDeclarations(node) {
    const nodes = [];
    ts.forEachChild(node, (child) => {
        if (child.kind === ts.SyntaxKind.VariableStatement) {
            const vStmt = child;
            vStmt.declarationList.declarations.forEach((decl) => {
                if (decl.name.kind !== ts.SyntaxKind.Identifier) {
                    return;
                }
                nodes.push(decl);
            });
        }
    });
    return nodes;
}
function isAngularCoreImport(node, isAngularCoreFile) {
    if (!(node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier))) {
        return false;
    }
    const importText = node.moduleSpecifier.text;
    // Imports to `@angular/core` are always core imports.
    if (importText === '@angular/core') {
        return true;
    }
    // Relative imports from a Angular core file are also core imports.
    if (isAngularCoreFile && (importText.startsWith('./') || importText.startsWith('../'))) {
        return true;
    }
    return false;
}
// Check if assignment is `Clazz.decorators = [...];`.
function isDecoratorAssignmentExpression(exprStmt) {
    if (!isAssignmentExpressionTo(exprStmt, 'decorators')) {
        return false;
    }
    const expr = exprStmt.expression;
    if (!ts.isArrayLiteralExpression(expr.right)) {
        return false;
    }
    return true;
}
// Check if assignment is `Clazz = __decorate([...], Clazz)`.
function isDecorateAssignmentExpression(exprStmt, tslibImports, checker) {
    if (!ts.isBinaryExpression(exprStmt.expression)) {
        return false;
    }
    const expr = exprStmt.expression;
    if (!ts.isIdentifier(expr.left)) {
        return false;
    }
    const classIdent = expr.left;
    let callExpr;
    if (ts.isCallExpression(expr.right)) {
        callExpr = expr.right;
    }
    else if (ts.isBinaryExpression(expr.right)) {
        // `Clazz = Clazz_1 = __decorate([...], Clazz)` can be found when there are static property
        // accesses.
        const innerExpr = expr.right;
        if (!ts.isIdentifier(innerExpr.left) || !ts.isCallExpression(innerExpr.right)) {
            return false;
        }
        callExpr = innerExpr.right;
    }
    else {
        return false;
    }
    if (!isTslibHelper(callExpr, '__decorate', tslibImports, checker)) {
        return false;
    }
    if (callExpr.arguments.length !== 2) {
        return false;
    }
    const classArg = callExpr.arguments[1];
    if (!ts.isIdentifier(classArg)) {
        return false;
    }
    if (classIdent.text !== classArg.text) {
        return false;
    }
    if (!ts.isArrayLiteralExpression(callExpr.arguments[0])) {
        return false;
    }
    return true;
}
// Check if expression is `__decorate([smt, __metadata("design:type", Object)], ...)`.
function isAngularDecoratorExpression(exprStmt, ngMetadata, tslibImports, checker) {
    if (!ts.isCallExpression(exprStmt.expression)) {
        return false;
    }
    const callExpr = exprStmt.expression;
    if (!isTslibHelper(callExpr, '__decorate', tslibImports, checker)) {
        return false;
    }
    if (callExpr.arguments.length !== 4) {
        return false;
    }
    const decorateArray = callExpr.arguments[0];
    if (!ts.isArrayLiteralExpression(decorateArray)) {
        return false;
    }
    // Check first array entry for Angular decorators.
    if (decorateArray.elements.length === 0 || !ts.isCallExpression(decorateArray.elements[0])) {
        return false;
    }
    return decorateArray.elements.some(decoratorCall => {
        if (!ts.isCallExpression(decoratorCall) || !ts.isIdentifier(decoratorCall.expression)) {
            return false;
        }
        const decoratorId = decoratorCall.expression;
        return identifierIsMetadata(decoratorId, ngMetadata, checker);
    });
}
// Check if assignment is `Clazz.propDecorators = [...];`.
function isPropDecoratorAssignmentExpression(exprStmt) {
    if (!isAssignmentExpressionTo(exprStmt, 'propDecorators')) {
        return false;
    }
    const expr = exprStmt.expression;
    if (expr.right.kind !== ts.SyntaxKind.ObjectLiteralExpression) {
        return false;
    }
    return true;
}
// Check if assignment is `Clazz.ctorParameters = [...];`.
function isCtorParamsAssignmentExpression(exprStmt) {
    if (!isAssignmentExpressionTo(exprStmt, 'ctorParameters')) {
        return false;
    }
    const expr = exprStmt.expression;
    if (expr.right.kind !== ts.SyntaxKind.FunctionExpression
        && expr.right.kind !== ts.SyntaxKind.ArrowFunction) {
        return false;
    }
    return true;
}
function isAssignmentExpressionTo(exprStmt, name) {
    if (!ts.isBinaryExpression(exprStmt.expression)) {
        return false;
    }
    const expr = exprStmt.expression;
    if (!ts.isPropertyAccessExpression(expr.left)) {
        return false;
    }
    const propAccess = expr.left;
    if (propAccess.name.text !== name) {
        return false;
    }
    if (!ts.isIdentifier(propAccess.expression)) {
        return false;
    }
    if (expr.operatorToken.kind !== ts.SyntaxKind.FirstAssignment) {
        return false;
    }
    return true;
}
function isIvyPrivateCallExpression(exprStmt) {
    // Each Ivy private call expression is inside an IIFE as single statements, so we must go down it.
    const expression = exprStmt.expression;
    if (!expression || !ts.isCallExpression(expression) || expression.arguments.length !== 0) {
        return null;
    }
    const parenExpr = expression;
    if (!ts.isParenthesizedExpression(parenExpr.expression)) {
        return null;
    }
    const funExpr = parenExpr.expression.expression;
    if (!ts.isFunctionExpression(funExpr)) {
        return null;
    }
    const innerStmts = funExpr.body.statements;
    if (innerStmts.length !== 1) {
        return null;
    }
    const innerExprStmt = innerStmts[0];
    if (!ts.isExpressionStatement(innerExprStmt)) {
        return null;
    }
    // Now we're in the IIFE and have the inner expression statement. We can check if it matches
    // a private Ivy call.
    const callExpr = innerExprStmt.expression;
    if (!ts.isCallExpression(callExpr)) {
        return false;
    }
    const propAccExpr = callExpr.expression;
    if (!ts.isPropertyAccessExpression(propAccExpr)) {
        return false;
    }
    if (propAccExpr.name.text !== 'ɵsetClassMetadata') {
        return false;
    }
    return true;
}
// Remove Angular decorators from`Clazz.decorators = [...];`, or expression itself if all are
// removed.
function pickDecorationNodesToRemove(exprStmt, ngMetadata, checker) {
    const expr = expect(exprStmt.expression, ts.SyntaxKind.BinaryExpression);
    const literal = expect(expr.right, ts.SyntaxKind.ArrayLiteralExpression);
    if (!literal.elements.every(elem => ts.isObjectLiteralExpression(elem))) {
        return [];
    }
    const elements = literal.elements;
    const ngDecorators = elements.filter((elem) => isAngularDecorator(elem, ngMetadata, checker));
    return (elements.length > ngDecorators.length) ? ngDecorators : [exprStmt];
}
// Remove Angular decorators from `Clazz = __decorate([...], Clazz)`, or expression itself if all
// are removed.
function pickDecorateNodesToRemove(exprStmt, tslibImports, ngMetadata, checker) {
    let callExpr;
    if (ts.isCallExpression(exprStmt.expression)) {
        callExpr = exprStmt.expression;
    }
    else if (ts.isBinaryExpression(exprStmt.expression)) {
        const expr = exprStmt.expression;
        if (ts.isCallExpression(expr.right)) {
            callExpr = expr.right;
        }
        else if (ts.isBinaryExpression(expr.right) && ts.isCallExpression(expr.right.right)) {
            callExpr = expr.right.right;
        }
    }
    if (!callExpr) {
        return [];
    }
    const arrLiteral = expect(callExpr.arguments[0], ts.SyntaxKind.ArrayLiteralExpression);
    if (!arrLiteral.elements.every((elem) => ts.isCallExpression(elem))) {
        return [];
    }
    const elements = arrLiteral.elements;
    const ngDecoratorCalls = elements.filter((el) => {
        if (!ts.isIdentifier(el.expression)) {
            return false;
        }
        return identifierIsMetadata(el.expression, ngMetadata, checker);
    });
    // Remove __metadata calls of type 'design:paramtypes'.
    const metadataCalls = elements.filter((el) => {
        if (!isTslibHelper(el, '__metadata', tslibImports, checker)) {
            return false;
        }
        if (el.arguments.length < 2 || !ts.isStringLiteral(el.arguments[0])) {
            return false;
        }
        return true;
    });
    // Remove all __param calls.
    const paramCalls = elements.filter((el) => {
        if (!isTslibHelper(el, '__param', tslibImports, checker)) {
            return false;
        }
        if (el.arguments.length !== 2 || !ts.isNumericLiteral(el.arguments[0])) {
            return false;
        }
        return true;
    });
    ngDecoratorCalls.push(...metadataCalls, ...paramCalls);
    // If all decorators are metadata decorators then return the whole `Class = __decorate([...])'`
    // statement so that it is removed in entirety
    return (elements.length === ngDecoratorCalls.length) ? [exprStmt] : ngDecoratorCalls;
}
// Remove Angular decorators from`Clazz.propDecorators = [...];`, or expression itself if all
// are removed.
function pickPropDecorationNodesToRemove(exprStmt, ngMetadata, checker) {
    const expr = expect(exprStmt.expression, ts.SyntaxKind.BinaryExpression);
    const literal = expect(expr.right, ts.SyntaxKind.ObjectLiteralExpression);
    if (!literal.properties.every(elem => ts.isPropertyAssignment(elem)
        && ts.isArrayLiteralExpression(elem.initializer))) {
        return [];
    }
    const assignments = literal.properties;
    // Consider each assignment individually. Either the whole assignment will be removed or
    // a particular decorator within will.
    const toRemove = assignments
        .map((assign) => {
        const decorators = expect(assign.initializer, ts.SyntaxKind.ArrayLiteralExpression).elements;
        if (!decorators.every((el) => ts.isObjectLiteralExpression(el))) {
            return [];
        }
        const decsToRemove = decorators.filter((expression) => {
            const lit = expect(expression, ts.SyntaxKind.ObjectLiteralExpression);
            return isAngularDecorator(lit, ngMetadata, checker);
        });
        if (decsToRemove.length === decorators.length) {
            return [assign];
        }
        return decsToRemove;
    })
        .reduce((accum, toRm) => accum.concat(toRm), []);
    // If every node to be removed is a property assignment (full property's decorators) and
    // all properties are accounted for, remove the whole assignment. Otherwise, remove the
    // nodes which were marked as safe.
    if (toRemove.length === assignments.length && toRemove.every((node) => ts.isPropertyAssignment(node))) {
        return [exprStmt];
    }
    return toRemove;
}
function isAngularDecorator(literal, ngMetadata, checker) {
    const types = literal.properties.filter(isTypeProperty);
    if (types.length !== 1) {
        return false;
    }
    const assign = expect(types[0], ts.SyntaxKind.PropertyAssignment);
    if (!ts.isIdentifier(assign.initializer)) {
        return false;
    }
    const id = assign.initializer;
    const res = identifierIsMetadata(id, ngMetadata, checker);
    return res;
}
function isTypeProperty(prop) {
    if (!ts.isPropertyAssignment(prop)) {
        return false;
    }
    if (!ts.isIdentifier(prop.name)) {
        return false;
    }
    return prop.name.text === 'type';
}
// Check if an identifier is part of the known Angular Metadata.
function identifierIsMetadata(id, metadata, checker) {
    const symbol = checker.getSymbolAtLocation(id);
    if (!symbol || !symbol.declarations || !symbol.declarations.length) {
        return false;
    }
    return symbol
        .declarations
        .some((spec) => metadata.includes(spec));
}
// Find all named imports for `tslib`.
function findTslibImports(node) {
    const imports = [];
    ts.forEachChild(node, child => {
        var _a;
        if (ts.isImportDeclaration(child) &&
            child.moduleSpecifier &&
            ts.isStringLiteral(child.moduleSpecifier) &&
            child.moduleSpecifier.text === 'tslib' &&
            !!((_a = child.importClause) === null || _a === void 0 ? void 0 : _a.namedBindings)) {
            imports.push(child.importClause.namedBindings);
        }
    });
    return imports;
}
// Check if a function call is a tslib helper.
function isTslibHelper(callExpr, helper, tslibImports, checker) {
    var _a;
    let identifier;
    if (ts.isPropertyAccessExpression(callExpr.expression)
        && callExpr.expression.name.text === helper
        && ts.isIdentifier(callExpr.expression.expression)) {
        // for namespaced imports
        identifier = callExpr.expression.expression;
    }
    else if (ts.isIdentifier(callExpr.expression) && callExpr.expression.text === helper) {
        // for named imports
        identifier = callExpr.expression;
    }
    else {
        return false;
    }
    const symbol = checker.getSymbolAtLocation(identifier);
    if (!((_a = symbol === null || symbol === void 0 ? void 0 : symbol.declarations) === null || _a === void 0 ? void 0 : _a.length)) {
        return false;
    }
    for (const name of tslibImports) {
        for (const dec of symbol.declarations) {
            if (ts.isNamedImports(name)) {
                if (ts.isImportSpecifier(dec) && name.elements.includes(dec)) {
                    return true;
                }
            }
            else if (dec === name) {
                // Namespace import
                return true;
            }
        }
    }
    return false;
}

@alan-agius4 alan-agius4 added freq1: low Only reported by a handful of users who observe it rarely severity5: regression area: devkit/build-optimizer labels Sep 4, 2020
@ngbot ngbot bot added this to the needsTriage milestone Sep 4, 2020
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Sep 4, 2020
@alan-agius4 alan-agius4 added needs: more info Reporter must clarify the issue and removed needs: repro steps We cannot reproduce the issue with the information given labels Sep 4, 2020
@eun-choi
Copy link
Author

eun-choi commented Sep 4, 2020

Trying with the changed function still doesn't work.

// Check if a function call is a tslib helper.
function isTslibHelper(callExpr, helper, tslibImports, checker) {
    var _a;
    let identifier;
    if (ts.isPropertyAccessExpression(callExpr.expression)
        && callExpr.expression.name.text === helper
        && ts.isIdentifier(callExpr.expression.expression)) {
        // for namespaced imports
        identifier = callExpr.expression.expression;
    }
    else if (ts.isIdentifier(callExpr.expression) && callExpr.expression.text === helper) {
        // for named imports
        identifier = callExpr.expression;
    }
    else {
        return false;
    }
    const symbol = checker.getSymbolAtLocation(identifier);
    if (!((_a = symbol === null || symbol === void 0 ? void 0 : symbol.declarations) === null || _a === void 0 ? void 0 : _a.length)) {
        return false;
    }
    for (const name of tslibImports) {
        for (const dec of symbol.declarations) {
            if (ts.isNamedImports(name)) {
                if (ts.isImportSpecifier(dec) && name.elements.includes(dec)) {
                    return true;
                }
            }
            else if (dec === name) {
                // Namespace import
                return true;
            }
        }
    }
    return false;
}

Fortunately, I found libraries with problems.
(Of the 50+ libraries I used, only 2 had problems.)

  "dependencies": {
    "@admob-plus/ionic": "^0.19.8",
    "kakao-sdk": "^3.1.0",
  },

I created a git page related to this.
https://github.com/eun-choi/ng-repro-app

Steps to reproduce:

  1. Clone git
  2. npm install
  3. ng serve --prod
  4. A problem occurred (Only a black screen is displayed)
  5. Modify scrub-file.js to next.7 version code
  6. ng serve --prod
  7. Print a sample screen (4 social login buttons)

@alan-agius4 alan-agius4 removed the needs: more info Reporter must clarify the issue label Sep 4, 2020
@alan-agius4
Copy link
Collaborator

Thanks for the reproduction. That should help us track down the problem much quicker.

@alan-agius4 alan-agius4 self-assigned this Sep 4, 2020
@alan-agius4
Copy link
Collaborator

By the way @eun-choi, I noticed you a Rio Ed that you tried to change the function. That alone is not enough you need to overwrite the file.

Anyways, I’ll take at the repro in the next couple of days.

@eun-choi
Copy link
Author

eun-choi commented Sep 4, 2020

Tested by overwriting the file, it was the same.

@alan-agius4
Copy link
Collaborator

HI, I started looking and there are a multiple reasons why using kakao-sdk is causing the failure.

  • You are using a JIT version of the library import { KakaoCordovaSDK } from 'kakao-sdk/ngx'; the AOT version is located at import { KakaoCordovaSDK } from 'kakao-sdk'; (Note: shipping both AOT and JIT is not needed, AOT compiled libraries can work in JIT mode as well.)
  • This Angular library package format doesn't conform with APF. The Angular package format is a spec used by library developers to package a valid Angular libraries. (See: https://github.com/ng-packagr/ng-packagr and https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs/preview)
  • tslib helpers are being inlined. This is discouraged as it will increase the application bundle size.

I will try to land a fix for this soon, however I strongly suggest that you reach out the library author to address the mentioned issues.

@eun-choi
Copy link
Author

eun-choi commented Sep 7, 2020

@alan-agius4 Okay. I'll share this issue with the developers of the 2 libraries. (kakao-sdk & @admob-plus/ionic)

alan-agius4 added a commit that referenced this issue Sep 8, 2020
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Oct 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: devkit/build-optimizer freq1: low Only reported by a handful of users who observe it rarely severity5: regression type: bug/fix
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants