8
8
9
9
import type { Config , Filesystem } from '@angular/service-worker/config' ;
10
10
import * as crypto from 'crypto' ;
11
- import { constants as fsConstants , promises as fsPromises } from 'fs' ;
11
+ import type { OutputFile } from 'esbuild' ;
12
+ import { existsSync , constants as fsConstants , promises as fsPromises } from 'node:fs' ;
12
13
import * as path from 'path' ;
13
14
import { assertIsError } from './error' ;
14
15
import { loadEsmModule } from './load-esm' ;
@@ -61,6 +62,49 @@ class CliFilesystem implements Filesystem {
61
62
}
62
63
}
63
64
65
+ class ResultFilesystem implements Filesystem {
66
+ private readonly fileReaders = new Map < string , ( ) => Promise < string > > ( ) ;
67
+
68
+ constructor ( outputFiles : OutputFile [ ] , assetFiles : { source : string ; destination : string } [ ] ) {
69
+ for ( const file of outputFiles ) {
70
+ this . fileReaders . set ( '/' + file . path . replace ( / \\ / g, '/' ) , async ( ) => file . text ) ;
71
+ }
72
+ for ( const file of assetFiles ) {
73
+ this . fileReaders . set ( '/' + file . destination . replace ( / \\ / g, '/' ) , ( ) =>
74
+ fsPromises . readFile ( file . source , 'utf-8' ) ,
75
+ ) ;
76
+ }
77
+ }
78
+
79
+ async list ( dir : string ) : Promise < string [ ] > {
80
+ if ( dir !== '/' ) {
81
+ throw new Error ( 'Serviceworker manifest generator should only list files from root.' ) ;
82
+ }
83
+
84
+ return [ ...this . fileReaders . keys ( ) ] ;
85
+ }
86
+
87
+ read ( file : string ) : Promise < string > {
88
+ const reader = this . fileReaders . get ( file ) ;
89
+ if ( reader === undefined ) {
90
+ throw new Error ( 'File does not exist.' ) ;
91
+ }
92
+
93
+ return reader ( ) ;
94
+ }
95
+
96
+ async hash ( file : string ) : Promise < string > {
97
+ return crypto
98
+ . createHash ( 'sha1' )
99
+ . update ( await this . read ( file ) )
100
+ . digest ( 'hex' ) ;
101
+ }
102
+
103
+ write ( ) : never {
104
+ throw new Error ( 'Serviceworker manifest generator should not attempted to write.' ) ;
105
+ }
106
+ }
107
+
64
108
export async function augmentAppWithServiceWorker (
65
109
appRoot : string ,
66
110
workspaceRoot : string ,
@@ -93,22 +137,37 @@ export async function augmentAppWithServiceWorker(
93
137
}
94
138
}
95
139
96
- return augmentAppWithServiceWorkerCore (
140
+ const result = await augmentAppWithServiceWorkerCore (
97
141
config ,
98
- outputPath ,
142
+ new CliFilesystem ( outputFileSystem , outputPath ) ,
99
143
baseHref ,
100
- inputputFileSystem ,
101
- outputFileSystem ,
102
144
) ;
145
+
146
+ const copy = async ( src : string , dest : string ) : Promise < void > => {
147
+ const resolvedDest = path . join ( outputPath , dest ) ;
148
+
149
+ return inputputFileSystem === outputFileSystem
150
+ ? // Native FS (Builder).
151
+ inputputFileSystem . copyFile ( src , resolvedDest , fsConstants . COPYFILE_FICLONE )
152
+ : // memfs (Webpack): Read the file from the input FS (disk) and write it to the output FS (memory).
153
+ outputFileSystem . writeFile ( resolvedDest , await inputputFileSystem . readFile ( src ) ) ;
154
+ } ;
155
+
156
+ await outputFileSystem . writeFile ( path . join ( outputPath , 'ngsw.json' ) , result . manifest ) ;
157
+
158
+ for ( const { source, destination } of result . assetFiles ) {
159
+ await copy ( source , destination ) ;
160
+ }
103
161
}
104
162
105
163
// This is currently used by the esbuild-based builder
106
164
export async function augmentAppWithServiceWorkerEsbuild (
107
165
workspaceRoot : string ,
108
166
configPath : string ,
109
- outputPath : string ,
110
167
baseHref : string ,
111
- ) : Promise < void > {
168
+ outputFiles : OutputFile [ ] ,
169
+ assetFiles : { source : string ; destination : string } [ ] ,
170
+ ) : Promise < { manifest : string ; assetFiles : { source : string ; destination : string } [ ] } > {
112
171
// Read the configuration file
113
172
let config : Config | undefined ;
114
173
try {
@@ -128,17 +187,18 @@ export async function augmentAppWithServiceWorkerEsbuild(
128
187
}
129
188
}
130
189
131
- // TODO: Return the output files and any errors/warnings
132
- return augmentAppWithServiceWorkerCore ( config , outputPath , baseHref ) ;
190
+ return augmentAppWithServiceWorkerCore (
191
+ config ,
192
+ new ResultFilesystem ( outputFiles , assetFiles ) ,
193
+ baseHref ,
194
+ ) ;
133
195
}
134
196
135
197
export async function augmentAppWithServiceWorkerCore (
136
198
config : Config ,
137
- outputPath : string ,
199
+ serviceWorkerFilesystem : Filesystem ,
138
200
baseHref : string ,
139
- inputputFileSystem = fsPromises ,
140
- outputFileSystem = fsPromises ,
141
- ) : Promise < void > {
201
+ ) : Promise < { manifest : string ; assetFiles : { source : string ; destination : string } [ ] } > {
142
202
// Load ESM `@angular/service-worker/config` using the TypeScript dynamic import workaround.
143
203
// Once TypeScript provides support for keeping the dynamic import this workaround can be
144
204
// changed to a direct dynamic import.
@@ -149,41 +209,27 @@ export async function augmentAppWithServiceWorkerCore(
149
209
) . Generator ;
150
210
151
211
// Generate the manifest
152
- const generator = new GeneratorConstructor (
153
- new CliFilesystem ( outputFileSystem , outputPath ) ,
154
- baseHref ,
155
- ) ;
212
+ const generator = new GeneratorConstructor ( serviceWorkerFilesystem , baseHref ) ;
156
213
const output = await generator . process ( config ) ;
157
214
158
215
// Write the manifest
159
216
const manifest = JSON . stringify ( output , null , 2 ) ;
160
- await outputFileSystem . writeFile ( path . join ( outputPath , 'ngsw.json' ) , manifest ) ;
161
217
162
218
// Find the service worker package
163
219
const workerPath = require . resolve ( '@angular/service-worker/ngsw-worker.js' ) ;
164
220
165
- const copy = async ( src : string , dest : string ) : Promise < void > => {
166
- const resolvedDest = path . join ( outputPath , dest ) ;
167
-
168
- return inputputFileSystem === outputFileSystem
169
- ? // Native FS (Builder).
170
- inputputFileSystem . copyFile ( src , resolvedDest , fsConstants . COPYFILE_FICLONE )
171
- : // memfs (Webpack): Read the file from the input FS (disk) and write it to the output FS (memory).
172
- outputFileSystem . writeFile ( resolvedDest , await inputputFileSystem . readFile ( src ) ) ;
221
+ const result = {
222
+ manifest,
223
+ // Main worker code
224
+ assetFiles : [ { source : workerPath , destination : 'ngsw-worker.js' } ] ,
173
225
} ;
174
226
175
- // Write the worker code
176
- await copy ( workerPath , 'ngsw-worker.js' ) ;
177
-
178
227
// If present, write the safety worker code
179
- try {
180
- const safetyPath = path . join ( path . dirname ( workerPath ) , 'safety-worker.js' ) ;
181
- await copy ( safetyPath , 'worker-basic.min.js' ) ;
182
- await copy ( safetyPath , 'safety-worker.js' ) ;
183
- } catch ( error ) {
184
- assertIsError ( error ) ;
185
- if ( error . code !== 'ENOENT' ) {
186
- throw error ;
187
- }
228
+ const safetyPath = path . join ( path . dirname ( workerPath ) , 'safety-worker.js' ) ;
229
+ if ( existsSync ( safetyPath ) ) {
230
+ result . assetFiles . push ( { source : safetyPath , destination : 'worker-basic.min.js' } ) ;
231
+ result . assetFiles . push ( { source : safetyPath , destination : 'safety-worker.js' } ) ;
188
232
}
233
+
234
+ return result ;
189
235
}
0 commit comments