Reputation: 7846
I'm using the latest versions of all Angular-related packages (so Angular 10).
I want to add some code to a component, but I only want this code to exist in dev, never in a production build. It needs to be completely stripped in prod builds. I found this comment, which indicates that environments do this automatically (because they're const
).
I tried using that exact code in my app, but the dev code is still there in a production build. I copied the code over to a new test app that I made with ng new
, and it does work properly there.
What things should I be looking for, how can I fix this? Is this possibly because I have CommonJS dependencies, and if so, can I do anything about that (since I can't remove those dependencies)?
Some notes:
environment
object is never written to anywhere in the codebase, I've searched thoroughly. (It's only used in a few places anyway.)if (false) { }
is properly stripped.environment{.prod}.ts
does not fix the problem.Here's environment.prod.ts
(environment.ts
is the same, just with false
instead of true
):
export const environment = {
production: true
};
export * from './services/services';
Here's the main.ts
that I'm testing with:
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));
Here's the relevant output code after running 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))
Here's the relevant part of 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": "./"
}
Here's 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
}
}
Here's 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"
}
}
Upvotes: 11
Views: 2342
Reputation: 7846
This question was answered by an Angular team member here on GitHub. The answer is that this is a Webpack issue--if the environment file is imported into multiple output files, then Webpack is unable to optimize it properly. I've pasted the full response below for posterity.
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 }; },
Upvotes: 2
Reputation: 802
as we know environment.ts file will get replace by environment.prod.ts file during prod build.you have written if else statements in the app.component.ts condition these condition will be evaluated during runtime & will not tree shakes.
I would like to suggest one alternate-native approach.Create two library projects called lib-dev & lib-prod.
use ng g library lib-prod
& ng g library lib-dev
to create library project.
create required module, components & services inside the library project.make sure component selector, module & services name should be same in both library projects.
name in package.json of lib-prod & lib-dev should be same.
{
"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/*"
]
}
In your app.module.ts use compiled library project.
import { MyLibModule } from "dist/my-lib";
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
.....
MyLibModule
],
providers: [
],
bootstrap: [AppComponent]
})
export class AppModule { }
package.json of main app
{
"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 "
},
For dev,
npm run start
command will first compile lib-dev
library project & then runs ng serve
.
it will use compiled lib-dev
in main app.
For prod,
npm run build:prod
command will first compile lib-prod
library project & then runs ng build --prod
.
Upvotes: 0
Reputation: 4472
I don't know what is wrong with your environment, but it seems that you don't need to do anything and that production build takes care of this.
For example I tested having a component with this code:
@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');
}
}
Then I ran ng build --prod
. This is how the constructor of the component was emitted uglified in output code:
{class t{constructor(){this.title="test1",console.log("A"),console.log("B"),console.log("D"),console.log("F"),console.log("G")}}
Note that the if conditions and console.log('C') and console.log('E') are not in the output.
And this is how it was emitted in the es5 output:
(Wu=function n(){v(this,n),this.title="test1",console.log("A"),console.log("B"),console.log("D"),console.log("F"),console.log("G")})
Again the if conditions and console.log('C') and console.log('E')
So just building with --prod flag will solve it unless something wrong in your environment.
Upvotes: 0
Reputation: 2346
The post that you linked to specifically states that the tree-shaking occurs for 'Code gated by constants in if statements' . So you may need to alter your if statement to:
if (environment.production===true) {
console.warn('this is a prod build');
enableProdMode();
}
else
{
console.warn('this is a dev build');
}
to introduce the presence of a constant.
Upvotes: 1
Reputation: 1151
You could apply the same logic as environment.ts
; create main.prod.ts
(without the dev specific code) and main.dev.ts
(with dev specific code), then use fileReplacements
in your config.
The config for prod would be:
"fileReplacements": [
...
{
"replace": "src/main.ts",
"with": "src/main.prod.ts"
}
Upvotes: 6