@jsenv/core 40.12.7 → 40.12.9

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.
@@ -9,8 +9,6 @@
9
9
 
10
10
  import {
11
11
  applyNodeEsmResolution,
12
- defaultLookupPackageScope,
13
- defaultReadPackageJson,
14
12
  readCustomConditionsFromProcessArgs,
15
13
  } from "@jsenv/node-esm-resolution";
16
14
  import { URL_META } from "@jsenv/url-meta";
@@ -18,18 +16,27 @@ import { urlToBasename, urlToExtension } from "@jsenv/urls";
18
16
  import { readFileSync } from "node:fs";
19
17
 
20
18
  export const createNodeEsmResolver = ({
19
+ packageDirectory,
21
20
  runtimeCompat,
22
21
  rootDirectoryUrl,
23
22
  packageConditions = {},
24
23
  packageConditionsConfig,
25
24
  preservesSymlink,
26
25
  }) => {
26
+ const applyNodeEsmResolutionMemo = (params) =>
27
+ applyNodeEsmResolution({
28
+ lookupPackageScope: packageDirectory.find,
29
+ readPackageJson: packageDirectory.read,
30
+ preservesSymlink,
31
+ ...params,
32
+ });
27
33
  const buildPackageConditions = createBuildPackageConditions(
28
34
  packageConditions,
29
35
  {
30
36
  packageConditionsConfig,
31
37
  rootDirectoryUrl,
32
38
  runtimeCompat,
39
+ preservesSymlink,
33
40
  },
34
41
  );
35
42
 
@@ -61,7 +68,7 @@ export const createNodeEsmResolver = ({
61
68
  reference.type === "sourcemap_comment";
62
69
 
63
70
  const resolveNodeEsmFallbackOnWeb = createResolverWithFallbackOnError(
64
- applyNodeEsmResolution,
71
+ applyNodeEsmResolutionMemo,
65
72
  ({ specifier, parentUrl }) => {
66
73
  const url = new URL(specifier, parentUrl).href;
67
74
  return { url };
@@ -70,7 +77,7 @@ export const createNodeEsmResolver = ({
70
77
  const DELEGATE_TO_WEB_RESOLUTION_PLUGIN = {};
71
78
  const resolveNodeEsmFallbackNullToDelegateToWebPlugin =
72
79
  createResolverWithFallbackOnError(
73
- applyNodeEsmResolution,
80
+ applyNodeEsmResolutionMemo,
74
81
  () => DELEGATE_TO_WEB_RESOLUTION_PLUGIN,
75
82
  );
76
83
 
@@ -78,11 +85,11 @@ export const createNodeEsmResolver = ({
78
85
  webResolutionFallback,
79
86
  resolver: webResolutionFallback
80
87
  ? resolveNodeEsmFallbackOnWeb
81
- : applyNodeEsmResolution,
88
+ : applyNodeEsmResolutionMemo,
82
89
  });
83
90
  const resolver = webResolutionFallback
84
91
  ? resolveNodeEsmFallbackNullToDelegateToWebPlugin
85
- : applyNodeEsmResolution;
92
+ : applyNodeEsmResolutionMemo;
86
93
 
87
94
  const result = resolver({
88
95
  conditions,
@@ -113,52 +120,79 @@ export const createNodeEsmResolver = ({
113
120
  if (ownerUrlInfo.context.build) {
114
121
  return url;
115
122
  }
116
- const dependsOnPackageJson =
117
- type !== "relative_specifier" &&
118
- type !== "absolute_specifier" &&
119
- type !== "node_builtin_specifier";
120
- if (dependsOnPackageJson) {
121
- // this reference depends on package.json and node_modules
122
- // to be resolved. Each file using this specifier
123
- // must be invalidated when corresponding package.json changes
124
- addRelationshipWithPackageJson({
125
- reference,
126
- packageJsonUrl: `${packageDirectoryUrl}package.json`,
127
- field: type.startsWith("field:")
128
- ? `#${type.slice("field:".length)}`
129
- : "",
130
- });
131
- }
132
- // without this check a file inside a project without package.json
133
- // could be considered as a node module if there is a ancestor package.json
134
- // but we want to version only node modules
135
- if (url.includes("/node_modules/")) {
136
- const packageDirectoryUrl = defaultLookupPackageScope(url);
137
- if (
138
- packageDirectoryUrl &&
139
- packageDirectoryUrl !== ownerUrlInfo.context.rootDirectoryUrl
140
- ) {
141
- const packageVersion =
142
- defaultReadPackageJson(packageDirectoryUrl).version;
143
- // package version can be null, see https://github.com/babel/babel/blob/2ce56e832c2dd7a7ed92c89028ba929f874c2f5c/packages/babel-runtime/helpers/esm/package.json#L2
144
- if (packageVersion) {
123
+
124
+ package_relationships: {
125
+ if (!url.startsWith("file:")) {
126
+ // data:, javascript:void(0), etc...
127
+ break package_relationships;
128
+ }
129
+
130
+ // packageDirectoryUrl can be already known thanks to node resolution
131
+ // otherwise we look for it
132
+ const closestPackageDirectoryUrl =
133
+ packageDirectoryUrl || packageDirectory.find(url);
134
+ if (!closestPackageDirectoryUrl) {
135
+ // happens for projects without package.json or some files outside of package scope
136
+ // (generated files like sourcemaps or cache files for example)
137
+ break package_relationships;
138
+ }
139
+
140
+ resolution_relationship: {
141
+ const dependsOnPackageJson = Boolean(packageDirectoryUrl);
142
+ if (dependsOnPackageJson) {
143
+ // this reference depends on package.json and node_modules
144
+ // to be resolved. Each file using this specifier
145
+ // must be invalidated when corresponding package.json changes
145
146
  addRelationshipWithPackageJson({
146
147
  reference,
147
148
  packageJsonUrl: `${packageDirectoryUrl}package.json`,
148
- field: "version",
149
- hasVersioningEffect: true,
149
+ field: type.startsWith("field:")
150
+ ? `#${type.slice("field:".length)}`
151
+ : "",
150
152
  });
151
153
  }
152
- reference.version = packageVersion;
154
+ }
155
+ version_relationship: {
156
+ const packageVersion = packageDirectory.read(
157
+ closestPackageDirectoryUrl,
158
+ ).version;
159
+ if (!packageVersion) {
160
+ // package version can be null, see https://github.com/babel/babel/blob/2ce56e832c2dd7a7ed92c89028ba929f874c2f5c/packages/babel-runtime/helpers/esm/package.json#L2
161
+ break version_relationship;
162
+ }
163
+ // We want the versioning effect
164
+ // which would put the file in browser cache for 1 year based on that version
165
+ // only for files we don't control and touch ourselves (node modules)
166
+ // which would never change until their version change
167
+ // (minus the case you update them yourselves in your node modules without updating the package version)
168
+ // (in that case you would have to clear browser cache to use the modified version of the node module files)
169
+ const hasVersioningEffect =
170
+ closestPackageDirectoryUrl !== packageDirectory.url &&
171
+ url.includes("/node_modules/");
172
+ addRelationshipWithPackageJson({
173
+ reference,
174
+ packageJsonUrl: `${closestPackageDirectoryUrl}package.json`,
175
+ field: "version",
176
+ hasVersioningEffect,
177
+ });
178
+ if (hasVersioningEffect) {
179
+ reference.version = packageVersion;
180
+ }
153
181
  }
154
182
  }
183
+
155
184
  return url;
156
185
  };
157
186
  };
158
187
 
159
188
  const createBuildPackageConditions = (
160
189
  packageConditions,
161
- { packageConditionsConfig, rootDirectoryUrl, runtimeCompat },
190
+ {
191
+ packageConditionsConfig,
192
+ rootDirectoryUrl,
193
+ runtimeCompat,
194
+ preservesSymlink,
195
+ },
162
196
  ) => {
163
197
  let resolveConditionsFromSpecifier = () => null;
164
198
  let resolveConditionsFromContext = () => [];
@@ -185,6 +219,7 @@ const createBuildPackageConditions = (
185
219
  const { packageDirectoryUrl } = applyNodeEsmResolution({
186
220
  specifier: key.slice(0, -1), // avoid package path not exported
187
221
  parentUrl: rootDirectoryUrl,
222
+ preservesSymlink,
188
223
  });
189
224
  const url = packageDirectoryUrl;
190
225
  associationsRaw[url] = associatedValue;
@@ -193,6 +228,7 @@ const createBuildPackageConditions = (
193
228
  const { url } = applyNodeEsmResolution({
194
229
  specifier: key,
195
230
  parentUrl: rootDirectoryUrl,
231
+ preservesSymlink,
196
232
  });
197
233
  associationsRaw[url] = associatedValue;
198
234
  } catch {
@@ -0,0 +1,119 @@
1
+ import { bundleJsModules } from "@jsenv/plugin-bundling";
2
+ import { injectQueryParams, injectQueryParamsIntoSpecifier } from "@jsenv/urls";
3
+
4
+ const PACKAGE_BUNDLE_QUERY_PARAM = "package_bundle";
5
+ const PACKAGE_NO_BUNDLE_QUERY_PARAM = "package_no_bundle";
6
+ const DYNAMIC_IMPORT_QUERY_PARAM = "dynamic_import";
7
+
8
+ export const jsenvPluginWorkspaceBundle = ({ packageDirectory }) => {
9
+ return {
10
+ name: "jsenv:workspace_bundle",
11
+ appliesDuring: "dev",
12
+ redirectReference: (reference) => {
13
+ if (!reference.url.startsWith("file:")) {
14
+ return null;
15
+ }
16
+ if (reference.searchParams.has(PACKAGE_BUNDLE_QUERY_PARAM)) {
17
+ return null;
18
+ }
19
+ if (reference.searchParams.has(PACKAGE_NO_BUNDLE_QUERY_PARAM)) {
20
+ return null;
21
+ }
22
+ if (
23
+ reference.ownerUrlInfo.searchParams.has(PACKAGE_NO_BUNDLE_QUERY_PARAM)
24
+ ) {
25
+ // we're cooking the bundle, without this check we would have infinite recursion to try to bundle
26
+ // we want to propagate the ?package_no_bundle
27
+ const noBundleUrl = injectQueryParams(reference.url, {
28
+ v: undefined,
29
+ [PACKAGE_NO_BUNDLE_QUERY_PARAM]: "",
30
+ });
31
+ // console.log(
32
+ // `redirecting ${reference.url} to ${noBundleUrl} to cook the bundle`,
33
+ // );
34
+ return noBundleUrl;
35
+ }
36
+ const packageDirectoryUrl = packageDirectory.find(reference.url);
37
+ if (!packageDirectoryUrl) {
38
+ return null;
39
+ }
40
+ if (packageDirectoryUrl === packageDirectory.url) {
41
+ // root package, we don't want to bundle
42
+ return null;
43
+ }
44
+ // we make sure we target the bundle version of the package
45
+ // otherwise we might execute some parts of the package code multiple times.
46
+ // so we need to redirect the potential reference to non entry point to the package main entry point
47
+ const packageJSON = packageDirectory.read(packageDirectoryUrl);
48
+ const rootReference = reference.ownerUrlInfo.dependencies.inject({
49
+ type: "js_import",
50
+ specifier: `${packageJSON.name}?${PACKAGE_BUNDLE_QUERY_PARAM}`,
51
+ });
52
+ // console.log(
53
+ // `redirecting ${reference.url} to ${rootReference.url} to target the package bundle version of the package`,
54
+ // );
55
+ const packageMainUrl = rootReference.url;
56
+ return packageMainUrl;
57
+ },
58
+ fetchUrlContent: async (urlInfo) => {
59
+ if (!urlInfo.searchParams.has(PACKAGE_BUNDLE_QUERY_PARAM)) {
60
+ return null;
61
+ }
62
+ const noBundleSpecifier = injectQueryParamsIntoSpecifier(
63
+ urlInfo.firstReference.specifier,
64
+ {
65
+ [PACKAGE_BUNDLE_QUERY_PARAM]: undefined,
66
+ [PACKAGE_NO_BUNDLE_QUERY_PARAM]: "",
67
+ },
68
+ );
69
+ const noBundleUrlInfo = urlInfo.redirect({
70
+ specifier: noBundleSpecifier,
71
+ });
72
+ if (!noBundleUrlInfo) {
73
+ return null;
74
+ }
75
+ await noBundleUrlInfo.cook();
76
+ await noBundleUrlInfo.cookDependencies({
77
+ // we ignore dynamic import to cook lazyly (as browser request the server)
78
+ // these dynamic imports must inherit "?package_bundle"
79
+ // This is done inside rollup for convenience
80
+ ignoreDynamicImport: true,
81
+ });
82
+ const bundleUrlInfos = await bundleJsModules([noBundleUrlInfo], {
83
+ chunks: false,
84
+ buildDirectoryUrl: new URL("./", import.meta.url),
85
+ preserveDynamicImports: true,
86
+ augmentDynamicImportUrlSearchParams: () => {
87
+ return {
88
+ [DYNAMIC_IMPORT_QUERY_PARAM]: "",
89
+ [PACKAGE_BUNDLE_QUERY_PARAM]: "",
90
+ };
91
+ },
92
+ });
93
+ const bundledUrlInfo = bundleUrlInfos[noBundleUrlInfo.url];
94
+ if (urlInfo.context.dev) {
95
+ for (const sourceUrl of bundledUrlInfo.sourceUrls) {
96
+ urlInfo.dependencies.inject({
97
+ isImplicit: true,
98
+ type: "js_url",
99
+ specifier: sourceUrl,
100
+ });
101
+ }
102
+ }
103
+ return {
104
+ content: bundledUrlInfo.content,
105
+ contentType: "text/javascript",
106
+ type: "js_module",
107
+ originalUrl: urlInfo.originalUrl,
108
+ originalContent: bundledUrlInfo.originalContent,
109
+ sourcemap: bundledUrlInfo.sourcemap,
110
+ data: bundledUrlInfo.data,
111
+ };
112
+ },
113
+ // transformReferenceSearchParams: () => {
114
+ // return {
115
+ // [PACKAGE_BUNDLE_QUERY_PARAM]: undefined,
116
+ // };
117
+ // },
118
+ };
119
+ };