Angular tree shaking 不是剥离开发代码,我应该寻找什么?

Angular tree shaking not stripping dev code, what things should I look for?

我使用的是所有 Angular 相关软件包的最新版本(所以 Angular 10)。

我想向组件添加一些代码,但我只希望此代码存在于开发中,而不是存在于生产构建中。它需要在产品构建中完全剥离。我发现 this comment,这表明环境会自动执行此操作(因为它们是 const)。

我尝试在我的应用程序中使用那个确切的代码,但开发代码仍然存在于生产版本中。我将代码复制到我用 ng new 制作的新测试应用程序中,它确实在那里正常工作。

我应该寻找什么东西,我该如何解决这个问题?这可能是因为我有 CommonJS 依赖项,如果是这样,我能做些什么吗(因为我无法删除这些依赖项)?

一些注意事项:


这里是environment.prod.tsenvironment.ts是一样的,只是用false代替了true):

export const environment = {
  production: true
};

export * from './services/services';

这是我正在测试的main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { environment } from 'environments/environment';
import { AppModule } from './app/app.module';

// tslint:disable:no-console

if (environment.production) {
  console.warn('this is a prod build');
  enableProdMode();
}

if (!environment.production) {
  console.warn('this is a dev build');
}

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));

下面是运行ng build -c my-prod-config后的相关输出代码:

o.X.production && (console.warn('this is a prod build'), Object(i.R) ()),
o.X.production || console.warn('this is a dev build'),
s.d().bootstrapModule(fi).catch (e=>console.error(e))

这是angular.json的相关部分:

"my-prod-config": {
  "optimization": true,
  "outputHashing": "all",
  "sourceMap": false,
  "extractCss": true,
  "namedChunks": false,
  "aot": true,
  "extractLicenses": true,
  "vendorChunk": false,
  "buildOptimizer": true,
  "stylePreprocessorOptions": {
    "includePaths": [
      "src/styles"
    ]
  },
  "fileReplacements": [
    {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.prod.ts"
    }
  ],
  "baseHref": "./"
}

这是tsconfig.base.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "downlevelIteration": true,
    "importHelpers": true,
    "module": "es2020",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "baseUrl": "src/",
    "experimentalDecorators": true,
    "allowJs": true,
    "target": "es2015",
    "lib": [
      "es2018",
      "dom"
    ],
    "paths": {
      "path1": [
        "app/modules/stripped-from-stack-overflow-example1"
      ],
      "path2": [
        "app/modules/stripped-from-stack-overflow-example2"
      ]
    }
  },
  "files": [
    "src/main.ts",
    "src/polyfills.ts"
  ],
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "strictTemplates": true,
    "strictInjectionParameters": true
  }
}

这里是package.json

{
  "name": "my-app",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "section stripped": "section stripped"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "10.0.8",
    "@angular/common": "10.0.8",
    "@angular/compiler": "10.0.8",
    "@angular/core": "10.0.8",
    "@angular/forms": "10.0.8",
    "@angular/platform-browser": "10.0.8",
    "@angular/platform-browser-dynamic": "10.0.8",
    "@angular/router": "10.0.8",
    "@ng-idle/core": "9.0.0-beta.1",
    "@ng-idle/keepalive": "9.0.0-beta.1",
    "@ngneat/until-destroy": "8.0.1",
    "angular-svg-icon": "10.0.0",
    "brace": "0.11.1",
    "caniuse-lite": "1.0.30001111",
    "chart.js": "2.9.3",
    "core-js": "3.6.5",
    "css-vars-ponyfill": "2.3.2",
    "detect-browser": "5.1.1",
    "element-closest-polyfill": "1.0.2",
    "file-saver": "2.0.2",
    "fomantic-ui": "2.8.6",
    "jsonexport": "3.0.1",
    "moment": "2.24.0",
    "ngx-drag-drop": "2.0.0",
    "rxjs": "6.6.2",
    "tslib": "^2.0.0",
    "typeface-roboto": "0.0.75",
    "uuid": "8.3.0",
    "zone.js": "0.10.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "0.1000.5",
    "@angular/cli": "10.0.5",
    "@angular/compiler-cli": "10.0.8",
    "@angular/language-service": "10.0.8",
    "@types/chart.js": "2.7.54",
    "@types/file-saver": "2.0.1",
    "@types/uuid": "8.0.1",
    "codelyzer": "^6.0.0",
    "rimraf": "3.0.2",
    "rxjs-tslint-rules": "4.34.0",
    "ts-node": "8.10.2",
    "tslint": "6.1.3",
    "tslint-angular": "3.0.2",
    "typescript": "3.9.7",
    "webpack-bundle-analyzer": "3.8.0"
  }
}

您可以应用与 environment.ts 相同的逻辑;创建 main.prod.ts(没有开发特定代码)和 main.dev.ts(带有开发特定代码),然后在您的配置中使用 fileReplacements

prod 的配置为:

 "fileReplacements": [
      ...
      {
        "replace": "src/main.ts",
        "with": "src/main.prod.ts"
      }

您链接到的 post 明确指出 tree-shaking 出现在 'Code gated by constants in if statements' 中。因此,您可能需要将 if 语句更改为:

if (environment.production===true) {
  console.warn('this is a prod build');
  enableProdMode();
}
else    
{
  console.warn('this is a dev build');
}

引入常量。

我不知道你的环境有什么问题,但似乎你不需要做任何事情,生产构建会处理这个问题。

例如,我用这个代码测试了一个组件:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'test1';

  constructor() {
    console.log('A');
    if (environment.production) {
      console.log('B');
    } else {
      console.log('C');
    }
    console.log('D');
    if (!environment.production) {
      console.log('E');
    } else {
      console.log('F');
    }
    console.log('G');
  }
}

那我运行ng build --prod。这就是组件的构造函数在输出代码中被丑化的方式:

{class t{constructor(){this.title="test1",console.log("A"),console.log("B"),console.log("D"),console.log("F"),console.log("G")}}

请注意,if 条件和 console.log('C') 和 console.log('E') 不在输出中。

这是它在 es5 输出中的发出方式:

(Wu=function n(){v(this,n),this.title="test1",console.log("A"),console.log("B"),console.log("D"),console.log("F"),console.log("G")})

再次使用 if 条件和 console.log('C') 和 console.log('E')

因此,除非您的环境出现问题,否则只需使用 --prod 标志进行构建即可解决问题。

据我们所知,environment.ts 文件将在生产期间被 environment.prod.ts 文件替换 build.you 在 app.component.ts 条件中编写了 if else 语句,这些条件将在期间进行评估运行时 & 不会摇树。

我想推荐一个 alternate-native approach.Create 两个名为 lib-dev 和 lib-prod 的图书馆项目。 使用 ng g library lib-prod & ng g library lib-dev 创建库项目。 在库中创建所需的模块、组件和服务 project.make 确保两个库项目中的组件选择器、模块和服务名称应该相同。

lib-prod 和 lib-dev 的 package.json 中的名称应该相同。

{
  "name": "my-lib",
  "version": "0.0.1",
  "peerDependencies": {
    "@angular/common": "^10.0.0",
    "@angular/core": "^10.0.0"
  }
}

tsconfig.json

   ....
    "paths": {
      "my-lib": [
        "dist/my-lib"
      ],
      "extension/*": [
        "dist/my-lib/*"
      ]
    }

在你的app.module.ts中使用已编译的库项目。

import { MyLibModule } from "dist/my-lib";

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    .....
    MyLibModule
  ],
  providers: [
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

package.json 主应用

{
  "name": "demandfarm-ngweb",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng build lib-dev && ng serve",
    ...
    "build:prod": "ng build lib-prod && ng build --prod "
  },

对于开发人员, npm run start 命令将首先编译 lib-dev 库项目,然后运行 ​​ng serve。 它将在主应用程序中使用已编译的 lib-dev

对于产品, npm run build:prod 命令将首先编译 lib-prod 库项目,然后运行 ​​ng build --prod.

Angular 团队成员 here on GitHub 回答了这个问题。答案是这是 Webpack 的问题——如果环境文件被导入到多个输出文件中,那么 Webpack 就无法对其进行适当的优化。我已经在下面粘贴了完整的回复以供后代使用。

Without a reproduction the definitive cause is hard to discern. However, a potential cause is the use of the environment JS module (environment.ts/environment.prod.ts) in more than one generated output file. This could be the case if the environment module is used in the main code and in the code for a lazy route. When this happens, Webpack cannot concatenate the environment module with the main module (as happens in a new project) because the environment module needs to be accessible to two different output modules. This then in turn prevents the optimizer from inlining the production property value since the environment object is now essentially an import from another module and not a local variable.

When this happens code similar to the following (which represents a separate Webpack module) should end up in the main output file for the application:

AytR: function (module, __webpack_exports__, __webpack_require__) {
  "use strict";
  __webpack_require__.d(__webpack_exports__, "a", function () {
    return environment;
  });
  const environment = { production: !0 };
},