Skip to content

Commit 0692cac

Browse files
alan-agius4vikerman
authored andcommitted
feat(@schematics/angular): account for root level assets and resourcesOutputPath (#13074)
* feat(@schematics/angular): account for root level assets and `resourcesOutputPath` By default we are only account for assets inside the assets folder. Which breaks the offline experience. Fixes #13067 * test: add test to verify root level assets in service workers
1 parent f392f18 commit 0692cac

File tree

5 files changed

+68
-23
lines changed

5 files changed

+68
-23
lines changed

packages/angular_devkit/build_angular/test/browser/service-worker_spec_large.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ describe('Browser Builder service worker', () => {
2020
name: 'app',
2121
installMode: 'prefetch',
2222
resources: {
23-
files: ['/favicon.ico', '/index.html'],
24-
versionedFiles: [
23+
files: [
24+
'/favicon.ico',
25+
'/index.html',
2526
'/*.bundle.css',
2627
'/*.bundle.js',
2728
'/*.chunk.js',
@@ -33,7 +34,10 @@ describe('Browser Builder service worker', () => {
3334
installMode: 'lazy',
3435
updateMode: 'prefetch',
3536
resources: {
36-
files: ['/assets/**'],
37+
files: [
38+
'/assets/**',
39+
'/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)',
40+
],
3741
},
3842
},
3943
],
@@ -55,6 +59,7 @@ describe('Browser Builder service worker', () => {
5559
host.writeMultipleFiles({
5660
'src/ngsw-config.json': JSON.stringify(manifest),
5761
'src/assets/folder-asset.txt': 'folder-asset.txt',
62+
'src/styles.css': `body { background: url(./spectrum.png); }`,
5863
});
5964

6065
const overrides = { serviceWorker: true };
@@ -91,6 +96,7 @@ describe('Browser Builder service worker', () => {
9196
updateMode: 'prefetch',
9297
urls: [
9398
'/assets/folder-asset.txt',
99+
'/spectrum.png',
94100
],
95101
patterns: [],
96102
},
@@ -100,6 +106,7 @@ describe('Browser Builder service worker', () => {
100106
'/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01',
101107
'/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4',
102108
'/index.html': '843c96f0aeadc8f093b1b2203c08891ecd8f7425',
109+
'/spectrum.png': '8d048ece46c0f3af4b598a95fd8e4709b631c3c0',
103110
},
104111
});
105112
}),

packages/schematics/angular/service-worker/files/ngsw-config.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"updateMode": "prefetch",
1919
"resources": {
2020
"files": [
21-
"/assets/**"
21+
"/assets/**",
22+
"<%= resourcesOutputPath %>/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
2223
]
2324
}
2425
}

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

+34-18
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,41 @@ import { getWorkspace, updateWorkspace } from '../utility/config';
2626
import { addPackageJsonDependency, getPackageJsonDependency } from '../utility/dependencies';
2727
import { getAppModulePath } from '../utility/ng-ast-utils';
2828
import { getProjectTargets, targetBuildNotFoundError } from '../utility/project-targets';
29-
import { BrowserBuilderOptions, BrowserBuilderTarget } from '../utility/workspace-models';
29+
import {
30+
BrowserBuilderOptions,
31+
BrowserBuilderTarget,
32+
WorkspaceSchema,
33+
} from '../utility/workspace-models';
3034
import { Schema as ServiceWorkerOptions } from './schema';
3135

32-
function updateConfigFile(options: ServiceWorkerOptions): Rule {
33-
return (host: Tree, context: SchematicContext) => {
34-
context.logger.debug('updating config file.');
35-
const workspace = getWorkspace(host);
36+
function getProjectConfiguration(
37+
workspace: WorkspaceSchema,
38+
options: ServiceWorkerOptions,
39+
): BrowserBuilderOptions {
40+
const projectTargets = getProjectTargets(workspace, options.project);
41+
if (!projectTargets[options.target]) {
42+
throw new Error(`Target is not defined for this project.`);
43+
}
3644

37-
const projectTargets = getProjectTargets(workspace, options.project);
45+
const target = projectTargets[options.target] as BrowserBuilderTarget;
46+
let applyTo = target.options;
3847

39-
if (!projectTargets[options.target]) {
40-
throw new Error(`Target is not defined for this project.`);
41-
}
48+
if (options.configuration &&
49+
target.configurations &&
50+
target.configurations[options.configuration]) {
51+
applyTo = target.configurations[options.configuration] as BrowserBuilderOptions;
52+
}
4253

43-
const target = projectTargets[options.target] as BrowserBuilderTarget;
44-
let applyTo = target.options;
54+
return applyTo;
55+
}
4556

46-
if (options.configuration &&
47-
target.configurations &&
48-
target.configurations[options.configuration]) {
49-
applyTo = target.configurations[options.configuration] as BrowserBuilderOptions;
50-
}
57+
function updateConfigFile(options: ServiceWorkerOptions): Rule {
58+
return (host: Tree, context: SchematicContext) => {
59+
context.logger.debug('updating config file.');
60+
const workspace = getWorkspace(host);
5161

52-
applyTo.serviceWorker = true;
62+
const config = getProjectConfiguration(workspace, options);
63+
config.serviceWorker = true;
5364

5465
return updateWorkspace(workspace);
5566
};
@@ -160,8 +171,13 @@ export default function (options: ServiceWorkerOptions): Rule {
160171
throw new SchematicsException(`Service worker requires a project type of "application".`);
161172
}
162173

174+
let { resourcesOutputPath = '' } = getProjectConfiguration(workspace, options);
175+
if (resourcesOutputPath) {
176+
resourcesOutputPath = '/' + resourcesOutputPath.split('/').filter(x => !!x).join('/');
177+
}
178+
163179
const templateSource = apply(url('./files'), [
164-
template({...options}),
180+
template({ ...options, resourcesOutputPath }),
165181
move(project.root),
166182
]);
167183

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

+21-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('Service Worker Schematic', () => {
5555
});
5656

5757
it('should update the target options if no configuration is set', () => {
58-
const options = {...defaultOptions, configuration: ''};
58+
const options = { ...defaultOptions, configuration: '' };
5959
const tree = schematicRunner.runSchematic('service-worker', options, appTree);
6060
const configText = tree.readContent('/angular.json');
6161
const config = JSON.parse(configText);
@@ -97,4 +97,24 @@ describe('Service Worker Schematic', () => {
9797
const path = '/projects/bar/ngsw-config.json';
9898
expect(tree.exists(path)).toEqual(true);
9999
});
100+
101+
it('should add root assets RegExp', () => {
102+
const tree = schematicRunner.runSchematic('service-worker', defaultOptions, appTree);
103+
const pkgText = tree.readContent('/projects/bar/ngsw-config.json');
104+
const config = JSON.parse(pkgText);
105+
expect(config.assetGroups[1].resources.files)
106+
.toContain('/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)');
107+
});
108+
109+
it('should add resourcesOutputPath to root assets when specified', () => {
110+
const config = JSON.parse(appTree.readContent('/angular.json'));
111+
config.projects.bar.architect.build.configurations.production.resourcesOutputPath = 'outDir';
112+
appTree.overwrite('/angular.json', JSON.stringify(config));
113+
const tree = schematicRunner.runSchematic('service-worker', defaultOptions, appTree);
114+
const pkgText = tree.readContent('/projects/bar/ngsw-config.json');
115+
const ngswConfig = JSON.parse(pkgText);
116+
expect(ngswConfig.assetGroups[1].resources.files)
117+
.toContain('/outDir/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)');
118+
});
119+
100120
});

packages/schematics/angular/utility/workspace-models.ts

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface BrowserBuilderOptions extends BrowserBuilderBaseOptions {
4747
serviceWorker?: boolean;
4848
optimization?: boolean;
4949
outputHashing?: 'all';
50+
resourcesOutputPath?: string;
5051
extractCss?: boolean;
5152
namedChunks?: boolean;
5253
aot?: boolean;

0 commit comments

Comments
 (0)