diff --git a/packages/schematics/angular/private/standalone.ts b/packages/schematics/angular/private/standalone.ts index e2df6ae8884b..a5aa5fad1421 100644 --- a/packages/schematics/angular/private/standalone.ts +++ b/packages/schematics/angular/private/standalone.ts @@ -163,6 +163,7 @@ export function addModuleImportToStandaloneBootstrap( * @param functionName Name of the function that should be called. * @param importPath Path from which to import the function. * @param args Arguments to use when calling the function. + * @returns The file path that the provider was added to. */ export function addFunctionalProvidersToStandaloneBootstrap( tree: Tree, @@ -170,7 +171,7 @@ export function addFunctionalProvidersToStandaloneBootstrap( functionName: string, importPath: string, args: ts.Expression[] = [], -) { +): string { const sourceFile = createSourceFile(tree, filePath); const bootstrapCall = findBootstrapApplicationCall(sourceFile); const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => { @@ -198,7 +199,7 @@ export function addFunctionalProvidersToStandaloneBootstrap( addImports(sourceFile, recorder); tree.commitUpdate(recorder); - return; + return filePath; } // If the config is a `mergeApplicationProviders` call, add another config to it. @@ -208,7 +209,7 @@ export function addFunctionalProvidersToStandaloneBootstrap( addImports(sourceFile, recorder); tree.commitUpdate(recorder); - return; + return filePath; } // Otherwise attempt to merge into the current config. @@ -235,6 +236,8 @@ export function addFunctionalProvidersToStandaloneBootstrap( } tree.commitUpdate(recorder); + + return configFilePath; } /** Finds the call to `bootstrapApplication` within a file. */ diff --git a/packages/schematics/angular/service-worker/index.ts b/packages/schematics/angular/service-worker/index.ts index 21dc767e7faf..9af5768f1b81 100644 --- a/packages/schematics/angular/service-worker/index.ts +++ b/packages/schematics/angular/service-worker/index.ts @@ -20,12 +20,13 @@ import { url, } from '@angular-devkit/schematics'; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; +import { addFunctionalProvidersToStandaloneBootstrap } from '../private/standalone'; import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript'; import { readWorkspace, writeWorkspace } from '../utility'; import { addSymbolToNgModuleMetadata, insertImport } from '../utility/ast-utils'; import { applyToUpdateRecorder } from '../utility/change'; import { addPackageJsonDependency, getPackageJsonDependency } from '../utility/dependencies'; -import { getAppModulePath } from '../utility/ng-ast-utils'; +import { getAppModulePath, isStandaloneApp } from '../utility/ng-ast-utils'; import { relativePathToWorkspaceRoot } from '../utility/paths'; import { targetBuildNotFoundError } from '../utility/project-targets'; import { BrowserBuilderOptions } from '../utility/workspace-models'; @@ -85,6 +86,44 @@ function updateAppModule(mainPath: string): Rule { }; } +function addProvideServiceWorker(mainPath: string): Rule { + return (host: Tree) => { + const updatedFilePath = addFunctionalProvidersToStandaloneBootstrap( + host, + mainPath, + 'provideServiceWorker', + '@angular/service-worker', + [ + ts.factory.createStringLiteral('ngsw-worker.js', true), + ts.factory.createObjectLiteralExpression( + [ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('enabled'), + ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + ts.factory.createCallExpression( + ts.factory.createIdentifier('isDevMode'), + undefined, + [], + ), + ), + ), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('registrationStrategy'), + ts.factory.createStringLiteral('registerWhenStable:30000', true), + ), + ], + true, + ), + ], + ); + + addImport(host, updatedFilePath, 'isDevMode', '@angular/core'); + + return host; + }; +} + function getTsSourceFile(host: Tree, path: string): ts.SourceFile { const content = host.readText(path); const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); @@ -116,23 +155,25 @@ export default function (options: ServiceWorkerOptions): Rule { resourcesOutputPath = normalize(`/${resourcesOutputPath}`); } - const templateSource = apply(url('./files'), [ - applyTemplates({ - ...options, - resourcesOutputPath, - relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(project.root), - }), - move(project.root), - ]); - context.addTask(new NodePackageInstallTask()); await writeWorkspace(host, workspace); + const { main } = buildOptions; + return chain([ - mergeWith(templateSource), + mergeWith( + apply(url('./files'), [ + applyTemplates({ + ...options, + resourcesOutputPath, + relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(project.root), + }), + move(project.root), + ]), + ), addDependencies(), - updateAppModule(buildOptions.main), + isStandaloneApp(host, main) ? addProvideServiceWorker(main) : updateAppModule(main), ]); }; } diff --git a/packages/schematics/angular/service-worker/index_spec.ts b/packages/schematics/angular/service-worker/index_spec.ts index 8c25e40fec73..63f962a4179f 100644 --- a/packages/schematics/angular/service-worker/index_spec.ts +++ b/packages/schematics/angular/service-worker/index_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import { tags } from '@angular-devkit/core'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { Schema as ApplicationOptions } from '../application/schema'; import { Schema as WorkspaceOptions } from '../workspace/schema'; @@ -164,4 +165,56 @@ describe('Service Worker Schematic', () => { const { projects } = JSON.parse(tree.readContent('/angular.json')); expect(projects.foo.architect.build.options.ngswConfigPath).toBe('ngsw-config.json'); }); + + describe('standalone', () => { + const name = 'buz'; + const standaloneAppOptions: ApplicationOptions = { + ...appOptions, + name, + standalone: true, + }; + const standaloneSWOptions: ServiceWorkerOptions = { + ...defaultOptions, + project: name, + }; + + beforeEach(async () => { + appTree = await schematicRunner.runSchematic('application', standaloneAppOptions, appTree); + }); + + it(`should add the 'provideServiceWorker' to providers`, async () => { + const tree = await schematicRunner.runSchematic( + 'service-worker', + standaloneSWOptions, + appTree, + ); + const content = tree.readContent('/projects/buz/src/app/app.config.ts'); + expect(tags.oneLine`${content}`).toContain(tags.oneLine` + providers: [provideServiceWorker('ngsw-worker.js', { + enabled: !isDevMode(), + registrationStrategy: 'registerWhenStable:30000' + })] + `); + }); + + it(`should import 'isDevMode' from '@angular/core'`, async () => { + const tree = await schematicRunner.runSchematic( + 'service-worker', + standaloneSWOptions, + appTree, + ); + const content = tree.readContent('/projects/buz/src/app/app.config.ts'); + expect(content).toContain(`import { ApplicationConfig, isDevMode } from '@angular/core';`); + }); + + it(`should import 'provideServiceWorker' from '@angular/service-worker'`, async () => { + const tree = await schematicRunner.runSchematic( + 'service-worker', + standaloneSWOptions, + appTree, + ); + const content = tree.readContent('/projects/buz/src/app/app.config.ts'); + expect(content).toContain(`import { provideServiceWorker } from '@angular/service-worker';`); + }); + }); });