diff --git a/packages/schematics/angular/service-worker/index.ts b/packages/schematics/angular/service-worker/index.ts index 3fc6ff4f5680..2d0442fd935e 100644 --- a/packages/schematics/angular/service-worker/index.ts +++ b/packages/schematics/angular/service-worker/index.ts @@ -20,7 +20,7 @@ import { } from '@angular-devkit/schematics'; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript'; -import { addSymbolToNgModuleMetadata, insertImport, isImported } from '../utility/ast-utils'; +import { addSymbolToNgModuleMetadata, getEnvironmentExportName, insertImport, isImported } from '../utility/ast-utils'; import { InsertChange } from '../utility/change'; import { addPackageJsonDependency, getPackageJsonDependency } from '../utility/dependencies'; import { getAppModulePath } from '../utility/ng-ast-utils'; @@ -71,10 +71,16 @@ function updateAppModule(mainPath: string): Rule { // add import for environments // import { environment } from '../environments/environment'; moduleSource = getTsSourceFile(host, modulePath); - importModule = 'environment'; + const environmentExportName = getEnvironmentExportName(moduleSource); + // if environemnt import already exists then use the found one + // otherwise use the default name + importModule = environmentExportName || 'environment'; // TODO: dynamically find environments relative path importPath = '../environments/environment'; - if (!isImported(moduleSource, importModule, importPath)) { + + if (!environmentExportName) { + // if environment import was not found then insert the new one + // with default path and default export name const change = insertImport(moduleSource, modulePath, importModule, importPath); if (change) { const recorder = host.beginUpdate(modulePath); @@ -85,7 +91,7 @@ function updateAppModule(mainPath: string): Rule { // register SW in app module const importText = - `ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })`; + `ServiceWorkerModule.register('ngsw-worker.js', { enabled: ${importModule}.production })`; moduleSource = getTsSourceFile(host, modulePath); const metadataChanges = addSymbolToNgModuleMetadata( moduleSource, modulePath, 'imports', importText); diff --git a/packages/schematics/angular/service-worker/index_spec.ts b/packages/schematics/angular/service-worker/index_spec.ts index a6240637b02f..501c943eb5a7 100644 --- a/packages/schematics/angular/service-worker/index_spec.ts +++ b/packages/schematics/angular/service-worker/index_spec.ts @@ -10,7 +10,7 @@ import { Schema as ApplicationOptions } from '../application/schema'; import { Schema as WorkspaceOptions } from '../workspace/schema'; import { Schema as ServiceWorkerOptions } from './schema'; - +// tslint:disable-next-line:no-big-function describe('Service Worker Schematic', () => { const schematicRunner = new SchematicTestRunner( '@schematics/angular', @@ -97,6 +97,64 @@ describe('Service Worker Schematic', () => { expect(pkgText).toContain(expectedText); }); + it('should add the SW import to the NgModule imports with aliased environment', async () => { + const moduleContent = ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + + import { AppComponent } from './app.component'; + import { environment as env } from '../environments/environment'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule + ], + bootstrap: [AppComponent] + }) + export class AppModule {} + `; + + appTree.overwrite('/projects/bar/src/app/app.module.ts', moduleContent); + + const tree = await schematicRunner.runSchematicAsync('service-worker', defaultOptions, appTree) + .toPromise(); + const pkgText = tree.readContent('/projects/bar/src/app/app.module.ts'); + const expectedText = 'ServiceWorkerModule.register(\'ngsw-worker.js\', { enabled: env.production })'; + expect(pkgText).toContain(expectedText); + }); + + it('should add the SW import to the NgModule imports with existing environment', async () => { + const moduleContent = ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + + import { AppComponent } from './app.component'; + import { environment } from '../environments/environment'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule + ], + bootstrap: [AppComponent] + }) + export class AppModule {} + `; + + appTree.overwrite('/projects/bar/src/app/app.module.ts', moduleContent); + + const tree = await schematicRunner.runSchematicAsync('service-worker', defaultOptions, appTree) + .toPromise(); + const pkgText = tree.readContent('/projects/bar/src/app/app.module.ts'); + const expectedText = 'ServiceWorkerModule.register(\'ngsw-worker.js\', { enabled: environment.production })'; + expect(pkgText).toContain(expectedText); + }); + it('should put the ngsw-config.json file in the project root', async () => { const tree = await schematicRunner.runSchematicAsync('service-worker', defaultOptions, appTree) .toPromise(); @@ -188,5 +246,4 @@ describe('Service Worker Schematic', () => { expect(projects.foo.architect.build.configurations.production.ngswConfigPath) .toBe('ngsw-config.json'); }); - }); diff --git a/packages/schematics/angular/utility/ast-utils.ts b/packages/schematics/angular/utility/ast-utils.ts index 833979609609..cfab119c6aa6 100644 --- a/packages/schematics/angular/utility/ast-utils.ts +++ b/packages/schematics/angular/utility/ast-utils.ts @@ -574,6 +574,50 @@ export function isImported(source: ts.SourceFile, return matchingNodes.length > 0; } +/** + * This function returns the name of the environment export + * whether this export is aliased or not. If the environment file + * is not imported, then it will return `null`. + */ +export function getEnvironmentExportName(source: ts.SourceFile): string | null { + // Initial value is `null` as we don't know yet if the user + // has imported `environment` into the root module or not. + let environmentExportName: string | null = null; + + const allNodes = getSourceNodes(source); + + allNodes + .filter(node => node.kind === ts.SyntaxKind.ImportDeclaration) + .filter( + (declaration: ts.ImportDeclaration) => + declaration.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral && + declaration.importClause !== undefined, + ) + .map((declaration: ts.ImportDeclaration) => + // If `importClause` property is defined then the first + // child will be `NamedImports` object (or `namedBindings`). + (declaration.importClause as ts.ImportClause).getChildAt(0), + ) + // Find those `NamedImports` object that contains `environment` keyword + // in its text. E.g. `{ environment as env }`. + .filter((namedImports: ts.NamedImports) => namedImports.getText().includes('environment')) + .forEach((namedImports: ts.NamedImports) => { + for (const specifier of namedImports.elements) { + // `propertyName` is defined if the specifier + // has an aliased import. + const name = specifier.propertyName || specifier.name; + + // Find specifier that contains `environment` keyword in its text. + // Whether it's `environment` or `environment as env`. + if (name.text.includes('environment')) { + environmentExportName = specifier.name.text; + } + } + }); + + return environmentExportName; +} + /** * Returns the RouterModule declaration from NgModule metadata, if any. */