Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/schematics/angular/private/standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,15 @@ 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,
filePath: string,
functionName: string,
importPath: string,
args: ts.Expression[] = [],
) {
): string {
const sourceFile = createSourceFile(tree, filePath);
const bootstrapCall = findBootstrapApplicationCall(sourceFile);
const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {
Expand Down Expand Up @@ -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.
Expand All @@ -208,7 +209,7 @@ export function addFunctionalProvidersToStandaloneBootstrap(
addImports(sourceFile, recorder);
tree.commitUpdate(recorder);

return;
return filePath;
}

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

tree.commitUpdate(recorder);

return configFilePath;
}

/** Finds the call to `bootstrapApplication` within a file. */
Expand Down
65 changes: 53 additions & 12 deletions packages/schematics/angular/service-worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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),
]);
};
}
Expand Down
53 changes: 53 additions & 0 deletions packages/schematics/angular/service-worker/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';`);
});
});
});