@jsenv/core 27.0.0-alpha.61 → 27.0.0-alpha.64

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.
Files changed (43) hide show
  1. package/dist/event_source_client.js +3 -3
  2. package/dist/event_source_client.js.map +7 -8
  3. package/dist/html_supervisor_installer.js +1 -1
  4. package/dist/html_supervisor_installer.js.map +2 -2
  5. package/dist/s.js +626 -0
  6. package/dist/s.js.map +207 -0
  7. package/main.js +1 -0
  8. package/package.json +8 -8
  9. package/src/build/build.js +23 -8
  10. package/src/build/inject_global_version_mappings.js +18 -5
  11. package/src/build/start_build_server.js +49 -32
  12. package/src/dev/start_dev_server.js +11 -3
  13. package/src/execute/runtimes/browsers/from_playwright.js +10 -0
  14. package/src/execute/runtimes/node/node_process.js +8 -0
  15. package/src/omega/kitchen.js +57 -150
  16. package/src/omega/server/file_service.js +34 -17
  17. package/src/omega/url_graph/url_graph_load.js +10 -17
  18. package/src/omega/url_graph/url_info_transformations.js +1 -4
  19. package/src/omega/url_graph.js +6 -2
  20. package/src/omega/url_specifier_encoding.js +59 -0
  21. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +7 -3
  22. package/src/plugins/commonjs_globals/jsenv_plugin_commonjs_globals.js +1 -1
  23. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +1 -1
  24. package/src/plugins/importmap/jsenv_plugin_importmap.js +2 -4
  25. package/src/plugins/inject_globals/jsenv_plugin_inject_globals.js +51 -42
  26. package/src/plugins/inline/jsenv_plugin_data_urls.js +1 -4
  27. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +3 -5
  28. package/src/plugins/inline/jsenv_plugin_inline_query_param.js +1 -4
  29. package/src/plugins/inline/jsenv_plugin_js_inline_content.js +1 -4
  30. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +4 -0
  31. package/src/plugins/plugins.js +4 -1
  32. package/src/plugins/transpilation/as_js_classic/client/s.js +362 -807
  33. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +28 -12
  34. package/src/plugins/transpilation/as_js_classic/{jsenv_plugin_workers_type_module_as_classic.js → jsenv_plugin_as_js_classic_workers.js} +2 -2
  35. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_script_type_module_as_classic.js +165 -133
  36. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +5 -2
  37. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +1 -2
  38. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +4 -1
  39. package/src/test/execute_plan.js +39 -14
  40. package/src/test/execute_test_plan.js +2 -0
  41. package/src/test/logs_file_execution.js +47 -38
  42. package/src/plugins/transpilation/as_js_classic/client/s.js.md +0 -1
  43. package/src/plugins/transpilation/fetch_original_url_info.js +0 -30
@@ -216,9 +216,14 @@ export const createRuntimeFromPlaywright = ({
216
216
  },
217
217
  })
218
218
  cleanupCallbackList.add(removeConsoleListener)
219
+ const actionOperation = Abort.startOperation()
220
+ actionOperation.addAbortSignal(signal)
219
221
  const winnerPromise = new Promise((resolve, reject) => {
220
222
  raceCallbacks(
221
223
  {
224
+ aborted: (cb) => {
225
+ return actionOperation.addAbortCallback(cb)
226
+ },
222
227
  // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-error
223
228
  error: (cb) => {
224
229
  return registerEvent({
@@ -317,6 +322,11 @@ export const createRuntimeFromPlaywright = ({
317
322
 
318
323
  const getResult = async () => {
319
324
  const winner = await winnerPromise
325
+ if (winner.name === "aborted") {
326
+ return {
327
+ status: "aborted",
328
+ }
329
+ }
320
330
  if (winner.name === "error" || winner.name === "pageerror") {
321
331
  const error = winner.data
322
332
  return {
@@ -162,6 +162,9 @@ nodeProcess.run = async ({
162
162
  const winnerPromise = new Promise((resolve) => {
163
163
  raceCallbacks(
164
164
  {
165
+ aborted: (cb) => {
166
+ return actionOperation.addAbortCallback(cb)
167
+ },
165
168
  // https://nodejs.org/api/child_process.html#child_process_event_disconnect
166
169
  // disconnect: (cb) => {
167
170
  // return onceProcessEvent(childProcess, "disconnect", cb)
@@ -194,6 +197,11 @@ nodeProcess.run = async ({
194
197
  },
195
198
  })
196
199
  const winner = await winnerPromise
200
+ if (winner.name === "aborted") {
201
+ return {
202
+ status: "aborted",
203
+ }
204
+ }
197
205
  if (winner.name === "error") {
198
206
  const error = winner.data
199
207
  removeOutputListener()
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  urlIsInsideOf,
3
3
  writeFileSync,
4
- isFileSystemPath,
5
- fileSystemPathToUrl,
6
4
  moveUrl,
7
5
  ensureWindowsDriveLetter,
8
6
  } from "@jsenv/filesystem"
9
7
  import { createDetailedMessage } from "@jsenv/logger"
10
8
 
9
+ import { getCallerPosition } from "@jsenv/utils/src/caller_position.js"
11
10
  import { stringifyUrlSite } from "@jsenv/utils/urls/url_trace.js"
12
11
  import { CONTENT_TYPE } from "@jsenv/utils/content_type/content_type.js"
13
12
  import { normalizeUrl, setUrlFilename } from "@jsenv/utils/urls/url_utils.js"
14
13
 
15
14
  import { createPluginController } from "../plugins/plugin_controller.js"
15
+ import { urlSpecifierEncoding } from "./url_specifier_encoding.js"
16
16
  import { createUrlInfoTransformer } from "./url_graph/url_info_transformations.js"
17
17
  import { RUNTIME_COMPAT } from "./compat/runtime_compat.js"
18
18
  import { defaultRuntimeCompat } from "./compat/default_runtime_compat.js"
@@ -54,7 +54,7 @@ export const createKitchen = ({
54
54
  scenario,
55
55
  })
56
56
  const jsenvDirectoryUrl = new URL(".jsenv/", rootDirectoryUrl).href
57
- const baseContext = {
57
+ const kitchenContext = {
58
58
  signal,
59
59
  logger,
60
60
  rootDirectoryUrl,
@@ -145,7 +145,7 @@ export const createKitchen = ({
145
145
  let resolvedUrl = pluginController.callHooksUntil(
146
146
  "resolveUrl",
147
147
  reference,
148
- baseContext,
148
+ kitchenContext,
149
149
  )
150
150
  if (!resolvedUrl) {
151
151
  throw new Error(`NO_RESOLVE`)
@@ -160,7 +160,7 @@ export const createKitchen = ({
160
160
  pluginController.callHooks(
161
161
  "redirectUrl",
162
162
  reference,
163
- baseContext,
163
+ kitchenContext,
164
164
  (returnValue) => {
165
165
  const normalizedReturnValue = normalizeUrl(returnValue)
166
166
  if (normalizedReturnValue === reference.url) {
@@ -173,7 +173,7 @@ export const createKitchen = ({
173
173
  )
174
174
 
175
175
  const urlInfo = urlGraph.reuseOrCreateUrlInfo(reference.url)
176
- applyReferenceEffectsOnUrlInfo(reference, urlInfo, baseContext)
176
+ applyReferenceEffectsOnUrlInfo(reference, urlInfo, kitchenContext)
177
177
 
178
178
  const referenceUrlObject = new URL(reference.url)
179
179
  reference.searchParams = referenceUrlObject.searchParams
@@ -187,7 +187,7 @@ export const createKitchen = ({
187
187
  pluginController.callHooks(
188
188
  "transformUrlSearchParams",
189
189
  reference,
190
- baseContext,
190
+ kitchenContext,
191
191
  (returnValue) => {
192
192
  Object.keys(returnValue).forEach((key) => {
193
193
  referenceUrlObject.searchParams.set(key, returnValue[key])
@@ -198,10 +198,10 @@ export const createKitchen = ({
198
198
  const returnValue = pluginController.callHooksUntil(
199
199
  "formatUrl",
200
200
  reference,
201
- baseContext,
201
+ kitchenContext,
202
202
  )
203
203
  reference.generatedSpecifier = returnValue || reference.generatedUrl
204
- reference.generatedSpecifier = urlSpecifierFormat.encode(reference)
204
+ reference.generatedSpecifier = urlSpecifierEncoding.encode(reference)
205
205
  return urlInfo
206
206
  } catch (error) {
207
207
  throw createResolveUrlError({
@@ -211,7 +211,7 @@ export const createKitchen = ({
211
211
  })
212
212
  }
213
213
  }
214
- baseContext.resolveReference = resolveReference
214
+ kitchenContext.resolveReference = resolveReference
215
215
  const urlInfoTransformer = createUrlInfoTransformer({
216
216
  logger,
217
217
  urlGraph,
@@ -258,7 +258,7 @@ export const createKitchen = ({
258
258
  },
259
259
  })
260
260
 
261
- const fetchUrlContent = async ({ reference, urlInfo, context }) => {
261
+ const fetchUrlContent = async (urlInfo, { reference, context }) => {
262
262
  if (reference.external) {
263
263
  urlInfo.external = true
264
264
  return
@@ -343,40 +343,32 @@ export const createKitchen = ({
343
343
  await urlInfoTransformer.initTransformations(urlInfo, context)
344
344
  }
345
345
 
346
- const _cook = async ({
347
- reference,
348
- urlInfo,
349
- outDirectoryUrl,
346
+ const _cook = async (urlInfo, dishContext) => {
350
347
  // during dev/test clientRuntimeCompat is a single runtime
351
348
  // during build clientRuntimeCompat is runtimeCompat
352
- clientRuntimeCompat = runtimeCompat,
353
- cookDuringCook = cook,
354
- }) => {
355
- baseContext.isSupportedOnCurrentClients = (feature) => {
349
+ const { clientRuntimeCompat = runtimeCompat } = dishContext
350
+ kitchenContext.isSupportedOnCurrentClients = (feature) => {
356
351
  return RUNTIME_COMPAT.isSupported(clientRuntimeCompat, feature)
357
352
  }
358
353
  const context = {
359
- ...baseContext,
360
- reference,
361
- outDirectoryUrl,
354
+ ...kitchenContext,
355
+ ...dishContext,
362
356
  clientRuntimeCompat,
363
- cook: (params) => {
364
- return cookDuringCook({
365
- outDirectoryUrl,
366
- clientRuntimeCompat,
367
- ...params,
368
- })
369
- },
370
- fetchUrlContent: (params) => {
371
- return fetchUrlContent({
372
- context,
373
- ...params,
374
- })
375
- },
357
+ }
358
+ const { cookDuringCook = cook } = dishContext
359
+ context.cook = (urlInfo, nestedDishContext) => {
360
+ return cookDuringCook(urlInfo, {
361
+ outDirectoryUrl: dishContext.outDirectoryUrl,
362
+ clientRuntimeCompat: dishContext.clientRuntimeCompat,
363
+ ...nestedDishContext,
364
+ })
365
+ }
366
+ context.fetchUrlContent = (urlInfo, { reference }) => {
367
+ return fetchUrlContent(urlInfo, { reference, context })
376
368
  }
377
369
 
378
370
  // "fetchUrlContent" hook
379
- await fetchUrlContent({ reference, urlInfo, context })
371
+ await fetchUrlContent(urlInfo, { reference: context.reference, context })
380
372
  if (urlInfo.external) {
381
373
  return
382
374
  }
@@ -504,21 +496,11 @@ export const createKitchen = ({
504
496
  },
505
497
  inject: ({ trace, ...rest }) => {
506
498
  if (trace === undefined) {
507
- const { prepareStackTrace } = Error
508
- Error.prepareStackTrace = (error, stack) => {
509
- Error.prepareStackTrace = prepareStackTrace
510
- return stack
511
- }
512
- const { stack } = new Error()
513
- const callerCallsite = stack[1]
514
- const fileName = callerCallsite.getFileName()
499
+ const { url, line, column } = getCallerPosition()
515
500
  trace = stringifyUrlSite({
516
- url:
517
- fileName && isFileSystemPath(fileName)
518
- ? fileSystemPathToUrl(fileName)
519
- : fileName,
520
- line: callerCallsite.getLineNumber(),
521
- column: callerCallsite.getColumnNumber(),
501
+ url,
502
+ line,
503
+ column,
522
504
  })
523
505
  }
524
506
  return addReference({
@@ -558,7 +540,7 @@ export const createKitchen = ({
558
540
  } catch (error) {
559
541
  throw createTransformUrlContentError({
560
542
  pluginController,
561
- reference,
543
+ reference: context.reference,
562
544
  urlInfo,
563
545
  error,
564
546
  })
@@ -581,7 +563,7 @@ export const createKitchen = ({
581
563
  } catch (error) {
582
564
  throw createFinalizeUrlContentError({
583
565
  pluginController,
584
- reference,
566
+ reference: context.reference,
585
567
  urlInfo,
586
568
  error,
587
569
  })
@@ -609,44 +591,30 @@ export const createKitchen = ({
609
591
  },
610
592
  )
611
593
  }
612
- const cook = memoizeCook(async ({ urlInfo, outDirectoryUrl, ...rest }) => {
613
- outDirectoryUrl = outDirectoryUrl ? String(outDirectoryUrl) : undefined
614
-
615
- const writeFiles = ({ gotError }) => {
616
- if (!writeGeneratedFiles || !outDirectoryUrl) {
617
- return
618
- }
619
- const { generatedUrl } = urlInfo
620
- // writing result inside ".jsenv" directory (debug purposes)
621
- if (!generatedUrl || !generatedUrl.startsWith("file:")) {
622
- return
623
- }
624
- // use writeSync to avoid concurrency on writing the file
625
- const write = gotError ? writeFileSync : writeFileSync
626
- write(new URL(generatedUrl), urlInfo.content)
627
- const { sourcemapGeneratedUrl, sourcemap } = urlInfo
628
- if (sourcemapGeneratedUrl && sourcemap) {
629
- write(
630
- new URL(sourcemapGeneratedUrl),
631
- JSON.stringify(sourcemap, null, " "),
632
- )
633
- }
594
+ const cook = memoizeCook(async (urlInfo, context) => {
595
+ if (!writeGeneratedFiles || !context.outDirectoryUrl) {
596
+ await _cook(urlInfo, context)
597
+ return
634
598
  }
635
-
599
+ // writing result inside ".jsenv" directory (debug purposes)
636
600
  try {
637
- await _cook({
638
- urlInfo,
639
- outDirectoryUrl,
640
- ...rest,
641
- })
642
- writeFiles({ gotError: false })
643
- } catch (e) {
644
- writeFiles({ gotError: true })
645
- throw e
601
+ await _cook(urlInfo, context)
602
+ } finally {
603
+ const { generatedUrl } = urlInfo
604
+ if (generatedUrl && generatedUrl.startsWith("file:")) {
605
+ writeFileSync(new URL(generatedUrl), urlInfo.content)
606
+ const { sourcemapGeneratedUrl, sourcemap } = urlInfo
607
+ if (sourcemapGeneratedUrl && sourcemap) {
608
+ writeFileSync(
609
+ new URL(sourcemapGeneratedUrl),
610
+ JSON.stringify(sourcemap, null, " "),
611
+ )
612
+ }
613
+ }
646
614
  }
647
615
  })
648
-
649
- baseContext.cook = cook
616
+ kitchenContext.fetchUrlContent = fetchUrlContent
617
+ kitchenContext.cook = cook
650
618
 
651
619
  const prepareEntryPoint = (params) => {
652
620
  const entryReference = createReference(params)
@@ -665,7 +633,7 @@ export const createKitchen = ({
665
633
  urlInfoTransformer,
666
634
  rootDirectoryUrl,
667
635
  jsenvDirectoryUrl,
668
- baseContext,
636
+ kitchenContext,
669
637
  cook,
670
638
  prepareEntryPoint,
671
639
  injectReference,
@@ -674,8 +642,7 @@ export const createKitchen = ({
674
642
 
675
643
  const memoizeCook = (cook) => {
676
644
  const pendingDishes = new Map()
677
- return async (params) => {
678
- const { urlInfo } = params
645
+ return async (urlInfo, context) => {
679
646
  const { url, modifiedTimestamp } = urlInfo
680
647
  const pendingDish = pendingDishes.get(url)
681
648
  if (pendingDish) {
@@ -690,7 +657,7 @@ const memoizeCook = (cook) => {
690
657
  pendingDishes.delete(url)
691
658
  }
692
659
  const timestamp = Date.now()
693
- const promise = cook(params)
660
+ const promise = cook(urlInfo, context)
694
661
  pendingDishes.set(url, {
695
662
  timestamp,
696
663
  promise,
@@ -844,66 +811,6 @@ const determineFileUrlForOutDirectory = ({ urlInfo, context }) => {
844
811
  })
845
812
  }
846
813
 
847
- const urlSpecifierFormat = {
848
- encode: (reference) => {
849
- const { generatedSpecifier } = reference
850
- if (generatedSpecifier.then) {
851
- return generatedSpecifier.then((value) => {
852
- reference.generatedSpecifier = value
853
- return urlSpecifierFormat.encode(reference)
854
- })
855
- }
856
- // allow plugin to return a function to bypas default formatting
857
- // (which is to use JSON.stringify when url is referenced inside js)
858
- if (typeof generatedSpecifier === "function") {
859
- return generatedSpecifier()
860
- }
861
- const formatter = formatters[reference.type]
862
- const value = formatter
863
- ? formatter.encode(generatedSpecifier)
864
- : generatedSpecifier
865
- if (reference.escape) {
866
- return reference.escape(value)
867
- }
868
- return value
869
- },
870
- decode: (reference) => {
871
- const formatter = formatters[reference.type]
872
- return formatter
873
- ? formatter.decode(reference.generatedSpecifier)
874
- : reference.generatedSpecifier
875
- },
876
- }
877
- const formatters = {
878
- "js_import_export": { encode: JSON.stringify, decode: JSON.parse },
879
- "js_url_specifier": { encode: JSON.stringify, decode: JSON.parse },
880
- "css_@import": { encode: JSON.stringify, code: JSON.stringify },
881
- // https://github.com/webpack-contrib/css-loader/pull/627/files
882
- "css_url": {
883
- encode: (url) => {
884
- // If url is already wrapped in quotes, remove them
885
- url = formatters.css_url.decode(url)
886
- // Should url be wrapped?
887
- // See https://drafts.csswg.org/css-values-3/#urls
888
- if (/["'() \t\n]/.test(url)) {
889
- return `"${url.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`
890
- }
891
- return url
892
- },
893
- decode: (url) => {
894
- const firstChar = url[0]
895
- const lastChar = url[url.length - 1]
896
- if (firstChar === `"` && lastChar === `"`) {
897
- return url.slice(1, -1)
898
- }
899
- if (firstChar === `'` && lastChar === `'`) {
900
- return url.slice(1, -1)
901
- }
902
- return url
903
- },
904
- },
905
- }
906
-
907
814
  // import { getOriginalPosition } from "@jsenv/core/src/utils/sourcemap/original_position.js"
908
815
  // const getUrlSite = async (
909
816
  // urlInfo,
@@ -44,24 +44,32 @@ export const createFileService = ({
44
44
  if (responseFromPlugin) {
45
45
  return responseFromPlugin
46
46
  }
47
- const [reference, urlInfo] = kitchen.prepareEntryPoint({
48
- parentUrl: inferParentFromRequest(request, rootDirectoryUrl),
49
- type: "entry_point",
50
- specifier: request.ressource,
51
- })
47
+ let reference
48
+ const parentUrl = inferParentFromRequest(request, rootDirectoryUrl)
49
+ if (parentUrl) {
50
+ reference = urlGraph.inferReference(request.ressource, parentUrl)
51
+ }
52
+ if (!reference) {
53
+ const entryPoint = kitchen.prepareEntryPoint({
54
+ trace: parentUrl || rootDirectoryUrl,
55
+ parentUrl: parentUrl || rootDirectoryUrl,
56
+ type: "entry_point",
57
+ specifier: request.ressource,
58
+ })
59
+ reference = entryPoint[0]
60
+ }
61
+ const urlInfo = urlGraph.reuseOrCreateUrlInfo(reference.url)
62
+
52
63
  const ifNoneMatch = request.headers["if-none-match"]
53
64
  if (ifNoneMatch && urlInfo.contentEtag === ifNoneMatch) {
54
65
  return {
55
66
  status: 304,
56
67
  headers: {
57
68
  "cache-control": `private,max-age=0,must-revalidate`,
69
+ ...urlInfo.responseHeaders,
58
70
  },
59
71
  }
60
72
  }
61
- const referenceFromGraph = urlGraph.inferReference(
62
- reference.url,
63
- reference.parentUrl,
64
- )
65
73
  try {
66
74
  // urlInfo objects are reused, they must be "reset" before cooking them again
67
75
  if (
@@ -76,20 +84,21 @@ export const createFileService = ({
76
84
  urlInfo.type = null
77
85
  urlInfo.subtype = null
78
86
  urlInfo.timing = {}
87
+ urlInfo.responseHeaders = {}
79
88
  }
80
89
  const { runtimeName, runtimeVersion } = parseUserAgentHeader(
81
90
  request.headers["user-agent"],
82
91
  )
83
- await kitchen.cook({
84
- reference: referenceFromGraph || reference,
85
- urlInfo,
92
+ await kitchen.cook(urlInfo, {
93
+ request,
94
+ reference,
95
+ clientRuntimeCompat: {
96
+ [runtimeName]: runtimeVersion,
97
+ },
86
98
  outDirectoryUrl:
87
99
  scenario === "dev"
88
100
  ? `${rootDirectoryUrl}.jsenv/${runtimeName}@${runtimeVersion}/`
89
101
  : `${rootDirectoryUrl}.jsenv/${scenario}/${runtimeName}@${runtimeVersion}/`,
90
- clientRuntimeCompat: {
91
- [runtimeName]: runtimeVersion,
92
- },
93
102
  })
94
103
  let { response, contentType, content, contentEtag } = urlInfo
95
104
  if (response) {
@@ -103,6 +112,7 @@ export const createFileService = ({
103
112
  "content-length": Buffer.byteLength(content),
104
113
  "cache-control": `private,max-age=0,must-revalidate`,
105
114
  "eTag": contentEtag,
115
+ ...urlInfo.responseHeaders,
106
116
  },
107
117
  body: content,
108
118
  timing: urlInfo.timing,
@@ -167,7 +177,6 @@ export const createFileService = ({
167
177
  }
168
178
  return async (request) => {
169
179
  let response = await getResponse(request)
170
-
171
180
  return response
172
181
  }
173
182
  }
@@ -175,7 +184,15 @@ export const createFileService = ({
175
184
  const inferParentFromRequest = (request, rootDirectoryUrl) => {
176
185
  const { referer } = request.headers
177
186
  if (!referer) {
178
- return rootDirectoryUrl
187
+ return null
188
+ }
189
+ const refererUrlObject = new URL(referer)
190
+ refererUrlObject.searchParams.delete("hmr")
191
+ refererUrlObject.searchParams.delete("v")
192
+ const { pathname, search } = refererUrlObject
193
+ if (pathname.startsWith("/@fs/")) {
194
+ const fsRootRelativeUrl = pathname.slice("/@fs/".length)
195
+ return `file:///${fsRootRelativeUrl}${search}`
179
196
  }
180
197
  return moveUrl({
181
198
  url: referer,
@@ -5,32 +5,31 @@ export const loadUrlGraph = async ({
5
5
  urlGraph,
6
6
  kitchen,
7
7
  startLoading,
8
+ writeGeneratedFiles,
8
9
  outDirectoryUrl,
9
10
  clientRuntimeCompat,
10
11
  }) => {
11
- if (outDirectoryUrl) {
12
+ if (writeGeneratedFiles && outDirectoryUrl) {
12
13
  await ensureEmptyDirectory(outDirectoryUrl)
13
14
  }
14
15
  const promises = []
15
16
  const promiseMap = new Map()
16
- const cook = ({ urlInfo, ...rest }) => {
17
+ const cook = (urlInfo, context) => {
17
18
  const promiseFromData = promiseMap.get(urlInfo)
18
19
  if (promiseFromData) return promiseFromData
19
- const promise = _cook({
20
- urlInfo,
20
+ const promise = _cook(urlInfo, {
21
21
  outDirectoryUrl,
22
22
  clientRuntimeCompat,
23
- ...rest,
23
+ ...context,
24
24
  })
25
25
  promises.push(promise)
26
26
  promiseMap.set(urlInfo, promise)
27
27
  return promise
28
28
  }
29
- const _cook = async ({ urlInfo, ...rest }) => {
30
- await kitchen.cook({
31
- urlInfo,
29
+ const _cook = async (urlInfo, context) => {
30
+ await kitchen.cook(urlInfo, {
32
31
  cookDuringCook: cook,
33
- ...rest,
32
+ ...context,
34
33
  })
35
34
  const { references } = urlInfo
36
35
  references.forEach((reference) => {
@@ -46,10 +45,7 @@ export const loadUrlGraph = async ({
46
45
  const referencedUrlInfo = urlGraph.reuseOrCreateUrlInfo(
47
46
  reference.generatedUrl,
48
47
  )
49
- cook({
50
- reference,
51
- urlInfo: referencedUrlInfo,
52
- })
48
+ cook(referencedUrlInfo, { reference })
53
49
  })
54
50
  }
55
51
  startLoading(
@@ -61,10 +57,7 @@ export const loadUrlGraph = async ({
61
57
  specifier,
62
58
  })
63
59
  entryUrlInfo.data.isEntryPoint = true
64
- cook({
65
- reference: entryReference,
66
- urlInfo: entryUrlInfo,
67
- })
60
+ cook(entryUrlInfo, { reference: entryReference })
68
61
  return [entryReference, entryUrlInfo]
69
62
  },
70
63
  )
@@ -91,10 +91,7 @@ export const createUrlInfoTransformer = ({
91
91
  specifierColumn: column,
92
92
  })
93
93
  try {
94
- await context.cook({
95
- reference: sourcemapReference,
96
- urlInfo: sourcemapUrlInfo,
97
- })
94
+ await context.cook(sourcemapUrlInfo, { reference: sourcemapReference })
98
95
  const sourcemap = JSON.parse(sourcemapUrlInfo.content)
99
96
  urlInfo.sourcemap = normalizeSourcemap(urlInfo, sourcemap)
100
97
  } catch (e) {
@@ -1,4 +1,5 @@
1
1
  import { urlToRelativeUrl } from "@jsenv/filesystem"
2
+ import { urlSpecifierEncoding } from "./url_specifier_encoding.js"
2
3
 
3
4
  export const createUrlGraph = ({
4
5
  clientFileChangeCallbackList,
@@ -23,13 +24,15 @@ export const createUrlGraph = ({
23
24
  urlInfos[url] = urlInfo
24
25
  return urlInfo
25
26
  }
26
- const inferReference = (url, parentUrl) => {
27
+ const inferReference = (specifier, parentUrl) => {
27
28
  const parentUrlInfo = urlInfos[parentUrl]
28
29
  if (!parentUrlInfo) {
29
30
  return null
30
31
  }
31
32
  const firstReferenceOnThatUrl = parentUrlInfo.references.find(
32
- (reference) => reference.url === url,
33
+ (reference) => {
34
+ return urlSpecifierEncoding.decode(reference) === specifier
35
+ },
33
36
  )
34
37
  return firstReferenceOnThatUrl
35
38
  }
@@ -192,5 +195,6 @@ const createUrlInfo = (url) => {
192
195
  sourcemap: null,
193
196
  sourcemapReference: null,
194
197
  timing: {},
198
+ responseHeaders: {},
195
199
  }
196
200
  }
@@ -0,0 +1,59 @@
1
+ export const urlSpecifierEncoding = {
2
+ encode: (reference) => {
3
+ const { generatedSpecifier } = reference
4
+ if (generatedSpecifier.then) {
5
+ return generatedSpecifier.then((value) => {
6
+ reference.generatedSpecifier = value
7
+ return urlSpecifierEncoding.encode(reference)
8
+ })
9
+ }
10
+ // allow plugin to return a function to bypas default formatting
11
+ // (which is to use JSON.stringify when url is referenced inside js)
12
+ if (typeof generatedSpecifier === "function") {
13
+ return generatedSpecifier()
14
+ }
15
+ const formatter = formatters[reference.type]
16
+ const value = formatter
17
+ ? formatter.encode(generatedSpecifier)
18
+ : generatedSpecifier
19
+ if (reference.escape) {
20
+ return reference.escape(value)
21
+ }
22
+ return value
23
+ },
24
+ decode: (reference) => {
25
+ const formatter = formatters[reference.type]
26
+ return formatter
27
+ ? formatter.decode(reference.generatedSpecifier)
28
+ : reference.generatedSpecifier
29
+ },
30
+ }
31
+ const formatters = {
32
+ "js_import_export": { encode: JSON.stringify, decode: JSON.parse },
33
+ "js_url_specifier": { encode: JSON.stringify, decode: JSON.parse },
34
+ "css_@import": { encode: JSON.stringify, code: JSON.stringify },
35
+ // https://github.com/webpack-contrib/css-loader/pull/627/files
36
+ "css_url": {
37
+ encode: (url) => {
38
+ // If url is already wrapped in quotes, remove them
39
+ url = formatters.css_url.decode(url)
40
+ // Should url be wrapped?
41
+ // See https://drafts.csswg.org/css-values-3/#urls
42
+ if (/["'() \t\n]/.test(url)) {
43
+ return `"${url.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`
44
+ }
45
+ return url
46
+ },
47
+ decode: (url) => {
48
+ const firstChar = url[0]
49
+ const lastChar = url[url.length - 1]
50
+ if (firstChar === `"` && lastChar === `"`) {
51
+ return url.slice(1, -1)
52
+ }
53
+ if (firstChar === `'` && lastChar === `'`) {
54
+ return url.slice(1, -1)
55
+ }
56
+ return url
57
+ },
58
+ },
59
+ }