UNPKG

6.82 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright Google LLC All Rights Reserved.
5 *
6 * Use of this source code is governed by an MIT-style license that can be
7 * found in the LICENSE file at https://angular.io/license
8 */
9Object.defineProperty(exports, "__esModule", { value: true });
10const schematics_1 = require("@angular-devkit/schematics");
11const utility_1 = require("@schematics/angular/utility");
12const path_1 = require("path");
13const stream_1 = require("stream");
14function updateIndexFile(path) {
15 return async (host) => {
16 const buffer = host.read(path);
17 if (buffer === null) {
18 throw new schematics_1.SchematicsException(`Could not read index file: ${path}`);
19 }
20 const { RewritingStream } = await loadEsmModule('parse5-html-rewriting-stream');
21 const rewriter = new RewritingStream();
22 let needsNoScript = true;
23 rewriter.on('startTag', (startTag) => {
24 if (startTag.tagName === 'noscript') {
25 needsNoScript = false;
26 }
27 rewriter.emitStartTag(startTag);
28 });
29 rewriter.on('endTag', (endTag) => {
30 if (endTag.tagName === 'head') {
31 rewriter.emitRaw(' <link rel="manifest" href="manifest.webmanifest">\n');
32 rewriter.emitRaw(' <meta name="theme-color" content="#1976d2">\n');
33 }
34 else if (endTag.tagName === 'body' && needsNoScript) {
35 rewriter.emitRaw(' <noscript>Please enable JavaScript to continue using this application.</noscript>\n');
36 }
37 rewriter.emitEndTag(endTag);
38 });
39 return new Promise((resolve) => {
40 const input = new stream_1.Readable({
41 encoding: 'utf8',
42 read() {
43 this.push(buffer);
44 this.push(null);
45 },
46 });
47 const chunks = [];
48 const output = new stream_1.Writable({
49 write(chunk, encoding, callback) {
50 chunks.push(typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk);
51 callback();
52 },
53 final(callback) {
54 const full = Buffer.concat(chunks);
55 host.overwrite(path, full.toString());
56 callback();
57 resolve();
58 },
59 });
60 input.pipe(rewriter).pipe(output);
61 });
62 };
63}
64function default_1(options) {
65 return async (host) => {
66 if (!options.title) {
67 options.title = options.project;
68 }
69 const workspace = await (0, utility_1.readWorkspace)(host);
70 if (!options.project) {
71 throw new schematics_1.SchematicsException('Option "project" is required.');
72 }
73 const project = workspace.projects.get(options.project);
74 if (!project) {
75 throw new schematics_1.SchematicsException(`Project is not defined in this workspace.`);
76 }
77 if (project.extensions['projectType'] !== 'application') {
78 throw new schematics_1.SchematicsException(`PWA requires a project type of "application".`);
79 }
80 // Find all the relevant targets for the project
81 if (project.targets.size === 0) {
82 throw new schematics_1.SchematicsException(`Targets are not defined for this project.`);
83 }
84 const buildTargets = [];
85 const testTargets = [];
86 for (const target of project.targets.values()) {
87 if (target.builder === '@angular-devkit/build-angular:browser' ||
88 target.builder === '@angular-devkit/build-angular:application') {
89 buildTargets.push(target);
90 }
91 else if (target.builder === '@angular-devkit/build-angular:karma') {
92 testTargets.push(target);
93 }
94 }
95 // Add manifest to asset configuration
96 const assetEntry = path_1.posix.join(project.sourceRoot ?? path_1.posix.join(project.root, 'src'), 'manifest.webmanifest');
97 for (const target of [...buildTargets, ...testTargets]) {
98 if (target.options) {
99 if (Array.isArray(target.options.assets)) {
100 target.options.assets.push(assetEntry);
101 }
102 else {
103 target.options.assets = [assetEntry];
104 }
105 }
106 else {
107 target.options = { assets: [assetEntry] };
108 }
109 }
110 // Find all index.html files in build targets
111 const indexFiles = new Set();
112 for (const target of buildTargets) {
113 if (typeof target.options?.index === 'string') {
114 indexFiles.add(target.options.index);
115 }
116 if (!target.configurations) {
117 continue;
118 }
119 for (const options of Object.values(target.configurations)) {
120 if (typeof options?.index === 'string') {
121 indexFiles.add(options.index);
122 }
123 }
124 }
125 // Setup sources for the assets files to add to the project
126 const sourcePath = project.sourceRoot ?? path_1.posix.join(project.root, 'src');
127 // Setup service worker schematic options
128 const { title, ...swOptions } = options;
129 await (0, utility_1.writeWorkspace)(host, workspace);
130 return (0, schematics_1.chain)([
131 (0, schematics_1.externalSchematic)('@schematics/angular', 'service-worker', swOptions),
132 (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('./files/root'), [(0, schematics_1.template)({ ...options }), (0, schematics_1.move)(sourcePath)])),
133 (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('./files/assets'), [(0, schematics_1.move)(path_1.posix.join(sourcePath, 'assets'))])),
134 ...[...indexFiles].map((path) => updateIndexFile(path)),
135 ]);
136 };
137}
138exports.default = default_1;
139/**
140 * This uses a dynamic import to load a module which may be ESM.
141 * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
142 * will currently, unconditionally downlevel dynamic import into a require call.
143 * require calls cannot load ESM code and will result in a runtime error. To workaround
144 * this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
145 * Once TypeScript provides support for keeping the dynamic import this workaround can
146 * be dropped.
147 *
148 * @param modulePath The path of the module to load.
149 * @returns A Promise that resolves to the dynamically imported module.
150 */
151function loadEsmModule(modulePath) {
152 return new Function('modulePath', `return import(modulePath);`)(modulePath);
153}