Skip to content

Commit 6f96764

Browse files
committed
feat: add support for @angular/service-worker and manifest generation
Adds the flag 'serviceWorker' to angular-cli.json that enables support for @angular/service-worker. When this flag is true, production builds will be set up with a service worker. A ngsw-manifest.json file will be generated (or augmented) in the dist/ root, and the service worker script will be copied there. A short script will be added to index.html to register the service worker. @angular/service-worker is a dependency of @angular/cli, but not of generated projects. It is desirable for users to be able to update the version of @angular/service-worker used in their apps independently of the CLI version. Thus, the CLI will error if serviceWorker=true but @angular/service-worker is not installed in the application's node_modules, as it pulls all the service worker scripts from there. If the flag is false the effect on the CLI is minimal - the webpack plugins associated with the SW are not even require()'d.
1 parent e92ab41 commit 6f96764

File tree

12 files changed

+276
-176
lines changed

12 files changed

+276
-176
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@angular/compiler": "^2.3.1",
4343
"@angular/compiler-cli": "^2.3.1",
4444
"@angular/core": "^2.3.1",
45+
"@angular/service-worker": "1.0.0-beta.3",
4546
"@angular/tsc-wrapped": "^0.5.0",
4647
"async": "^2.1.4",
4748
"autoprefixer": "^6.5.3",

packages/@angular/cli/blueprints/ng2/files/angular-cli.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"index": "index.html",
1616
"main": "main.ts",
1717
"polyfills": "polyfills.ts",
18+
"serviceWorker": false,
1819
"test": "test.ts",
1920
"tsconfig": "tsconfig.json",
2021
"prefix": "<%= prefix %>",

packages/@angular/cli/lib/config/schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"prefix": {
6868
"type": "string"
6969
},
70-
"mobile": {
70+
"serviceWorker": {
71+
"description": "Experimental support for a service worker from @angular/service-worker.",
7172
"type": "boolean",
7273
"default": false
7374
},

packages/@angular/cli/models/webpack-configs/production.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import * as path from 'path';
22
import * as webpack from 'webpack';
3+
import * as fs from 'fs';
4+
import { stripIndent } from 'common-tags';
5+
import { StaticAssetPlugin } from '../../plugins/static-asset';
6+
import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin';
37
import { CompressionPlugin } from '../../lib/webpack/compression-plugin';
48
import { WebpackConfigOptions } from '../webpack-config';
59

@@ -8,7 +12,61 @@ export const getProdConfig = function (wco: WebpackConfigOptions) {
812
const { projectRoot, buildOptions, appConfig } = wco;
913
const appRoot = path.resolve(projectRoot, appConfig.root);
1014

15+
let extraPlugins: any[] = [];
16+
let entryPoints: {[key: string]: string[]} = {};
17+
18+
if (appConfig.serviceWorker) {
19+
const nodeModules = path.resolve(projectRoot, 'node_modules');
20+
const swModule = path.resolve(nodeModules, '@angular/service-worker');
21+
22+
// @angular/service-worker is required to be installed when serviceWorker is true.
23+
if (!fs.existsSync(swModule)) {
24+
throw new Error(stripIndent`
25+
Your project is configured with serviceWorker = true, but @angular/service-worker
26+
is not installed. Run \`npm install --save-dev @angular/service-worker\`
27+
and try again, or run \`ng set apps.0.serviceWorker=false\` in your angular-cli.json.
28+
`);
29+
}
30+
31+
// Path to the worker script itself.
32+
const workerPath = path.resolve(swModule, 'bundles/worker-basic.min.js');
33+
34+
// Path to a small script to register a service worker.
35+
const registerPath = path.resolve(swModule, 'build/assets/register-basic.min.js');
36+
37+
// Sanity check - both of these files should be present in @angular/service-worker.
38+
if (!fs.existsSync(workerPath) || !fs.existsSync(registerPath)) {
39+
throw new Error(stripIndent`
40+
The installed version of @angular/service-worker isn't supported by the CLI.
41+
Please install a supported version. The following files should exist:
42+
- ${registerPath}
43+
- ${workerPath}
44+
`);
45+
}
46+
47+
extraPlugins.push(new GlobCopyWebpackPlugin({
48+
patterns: ['ngsw-manifest.json'],
49+
globOptions: {
50+
optional: true,
51+
},
52+
}));
53+
54+
// Load the Webpack plugin for manifest generation and install it.
55+
const AngularServiceWorkerPlugin = require('@angular/service-worker/build/webpack')
56+
.AngularServiceWorkerPlugin;
57+
extraPlugins.push(new AngularServiceWorkerPlugin());
58+
59+
// Copy the worker script into assets.
60+
const workerContents = fs.readFileSync(workerPath).toString();
61+
extraPlugins.push(new StaticAssetPlugin('worker-basic.min.js', workerContents));
62+
63+
// Add a script to index.html that registers the service worker.
64+
// TODO(alxhub): inline this script somehow.
65+
entryPoints['sw-register'] = [registerPath];
66+
}
67+
1168
return {
69+
entry: entryPoints,
1270
plugins: [
1371
new webpack.DefinePlugin({
1472
'process.env.NODE_ENV': JSON.stringify('production')
@@ -24,6 +82,6 @@ export const getProdConfig = function (wco: WebpackConfigOptions) {
2482
test: /\.js$|\.html$|\.css$/,
2583
threshold: 10240
2684
})
27-
]
85+
].concat(extraPlugins)
2886
};
2987
};

packages/@angular/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"homepage": "https://github.com/angular/angular-cli",
2828
"dependencies": {
2929
"@ngtools/json-schema": "^1.0.0",
30+
"@angular/service-worker": "1.0.0-beta.3",
3031
"@ngtools/webpack": "^1.2.3",
3132
"async": "^2.1.4",
3233
"autoprefixer": "^6.5.3",

packages/@angular/cli/plugins/glob-copy-webpack-plugin.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ import * as denodeify from 'denodeify';
66
const globPromise = <any>denodeify(glob);
77
const statPromise = <any>denodeify(fs.stat);
88

9+
function isDirectory(path: string) {
10+
try {
11+
return fs.statSync(path).isDirectory();
12+
} catch (_) {
13+
return false;
14+
}
15+
}
16+
917
export interface GlobCopyWebpackPluginOptions {
1018
patterns: string[];
1119
globOptions: any;
@@ -17,9 +25,10 @@ export class GlobCopyWebpackPlugin {
1725
apply(compiler: any): void {
1826
let { patterns, globOptions } = this.options;
1927
let context = globOptions.cwd || compiler.options.context;
28+
let optional = !!globOptions.optional;
2029

2130
// convert dir patterns to globs
22-
patterns = patterns.map(pattern => fs.statSync(path.resolve(context, pattern)).isDirectory()
31+
patterns = patterns.map(pattern => isDirectory(path.resolve(context, pattern))
2332
? pattern += '/**/*'
2433
: pattern
2534
);
@@ -37,7 +46,8 @@ export class GlobCopyWebpackPlugin {
3746
.then((stat: any) => compilation.assets[relPath] = {
3847
size: () => stat.size,
3948
source: () => fs.readFileSync(path.resolve(context, relPath))
40-
});
49+
})
50+
.catch((err: any) => optional ? Promise.resolve() : Promise.reject(err));
4151

4252
Promise.all(globs)
4353
// flatten results
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as fs from 'fs';
2+
3+
export class StaticAssetPlugin {
4+
5+
constructor(private name: string, private contents: string) {}
6+
7+
apply(compiler: any): void {
8+
compiler.plugin('emit', (compilation: any, cb: Function) => {
9+
compilation.assets[this.name] = {
10+
size: () => this.contents.length,
11+
source: () => this.contents,
12+
};
13+
cb();
14+
});
15+
}
16+
}

packages/@angular/cli/utilities/package-chunk-sort.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ExtraEntry, extraEntryParser } from '../models/webpack-configs/utils';
33
// Sort chunks according to a predefined order:
44
// inline, polyfills, all scripts, all styles, vendor, main
55
export function packageChunkSort(appConfig: any) {
6-
let entryPoints = ['inline', 'polyfills'];
6+
let entryPoints = ['inline', 'polyfills', 'sw-register'];
77

88
const pushExtraEntries = (extraEntry: ExtraEntry) => {
99
if (entryPoints.indexOf(extraEntry.entry) === -1) {

packages/@ngtools/webpack/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"npm": ">= 3.0.0"
2626
},
2727
"dependencies": {
28+
"@angular/service-worker": "1.0.0-beta.3",
2829
"enhanced-resolve": "^2.3.0",
2930
"loader-utils": "^0.2.16",
3031
"magic-string": "^0.16.0",

scripts/publish/validate_dependencies.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const NODE_PACKAGES = [
2525
const ANGULAR_PACKAGES = [
2626
'@angular/compiler',
2727
'@angular/compiler-cli',
28-
'@angular/core'
28+
'@angular/core',
29+
'@angular/service-worker'
2930
];
3031

3132

0 commit comments

Comments
 (0)