@jsenv/core 37.1.5 → 38.0.1

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 (29) hide show
  1. package/dist/js/autoreload.js +2 -2
  2. package/dist/jsenv_core.js +3231 -2483
  3. package/package.json +14 -13
  4. package/src/build/build.js +250 -1028
  5. package/src/build/build_specifier_manager.js +1200 -0
  6. package/src/build/build_urls_generator.js +40 -18
  7. package/src/build/version_mappings_injection.js +14 -16
  8. package/src/dev/file_service.js +0 -10
  9. package/src/dev/start_dev_server.js +0 -2
  10. package/src/kitchen/kitchen.js +54 -37
  11. package/src/kitchen/url_graph/references.js +84 -93
  12. package/src/kitchen/url_graph/url_graph.js +16 -6
  13. package/src/kitchen/url_graph/url_info_transformations.js +124 -55
  14. package/src/plugins/autoreload/client/autoreload.js +6 -2
  15. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +20 -16
  16. package/src/plugins/autoreload/jsenv_plugin_hot_search_param.js +1 -1
  17. package/src/plugins/cache_control/jsenv_plugin_cache_control.js +2 -2
  18. package/src/plugins/clean_html/jsenv_plugin_clean_html.js +16 -0
  19. package/src/plugins/importmap/jsenv_plugin_importmap.js +11 -23
  20. package/src/plugins/inlining/jsenv_plugin_inlining_as_data_url.js +16 -1
  21. package/src/plugins/inlining/jsenv_plugin_inlining_into_html.js +14 -24
  22. package/src/plugins/plugin_controller.js +37 -25
  23. package/src/plugins/plugins.js +2 -0
  24. package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +31 -16
  25. package/src/plugins/reference_analysis/directory/jsenv_plugin_directory_reference_analysis.js +12 -6
  26. package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +33 -54
  27. package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +2 -9
  28. package/src/plugins/reference_analysis/jsenv_plugin_reference_analysis.js +15 -8
  29. package/src/build/build_versions_manager.js +0 -492
@@ -1,25 +1,48 @@
1
1
  import { urlToFilename, urlToRelativeUrl } from "@jsenv/urls";
2
- import { memoizeByFirstArgument } from "@jsenv/utils/src/memoize/memoize_by_first_argument.js";
2
+ import { ANSI } from "@jsenv/log";
3
3
 
4
4
  export const createBuildUrlsGenerator = ({
5
+ logger,
6
+ sourceDirectoryUrl,
5
7
  buildDirectoryUrl,
6
8
  assetsDirectory,
7
9
  }) => {
8
10
  const cache = {};
9
-
10
11
  const getUrlName = (url, urlInfo) => {
11
12
  if (!urlInfo) {
12
13
  return urlToFilename(url);
13
14
  }
14
- if (urlInfo.filename) {
15
- return urlInfo.filename;
15
+ if (urlInfo.filenameHint) {
16
+ return urlInfo.filenameHint;
16
17
  }
17
18
  return urlToFilename(url);
18
19
  };
19
20
 
20
- const generate = memoizeByFirstArgument((url, { urlInfo, ownerUrlInfo }) => {
21
+ const buildUrlCache = new Map();
22
+
23
+ const associateBuildUrl = (url, buildUrl) => {
24
+ buildUrlCache.set(url, buildUrl);
25
+ logger.debug(`associate a build url
26
+ ${ANSI.color(url, ANSI.GREY)} ->
27
+ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
28
+ `);
29
+ };
30
+
31
+ const generate = (url, { urlInfo, ownerUrlInfo }) => {
32
+ const buildUrlFromCache = buildUrlCache.get(url);
33
+ if (buildUrlFromCache) {
34
+ return buildUrlFromCache;
35
+ }
36
+ if (urlInfo.type === "directory") {
37
+ const directoryPath = urlToRelativeUrl(url, sourceDirectoryUrl);
38
+ const { search } = new URL(url);
39
+ const buildUrl = `${buildDirectoryUrl}${directoryPath}${search}`;
40
+ associateBuildUrl(url, buildUrl);
41
+ return buildUrl;
42
+ }
43
+
21
44
  const directoryPath = determineDirectoryPath({
22
- buildDirectoryUrl,
45
+ sourceDirectoryUrl,
23
46
  assetsDirectory,
24
47
  urlInfo,
25
48
  ownerUrlInfo,
@@ -45,8 +68,11 @@ export const createBuildUrlsGenerator = ({
45
68
  integer++;
46
69
  nameCandidate = `${basename}${integer}${extension}`;
47
70
  }
48
- return `${buildDirectoryUrl}${directoryPath}${nameCandidate}${search}${hash}`;
49
- });
71
+ hash = "";
72
+ const buildUrl = `${buildDirectoryUrl}${directoryPath}${nameCandidate}${search}${hash}`;
73
+ associateBuildUrl(url, buildUrl);
74
+ return buildUrl;
75
+ };
50
76
 
51
77
  return {
52
78
  generate,
@@ -74,26 +100,22 @@ const splitFileExtension = (filename) => {
74
100
  };
75
101
 
76
102
  const determineDirectoryPath = ({
77
- buildDirectoryUrl,
103
+ sourceDirectoryUrl,
78
104
  assetsDirectory,
79
105
  urlInfo,
80
106
  ownerUrlInfo,
81
107
  }) => {
108
+ if (urlInfo.dirnameHint) {
109
+ return urlInfo.dirnameHint;
110
+ }
82
111
  if (urlInfo.type === "directory") {
83
112
  return "";
84
113
  }
85
- if (ownerUrlInfo && ownerUrlInfo.type === "directory") {
86
- const ownerDirectoryPath = urlToRelativeUrl(
87
- ownerUrlInfo.url,
88
- buildDirectoryUrl,
89
- );
90
- return ownerDirectoryPath;
91
- }
92
114
  if (urlInfo.isInline) {
93
115
  const parentDirectoryPath = determineDirectoryPath({
94
- buildDirectoryUrl,
116
+ sourceDirectoryUrl,
95
117
  assetsDirectory,
96
- urlInfo: ownerUrlInfo,
118
+ urlInfo: ownerUrlInfo || urlInfo.firstReference.ownerUrlInfo,
97
119
  });
98
120
  return parentDirectoryPath;
99
121
  }
@@ -15,27 +15,25 @@ export const injectVersionMappingsAsGlobal = async (
15
15
  versionMappings,
16
16
  ) => {
17
17
  if (urlInfo.type === "html") {
18
- await prependContent(urlInfo, {
19
- type: "js_classic",
20
- content: generateClientCodeForVersionMappings(versionMappings, {
21
- globalName: "window",
22
- minification: Boolean(
23
- urlInfo.context.getPluginMeta("willMinifyJsClassic"),
24
- ),
25
- }),
18
+ const minification = Boolean(
19
+ urlInfo.context.getPluginMeta("willMinifyJsClassic"),
20
+ );
21
+ const content = generateClientCodeForVersionMappings(versionMappings, {
22
+ globalName: "window",
23
+ minification,
26
24
  });
25
+ await prependContent(urlInfo, { type: "js_classic", content });
27
26
  return;
28
27
  }
29
28
  if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
30
- await prependContent(urlInfo, {
31
- type: "js_classic",
32
- content: generateClientCodeForVersionMappings(versionMappings, {
33
- globalName: isWebWorkerUrlInfo(urlInfo) ? "self" : "window",
34
- minification: Boolean(
35
- urlInfo.context.getPluginMeta("willMinifyJsClassic"),
36
- ),
37
- }),
29
+ const minification = Boolean(
30
+ urlInfo.context.getPluginMeta("willMinifyJsClassic"),
31
+ );
32
+ const content = generateClientCodeForVersionMappings(versionMappings, {
33
+ globalName: isWebWorkerUrlInfo(urlInfo) ? "self" : "window",
34
+ minification,
38
35
  });
36
+ await prependContent(urlInfo, { type: "js_classic", content });
39
37
  return;
40
38
  }
41
39
  };
@@ -2,7 +2,6 @@ import { readFileSync } from "node:fs";
2
2
  import { serveDirectory, composeTwoResponses } from "@jsenv/server";
3
3
  import { bufferToEtag } from "@jsenv/filesystem";
4
4
  import { URL_META } from "@jsenv/url-meta";
5
- import { RUNTIME_COMPAT } from "@jsenv/runtime-compat";
6
5
 
7
6
  import { WEB_URL_CONVERTER } from "../helpers/web_url_converter.js";
8
7
  import { watchSourceFiles } from "../helpers/watch_source_files.js";
@@ -37,7 +36,6 @@ export const createFileService = ({
37
36
  cacheControl,
38
37
  ribbon,
39
38
  sourcemaps,
40
- sourcemapsSourcesProtocol,
41
39
  sourcemapsSourcesContent,
42
40
  outDirectoryUrl,
43
41
  }) => {
@@ -103,13 +101,6 @@ export const createFileService = ({
103
101
  dev: true,
104
102
  runtimeCompat,
105
103
  clientRuntimeCompat,
106
- systemJsTranspilation:
107
- !RUNTIME_COMPAT.isSupported(
108
- clientRuntimeCompat,
109
- "script_type_module",
110
- ) ||
111
- !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_dynamic") ||
112
- !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_meta"),
113
104
  plugins: [
114
105
  ...plugins,
115
106
  ...getCorePlugins({
@@ -131,7 +122,6 @@ export const createFileService = ({
131
122
  supervisor,
132
123
  minification: false,
133
124
  sourcemaps,
134
- sourcemapsSourcesProtocol,
135
125
  sourcemapsSourcesContent,
136
126
  outDirectoryUrl: outDirectoryUrl
137
127
  ? new URL(`${runtimeName}@${runtimeVersion}/`, outDirectoryUrl)
@@ -62,7 +62,6 @@ export const startDevServer = async ({
62
62
  onKitchenCreated = () => {},
63
63
 
64
64
  sourcemaps = "inline",
65
- sourcemapsSourcesProtocol,
66
65
  sourcemapsSourcesContent,
67
66
  outDirectoryUrl,
68
67
  ...rest
@@ -203,7 +202,6 @@ export const startDevServer = async ({
203
202
  cacheControl,
204
203
  ribbon,
205
204
  sourcemaps,
206
- sourcemapsSourcesProtocol,
207
205
  sourcemapsSourcesContent,
208
206
  outDirectoryUrl,
209
207
  }),
@@ -39,7 +39,6 @@ export const createKitchen = ({
39
39
  supportedProtocols = ["file:", "data:", "virtual:", "http:", "https:"],
40
40
  dev = false,
41
41
  build = false,
42
- shape = false,
43
42
  runtimeCompat,
44
43
  // during dev/test clientRuntimeCompat is a single runtime
45
44
  // during build clientRuntimeCompat is runtimeCompat
@@ -47,16 +46,18 @@ export const createKitchen = ({
47
46
  plugins,
48
47
  supervisor,
49
48
  sourcemaps = dev ? "inline" : "none", // "programmatic" and "file" also allowed
49
+ sourcemapsComment,
50
+ sourcemapsSources,
50
51
  sourcemapsSourcesProtocol,
51
52
  sourcemapsSourcesContent,
52
- sourcemapsSourcesRelative,
53
53
  outDirectoryUrl,
54
- baseContext = {},
54
+ initialContext = {},
55
+ initialPluginsMeta = {},
55
56
  }) => {
56
57
  const logger = createLogger({ logLevel });
57
58
  const kitchen = {
58
59
  context: {
59
- ...baseContext,
60
+ ...initialContext,
60
61
  kitchen: null,
61
62
  signal,
62
63
  logger,
@@ -64,7 +65,6 @@ export const createKitchen = ({
64
65
  mainFilePath,
65
66
  dev,
66
67
  build,
67
- shape,
68
68
  runtimeCompat,
69
69
  clientRuntimeCompat,
70
70
  inlineContentClientFileUrl,
@@ -88,11 +88,11 @@ export const createKitchen = ({
88
88
  });
89
89
  kitchen.graph = graph;
90
90
 
91
- const pluginController = createPluginController(kitchenContext);
92
- kitchen.pluginController = pluginController;
93
- kitchenContext.getPluginMeta = memoizeGetPluginMeta(
94
- pluginController.getPluginMeta,
91
+ const pluginController = createPluginController(
92
+ kitchenContext,
93
+ initialPluginsMeta,
95
94
  );
95
+ kitchen.pluginController = pluginController;
96
96
  plugins.forEach((pluginEntry) => {
97
97
  pluginController.pushPlugin(pluginEntry);
98
98
  });
@@ -100,9 +100,10 @@ export const createKitchen = ({
100
100
  const urlInfoTransformer = createUrlInfoTransformer({
101
101
  logger,
102
102
  sourcemaps,
103
+ sourcemapsComment,
104
+ sourcemapsSources,
103
105
  sourcemapsSourcesProtocol,
104
106
  sourcemapsSourcesContent,
105
- sourcemapsSourcesRelative,
106
107
  outDirectoryUrl,
107
108
  supervisor,
108
109
  });
@@ -229,6 +230,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
229
230
  );
230
231
  }
231
232
  reference.generatedUrl = reference.url;
233
+ reference.generatedSearchParams = reference.searchParams;
232
234
  return reference;
233
235
  } catch (error) {
234
236
  throw createResolveUrlError({
@@ -255,19 +257,33 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
255
257
  // - convey information (?hot)
256
258
  // But do not represent an other resource, it is considered as
257
259
  // the same resource under the hood
260
+ const searchParamTransformationMap = new Map();
258
261
  pluginController.callHooks(
259
262
  "transformReferenceSearchParams",
260
263
  reference,
261
264
  (returnValue) => {
262
265
  Object.keys(returnValue).forEach((key) => {
263
- reference.searchParams.set(key, returnValue[key]);
266
+ searchParamTransformationMap.set(key, returnValue[key]);
264
267
  });
265
- const referencedUrlObject = new URL(reference.url);
266
- const search = reference.searchParams.toString();
267
- referencedUrlObject.search = search;
268
- reference.generatedUrl = normalizeUrl(referencedUrlObject.href);
269
268
  },
270
269
  );
270
+ if (searchParamTransformationMap.size) {
271
+ const generatedSearchParams = new URLSearchParams(
272
+ reference.searchParams,
273
+ );
274
+ searchParamTransformationMap.forEach((value, key) => {
275
+ if (value === undefined) {
276
+ generatedSearchParams.delete(key);
277
+ } else {
278
+ generatedSearchParams.set(key, value);
279
+ }
280
+ });
281
+ const generatedUrlObject = new URL(reference.url);
282
+ const generatedSearch = generatedSearchParams.toString();
283
+ generatedUrlObject.search = generatedSearch;
284
+ reference.generatedUrl = normalizeUrl(generatedUrlObject.href);
285
+ reference.generatedSearchParams = generatedSearchParams;
286
+ }
271
287
  }
272
288
  format: {
273
289
  const returnValue = pluginController.callHooksUntil(
@@ -313,7 +329,6 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
313
329
  subtype,
314
330
  originalUrl,
315
331
  sourcemap,
316
- filename,
317
332
 
318
333
  status = 200,
319
334
  headers = {},
@@ -348,9 +363,6 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
348
363
  if (typeof isEntryPoint === "boolean") {
349
364
  urlInfo.isEntryPoint = isEntryPoint;
350
365
  }
351
- if (filename && !urlInfo.filename) {
352
- urlInfo.filename = filename;
353
- }
354
366
  assertFetchedContentCompliance({
355
367
  urlInfo,
356
368
  content,
@@ -468,16 +480,17 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
468
480
  if (e.code === "DIRECTORY_REFERENCE_NOT_ALLOWED") {
469
481
  throw e;
470
482
  }
471
- if (urlInfo.isInline) {
483
+ if (urlInfo.isInline && errorOnInlineContentCanSkipThrow(urlInfo)) {
472
484
  // When something like <style> or <script> contains syntax error
473
485
  // the HTML in itself it still valid
474
486
  // keep the syntax error and continue with the HTML
475
487
  const errorInfo =
476
488
  e.code === "PARSE_ERROR"
477
489
  ? `${e.cause.reasonCode}\n${e.traceMessage}`
478
- : `${e.traceMessage}`;
490
+ : e.stack;
479
491
  logger.error(
480
- `Error while handling ${urlInfo.type} declared in ${urlInfo.firstReference.trace.message}: ${errorInfo}`,
492
+ `Error while handling ${urlInfo.type} declared in ${urlInfo.firstReference.trace.message}:
493
+ ${errorInfo}`,
481
494
  );
482
495
  } else {
483
496
  throw e;
@@ -571,6 +584,23 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
571
584
  return kitchen;
572
585
  };
573
586
 
587
+ // if we are cooking the inline content internally it's better not to throw
588
+ // because the main url info (html) is still valid
589
+ // but if we are explicitely requesting inline content during dev
590
+ // then we should throw
591
+ const errorOnInlineContentCanSkipThrow = (urlInfo) => {
592
+ if (urlInfo.context.build) {
593
+ return true;
594
+ }
595
+ if (
596
+ urlInfo.context.reference &&
597
+ urlInfo.context.reference.url === urlInfo.url
598
+ ) {
599
+ return false;
600
+ }
601
+ return true;
602
+ };
603
+
574
604
  const debounceCook = (cook) => {
575
605
  const pendingDishes = new Map();
576
606
  return async (urlInfo, context) => {
@@ -619,19 +649,6 @@ const memoizeCook = (cook) => {
619
649
  };
620
650
  };
621
651
 
622
- const memoizeGetPluginMeta = (getPluginMeta) => {
623
- const cache = new Map();
624
- return (id) => {
625
- const fromCache = cache.get(id);
626
- if (fromCache) {
627
- return fromCache;
628
- }
629
- const value = getPluginMeta(id);
630
- cache.set(id, value);
631
- return value;
632
- };
633
- };
634
-
635
652
  const memoizeIsSupported = (runtimeCompat) => {
636
653
  const cache = new Map();
637
654
  return (feature, featureCompat) => {
@@ -697,8 +714,8 @@ const determineFileUrlForOutDirectory = (urlInfo) => {
697
714
  fsRootUrl.length,
698
715
  )}`;
699
716
  }
700
- if (urlInfo.filename) {
701
- url = setUrlFilename(url, urlInfo.filename);
717
+ if (urlInfo.filenameHint) {
718
+ url = setUrlFilename(url, urlInfo.filenameHint);
702
719
  }
703
720
  return moveUrl({
704
721
  url,
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  getCallerPosition,
3
3
  stringifyUrlSite,
4
- generateInlineContentUrl,
5
4
  urlToBasename,
6
5
  urlToExtension,
7
6
  } from "@jsenv/urls";
7
+ import { generateUrlForInlineContent } from "@jsenv/ast";
8
8
 
9
9
  import { isWebWorkerEntryPointReference } from "../web_workers.js";
10
10
  import { prependContent } from "../prepend_content.js";
11
- import { GRAPH_VISITOR } from "./url_graph_visitor.js";
11
+
12
+ let referenceId = 0;
12
13
 
13
14
  export const createDependencies = (ownerUrlInfo) => {
14
15
  const { referenceToOthersSet } = ownerUrlInfo;
@@ -101,37 +102,10 @@ export const createDependencies = (ownerUrlInfo) => {
101
102
  specifier: sideEffectFileUrl,
102
103
  ...rest,
103
104
  });
104
- const parentUrlInfo = ownerUrlInfo.findParentIfInline() || ownerUrlInfo;
105
-
106
- const associateIfReferencedBy = (urlInfo) => {
107
- for (const referenceToOther of urlInfo.referenceToOthersSet) {
108
- if (referenceToOther === sideEffectFileReference) {
109
- continue;
110
- }
111
- if (referenceToOther.url === sideEffectFileUrl) {
112
- // consider this reference becomes the last reference
113
- // this ensure this ref is properly detected as inlined by urlInfo.isUsed()
114
- sideEffectFileReference.next =
115
- referenceToOther.next || referenceToOther;
116
- return true;
117
- }
118
- if (
119
- referenceToOther.original &&
120
- referenceToOther.original.url === sideEffectFileUrl
121
- ) {
122
- // consider this reference becomes the last reference
123
- // this ensure this ref is properly detected as inlined by urlInfo.isUsed()
124
- sideEffectFileReference.next =
125
- referenceToOther.next || referenceToOther;
126
- return true;
127
- }
128
- }
129
- return false;
130
- };
131
105
 
132
106
  const injectAsBannerCodeBeforeFinalize = (urlInfoReceiver) => {
133
107
  const basename = urlToBasename(sideEffectFileUrl);
134
- const inlineUrl = generateInlineContentUrl({
108
+ const inlineUrl = generateUrlForInlineContent({
135
109
  url: urlInfoReceiver.url,
136
110
  basename,
137
111
  extension: urlToExtension(sideEffectFileUrl),
@@ -158,59 +132,69 @@ export const createDependencies = (ownerUrlInfo) => {
158
132
  // during dev cooking files is incremental
159
133
  // so HTML/JS is already executed by the browser
160
134
  // we can't late inject into entry point
161
- if (ownerUrlInfo.context.dev) {
162
- if (associateIfReferencedBy(ownerUrlInfo)) {
163
- return;
164
- }
165
- const dependentReferencingThatFile = GRAPH_VISITOR.findDependent(
166
- parentUrlInfo,
167
- (ancestorUrlInfo) => {
168
- if (ancestorUrlInfo.isRoot) {
169
- return false;
170
- }
171
- return associateIfReferencedBy(ancestorUrlInfo);
172
- },
173
- );
174
- if (dependentReferencingThatFile) {
175
- return;
176
- }
177
- injectAsBannerCodeBeforeFinalize(parentUrlInfo);
178
- return;
179
- }
180
-
181
135
  // During build:
182
- // during build, files are not executed so it's
183
- // possible to inject reference when discovering a side effect file
184
- if (associateIfReferencedBy(ownerUrlInfo)) {
185
- return;
186
- }
187
- // The thing to do here is to inject side effect file into entry point
188
- // only when:
189
- // - entry point does not already has it
190
- // - nothing between entry point and the file has it
191
- const entryPoints = parentUrlInfo.graph.getEntryPoints();
192
- for (const entryPointUrlInfo of entryPoints) {
193
- if (associateIfReferencedBy(entryPointUrlInfo)) {
194
- continue;
195
- }
196
- if (parentUrlInfo.isEntryPoint) {
197
- injectAsBannerCodeBeforeFinalize(entryPointUrlInfo);
198
- continue;
136
+ // files are not executed so it's possible to inject reference
137
+ // when discovering a side effect file
138
+ const visitedMap = new Map();
139
+ let foundOrInjectedOnce = false;
140
+ const visit = (urlInfo) => {
141
+ urlInfo = urlInfo.findParentIfInline() || urlInfo;
142
+ const value = visitedMap.get(urlInfo);
143
+ if (value !== undefined) {
144
+ return value;
199
145
  }
200
- let found = false;
201
- GRAPH_VISITOR.findDependency(entryPointUrlInfo, (dependencyUrlInfo) => {
202
- if (associateIfReferencedBy(dependencyUrlInfo)) {
203
- found = true;
146
+
147
+ // search if already referenced
148
+ for (const referenceToOther of urlInfo.referenceToOthersSet) {
149
+ if (referenceToOther === sideEffectFileReference) {
150
+ continue;
151
+ }
152
+ if (referenceToOther.url === sideEffectFileUrl) {
153
+ // consider this reference becomes the last reference
154
+ // this ensure this ref is properly detected as inlined by urlInfo.isUsed()
155
+ sideEffectFileReference.next =
156
+ referenceToOther.next || referenceToOther;
157
+ foundOrInjectedOnce = true;
158
+ visitedMap.set(urlInfo, true);
204
159
  return true;
205
160
  }
206
- if (dependencyUrlInfo === parentUrlInfo) {
161
+ if (
162
+ referenceToOther.original &&
163
+ referenceToOther.original.url === sideEffectFileUrl
164
+ ) {
165
+ // consider this reference becomes the last reference
166
+ // this ensure this ref is properly detected as inlined by urlInfo.isUsed()
167
+ sideEffectFileReference.next =
168
+ referenceToOther.next || referenceToOther;
169
+ foundOrInjectedOnce = true;
170
+ visitedMap.set(urlInfo, true);
207
171
  return true;
208
172
  }
209
- return false;
210
- });
211
- if (!found) {
212
- injectAsBannerCodeBeforeFinalize(entryPointUrlInfo);
213
173
  }
174
+ // not referenced and we reach an entry point, stop there
175
+ if (urlInfo.isEntryPoint) {
176
+ foundOrInjectedOnce = true;
177
+ visitedMap.set(urlInfo, true);
178
+ injectAsBannerCodeBeforeFinalize(urlInfo);
179
+ return true;
180
+ }
181
+ visitedMap.set(urlInfo, false);
182
+ for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
183
+ const urlInfoReferencingThisOne = referenceFromOther.ownerUrlInfo;
184
+ visit(urlInfoReferencingThisOne);
185
+ // during dev the first urlInfo where we inject the side effect file is enough
186
+ // during build we want to inject into every possible entry point
187
+ if (foundOrInjectedOnce && urlInfo.context.dev) {
188
+ break;
189
+ }
190
+ }
191
+ return false;
192
+ };
193
+ visit(ownerUrlInfo);
194
+ if (ownerUrlInfo.context.dev && !foundOrInjectedOnce) {
195
+ injectAsBannerCodeBeforeFinalize(
196
+ ownerUrlInfo.findParentIfInline() || ownerUrlInfo,
197
+ );
214
198
  }
215
199
  };
216
200
 
@@ -275,7 +259,7 @@ const createReference = ({
275
259
  expectedContentType,
276
260
  expectedType,
277
261
  expectedSubtype,
278
- filename,
262
+ filenameHint,
279
263
  integrity,
280
264
  crossorigin,
281
265
  specifier,
@@ -326,6 +310,7 @@ const createReference = ({
326
310
  }
327
311
  }
328
312
  const reference = {
313
+ id: ++referenceId,
329
314
  ownerUrlInfo,
330
315
  original,
331
316
  prev,
@@ -342,7 +327,7 @@ const createReference = ({
342
327
  expectedContentType,
343
328
  expectedType,
344
329
  expectedSubtype,
345
- filename,
330
+ filenameHint,
346
331
  integrity,
347
332
  crossorigin,
348
333
  specifier,
@@ -445,6 +430,7 @@ const createReference = ({
445
430
  original: reference.original || reference,
446
431
  prev: reference,
447
432
  trace,
433
+ injected: reference.injected,
448
434
  expectedType: reference.expectedType,
449
435
  ...props,
450
436
  });
@@ -625,6 +611,8 @@ const applyDependencyRemovalEffects = (reference) => {
625
611
  }
626
612
  return false;
627
613
  }
614
+ // referencedUrlInfo.firstReference = null;
615
+ // referencedUrlInfo.lastReference = null;
628
616
  referencedUrlInfo.onDereferenced(reference);
629
617
  return true;
630
618
  };
@@ -691,8 +679,23 @@ const getRedirectedReferenceProps = (reference, url) => {
691
679
 
692
680
  const applyReferenceEffectsOnUrlInfo = (reference) => {
693
681
  const referencedUrlInfo = reference.urlInfo;
682
+ referencedUrlInfo.lastReference = reference;
683
+ if (reference.isInline) {
684
+ referencedUrlInfo.isInline = true;
685
+ referencedUrlInfo.inlineUrlSite = {
686
+ url: reference.ownerUrlInfo.url,
687
+ content: reference.isOriginalPosition
688
+ ? reference.ownerUrlInfo.originalContent
689
+ : reference.ownerUrlInfo.content,
690
+ line: reference.specifierLine,
691
+ column: reference.specifierColumn,
692
+ };
693
+ }
694
694
 
695
- if (referencedUrlInfo.firstReference) {
695
+ if (
696
+ referencedUrlInfo.firstReference &&
697
+ !referencedUrlInfo.firstReference.isWeak
698
+ ) {
696
699
  return;
697
700
  }
698
701
  referencedUrlInfo.firstReference = reference;
@@ -707,21 +710,9 @@ const applyReferenceEffectsOnUrlInfo = (reference) => {
707
710
  if (reference.injected) {
708
711
  referencedUrlInfo.injected = true;
709
712
  }
710
- if (reference.filename && !referencedUrlInfo.filename) {
711
- referencedUrlInfo.filename = reference.filename;
713
+ if (reference.filenameHint && !referencedUrlInfo.filenameHint) {
714
+ referencedUrlInfo.filenameHint = reference.filenameHint;
712
715
  }
713
- if (reference.isInline) {
714
- referencedUrlInfo.isInline = true;
715
- referencedUrlInfo.inlineUrlSite = {
716
- url: reference.ownerUrlInfo.url,
717
- content: reference.isOriginalPosition
718
- ? reference.ownerUrlInfo.originalContent
719
- : reference.ownerUrlInfo.content,
720
- line: reference.specifierLine,
721
- column: reference.specifierColumn,
722
- };
723
- }
724
-
725
716
  if (reference.debug) {
726
717
  referencedUrlInfo.debug = true;
727
718
  }