Skip to content

Commit c2d2da4

Browse files
alan-agius4angular-robot[bot]
authored andcommitted
feat(@schematics/angular): add support to add service worker to standalone application
This commit adds support to generate a service worker in a standalone application.
1 parent 584b519 commit c2d2da4

File tree

3 files changed

+112
-15
lines changed

3 files changed

+112
-15
lines changed

packages/schematics/angular/private/standalone.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,15 @@ export function addModuleImportToStandaloneBootstrap(
163163
* @param functionName Name of the function that should be called.
164164
* @param importPath Path from which to import the function.
165165
* @param args Arguments to use when calling the function.
166+
* @returns The file path that the provider was added to.
166167
*/
167168
export function addFunctionalProvidersToStandaloneBootstrap(
168169
tree: Tree,
169170
filePath: string,
170171
functionName: string,
171172
importPath: string,
172173
args: ts.Expression[] = [],
173-
) {
174+
): string {
174175
const sourceFile = createSourceFile(tree, filePath);
175176
const bootstrapCall = findBootstrapApplicationCall(sourceFile);
176177
const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {
@@ -198,7 +199,7 @@ export function addFunctionalProvidersToStandaloneBootstrap(
198199
addImports(sourceFile, recorder);
199200
tree.commitUpdate(recorder);
200201

201-
return;
202+
return filePath;
202203
}
203204

204205
// If the config is a `mergeApplicationProviders` call, add another config to it.
@@ -208,7 +209,7 @@ export function addFunctionalProvidersToStandaloneBootstrap(
208209
addImports(sourceFile, recorder);
209210
tree.commitUpdate(recorder);
210211

211-
return;
212+
return filePath;
212213
}
213214

214215
// Otherwise attempt to merge into the current config.
@@ -235,6 +236,8 @@ export function addFunctionalProvidersToStandaloneBootstrap(
235236
}
236237

237238
tree.commitUpdate(recorder);
239+
240+
return configFilePath;
238241
}
239242

240243
/** Finds the call to `bootstrapApplication` within a file. */

packages/schematics/angular/service-worker/index.ts

+53-12
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ import {
2020
url,
2121
} from '@angular-devkit/schematics';
2222
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
23+
import { addFunctionalProvidersToStandaloneBootstrap } from '../private/standalone';
2324
import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript';
2425
import { readWorkspace, writeWorkspace } from '../utility';
2526
import { addSymbolToNgModuleMetadata, insertImport } from '../utility/ast-utils';
2627
import { applyToUpdateRecorder } from '../utility/change';
2728
import { addPackageJsonDependency, getPackageJsonDependency } from '../utility/dependencies';
28-
import { getAppModulePath } from '../utility/ng-ast-utils';
29+
import { getAppModulePath, isStandaloneApp } from '../utility/ng-ast-utils';
2930
import { relativePathToWorkspaceRoot } from '../utility/paths';
3031
import { targetBuildNotFoundError } from '../utility/project-targets';
3132
import { BrowserBuilderOptions } from '../utility/workspace-models';
@@ -85,6 +86,44 @@ function updateAppModule(mainPath: string): Rule {
8586
};
8687
}
8788

89+
function addProvideServiceWorker(mainPath: string): Rule {
90+
return (host: Tree) => {
91+
const updatedFilePath = addFunctionalProvidersToStandaloneBootstrap(
92+
host,
93+
mainPath,
94+
'provideServiceWorker',
95+
'@angular/service-worker',
96+
[
97+
ts.factory.createStringLiteral('ngsw-worker.js', true),
98+
ts.factory.createObjectLiteralExpression(
99+
[
100+
ts.factory.createPropertyAssignment(
101+
ts.factory.createIdentifier('enabled'),
102+
ts.factory.createPrefixUnaryExpression(
103+
ts.SyntaxKind.ExclamationToken,
104+
ts.factory.createCallExpression(
105+
ts.factory.createIdentifier('isDevMode'),
106+
undefined,
107+
[],
108+
),
109+
),
110+
),
111+
ts.factory.createPropertyAssignment(
112+
ts.factory.createIdentifier('registrationStrategy'),
113+
ts.factory.createStringLiteral('registerWhenStable:30000', true),
114+
),
115+
],
116+
true,
117+
),
118+
],
119+
);
120+
121+
addImport(host, updatedFilePath, 'isDevMode', '@angular/core');
122+
123+
return host;
124+
};
125+
}
126+
88127
function getTsSourceFile(host: Tree, path: string): ts.SourceFile {
89128
const content = host.readText(path);
90129
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
@@ -116,23 +155,25 @@ export default function (options: ServiceWorkerOptions): Rule {
116155
resourcesOutputPath = normalize(`/${resourcesOutputPath}`);
117156
}
118157

119-
const templateSource = apply(url('./files'), [
120-
applyTemplates({
121-
...options,
122-
resourcesOutputPath,
123-
relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(project.root),
124-
}),
125-
move(project.root),
126-
]);
127-
128158
context.addTask(new NodePackageInstallTask());
129159

130160
await writeWorkspace(host, workspace);
131161

162+
const { main } = buildOptions;
163+
132164
return chain([
133-
mergeWith(templateSource),
165+
mergeWith(
166+
apply(url('./files'), [
167+
applyTemplates({
168+
...options,
169+
resourcesOutputPath,
170+
relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(project.root),
171+
}),
172+
move(project.root),
173+
]),
174+
),
134175
addDependencies(),
135-
updateAppModule(buildOptions.main),
176+
isStandaloneApp(host, main) ? addProvideServiceWorker(main) : updateAppModule(main),
136177
]);
137178
};
138179
}

packages/schematics/angular/service-worker/index_spec.ts

+53
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import { tags } from '@angular-devkit/core';
910
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
1011
import { Schema as ApplicationOptions } from '../application/schema';
1112
import { Schema as WorkspaceOptions } from '../workspace/schema';
@@ -164,4 +165,56 @@ describe('Service Worker Schematic', () => {
164165
const { projects } = JSON.parse(tree.readContent('/angular.json'));
165166
expect(projects.foo.architect.build.options.ngswConfigPath).toBe('ngsw-config.json');
166167
});
168+
169+
describe('standalone', () => {
170+
const name = 'buz';
171+
const standaloneAppOptions: ApplicationOptions = {
172+
...appOptions,
173+
name,
174+
standalone: true,
175+
};
176+
const standaloneSWOptions: ServiceWorkerOptions = {
177+
...defaultOptions,
178+
project: name,
179+
};
180+
181+
beforeEach(async () => {
182+
appTree = await schematicRunner.runSchematic('application', standaloneAppOptions, appTree);
183+
});
184+
185+
it(`should add the 'provideServiceWorker' to providers`, async () => {
186+
const tree = await schematicRunner.runSchematic(
187+
'service-worker',
188+
standaloneSWOptions,
189+
appTree,
190+
);
191+
const content = tree.readContent('/projects/buz/src/app/app.config.ts');
192+
expect(tags.oneLine`${content}`).toContain(tags.oneLine`
193+
providers: [provideServiceWorker('ngsw-worker.js', {
194+
enabled: !isDevMode(),
195+
registrationStrategy: 'registerWhenStable:30000'
196+
})]
197+
`);
198+
});
199+
200+
it(`should import 'isDevMode' from '@angular/core'`, async () => {
201+
const tree = await schematicRunner.runSchematic(
202+
'service-worker',
203+
standaloneSWOptions,
204+
appTree,
205+
);
206+
const content = tree.readContent('/projects/buz/src/app/app.config.ts');
207+
expect(content).toContain(`import { ApplicationConfig, isDevMode } from '@angular/core';`);
208+
});
209+
210+
it(`should import 'provideServiceWorker' from '@angular/service-worker'`, async () => {
211+
const tree = await schematicRunner.runSchematic(
212+
'service-worker',
213+
standaloneSWOptions,
214+
appTree,
215+
);
216+
const content = tree.readContent('/projects/buz/src/app/app.config.ts');
217+
expect(content).toContain(`import { provideServiceWorker } from '@angular/service-worker';`);
218+
});
219+
});
167220
});

0 commit comments

Comments
 (0)