mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-24 08:06:17 +08:00
140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import { Path, getSystemPath, normalize } from '@angular-devkit/core';
|
|
import type { Config, Filesystem } from '@angular/service-worker/config';
|
|
import * as crypto from 'crypto';
|
|
import { createReadStream, promises as fs, constants as fsConstants } from 'fs';
|
|
import * as path from 'path';
|
|
import { pipeline } from 'stream';
|
|
import { loadEsmModule } from './load-esm';
|
|
|
|
class CliFilesystem implements Filesystem {
|
|
constructor(private base: string) {}
|
|
|
|
list(dir: string): Promise<string[]> {
|
|
return this._recursiveList(this._resolve(dir), []);
|
|
}
|
|
|
|
read(file: string): Promise<string> {
|
|
return fs.readFile(this._resolve(file), 'utf-8');
|
|
}
|
|
|
|
hash(file: string): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
const hash = crypto.createHash('sha1').setEncoding('hex');
|
|
pipeline(createReadStream(this._resolve(file)), hash, (error) =>
|
|
error ? reject(error) : resolve(hash.read()),
|
|
);
|
|
});
|
|
}
|
|
|
|
write(file: string, content: string): Promise<void> {
|
|
return fs.writeFile(this._resolve(file), content);
|
|
}
|
|
|
|
private _resolve(file: string): string {
|
|
return path.join(this.base, file);
|
|
}
|
|
|
|
private async _recursiveList(dir: string, items: string[]): Promise<string[]> {
|
|
const subdirectories = [];
|
|
for await (const entry of await fs.opendir(dir)) {
|
|
if (entry.isFile()) {
|
|
// Uses posix paths since the service worker expects URLs
|
|
items.push('/' + path.relative(this.base, path.join(dir, entry.name)).replace(/\\/g, '/'));
|
|
} else if (entry.isDirectory()) {
|
|
subdirectories.push(path.join(dir, entry.name));
|
|
}
|
|
}
|
|
|
|
for (const subdirectory of subdirectories) {
|
|
await this._recursiveList(subdirectory, items);
|
|
}
|
|
|
|
return items;
|
|
}
|
|
}
|
|
|
|
export async function augmentAppWithServiceWorker(
|
|
appRoot: Path,
|
|
outputPath: Path,
|
|
baseHref: string,
|
|
ngswConfigPath?: string,
|
|
): Promise<void> {
|
|
const distPath = getSystemPath(normalize(outputPath));
|
|
|
|
// Determine the configuration file path
|
|
const configPath = ngswConfigPath
|
|
? getSystemPath(normalize(ngswConfigPath))
|
|
: path.join(getSystemPath(appRoot), 'ngsw-config.json');
|
|
|
|
// Read the configuration file
|
|
let config: Config | undefined;
|
|
try {
|
|
const configurationData = await fs.readFile(configPath, 'utf-8');
|
|
config = JSON.parse(configurationData) as Config;
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
throw new Error(
|
|
'Error: Expected to find an ngsw-config.json configuration file' +
|
|
` in the ${getSystemPath(appRoot)} folder. Either provide one or` +
|
|
' disable Service Worker in the angular.json configuration file.',
|
|
);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Load ESM `@angular/service-worker/config` using the TypeScript dynamic import workaround.
|
|
// Once TypeScript provides support for keeping the dynamic import this workaround can be
|
|
// changed to a direct dynamic import.
|
|
const GeneratorConstructor = (
|
|
await loadEsmModule<typeof import('@angular/service-worker/config')>(
|
|
'@angular/service-worker/config',
|
|
)
|
|
).Generator;
|
|
|
|
// Generate the manifest
|
|
const generator = new GeneratorConstructor(new CliFilesystem(distPath), baseHref);
|
|
const output = await generator.process(config);
|
|
|
|
// Write the manifest
|
|
const manifest = JSON.stringify(output, null, 2);
|
|
await fs.writeFile(path.join(distPath, 'ngsw.json'), manifest);
|
|
|
|
// Find the service worker package
|
|
const workerPath = require.resolve('@angular/service-worker/ngsw-worker.js');
|
|
|
|
// Write the worker code
|
|
await fs.copyFile(
|
|
workerPath,
|
|
path.join(distPath, 'ngsw-worker.js'),
|
|
fsConstants.COPYFILE_FICLONE,
|
|
);
|
|
|
|
// If present, write the safety worker code
|
|
const safetyPath = path.join(path.dirname(workerPath), 'safety-worker.js');
|
|
try {
|
|
await fs.copyFile(
|
|
safetyPath,
|
|
path.join(distPath, 'worker-basic.min.js'),
|
|
fsConstants.COPYFILE_FICLONE,
|
|
);
|
|
await fs.copyFile(
|
|
safetyPath,
|
|
path.join(distPath, 'safety-worker.js'),
|
|
fsConstants.COPYFILE_FICLONE,
|
|
);
|
|
} catch (error) {
|
|
if (error.code !== 'ENOENT') {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|