5
5
* Use of this source code is governed by an MIT-style license that can be
6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
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' ;
23
10
import * 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' ;
24
14
25
15
class CliFilesystem implements Filesystem {
26
- constructor ( private _host : virtualFs . Host , private base : string ) { }
16
+ constructor ( private base : string ) { }
27
17
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 ) , [ ] ) ;
30
20
}
31
21
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' ) ;
34
24
}
35
25
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
+ } ) ;
46
35
}
47
36
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 ) ;
50
39
}
51
40
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 ) ;
54
43
}
55
44
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 ) ) ;
66
53
}
67
54
}
68
55
56
+ for ( const subdirectory of subdirectories ) {
57
+ await this . _recursiveList ( subdirectory , items ) ;
58
+ }
59
+
69
60
return items ;
70
61
}
71
62
}
@@ -77,61 +68,74 @@ export async function augmentAppWithServiceWorker(
77
68
baseHref : string ,
78
69
ngswConfigPath ?: string ,
79
70
) : Promise < void > {
80
- const host = new NodeJsSyncHost ( ) ;
81
- const distPath = normalize ( outputPath ) ;
71
+ const distPath = getSystemPath ( normalize ( outputPath ) ) ;
82
72
const systemProjectRoot = getSystemPath ( projectRoot ) ;
83
73
84
74
// 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
+ } ) ;
92
81
93
82
// Determine the configuration file path
94
83
let configPath ;
95
84
if ( ngswConfigPath ) {
96
- configPath = normalize ( ngswConfigPath ) ;
85
+ configPath = getSystemPath ( normalize ( ngswConfigPath ) ) ;
97
86
} 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' ) ;
109
88
}
110
89
111
90
// 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
+ }
113
106
114
107
// Generate the manifest
115
108
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 ) ;
117
110
const output = await generator . process ( config ) ;
118
111
119
112
// Write the manifest
120
113
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 ) ;
122
115
123
116
// 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
+ ) ;
128
122
129
123
// 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
+ }
136
140
}
137
141
}
0 commit comments