55 * Use of this source code is governed by an MIT-style license that can be
66 * found in the LICENSE file at https://angular.io/license
77 */
8- import {
9- Path ,
10- dirname ,
11- getSystemPath ,
12- join ,
13- normalize ,
14- relative ,
15- tags ,
16- virtualFs ,
17- } from '@angular-devkit/core' ;
18- import { NodeJsSyncHost } from '@angular-devkit/core/node' ;
19- import {
20- Filesystem ,
21- Generator ,
22- } from '@angular/service-worker/config' ;
8+ import { Path , getSystemPath , normalize } from '@angular-devkit/core' ;
9+ import { Config , Filesystem , Generator } from '@angular/service-worker/config' ;
2310import * as crypto from 'crypto' ;
11+ import { constants as fsConstants , createReadStream , promises as fs } from 'fs' ;
12+ import * as path from 'path' ;
13+ import { pipeline } from 'stream' ;
2414
2515class CliFilesystem implements Filesystem {
26- constructor ( private _host : virtualFs . Host , private base : string ) { }
16+ constructor ( private base : string ) { }
2717
28- list ( path : string ) : Promise < string [ ] > {
29- return this . _recursiveList ( this . _resolve ( path ) , [ ] ) . catch ( ( ) => [ ] ) ;
18+ list ( dir : string ) : Promise < string [ ] > {
19+ return this . _recursiveList ( this . _resolve ( dir ) , [ ] ) ;
3020 }
3121
32- async read ( path : string ) : Promise < string > {
33- return virtualFs . fileBufferToString ( await this . _readIntoBuffer ( path ) ) ;
22+ read ( file : string ) : Promise < string > {
23+ return fs . readFile ( this . _resolve ( file ) , 'utf-8' ) ;
3424 }
3525
36- async hash ( path : string ) : Promise < string > {
37- const sha1 = crypto . createHash ( 'sha1' ) ;
38- sha1 . update ( Buffer . from ( await this . _readIntoBuffer ( path ) ) ) ;
39-
40- return sha1 . digest ( 'hex' ) ;
41- }
42-
43- write ( path : string , content : string ) : Promise < void > {
44- return this . _host . write ( this . _resolve ( path ) , virtualFs . stringToFileBuffer ( content ) )
45- . toPromise ( ) ;
26+ hash ( file : string ) : Promise < string > {
27+ return new Promise ( ( resolve , reject ) => {
28+ const hash = crypto . createHash ( 'sha1' ) . setEncoding ( 'hex' ) ;
29+ pipeline (
30+ createReadStream ( this . _resolve ( file ) ) ,
31+ hash ,
32+ ( error ) => error ? reject ( error ) : resolve ( hash . read ( ) ) ,
33+ ) ;
34+ } ) ;
4635 }
4736
48- private _readIntoBuffer ( path : string ) : Promise < ArrayBuffer > {
49- return this . _host . read ( this . _resolve ( path ) ) . toPromise ( ) ;
37+ write ( file : string , content : string ) : Promise < void > {
38+ return fs . writeFile ( this . _resolve ( file ) , content ) ;
5039 }
5140
52- private _resolve ( path : string ) : Path {
53- return join ( normalize ( this . base ) , normalize ( path ) ) ;
41+ private _resolve ( file : string ) : string {
42+ return path . join ( this . base , file ) ;
5443 }
5544
56- private async _recursiveList ( path : Path , items : string [ ] ) : Promise < string [ ] > {
57- const fragments = await this . _host . list ( path ) . toPromise ( ) ;
58-
59- for ( const fragment of fragments ) {
60- const item = join ( path , fragment ) ;
61-
62- if ( await this . _host . isDirectory ( item ) . toPromise ( ) ) {
63- await this . _recursiveList ( item , items ) ;
64- } else {
65- items . push ( '/' + relative ( normalize ( this . base ) , item ) ) ;
45+ private async _recursiveList ( dir : string , items : string [ ] ) : Promise < string [ ] > {
46+ const subdirectories = [ ] ;
47+ for await ( const entry of await fs . opendir ( dir ) ) {
48+ if ( entry . isFile ( ) ) {
49+ items . push ( '/' + path . relative ( this . base , path . join ( dir , entry . name ) ) ) ;
50+ } else if ( entry . isDirectory ( ) ) {
51+ subdirectories . push ( path . join ( dir , entry . name ) ) ;
6652 }
6753 }
6854
55+ for ( const subdirectory of subdirectories ) {
56+ await this . _recursiveList ( subdirectory , items ) ;
57+ }
58+
6959 return items ;
7060 }
7161}
@@ -77,61 +67,74 @@ export async function augmentAppWithServiceWorker(
7767 baseHref : string ,
7868 ngswConfigPath ?: string ,
7969) : Promise < void > {
80- const host = new NodeJsSyncHost ( ) ;
81- const distPath = normalize ( outputPath ) ;
70+ const distPath = getSystemPath ( normalize ( outputPath ) ) ;
8271 const systemProjectRoot = getSystemPath ( projectRoot ) ;
8372
8473 // Find the service worker package
85- const workerPath = normalize (
86- require . resolve ( '@angular/service-worker/ngsw-worker.js' , { paths : [ systemProjectRoot ] } ) ,
87- ) ;
88- const swConfigPath = require . resolve (
89- '@angular/service-worker/config' ,
90- { paths : [ systemProjectRoot ] } ,
91- ) ;
74+ const workerPath = require . resolve ( '@angular/service-worker/ngsw-worker.js' , {
75+ paths : [ systemProjectRoot ] ,
76+ } ) ;
77+ const swConfigPath = require . resolve ( '@angular/service-worker/config' , {
78+ paths : [ systemProjectRoot ] ,
79+ } ) ;
9280
9381 // Determine the configuration file path
9482 let configPath ;
9583 if ( ngswConfigPath ) {
96- configPath = normalize ( ngswConfigPath ) ;
84+ configPath = getSystemPath ( normalize ( ngswConfigPath ) ) ;
9785 } else {
98- configPath = join ( appRoot , 'ngsw-config.json' ) ;
99- }
100-
101- // Ensure the configuration file exists
102- const configExists = await host . exists ( configPath ) . toPromise ( ) ;
103- if ( ! configExists ) {
104- throw new Error ( tags . oneLine `
105- Error: Expected to find an ngsw-config.json configuration
106- file in the ${ getSystemPath ( appRoot ) } folder. Either provide one or disable Service Worker
107- in your angular.json configuration file.
108- ` ) ;
86+ configPath = path . join ( getSystemPath ( appRoot ) , 'ngsw-config.json' ) ;
10987 }
11088
11189 // Read the configuration file
112- const config = JSON . parse ( virtualFs . fileBufferToString ( await host . read ( configPath ) . toPromise ( ) ) ) ;
90+ let config : Config | undefined ;
91+ try {
92+ const configurationData = await fs . readFile ( configPath , 'utf-8' ) ;
93+ config = JSON . parse ( configurationData ) as Config ;
94+ } catch ( error ) {
95+ if ( error . code === 'ENOENT' ) {
96+ throw new Error (
97+ 'Error: Expected to find an ngsw-config.json configuration file' +
98+ ` in the ${ getSystemPath ( appRoot ) } folder. Either provide one or` +
99+ ' disable Service Worker in the angular.json configuration file.' ,
100+ ) ;
101+ } else {
102+ throw error ;
103+ }
104+ }
113105
114106 // Generate the manifest
115107 const GeneratorConstructor = require ( swConfigPath ) . Generator as typeof Generator ;
116- const generator = new GeneratorConstructor ( new CliFilesystem ( host , outputPath ) , baseHref ) ;
108+ const generator = new GeneratorConstructor ( new CliFilesystem ( distPath ) , baseHref ) ;
117109 const output = await generator . process ( config ) ;
118110
119111 // Write the manifest
120112 const manifest = JSON . stringify ( output , null , 2 ) ;
121- await host . write ( join ( distPath , 'ngsw.json' ) , virtualFs . stringToFileBuffer ( manifest ) ) . toPromise ( ) ;
113+ await fs . writeFile ( path . join ( distPath , 'ngsw.json' ) , manifest ) ;
122114
123115 // Write the worker code
124- // NOTE: This is inefficient (kernel -> userspace -> kernel).
125- // `fs.copyFile` would be a better option but breaks the host abstraction
126- const workerCode = await host . read ( workerPath ) . toPromise ( ) ;
127- await host . write ( join ( distPath , 'ngsw-worker.js' ) , workerCode ) . toPromise ( ) ;
116+ await fs . copyFile (
117+ workerPath ,
118+ path . join ( distPath , 'ngsw-worker.js' ) ,
119+ fsConstants . COPYFILE_FICLONE ,
120+ ) ;
128121
129122 // If present, write the safety worker code
130- const safetyPath = join ( dirname ( workerPath ) , 'safety-worker.js' ) ;
131- if ( await host . exists ( safetyPath ) . toPromise ( ) ) {
132- const safetyCode = await host . read ( safetyPath ) . toPromise ( ) ;
133-
134- await host . write ( join ( distPath , 'worker-basic.min.js' ) , safetyCode ) . toPromise ( ) ;
135- await host . write ( join ( distPath , 'safety-worker.js' ) , safetyCode ) . toPromise ( ) ;
123+ const safetyPath = path . join ( path . dirname ( workerPath ) , 'safety-worker.js' ) ;
124+ try {
125+ await fs . copyFile (
126+ safetyPath ,
127+ path . join ( distPath , 'worker-basic.min.js' ) ,
128+ fsConstants . COPYFILE_FICLONE ,
129+ ) ;
130+ await fs . copyFile (
131+ safetyPath ,
132+ path . join ( distPath , 'safety-worker.js' ) ,
133+ fsConstants . COPYFILE_FICLONE ,
134+ ) ;
135+ } catch ( error ) {
136+ if ( error . code !== 'ENOENT' ) {
137+ throw error ;
138+ }
136139 }
137140}
0 commit comments