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+ // Uses posix paths since the service worker expects URLs
50+ items . push ( '/' + path . posix . relative ( this . base , path . posix . join ( dir , entry . name ) ) ) ;
51+ } else if ( entry . isDirectory ( ) ) {
52+ subdirectories . push ( path . join ( dir , entry . name ) ) ;
6653 }
6754 }
6855
56+ for ( const subdirectory of subdirectories ) {
57+ await this . _recursiveList ( subdirectory , items ) ;
58+ }
59+
6960 return items ;
7061 }
7162}
@@ -77,61 +68,74 @@ export async function augmentAppWithServiceWorker(
7768 baseHref : string ,
7869 ngswConfigPath ?: string ,
7970) : Promise < void > {
80- const host = new NodeJsSyncHost ( ) ;
81- const distPath = normalize ( outputPath ) ;
71+ const distPath = getSystemPath ( normalize ( outputPath ) ) ;
8272 const systemProjectRoot = getSystemPath ( projectRoot ) ;
8373
8474 // 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- ) ;
75+ const workerPath = require . resolve ( '@angular/service-worker/ngsw-worker.js' , {
76+ paths : [ systemProjectRoot ] ,
77+ } ) ;
78+ const swConfigPath = require . resolve ( '@angular/service-worker/config' , {
79+ paths : [ systemProjectRoot ] ,
80+ } ) ;
9281
9382 // Determine the configuration file path
9483 let configPath ;
9584 if ( ngswConfigPath ) {
96- configPath = normalize ( ngswConfigPath ) ;
85+ configPath = getSystemPath ( normalize ( ngswConfigPath ) ) ;
9786 } 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- ` ) ;
87+ configPath = path . join ( getSystemPath ( appRoot ) , 'ngsw-config.json' ) ;
10988 }
11089
11190 // Read the configuration file
112- const config = JSON . parse ( virtualFs . fileBufferToString ( await host . read ( configPath ) . toPromise ( ) ) ) ;
91+ let config : Config | undefined ;
92+ try {
93+ const configurationData = await fs . readFile ( configPath , 'utf-8' ) ;
94+ config = JSON . parse ( configurationData ) as Config ;
95+ } catch ( error ) {
96+ if ( error . code === 'ENOENT' ) {
97+ throw new Error (
98+ 'Error: Expected to find an ngsw-config.json configuration file' +
99+ ` in the ${ getSystemPath ( appRoot ) } folder. Either provide one or` +
100+ ' disable Service Worker in the angular.json configuration file.' ,
101+ ) ;
102+ } else {
103+ throw error ;
104+ }
105+ }
113106
114107 // Generate the manifest
115108 const GeneratorConstructor = require ( swConfigPath ) . Generator as typeof Generator ;
116- const generator = new GeneratorConstructor ( new CliFilesystem ( host , outputPath ) , baseHref ) ;
109+ const generator = new GeneratorConstructor ( new CliFilesystem ( distPath ) , baseHref ) ;
117110 const output = await generator . process ( config ) ;
118111
119112 // Write the manifest
120113 const manifest = JSON . stringify ( output , null , 2 ) ;
121- await host . write ( join ( distPath , 'ngsw.json' ) , virtualFs . stringToFileBuffer ( manifest ) ) . toPromise ( ) ;
114+ await fs . writeFile ( path . join ( distPath , 'ngsw.json' ) , manifest ) ;
122115
123116 // 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 ( ) ;
117+ await fs . copyFile (
118+ workerPath ,
119+ path . join ( distPath , 'ngsw-worker.js' ) ,
120+ fsConstants . COPYFILE_FICLONE ,
121+ ) ;
128122
129123 // 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 ( ) ;
124+ const safetyPath = path . join ( path . dirname ( workerPath ) , 'safety-worker.js' ) ;
125+ try {
126+ await fs . copyFile (
127+ safetyPath ,
128+ path . join ( distPath , 'worker-basic.min.js' ) ,
129+ fsConstants . COPYFILE_FICLONE ,
130+ ) ;
131+ await fs . copyFile (
132+ safetyPath ,
133+ path . join ( distPath , 'safety-worker.js' ) ,
134+ fsConstants . COPYFILE_FICLONE ,
135+ ) ;
136+ } catch ( error ) {
137+ if ( error . code !== 'ENOENT' ) {
138+ throw error ;
139+ }
136140 }
137141}
0 commit comments