@jsenv/core 27.0.0-alpha.55 → 27.0.0-alpha.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,9 @@ import {
14
14
  urlToExtension,
15
15
  urlToRelativeUrl,
16
16
  writeFile,
17
+ registerDirectoryLifecycle,
17
18
  } from "@jsenv/filesystem"
19
+ import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
18
20
  import { createLogger } from "@jsenv/logger"
19
21
 
20
22
  import { createTaskLog } from "@jsenv/utils/logs/task_log.js"
@@ -79,6 +81,7 @@ import { resyncRessourceHints } from "./resync_ressource_hints.js"
79
81
  */
80
82
  export const build = async ({
81
83
  signal = new AbortController().signal,
84
+ handleSIGINT = true,
82
85
  logLevel = "info",
83
86
  rootDirectoryUrl,
84
87
  buildDirectoryUrl,
@@ -97,13 +100,34 @@ export const build = async ({
97
100
  versioningMethod = "search_param", // "filename", "search_param"
98
101
  lineBreakNormalization = process.platform === "win32",
99
102
 
103
+ clientFiles = {
104
+ "./**": true,
105
+ "./**/.*/": false, // any folder starting with a dot is ignored (includes .git,.jsenv for instance)
106
+ "./dist/": false,
107
+ "./**/node_modules/": false,
108
+ },
109
+ cooldownBetweenFileEvents,
110
+ watch = false,
111
+
100
112
  writeOnFileSystem = true,
101
113
  buildDirectoryClean = true,
102
114
  baseUrl = "/",
103
115
  assetManifest = true,
104
116
  assetManifestFileRelativeUrl = "asset-manifest.json",
105
117
  }) => {
106
- const logger = createLogger({ logLevel })
118
+ const operation = Abort.startOperation()
119
+ operation.addAbortSignal(signal)
120
+ if (handleSIGINT) {
121
+ operation.addAbortSource((abort) => {
122
+ return raceProcessTeardownEvents(
123
+ {
124
+ SIGINT: true,
125
+ },
126
+ abort,
127
+ )
128
+ })
129
+ }
130
+
107
131
  rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
108
132
  buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl)
109
133
  assertEntryPoints({ entryPoints })
@@ -113,659 +137,727 @@ export const build = async ({
113
137
  )
114
138
  }
115
139
 
116
- const entryPointKeys = Object.keys(entryPoints)
117
- if (entryPointKeys.length === 1) {
118
- logger.info(`
140
+ const runBuild = async ({ signal, logLevel }) => {
141
+ const logger = createLogger({ logLevel })
142
+ const buildOperation = Abort.startOperation()
143
+ buildOperation.addAbortSignal(signal)
144
+ const entryPointKeys = Object.keys(entryPoints)
145
+ if (entryPointKeys.length === 1) {
146
+ logger.info(`
119
147
  build "${entryPointKeys[0]}"`)
120
- } else {
121
- logger.info(`
148
+ } else {
149
+ logger.info(`
122
150
  build ${entryPointKeys.length} entry points`)
123
- }
124
- const rawGraph = createUrlGraph()
125
- const prebuildTask = createTaskLog(logger, "prebuild")
126
- let urlCount = 0
127
- const rawGraphKitchen = createKitchen({
128
- signal,
129
- logger,
130
- rootDirectoryUrl,
131
- urlGraph: rawGraph,
132
- scenario: "build",
133
- sourcemaps,
134
- runtimeCompat,
135
- plugins: [
136
- ...plugins,
137
- {
138
- name: "jsenv:build_log",
139
- appliesDuring: { build: true },
140
- cooked: () => {
141
- urlCount++
142
- prebuildTask.setRightText(urlCount)
143
- },
144
- },
145
- ...getCorePlugins({
146
- rootDirectoryUrl,
147
- urlGraph: rawGraph,
148
- scenario: "build",
151
+ }
149
152
 
150
- nodeEsmResolution,
151
- fileSystemMagicResolution,
152
- injectedGlobals,
153
- transpilation: {
154
- ...transpilation,
155
- jsModuleAsJsClassic: false,
156
- },
157
- minification,
158
- bundling,
159
- }),
160
- ],
161
- })
162
- const entryUrls = []
163
- try {
164
- await loadUrlGraph({
153
+ const rawGraph = createUrlGraph()
154
+ const prebuildTask = createTaskLog(logger, "prebuild")
155
+ let urlCount = 0
156
+ const rawGraphKitchen = createKitchen({
157
+ signal,
158
+ logger,
159
+ rootDirectoryUrl,
165
160
  urlGraph: rawGraph,
166
- kitchen: rawGraphKitchen,
167
- outDirectoryUrl: new URL(`.jsenv/build/`, rootDirectoryUrl),
168
- startLoading: (cookEntryFile) => {
169
- Object.keys(entryPoints).forEach((key) => {
170
- const [, entryUrlInfo] = cookEntryFile({
171
- trace: `"${key}" in entryPoints parameter`,
172
- type: "entry_point",
173
- specifier: key,
174
- })
175
- entryUrls.push(entryUrlInfo.url)
176
- entryUrlInfo.filename = entryPoints[key]
177
- })
178
- },
179
- })
180
- } catch (e) {
181
- prebuildTask.fail()
182
- throw e
183
- }
184
- // here we can perform many checks such as ensuring ressource hints are used
185
- prebuildTask.done()
186
- logger.debug(
187
- `raw graph urls:
188
- ${Object.keys(rawGraph.urlInfos).join("\n")}`,
189
- )
161
+ scenario: "build",
162
+ sourcemaps,
163
+ runtimeCompat,
164
+ plugins: [
165
+ ...plugins,
166
+ {
167
+ name: "jsenv:build_log",
168
+ appliesDuring: { build: true },
169
+ cooked: () => {
170
+ urlCount++
171
+ prebuildTask.setRightText(urlCount)
172
+ },
173
+ },
174
+ ...getCorePlugins({
175
+ rootDirectoryUrl,
176
+ urlGraph: rawGraph,
177
+ scenario: "build",
190
178
 
191
- const buildUrlsGenerator = createBuilUrlsGenerator({
192
- buildDirectoryUrl,
193
- })
194
- const rawUrls = {}
195
- const buildUrls = {}
196
- const rawUrlRedirections = {}
197
- const bundleUrlInfos = {}
198
- const bundlers = {}
199
- rawGraphKitchen.pluginController.plugins.forEach((plugin) => {
200
- const bundle = plugin.bundle
201
- if (!bundle) {
202
- return
203
- }
204
- if (typeof bundle !== "object") {
205
- throw new Error(
206
- `bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
207
- )
179
+ nodeEsmResolution,
180
+ fileSystemMagicResolution,
181
+ injectedGlobals,
182
+ transpilation: {
183
+ ...transpilation,
184
+ jsModuleAsJsClassic: false,
185
+ },
186
+ minification,
187
+ bundling,
188
+ }),
189
+ ],
190
+ })
191
+ const entryUrls = []
192
+ try {
193
+ await loadUrlGraph({
194
+ operation: buildOperation,
195
+ urlGraph: rawGraph,
196
+ kitchen: rawGraphKitchen,
197
+ outDirectoryUrl: new URL(`.jsenv/build/`, rootDirectoryUrl),
198
+ startLoading: (cookEntryFile) => {
199
+ Object.keys(entryPoints).forEach((key) => {
200
+ const [, entryUrlInfo] = cookEntryFile({
201
+ trace: `"${key}" in entryPoints parameter`,
202
+ type: "entry_point",
203
+ specifier: key,
204
+ })
205
+ entryUrls.push(entryUrlInfo.url)
206
+ entryUrlInfo.filename = entryPoints[key]
207
+ })
208
+ },
209
+ })
210
+ } catch (e) {
211
+ prebuildTask.fail()
212
+ throw e
208
213
  }
209
- Object.keys(bundle).forEach((type) => {
210
- const bundleFunction = bundle[type]
211
- if (!bundleFunction) {
212
- return
213
- }
214
- const bundlerForThatType = bundlers[type]
215
- if (bundlerForThatType) {
216
- // first plugin to define a bundle hook wins
214
+ prebuildTask.done()
215
+
216
+ const buildUrlsGenerator = createBuilUrlsGenerator({
217
+ buildDirectoryUrl,
218
+ })
219
+ const rawUrls = {}
220
+ const buildUrls = {}
221
+ const rawUrlRedirections = {}
222
+ const bundleUrlInfos = {}
223
+ const bundlers = {}
224
+ rawGraphKitchen.pluginController.plugins.forEach((plugin) => {
225
+ const bundle = plugin.bundle
226
+ if (!bundle) {
217
227
  return
218
228
  }
219
- bundlers[type] = {
220
- plugin,
221
- bundleFunction: bundle[type],
222
- urlInfos: [],
229
+ if (typeof bundle !== "object") {
230
+ throw new Error(
231
+ `bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
232
+ )
223
233
  }
234
+ Object.keys(bundle).forEach((type) => {
235
+ const bundleFunction = bundle[type]
236
+ if (!bundleFunction) {
237
+ return
238
+ }
239
+ const bundlerForThatType = bundlers[type]
240
+ if (bundlerForThatType) {
241
+ // first plugin to define a bundle hook wins
242
+ return
243
+ }
244
+ bundlers[type] = {
245
+ plugin,
246
+ bundleFunction: bundle[type],
247
+ urlInfos: [],
248
+ }
249
+ })
224
250
  })
225
- })
226
- const addToBundlerIfAny = (rawUrlInfo) => {
227
- const bundler = bundlers[rawUrlInfo.type]
228
- if (bundler) {
229
- bundler.urlInfos.push(rawUrlInfo)
230
- return
251
+ const addToBundlerIfAny = (rawUrlInfo) => {
252
+ const bundler = bundlers[rawUrlInfo.type]
253
+ if (bundler) {
254
+ bundler.urlInfos.push(rawUrlInfo)
255
+ return
256
+ }
231
257
  }
232
- }
233
- GRAPH.forEach(rawGraph, (rawUrlInfo) => {
234
- if (rawUrlInfo.data.isEntryPoint) {
235
- addToBundlerIfAny(rawUrlInfo)
236
- if (rawUrlInfo.type === "html") {
237
- rawUrlInfo.dependencies.forEach((dependencyUrl) => {
238
- const dependencyUrlInfo = rawGraph.getUrlInfo(dependencyUrl)
239
- if (dependencyUrlInfo.isInline) {
240
- if (dependencyUrlInfo.type === "js_module") {
241
- // bundle inline script type module deps
242
- dependencyUrlInfo.references.forEach((inlineScriptRef) => {
243
- if (inlineScriptRef.type === "js_import_export") {
244
- const inlineUrlInfo = rawGraph.getUrlInfo(inlineScriptRef.url)
245
- addToBundlerIfAny(inlineUrlInfo)
246
- }
247
- })
258
+ GRAPH.forEach(rawGraph, (rawUrlInfo) => {
259
+ if (rawUrlInfo.data.isEntryPoint) {
260
+ addToBundlerIfAny(rawUrlInfo)
261
+ if (rawUrlInfo.type === "html") {
262
+ rawUrlInfo.dependencies.forEach((dependencyUrl) => {
263
+ const dependencyUrlInfo = rawGraph.getUrlInfo(dependencyUrl)
264
+ if (dependencyUrlInfo.isInline) {
265
+ if (dependencyUrlInfo.type === "js_module") {
266
+ // bundle inline script type module deps
267
+ dependencyUrlInfo.references.forEach((inlineScriptRef) => {
268
+ if (inlineScriptRef.type === "js_import_export") {
269
+ const inlineUrlInfo = rawGraph.getUrlInfo(
270
+ inlineScriptRef.url,
271
+ )
272
+ addToBundlerIfAny(inlineUrlInfo)
273
+ }
274
+ })
275
+ }
276
+ // inline content cannot be bundled
277
+ return
248
278
  }
249
- // inline content cannot be bundled
250
- return
251
- }
252
- addToBundlerIfAny(dependencyUrlInfo)
253
- })
254
- rawUrlInfo.references.forEach((reference) => {
255
- if (
256
- reference.isRessourceHint &&
257
- reference.expectedType === "js_module"
258
- ) {
259
- const referencedUrlInfo = rawGraph.getUrlInfo(reference.url)
279
+ addToBundlerIfAny(dependencyUrlInfo)
280
+ })
281
+ rawUrlInfo.references.forEach((reference) => {
260
282
  if (
261
- referencedUrlInfo &&
262
- // something else than the ressource hint is using this url
263
- referencedUrlInfo.dependents.size > 0
283
+ reference.isRessourceHint &&
284
+ reference.expectedType === "js_module"
264
285
  ) {
265
- addToBundlerIfAny(referencedUrlInfo)
286
+ const referencedUrlInfo = rawGraph.getUrlInfo(reference.url)
287
+ if (
288
+ referencedUrlInfo &&
289
+ // something else than the ressource hint is using this url
290
+ referencedUrlInfo.dependents.size > 0
291
+ ) {
292
+ addToBundlerIfAny(referencedUrlInfo)
293
+ }
266
294
  }
295
+ })
296
+ return
297
+ }
298
+ }
299
+ // File referenced with new URL('./file.js', import.meta.url)
300
+ // are entry points that can be bundled
301
+ // For instance we will bundle service worker/workers detected like this
302
+ if (rawUrlInfo.type === "js_module") {
303
+ rawUrlInfo.references.forEach((reference) => {
304
+ if (reference.type === "js_url_specifier") {
305
+ const urlInfo = rawGraph.getUrlInfo(reference.url)
306
+ addToBundlerIfAny(urlInfo)
267
307
  }
268
308
  })
309
+ }
310
+ })
311
+ const bundleUrlRedirections = {}
312
+ await Object.keys(bundlers).reduce(async (previous, type) => {
313
+ await previous
314
+ const bundler = bundlers[type]
315
+ const urlInfosToBundle = bundler.urlInfos
316
+ if (urlInfosToBundle.length === 0) {
269
317
  return
270
318
  }
271
- }
272
- // File referenced with new URL('./file.js', import.meta.url)
273
- // are entry points that can be bundled
274
- // For instance we will bundle service worker/workers detected like this
275
- if (rawUrlInfo.type === "js_module") {
276
- rawUrlInfo.references.forEach((reference) => {
277
- if (reference.type === "js_url_specifier") {
278
- const urlInfo = rawGraph.getUrlInfo(reference.url)
279
- addToBundlerIfAny(urlInfo)
280
- }
281
- })
282
- }
283
- })
284
- const bundleUrlRedirections = {}
285
- await Object.keys(bundlers).reduce(async (previous, type) => {
286
- await previous
287
- const bundler = bundlers[type]
288
- const urlInfosToBundle = bundler.urlInfos
289
- if (urlInfosToBundle.length === 0) {
290
- return
291
- }
292
- const bundleTask = createTaskLog(logger, `bundle "${type}"`)
293
- try {
294
- const bundlerGeneratedUrlInfos =
295
- await rawGraphKitchen.pluginController.callAsyncHook(
296
- {
297
- plugin: bundler.plugin,
298
- hookName: "bundle",
299
- value: bundler.bundleFunction,
300
- },
301
- urlInfosToBundle,
302
- {
303
- ...rawGraphKitchen.baseContext,
304
- buildDirectoryUrl,
305
- },
306
- )
307
- Object.keys(bundlerGeneratedUrlInfos).forEach((url) => {
308
- const rawUrlInfo = rawGraph.getUrlInfo(url)
309
- const bundlerGeneratedUrlInfo = bundlerGeneratedUrlInfos[url]
310
- const bundleUrlInfo = {
311
- type,
312
- subtype: rawUrlInfo ? rawUrlInfo.subtype : undefined,
313
- filename: rawUrlInfo ? rawUrlInfo.filename : undefined,
314
- ...bundlerGeneratedUrlInfo,
315
- data: {
316
- ...(rawUrlInfo ? rawUrlInfo.data : {}),
317
- ...bundlerGeneratedUrlInfo.data,
318
- fromBundle: true,
319
- },
320
- }
321
- const buildUrl = buildUrlsGenerator.generate(url, {
322
- urlInfo: bundleUrlInfo,
323
- })
324
- rawUrlRedirections[url] = buildUrl
325
- rawUrls[buildUrl] = url
326
- bundleUrlInfos[buildUrl] = bundleUrlInfo
327
- if (bundlerGeneratedUrlInfo.data.bundleRelativeUrl) {
328
- const urlForBundler = new URL(
329
- bundlerGeneratedUrlInfo.data.bundleRelativeUrl,
330
- buildDirectoryUrl,
331
- ).href
332
- bundleUrlRedirections[urlForBundler] = buildUrl
333
- }
334
- })
335
- } catch (e) {
336
- bundleTask.fail()
337
- throw e
338
- }
339
- bundleTask.done()
340
- }, Promise.resolve())
341
-
342
- const buildUrlRedirections = {}
343
- const finalGraph = createUrlGraph()
344
- const optimizeUrlContentHooks =
345
- rawGraphKitchen.pluginController.addHook("optimizeUrlContent")
346
- const finalGraphKitchen = createKitchen({
347
- logger,
348
- rootDirectoryUrl,
349
- urlGraph: finalGraph,
350
- scenario: "build",
351
- sourcemaps,
352
- sourcemapsRelativeSources: !versioning,
353
- runtimeCompat,
354
- plugins: [
355
- jsenvPluginUrlAnalysis(),
356
- jsenvPluginAsJsClassic({
357
- systemJsInjection: true,
358
- }),
359
- jsenvPluginInline({
360
- fetchInlineUrls: false,
361
- }),
362
- {
363
- name: "jsenv:postbuild",
364
- appliesDuring: { build: true },
365
- resolveUrl: (reference) => {
366
- if (reference.specifier[0] === "#") {
367
- reference.external = true
368
- }
369
- let url =
370
- reference.specifier[0] === "/"
371
- ? new URL(reference.specifier.slice(1), buildDirectoryUrl).href
372
- : new URL(reference.specifier, reference.parentUrl).href
373
- const urlRedirectedByBundle = bundleUrlRedirections[url]
374
- if (urlRedirectedByBundle) {
375
- return urlRedirectedByBundle
376
- }
377
- const urlRedirected = rawUrlRedirections[url]
378
- return urlRedirected || url
379
- },
380
- redirectUrl: (reference) => {
381
- if (!reference.url.startsWith("file:")) {
382
- return null
383
- }
384
- // already a build url
385
- const rawUrl = rawUrls[reference.url]
386
- if (rawUrl) {
387
- return reference.url
319
+ const bundleTask = createTaskLog(logger, `bundle "${type}"`)
320
+ try {
321
+ const bundlerGeneratedUrlInfos =
322
+ await rawGraphKitchen.pluginController.callAsyncHook(
323
+ {
324
+ plugin: bundler.plugin,
325
+ hookName: "bundle",
326
+ value: bundler.bundleFunction,
327
+ },
328
+ urlInfosToBundle,
329
+ {
330
+ ...rawGraphKitchen.baseContext,
331
+ buildDirectoryUrl,
332
+ },
333
+ )
334
+ Object.keys(bundlerGeneratedUrlInfos).forEach((url) => {
335
+ const rawUrlInfo = rawGraph.getUrlInfo(url)
336
+ const bundlerGeneratedUrlInfo = bundlerGeneratedUrlInfos[url]
337
+ const bundleUrlInfo = {
338
+ type,
339
+ subtype: rawUrlInfo ? rawUrlInfo.subtype : undefined,
340
+ filename: rawUrlInfo ? rawUrlInfo.filename : undefined,
341
+ ...bundlerGeneratedUrlInfo,
342
+ data: {
343
+ ...(rawUrlInfo ? rawUrlInfo.data : {}),
344
+ ...bundlerGeneratedUrlInfo.data,
345
+ fromBundle: true,
346
+ },
388
347
  }
389
- // from rollup or postcss
390
- const bundleUrlInfo = bundleUrlInfos[reference.url]
391
- if (bundleUrlInfo) {
392
- return reference.url
348
+ const buildUrl = buildUrlsGenerator.generate(url, {
349
+ urlInfo: bundleUrlInfo,
350
+ })
351
+ rawUrlRedirections[url] = buildUrl
352
+ rawUrls[buildUrl] = url
353
+ bundleUrlInfos[buildUrl] = bundleUrlInfo
354
+ if (bundlerGeneratedUrlInfo.data.bundleRelativeUrl) {
355
+ const urlForBundler = new URL(
356
+ bundlerGeneratedUrlInfo.data.bundleRelativeUrl,
357
+ buildDirectoryUrl,
358
+ ).href
359
+ bundleUrlRedirections[urlForBundler] = buildUrl
393
360
  }
394
- // from "js_module_as_js_classic":
395
- // - injecting "?as_js_classic" for the first time
396
- // - injecting "?as_js_classic" because the parentUrl has it
397
- if (reference.original) {
398
- const referenceOriginalUrl = reference.original.url
399
- let originalBuildUrl
400
- if (urlIsInsideOf(referenceOriginalUrl, buildDirectoryUrl)) {
401
- originalBuildUrl = referenceOriginalUrl
402
- } else {
403
- originalBuildUrl = Object.keys(rawUrls).find(
404
- (key) => rawUrls[key] === referenceOriginalUrl,
405
- )
361
+ })
362
+ } catch (e) {
363
+ bundleTask.fail()
364
+ throw e
365
+ }
366
+ bundleTask.done()
367
+ }, Promise.resolve())
368
+
369
+ const buildUrlRedirections = {}
370
+ const finalGraph = createUrlGraph()
371
+ const optimizeUrlContentHooks =
372
+ rawGraphKitchen.pluginController.addHook("optimizeUrlContent")
373
+ const finalGraphKitchen = createKitchen({
374
+ logger,
375
+ rootDirectoryUrl,
376
+ urlGraph: finalGraph,
377
+ scenario: "build",
378
+ sourcemaps,
379
+ sourcemapsRelativeSources: !versioning,
380
+ runtimeCompat,
381
+ plugins: [
382
+ jsenvPluginUrlAnalysis(),
383
+ jsenvPluginAsJsClassic({
384
+ systemJsInjection: true,
385
+ }),
386
+ jsenvPluginInline({
387
+ fetchInlineUrls: false,
388
+ }),
389
+ {
390
+ name: "jsenv:postbuild",
391
+ appliesDuring: { build: true },
392
+ resolveUrl: (reference) => {
393
+ if (reference.specifier[0] === "#") {
394
+ reference.external = true
406
395
  }
407
- let rawUrl
408
- if (urlIsInsideOf(reference.url, buildDirectoryUrl)) {
409
- // rawUrl = rawUrls[reference.url] || reference.url
410
- const originalBuildUrl =
411
- buildUrlRedirections[referenceOriginalUrl]
412
- rawUrl = originalBuildUrl
413
- ? rawUrls[originalBuildUrl]
414
- : reference.url
415
- } else {
416
- rawUrl = reference.url
396
+ let url =
397
+ reference.specifier[0] === "/"
398
+ ? new URL(reference.specifier.slice(1), buildDirectoryUrl).href
399
+ : new URL(reference.specifier, reference.parentUrl).href
400
+ const urlRedirectedByBundle = bundleUrlRedirections[url]
401
+ if (urlRedirectedByBundle) {
402
+ return urlRedirectedByBundle
417
403
  }
418
- // the url info do not exists yet (it will be created after this "normalize" hook)
419
- // And the content will be generated when url is cooked by url graph loader.
420
- // Here we just want to reserve an url for that file
421
- const buildUrl = buildUrlsGenerator.generate(rawUrl, {
422
- urlInfo: {
423
- data: {
424
- ...reference.data,
425
- isWebWorkerEntryPoint:
426
- isWebWorkerEntryPointReference(reference),
427
- },
428
- type: reference.expectedType,
429
- subtype: reference.expectedSubtype,
430
- filename: reference.filename,
431
- },
432
- })
433
- buildUrlRedirections[originalBuildUrl] = buildUrl
434
- rawUrls[buildUrl] = rawUrl
435
- return buildUrl
436
- }
437
- if (reference.isInline) {
438
- const rawUrlInfo = GRAPH.find(rawGraph, (rawUrlInfo) => {
439
- if (!rawUrlInfo.isInline) {
440
- return false
404
+ const urlRedirected = rawUrlRedirections[url]
405
+ return urlRedirected || url
406
+ },
407
+ redirectUrl: (reference) => {
408
+ if (!reference.url.startsWith("file:")) {
409
+ return null
410
+ }
411
+ // already a build url
412
+ const rawUrl = rawUrls[reference.url]
413
+ if (rawUrl) {
414
+ return reference.url
415
+ }
416
+ // from rollup or postcss
417
+ const bundleUrlInfo = bundleUrlInfos[reference.url]
418
+ if (bundleUrlInfo) {
419
+ return reference.url
420
+ }
421
+ // from "js_module_as_js_classic":
422
+ // - injecting "?as_js_classic" for the first time
423
+ // - injecting "?as_js_classic" because the parentUrl has it
424
+ if (reference.original) {
425
+ const referenceOriginalUrl = reference.original.url
426
+ let originalBuildUrl
427
+ if (urlIsInsideOf(referenceOriginalUrl, buildDirectoryUrl)) {
428
+ originalBuildUrl = referenceOriginalUrl
429
+ } else {
430
+ originalBuildUrl = Object.keys(rawUrls).find(
431
+ (key) => rawUrls[key] === referenceOriginalUrl,
432
+ )
441
433
  }
442
- if (rawUrlInfo.content === reference.content) {
443
- return true
434
+ let rawUrl
435
+ if (urlIsInsideOf(reference.url, buildDirectoryUrl)) {
436
+ // rawUrl = rawUrls[reference.url] || reference.url
437
+ const originalBuildUrl =
438
+ buildUrlRedirections[referenceOriginalUrl]
439
+ rawUrl = originalBuildUrl
440
+ ? rawUrls[originalBuildUrl]
441
+ : reference.url
442
+ } else {
443
+ rawUrl = reference.url
444
444
  }
445
- if (rawUrlInfo.originalContent === reference.content) {
446
- return true
445
+ // the url info do not exists yet (it will be created after this "normalize" hook)
446
+ // And the content will be generated when url is cooked by url graph loader.
447
+ // Here we just want to reserve an url for that file
448
+ const buildUrl = buildUrlsGenerator.generate(rawUrl, {
449
+ urlInfo: {
450
+ data: {
451
+ ...reference.data,
452
+ isWebWorkerEntryPoint:
453
+ isWebWorkerEntryPointReference(reference),
454
+ },
455
+ type: reference.expectedType,
456
+ subtype: reference.expectedSubtype,
457
+ filename: reference.filename,
458
+ },
459
+ })
460
+ buildUrlRedirections[originalBuildUrl] = buildUrl
461
+ rawUrls[buildUrl] = rawUrl
462
+ return buildUrl
463
+ }
464
+ if (reference.isInline) {
465
+ const rawUrlInfo = GRAPH.find(rawGraph, (rawUrlInfo) => {
466
+ if (!rawUrlInfo.isInline) {
467
+ return false
468
+ }
469
+ if (rawUrlInfo.content === reference.content) {
470
+ return true
471
+ }
472
+ if (rawUrlInfo.originalContent === reference.content) {
473
+ return true
474
+ }
475
+ return false
476
+ })
477
+ const parentUrlInfo = finalGraph.getUrlInfo(reference.parentUrl)
478
+ if (!rawUrlInfo) {
479
+ // generated during final graph
480
+ // (happens for JSON.parse injected for import assertions for instance)
481
+ // throw new Error(`cannot find raw url for "${reference.url}"`)
482
+ return reference.url
447
483
  }
448
- return false
449
- })
450
- const parentUrlInfo = finalGraph.getUrlInfo(reference.parentUrl)
451
- if (!rawUrlInfo) {
452
- // generated during final graph
453
- // (happens for JSON.parse injected for import assertions for instance)
454
- // throw new Error(`cannot find raw url for "${reference.url}"`)
455
- return reference.url
484
+ const buildUrl = buildUrlsGenerator.generate(reference.url, {
485
+ urlInfo: rawUrlInfo,
486
+ parentUrlInfo,
487
+ })
488
+ rawUrls[buildUrl] = rawUrlInfo.url
489
+ return buildUrl
456
490
  }
457
- const buildUrl = buildUrlsGenerator.generate(reference.url, {
458
- urlInfo: rawUrlInfo,
459
- parentUrlInfo,
460
- })
461
- rawUrls[buildUrl] = rawUrlInfo.url
462
- return buildUrl
463
- }
464
- // from "js_module_as_js_classic":
465
- // - to inject "s.js"
466
- if (reference.injected) {
491
+ // from "js_module_as_js_classic":
492
+ // - to inject "s.js"
493
+ if (reference.injected) {
494
+ const buildUrl = buildUrlsGenerator.generate(reference.url, {
495
+ urlInfo: {
496
+ data: {},
497
+ type: "js_classic",
498
+ },
499
+ })
500
+ rawUrls[buildUrl] = reference.url
501
+ return buildUrl
502
+ }
503
+ const rawUrlInfo = rawGraph.getUrlInfo(reference.url)
504
+ // files from root directory but not given to rollup nor postcss
505
+ if (rawUrlInfo) {
506
+ const buildUrl = buildUrlsGenerator.generate(reference.url, {
507
+ urlInfo: rawUrlInfo,
508
+ })
509
+ rawUrls[buildUrl] = rawUrlInfo.url
510
+ return buildUrl
511
+ }
512
+ if (reference.type === "sourcemap_comment") {
513
+ // inherit parent build url
514
+ return generateSourcemapUrl(reference.parentUrl)
515
+ }
516
+ // files generated during the final graph:
517
+ // - sourcemaps
518
+ // const finalUrlInfo = finalGraph.getUrlInfo(url)
467
519
  const buildUrl = buildUrlsGenerator.generate(reference.url, {
468
520
  urlInfo: {
469
521
  data: {},
470
- type: "js_classic",
522
+ type: "asset",
471
523
  },
472
524
  })
473
- rawUrls[buildUrl] = reference.url
474
525
  return buildUrl
475
- }
476
- const rawUrlInfo = rawGraph.getUrlInfo(reference.url)
477
- // files from root directory but not given to rollup nor postcss
478
- if (rawUrlInfo) {
479
- const buildUrl = buildUrlsGenerator.generate(reference.url, {
480
- urlInfo: rawUrlInfo,
481
- })
482
- rawUrls[buildUrl] = rawUrlInfo.url
483
- return buildUrl
484
- }
485
- if (reference.type === "sourcemap_comment") {
486
- // inherit parent build url
487
- return generateSourcemapUrl(reference.parentUrl)
488
- }
489
- // files generated during the final graph:
490
- // - sourcemaps
491
- // const finalUrlInfo = finalGraph.getUrlInfo(url)
492
- const buildUrl = buildUrlsGenerator.generate(reference.url, {
493
- urlInfo: {
494
- data: {},
495
- type: "asset",
496
- },
497
- })
498
- return buildUrl
499
- },
500
- formatUrl: (reference) => {
501
- if (!reference.generatedUrl.startsWith("file:")) {
502
- return null
503
- }
504
- if (!urlIsInsideOf(reference.generatedUrl, buildDirectoryUrl)) {
505
- throw new Error(
506
- `urls should be inside build directory at this stage, found "${reference.url}"`,
507
- )
508
- }
509
- // remove eventual search params and hash
510
- const urlUntilPathname = asUrlUntilPathname(reference.generatedUrl)
511
- let specifier
512
- if (baseUrl === "./") {
513
- const relativeUrl = urlToRelativeUrl(
514
- urlUntilPathname,
515
- reference.parentUrl === rootDirectoryUrl
516
- ? buildDirectoryUrl
517
- : reference.parentUrl,
518
- )
519
- // ensure "./" on relative url (otherwise it could be a "bare specifier")
520
- specifier =
521
- relativeUrl[0] === "." ? relativeUrl : `./${relativeUrl}`
522
- } else {
523
- // if a file is in the same directory we could prefer the relative notation
524
- // but to keep things simple let's keep the "absolutely relative" to baseUrl for now
525
- specifier = `${baseUrl}${urlToRelativeUrl(
526
- urlUntilPathname,
527
- buildDirectoryUrl,
528
- )}`
529
- }
530
- buildUrls[specifier] = reference.generatedUrl
531
- return specifier
532
- },
533
- fetchUrlContent: async (finalUrlInfo, context) => {
534
- if (!finalUrlInfo.url.startsWith("file:")) {
535
- return { external: true }
536
- }
537
- const fromBundleOrRawGraph = (url) => {
538
- const bundleUrlInfo = bundleUrlInfos[url]
539
- if (bundleUrlInfo) {
540
- return bundleUrlInfo
526
+ },
527
+ formatUrl: (reference) => {
528
+ if (!reference.generatedUrl.startsWith("file:")) {
529
+ return null
530
+ }
531
+ if (!urlIsInsideOf(reference.generatedUrl, buildDirectoryUrl)) {
532
+ throw new Error(
533
+ `urls should be inside build directory at this stage, found "${reference.url}"`,
534
+ )
541
535
  }
542
- const rawUrl = rawUrls[url] || url
543
- const rawUrlInfo = rawGraph.getUrlInfo(rawUrl)
544
- if (!rawUrlInfo) {
545
- const originalBuildUrl = buildUrlRedirections[url]
546
- if (originalBuildUrl) {
547
- return fromBundleOrRawGraph(originalBuildUrl)
536
+ // remove eventual search params and hash
537
+ const urlUntilPathname = asUrlUntilPathname(reference.generatedUrl)
538
+ let specifier
539
+ if (baseUrl === "./") {
540
+ const relativeUrl = urlToRelativeUrl(
541
+ urlUntilPathname,
542
+ reference.parentUrl === rootDirectoryUrl
543
+ ? buildDirectoryUrl
544
+ : reference.parentUrl,
545
+ )
546
+ // ensure "./" on relative url (otherwise it could be a "bare specifier")
547
+ specifier =
548
+ relativeUrl[0] === "." ? relativeUrl : `./${relativeUrl}`
549
+ } else {
550
+ // if a file is in the same directory we could prefer the relative notation
551
+ // but to keep things simple let's keep the "absolutely relative" to baseUrl for now
552
+ specifier = `${baseUrl}${urlToRelativeUrl(
553
+ urlUntilPathname,
554
+ buildDirectoryUrl,
555
+ )}`
556
+ }
557
+ buildUrls[specifier] = reference.generatedUrl
558
+ return specifier
559
+ },
560
+ fetchUrlContent: async (finalUrlInfo, context) => {
561
+ if (!finalUrlInfo.url.startsWith("file:")) {
562
+ return { external: true }
563
+ }
564
+ const fromBundleOrRawGraph = (url) => {
565
+ const bundleUrlInfo = bundleUrlInfos[url]
566
+ if (bundleUrlInfo) {
567
+ return bundleUrlInfo
568
+ }
569
+ const rawUrl = rawUrls[url] || url
570
+ const rawUrlInfo = rawGraph.getUrlInfo(rawUrl)
571
+ if (!rawUrlInfo) {
572
+ const originalBuildUrl = buildUrlRedirections[url]
573
+ if (originalBuildUrl) {
574
+ return fromBundleOrRawGraph(originalBuildUrl)
575
+ }
576
+ throw new Error(`Cannot find url`)
548
577
  }
549
- throw new Error(`Cannot find url`)
578
+ if (rawUrlInfo.isInline) {
579
+ // Inline content, such as <script> inside html, is transformed during the previous phase.
580
+ // If we read the inline content it would be considered as the original content.
581
+ // - It could be "fixed" by taking into account sourcemap and consider sourcemap sources
582
+ // as the original content.
583
+ // - But it would not work when sourcemap are not generated
584
+ // - would be a bit slower
585
+ // - So instead of reading the inline content directly, we search into raw graph
586
+ // to get "originalContent" and "sourcemap"
587
+ finalUrlInfo.type = rawUrlInfo.type
588
+ finalUrlInfo.subtype = rawUrlInfo.subtype
589
+ return rawUrlInfo
590
+ }
591
+ return rawUrlInfo
550
592
  }
551
- if (rawUrlInfo.isInline) {
552
- // Inline content, such as <script> inside html, is transformed during the previous phase.
553
- // If we read the inline content it would be considered as the original content.
554
- // - It could be "fixed" by taking into account sourcemap and consider sourcemap sources
555
- // as the original content.
556
- // - But it would not work when sourcemap are not generated
557
- // - would be a bit slower
558
- // - So instead of reading the inline content directly, we search into raw graph
559
- // to get "originalContent" and "sourcemap"
560
- finalUrlInfo.type = rawUrlInfo.type
561
- finalUrlInfo.subtype = rawUrlInfo.subtype
593
+ // reference injected during "postbuild":
594
+ // - happens for "as_js_classic" injecting "s.js"
595
+ if (context.reference.injected) {
596
+ const [ref, rawUrlInfo] = rawGraphKitchen.injectReference({
597
+ type: context.reference.type,
598
+ expectedType: context.reference.expectedType,
599
+ expectedSubtype: context.reference.expectedSubtype,
600
+ parentUrl: rawUrls[context.reference.parentUrl],
601
+ specifier: context.reference.specifier,
602
+ injected: true,
603
+ })
604
+ await rawGraphKitchen.cook({
605
+ reference: ref,
606
+ urlInfo: rawUrlInfo,
607
+ })
562
608
  return rawUrlInfo
563
609
  }
564
- return rawUrlInfo
565
- }
566
- // reference injected during "postbuild":
567
- // - happens for "as_js_classic" injecting "s.js"
568
- if (context.reference.injected) {
569
- const [ref, rawUrlInfo] = rawGraphKitchen.injectReference({
570
- type: context.reference.type,
571
- expectedType: context.reference.expectedType,
572
- expectedSubtype: context.reference.expectedSubtype,
573
- parentUrl: rawUrls[context.reference.parentUrl],
574
- specifier: context.reference.specifier,
575
- injected: true,
576
- })
577
- await rawGraphKitchen.cook({
578
- reference: ref,
579
- urlInfo: rawUrlInfo,
580
- })
581
- return rawUrlInfo
582
- }
583
- // reference updated during "postbuild":
584
- // - happens for "as_js_classic"
585
- if (context.reference.original) {
586
- return fromBundleOrRawGraph(context.reference.original.url)
587
- }
588
- return fromBundleOrRawGraph(finalUrlInfo.url)
610
+ // reference updated during "postbuild":
611
+ // - happens for "as_js_classic"
612
+ if (context.reference.original) {
613
+ return fromBundleOrRawGraph(context.reference.original.url)
614
+ }
615
+ return fromBundleOrRawGraph(finalUrlInfo.url)
616
+ },
589
617
  },
590
- },
591
- {
592
- name: "jsenv:optimize",
593
- appliesDuring: { build: true },
594
- finalizeUrlContent: async (urlInfo, context) => {
595
- if (optimizeUrlContentHooks.length) {
596
- await rawGraphKitchen.pluginController.callAsyncHooks(
597
- "optimizeUrlContent",
598
- urlInfo,
599
- context,
600
- async (optimizeReturnValue) => {
601
- await finalGraphKitchen.urlInfoTransformer.applyFinalTransformations(
602
- urlInfo,
603
- optimizeReturnValue,
604
- )
605
- },
606
- )
607
- }
618
+ {
619
+ name: "jsenv:optimize",
620
+ appliesDuring: { build: true },
621
+ finalizeUrlContent: async (urlInfo, context) => {
622
+ if (optimizeUrlContentHooks.length) {
623
+ await rawGraphKitchen.pluginController.callAsyncHooks(
624
+ "optimizeUrlContent",
625
+ urlInfo,
626
+ context,
627
+ async (optimizeReturnValue) => {
628
+ await finalGraphKitchen.urlInfoTransformer.applyFinalTransformations(
629
+ urlInfo,
630
+ optimizeReturnValue,
631
+ )
632
+ },
633
+ )
634
+ }
635
+ },
608
636
  },
609
- },
610
- ],
611
- })
612
- const buildTask = createTaskLog(logger, "build")
613
- const postBuildEntryUrls = []
614
- try {
615
- await loadUrlGraph({
616
- urlGraph: finalGraph,
617
- kitchen: finalGraphKitchen,
618
- outDirectoryUrl: new URL(".jsenv/postbuild/", rootDirectoryUrl),
619
- skipRessourceHint: true,
620
- startLoading: (cookEntryFile) => {
621
- entryUrls.forEach((entryUrl) => {
622
- const [, postBuildEntryUrlInfo] = cookEntryFile({
623
- trace: `entryPoint`,
624
- type: "entry_point",
625
- specifier: entryUrl,
626
- })
627
- postBuildEntryUrls.push(postBuildEntryUrlInfo.url)
628
- })
629
- },
637
+ ],
630
638
  })
631
- } catch (e) {
632
- buildTask.fail()
633
- throw e
634
- }
635
- buildTask.done()
639
+ const buildTask = createTaskLog(logger, "build")
640
+ const postBuildEntryUrls = []
641
+ try {
642
+ await loadUrlGraph({
643
+ operation: buildOperation,
644
+ urlGraph: finalGraph,
645
+ kitchen: finalGraphKitchen,
646
+ outDirectoryUrl: new URL(".jsenv/postbuild/", rootDirectoryUrl),
647
+ skipRessourceHint: true,
648
+ startLoading: (cookEntryFile) => {
649
+ entryUrls.forEach((entryUrl) => {
650
+ const [, postBuildEntryUrlInfo] = cookEntryFile({
651
+ trace: `entryPoint`,
652
+ type: "entry_point",
653
+ specifier: entryUrl,
654
+ })
655
+ postBuildEntryUrls.push(postBuildEntryUrlInfo.url)
656
+ })
657
+ },
658
+ })
659
+ } catch (e) {
660
+ buildTask.fail()
661
+ throw e
662
+ }
663
+ buildTask.done()
636
664
 
637
- logger.debug(
638
- `graph urls pre-versioning:
665
+ logger.debug(
666
+ `graph urls pre-versioning:
639
667
  ${Object.keys(finalGraph.urlInfos).join("\n")}`,
640
- )
641
- if (versioning) {
642
- await applyUrlVersioning({
643
- logger,
644
- buildDirectoryUrl,
668
+ )
669
+ if (versioning) {
670
+ await applyUrlVersioning({
671
+ buildOperation,
672
+ logger,
673
+ buildDirectoryUrl,
674
+ rawUrls,
675
+ buildUrls,
676
+ baseUrl,
677
+ postBuildEntryUrls,
678
+ sourcemaps,
679
+ runtimeCompat,
680
+ rawGraph,
681
+ finalGraph,
682
+ finalGraphKitchen,
683
+ lineBreakNormalization,
684
+ versioningMethod,
685
+ })
686
+ }
687
+ GRAPH.forEach(finalGraph, (urlInfo) => {
688
+ if (!urlInfo.url.startsWith("file:")) {
689
+ return
690
+ }
691
+ if (urlInfo.external) {
692
+ return
693
+ }
694
+ if (urlInfo.type === "html") {
695
+ const htmlAst = parseHtmlString(urlInfo.content, {
696
+ storeOriginalPositions: false,
697
+ })
698
+ urlInfo.content = stringifyHtmlAst(htmlAst, {
699
+ removeOriginalPositionAttributes: true,
700
+ })
701
+ }
702
+ const version = urlInfo.data.version
703
+ const useVersionedUrl = version && canUseVersionedUrl(urlInfo, finalGraph)
704
+ const buildUrl = useVersionedUrl ? urlInfo.data.versionedUrl : urlInfo.url
705
+ const buildUrlSpecifier = Object.keys(buildUrls).find(
706
+ (key) => buildUrls[key] === buildUrl,
707
+ )
708
+ urlInfo.data.buildUrl = buildUrl
709
+ urlInfo.data.buildUrlIsVersioned = useVersionedUrl
710
+ urlInfo.data.buildUrlSpecifier = buildUrlSpecifier
711
+ })
712
+ await resyncRessourceHints({
713
+ finalGraphKitchen,
714
+ finalGraph,
645
715
  rawUrls,
646
716
  buildUrls,
647
- baseUrl,
648
- postBuildEntryUrls,
649
- sourcemaps,
650
- runtimeCompat,
651
- rawGraph,
652
- finalGraph,
717
+ })
718
+ buildOperation.throwIfAborted()
719
+ const cleanupActions = []
720
+ GRAPH.forEach(finalGraph, (urlInfo) => {
721
+ // nothing uses this url anymore
722
+ // - versioning update inline content
723
+ // - file converted for import assertion of js_classic conversion
724
+ if (
725
+ !urlInfo.data.isEntryPoint &&
726
+ urlInfo.type !== "sourcemap" &&
727
+ urlInfo.dependents.size === 0
728
+ ) {
729
+ cleanupActions.push(() => {
730
+ finalGraph.deleteUrlInfo(urlInfo.url)
731
+ })
732
+ }
733
+ })
734
+ cleanupActions.forEach((cleanupAction) => cleanupAction())
735
+ await injectServiceWorkerUrls({
653
736
  finalGraphKitchen,
737
+ finalGraph,
654
738
  lineBreakNormalization,
655
- versioningMethod,
656
739
  })
657
- }
658
- GRAPH.forEach(finalGraph, (urlInfo) => {
659
- if (!urlInfo.url.startsWith("file:")) {
660
- return
661
- }
662
- if (urlInfo.external) {
663
- return
664
- }
665
- if (urlInfo.type === "html") {
666
- const htmlAst = parseHtmlString(urlInfo.content, {
667
- storeOriginalPositions: false,
668
- })
669
- urlInfo.content = stringifyHtmlAst(htmlAst, {
670
- removeOriginalPositionAttributes: true,
671
- })
672
- }
673
- const version = urlInfo.data.version
674
- const useVersionedUrl = version && canUseVersionedUrl(urlInfo, finalGraph)
675
- const buildUrl = useVersionedUrl ? urlInfo.data.versionedUrl : urlInfo.url
676
- const buildUrlSpecifier = Object.keys(buildUrls).find(
677
- (key) => buildUrls[key] === buildUrl,
678
- )
679
- urlInfo.data.buildUrl = buildUrl
680
- urlInfo.data.buildUrlIsVersioned = useVersionedUrl
681
- urlInfo.data.buildUrlSpecifier = buildUrlSpecifier
682
- })
683
- await resyncRessourceHints({
684
- finalGraphKitchen,
685
- finalGraph,
686
- rawUrls,
687
- buildUrls,
688
- })
689
- const cleanupActions = []
690
- GRAPH.forEach(finalGraph, (urlInfo) => {
691
- // nothing uses this url anymore
692
- // - versioning update inline content
693
- // - file converted for import assertion of js_classic conversion
694
- if (
695
- !urlInfo.data.isEntryPoint &&
696
- urlInfo.type !== "sourcemap" &&
697
- urlInfo.dependents.size === 0
698
- ) {
699
- cleanupActions.push(() => {
700
- finalGraph.deleteUrlInfo(urlInfo.url)
701
- })
702
- }
703
- })
704
- cleanupActions.forEach((cleanupAction) => cleanupAction())
705
- await injectServiceWorkerUrls({
706
- finalGraphKitchen,
707
- finalGraph,
708
- lineBreakNormalization,
709
- })
710
- logger.debug(
711
- `graph urls post-versioning:
712
- ${Object.keys(finalGraph.urlInfos).join("\n")}`,
713
- )
740
+ buildOperation.throwIfAborted()
714
741
 
715
- const buildManifest = {}
716
- const buildFileContents = {}
717
- const buildInlineContents = {}
718
- GRAPH.forEach(finalGraph, (urlInfo) => {
719
- if (urlInfo.external) {
720
- return
721
- }
722
- if (urlInfo.url.startsWith("data:")) {
723
- return
724
- }
725
- const buildRelativeUrl = urlToRelativeUrl(
726
- urlInfo.data.buildUrl,
727
- buildDirectoryUrl,
728
- )
729
- if (urlInfo.isInline) {
730
- buildInlineContents[buildRelativeUrl] = urlInfo.content
731
- } else {
732
- buildFileContents[buildRelativeUrl] = urlInfo.content
733
- const buildRelativeUrlWithoutVersioning = urlToRelativeUrl(
734
- urlInfo.url,
742
+ const buildManifest = {}
743
+ const buildFileContents = {}
744
+ const buildInlineContents = {}
745
+ GRAPH.forEach(finalGraph, (urlInfo) => {
746
+ if (urlInfo.external) {
747
+ return
748
+ }
749
+ if (urlInfo.url.startsWith("data:")) {
750
+ return
751
+ }
752
+ const buildRelativeUrl = urlToRelativeUrl(
753
+ urlInfo.data.buildUrl,
735
754
  buildDirectoryUrl,
736
755
  )
737
- buildManifest[buildRelativeUrlWithoutVersioning] = buildRelativeUrl
738
- }
739
- })
740
- if (writeOnFileSystem) {
741
- if (buildDirectoryClean) {
742
- await ensureEmptyDirectory(buildDirectoryUrl)
743
- }
744
- const buildRelativeUrls = Object.keys(buildFileContents)
745
- await Promise.all(
746
- buildRelativeUrls.map(async (buildRelativeUrl) => {
747
- await writeFile(
748
- new URL(buildRelativeUrl, buildDirectoryUrl),
749
- buildFileContents[buildRelativeUrl],
756
+ if (urlInfo.isInline) {
757
+ buildInlineContents[buildRelativeUrl] = urlInfo.content
758
+ } else {
759
+ buildFileContents[buildRelativeUrl] = urlInfo.content
760
+ const buildRelativeUrlWithoutVersioning = urlToRelativeUrl(
761
+ urlInfo.url,
762
+ buildDirectoryUrl,
750
763
  )
751
- }),
752
- )
753
- if (versioning && assetManifest && Object.keys(buildManifest).length) {
754
- await writeFile(
755
- new URL(assetManifestFileRelativeUrl, buildDirectoryUrl),
756
- JSON.stringify(buildManifest, null, " "),
764
+ buildManifest[buildRelativeUrlWithoutVersioning] = buildRelativeUrl
765
+ }
766
+ })
767
+ if (writeOnFileSystem) {
768
+ if (buildDirectoryClean) {
769
+ await ensureEmptyDirectory(buildDirectoryUrl)
770
+ }
771
+ const buildRelativeUrls = Object.keys(buildFileContents)
772
+ await Promise.all(
773
+ buildRelativeUrls.map(async (buildRelativeUrl) => {
774
+ await writeFile(
775
+ new URL(buildRelativeUrl, buildDirectoryUrl),
776
+ buildFileContents[buildRelativeUrl],
777
+ )
778
+ }),
757
779
  )
780
+ if (versioning && assetManifest && Object.keys(buildManifest).length) {
781
+ await writeFile(
782
+ new URL(assetManifestFileRelativeUrl, buildDirectoryUrl),
783
+ JSON.stringify(buildManifest, null, " "),
784
+ )
785
+ }
786
+ }
787
+ logger.info(createUrlGraphSummary(finalGraph, { title: "build files" }))
788
+ return {
789
+ buildFileContents,
790
+ buildInlineContents,
791
+ buildManifest,
758
792
  }
759
793
  }
760
- logger.info(createUrlGraphSummary(finalGraph, { title: "build files" }))
761
- return {
762
- buildFileContents,
763
- buildInlineContents,
764
- buildManifest,
794
+
795
+ if (!watch) {
796
+ return runBuild({ signal: operation.signal, logLevel })
765
797
  }
798
+
799
+ const watchLogger = createLogger({ logLevel: "info" })
800
+ let buildAbortController
801
+ let watchFilesTask
802
+ const startBuild = async () => {
803
+ const buildTask = createTaskLog(watchLogger, "build")
804
+ buildAbortController = new AbortController()
805
+ try {
806
+ await runBuild({ signal: buildAbortController.signal, logLevel: "warn" })
807
+ buildTask.done()
808
+ watchFilesTask = createTaskLog(watchLogger, "watch files")
809
+ } catch (e) {
810
+ if (Abort.isAbortError(e)) {
811
+ buildTask.fail(`build aborted`)
812
+ } else if (e.code === "PARSE_ERROR") {
813
+ buildTask.fail()
814
+ watchLogger.error(e.stack)
815
+ watchFilesTask = createTaskLog(watchLogger, "watch files")
816
+ } else {
817
+ buildTask.fail()
818
+ throw e
819
+ }
820
+ }
821
+ }
822
+
823
+ startBuild()
824
+ let startTimeout
825
+ const clientFileChangeCallback = ({ relativeUrl, event }) => {
826
+ const url = new URL(relativeUrl, rootDirectoryUrl).href
827
+ if (watchFilesTask) {
828
+ watchFilesTask.happen(`${url.slice(rootDirectoryUrl.length)} ${event}`)
829
+ watchFilesTask = null
830
+ }
831
+ buildAbortController.abort()
832
+ // setTimeout is to ensure the abortController.abort() above
833
+ // is properly taken into account so that logs about abort comes first
834
+ // then logs about re-running the build happens
835
+ clearTimeout(startTimeout)
836
+ startTimeout = setTimeout(startBuild, 20)
837
+ }
838
+ const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
839
+ watchPatterns: clientFiles,
840
+ cooldownBetweenFileEvents,
841
+ keepProcessAlive: true,
842
+ recursive: true,
843
+ added: ({ relativeUrl }) => {
844
+ clientFileChangeCallback({ relativeUrl, event: "added" })
845
+ },
846
+ updated: ({ relativeUrl }) => {
847
+ clientFileChangeCallback({ relativeUrl, event: "modified" })
848
+ },
849
+ removed: ({ relativeUrl }) => {
850
+ clientFileChangeCallback({ relativeUrl, event: "removed" })
851
+ },
852
+ })
853
+ operation.addAbortCallback(() => {
854
+ stopWatchingClientFiles()
855
+ })
856
+ return stopWatchingClientFiles
766
857
  }
767
858
 
768
859
  const applyUrlVersioning = async ({
860
+ buildOperation,
769
861
  logger,
770
862
  buildDirectoryUrl,
771
863
  rawUrls,
@@ -968,6 +1060,7 @@ const applyUrlVersioning = async ({
968
1060
  ],
969
1061
  })
970
1062
  await loadUrlGraph({
1063
+ operation: buildOperation,
971
1064
  urlGraph: finalGraph,
972
1065
  kitchen: versioningKitchen,
973
1066
  skipRessourceHint: true,