@jsenv/core 23.11.1 → 24.1.0-alpha.0

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.
@@ -3,6 +3,7 @@ import {
3
3
  collectFiles,
4
4
  urlToRelativeUrl,
5
5
  } from "@jsenv/filesystem"
6
+ import { setupRoutes } from "@jsenv/server"
6
7
 
7
8
  import { jsenvCoreDirectoryUrl } from "./internal/jsenvCoreDirectoryUrl.js"
8
9
  import {
@@ -25,12 +26,24 @@ import {
25
26
  } from "./internal/jsenvInternalFiles.js"
26
27
  import { jsenvRuntimeSupportDuringDev } from "./jsenvRuntimeSupportDuringDev.js"
27
28
 
28
- export const startExploring = async ({
29
+ export const startDevServer = async ({
29
30
  signal = new AbortController().signal,
30
31
  handleSIGINT = true,
32
+ port,
33
+ ip,
34
+ protocol,
35
+ http2,
36
+ certificate,
37
+ privateKey,
38
+ plugins,
39
+ customServices,
31
40
 
32
- explorableConfig = jsenvExplorableConfig,
33
41
  projectDirectoryUrl,
42
+ explorableConfig = jsenvExplorableConfig,
43
+ mainFileRelativeUrl = urlToRelativeUrl(
44
+ jsenvExploringIndexHtmlFileInfo.url,
45
+ projectDirectoryUrl,
46
+ ),
34
47
  jsenvDirectoryRelativeUrl,
35
48
  outDirectoryName = "dev",
36
49
  jsenvToolbar = true,
@@ -40,18 +53,11 @@ export const startExploring = async ({
40
53
 
41
54
  babelPluginMap,
42
55
  runtimeSupportDuringDev = jsenvRuntimeSupportDuringDev,
43
- compileServerLogLevel,
56
+ logLevel,
44
57
  compileServerCanReadFromFilesystem,
45
58
  compileServerCanWriteOnFilesystem,
46
- compileServerPort,
47
- compileServerProtocol,
48
- compileServerHttp2,
49
- compileServerCertificate,
50
- compileServerPrivateKey,
51
59
  sourcemapMethod,
52
- customServices,
53
60
  customCompilers,
54
- serverPlugins,
55
61
  livereloadWatchConfig,
56
62
  jsenvDirectoryClean,
57
63
  }) => {
@@ -67,6 +73,7 @@ export const startExploring = async ({
67
73
  const redirectFiles = createRedirectFilesService({
68
74
  projectDirectoryUrl,
69
75
  outDirectoryRelativeUrl,
76
+ mainFileRelativeUrl,
70
77
  })
71
78
  const serveExploringData = createExploringDataService({
72
79
  projectDirectoryUrl,
@@ -84,30 +91,31 @@ export const startExploring = async ({
84
91
  signal,
85
92
  handleSIGINT,
86
93
  keepProcessAlive,
94
+ logLevel,
95
+ port,
96
+ ip,
97
+ protocol,
98
+ http2,
99
+ certificate,
100
+ privateKey,
101
+ plugins,
102
+ customServices: {
103
+ ...customServices,
104
+ "jsenv:redirector": redirectFiles,
105
+ "jsenv:exploring-data": serveExploringData,
106
+ "jsenv:explorables": serveExplorableListAsJson,
107
+ },
87
108
 
88
109
  projectDirectoryUrl,
89
110
  livereloadSSE: livereloading,
90
111
  jsenvToolbarInjection: jsenvToolbar,
91
- customServices: {
92
- ...customServices,
93
- "service:exploring-redirect": (request) => redirectFiles(request),
94
- "service:exploring-data": (request) => serveExploringData(request),
95
- "service:explorables": (request) => serveExplorableListAsJson(request),
96
- },
97
- customCompilers,
98
- serverPlugins,
99
112
  jsenvDirectoryRelativeUrl,
100
113
  outDirectoryName,
101
114
  inlineImportMapIntoHTML,
102
115
 
103
- compileServerLogLevel,
104
116
  compileServerCanReadFromFilesystem,
105
117
  compileServerCanWriteOnFilesystem,
106
- compileServerPort,
107
- compileServerProtocol,
108
- compileServerHttp2,
109
- compileServerCertificate,
110
- compileServerPrivateKey,
118
+ customCompilers,
111
119
  sourcemapMethod,
112
120
  babelPluginMap,
113
121
  runtimeSupport: runtimeSupportDuringDev,
@@ -118,7 +126,10 @@ export const startExploring = async ({
118
126
  return compileServer
119
127
  }
120
128
 
121
- const createRedirectFilesService = ({ projectDirectoryUrl }) => {
129
+ const createRedirectFilesService = ({
130
+ projectDirectoryUrl,
131
+ mainFileRelativeUrl,
132
+ }) => {
122
133
  const jsenvExploringRedirectorHtmlRelativeUrlForProject = urlToRelativeUrl(
123
134
  jsenvExploringRedirectorHtmlFileInfo.url,
124
135
  projectDirectoryUrl,
@@ -136,31 +147,28 @@ const createRedirectFilesService = ({ projectDirectoryUrl }) => {
136
147
  projectDirectoryUrl,
137
148
  )
138
149
 
139
- // unfortunately browser resolves sourcemap to url before redirection (not after).
140
- // It means browser tries to load source map from "/.jsenv/jsenv-toolbar.js.map"
141
- // we could also inline sourcemap but it's not yet possible
142
- // inside buildProject
143
- const jsenvExploringIndexSourcemapInfo = {
144
- pathForBrowser: `/.jsenv/jsenv_exploring_index.js.map`,
145
- pathForServer: `/${jsenvExploringJsBuildRelativeUrlForProject}.map`,
146
- }
147
- const jsenvToolbarSourcemapInfo = {
148
- pathForBrowser: `/.jsenv/jsenv_toolbar.js.map`,
149
- pathForServer: `/${jsenvToolbarJsBuildRelativeUrlForProject}.map`,
150
- }
151
-
152
- return (request) => {
153
- // exploring redirection
154
- if (request.ressource === "/") {
155
- const jsenvExploringRedirectorHtmlServerUrl = `${request.origin}/${jsenvExploringRedirectorHtmlRelativeUrlForProject}`
150
+ return setupRoutes({
151
+ "/": (request) => {
152
+ const redirectTarget = mainFileRelativeUrl
153
+ const jsenvExploringRedirectorHtmlServerUrl = `${request.origin}/${jsenvExploringRedirectorHtmlRelativeUrlForProject}?redirect=${redirectTarget}`
156
154
  return {
157
155
  status: 307,
158
156
  headers: {
159
157
  location: jsenvExploringRedirectorHtmlServerUrl,
160
158
  },
161
159
  }
162
- }
163
- if (request.ressource === "/.jsenv/exploring.redirector.js") {
160
+ },
161
+ "/.jsenv/redirect/:rest*": (request) => {
162
+ const redirectTarget = request.ressource.slice("/.jsenv/redirect/".length)
163
+ const jsenvExploringRedirectorHtmlServerUrl = `${request.origin}/${jsenvExploringRedirectorHtmlRelativeUrlForProject}?redirect=${redirectTarget}`
164
+ return {
165
+ status: 307,
166
+ headers: {
167
+ location: jsenvExploringRedirectorHtmlServerUrl,
168
+ },
169
+ }
170
+ },
171
+ "/.jsenv/exploring.redirector.js": (request) => {
164
172
  const jsenvExploringRedirectorBuildServerUrl = `${request.origin}/${jsenvExploringRedirectorJsBuildRelativeUrlForProject}`
165
173
  return {
166
174
  status: 307,
@@ -168,10 +176,8 @@ const createRedirectFilesService = ({ projectDirectoryUrl }) => {
168
176
  location: jsenvExploringRedirectorBuildServerUrl,
169
177
  },
170
178
  }
171
- }
172
-
173
- // exploring index
174
- if (request.ressource === "/.jsenv/exploring.index.js") {
179
+ },
180
+ "/.jsenv/exploring.index.js": (request) => {
175
181
  const jsenvExploringJsBuildServerUrl = `${request.origin}/${jsenvExploringJsBuildRelativeUrlForProject}`
176
182
  return {
177
183
  status: 307,
@@ -179,37 +185,37 @@ const createRedirectFilesService = ({ projectDirectoryUrl }) => {
179
185
  location: jsenvExploringJsBuildServerUrl,
180
186
  },
181
187
  }
182
- }
183
- if (request.ressource === jsenvExploringIndexSourcemapInfo.pathForBrowser) {
188
+ },
189
+ "/.jsenv/toolbar.main.js": (request) => {
190
+ const jsenvToolbarJsBuildServerUrl = `${request.origin}/${jsenvToolbarJsBuildRelativeUrlForProject}`
184
191
  return {
185
192
  status: 307,
186
193
  headers: {
187
- location: `${request.origin}${jsenvExploringIndexSourcemapInfo.pathForServer}`,
194
+ location: jsenvToolbarJsBuildServerUrl,
188
195
  },
189
196
  }
190
- }
191
-
192
- // toolbar
193
- if (request.ressource === "/.jsenv/toolbar.main.js") {
194
- const jsenvToolbarJsBuildServerUrl = `${request.origin}/${jsenvToolbarJsBuildRelativeUrlForProject}`
197
+ },
198
+ // unfortunately browser resolves sourcemap to url before redirection (not after).
199
+ // It means browser tries to load source map from "/.jsenv/jsenv-toolbar.js.map"
200
+ // we could also inline sourcemap but it's not yet possible
201
+ // inside buildProject
202
+ "/.jsenv/jsenv_exploring_index.js.map": (request) => {
195
203
  return {
196
204
  status: 307,
197
205
  headers: {
198
- location: jsenvToolbarJsBuildServerUrl,
206
+ location: `${request.origin}/${jsenvExploringJsBuildRelativeUrlForProject}.map`,
199
207
  },
200
208
  }
201
- }
202
- if (request.ressource === jsenvToolbarSourcemapInfo.pathForBrowser) {
209
+ },
210
+ "/.jsenv/jsenv_toolbar.js.map": (request) => {
203
211
  return {
204
212
  status: 307,
205
213
  headers: {
206
- location: `${request.origin}${jsenvToolbarSourcemapInfo.pathForServer}`,
214
+ location: `${request.origin}/${jsenvToolbarJsBuildRelativeUrlForProject}.map`,
207
215
  },
208
216
  }
209
- }
210
-
211
- return null
212
- }
217
+ },
218
+ })
213
219
  }
214
220
 
215
221
  const createExploringDataService = ({
package/src/execute.js CHANGED
@@ -39,11 +39,11 @@ export const execute = async ({
39
39
  gracefulStopAllocatedMs,
40
40
  ignoreError = false,
41
41
 
42
- compileServerProtocol,
43
- compileServerPrivateKey,
44
- compileServerCertificate,
45
- compileServerIp,
46
- compileServerPort,
42
+ protocol,
43
+ privateKey,
44
+ certificate,
45
+ ip,
46
+ port,
47
47
  babelPluginMap,
48
48
  customCompilers,
49
49
  compileServerCanReadFromFilesystem,
@@ -94,7 +94,7 @@ export const execute = async ({
94
94
  stop,
95
95
  } = await startCompileServer({
96
96
  signal: executeOperation.signal,
97
- compileServerLogLevel,
97
+ logLevel: compileServerLogLevel,
98
98
 
99
99
  projectDirectoryUrl,
100
100
  jsenvDirectoryRelativeUrl,
@@ -103,11 +103,11 @@ export const execute = async ({
103
103
 
104
104
  importDefaultExtension,
105
105
 
106
- compileServerProtocol,
107
- compileServerPrivateKey,
108
- compileServerCertificate,
109
- compileServerIp,
110
- compileServerPort,
106
+ protocol,
107
+ privateKey,
108
+ certificate,
109
+ ip,
110
+ port,
111
111
  babelPluginMap,
112
112
  customCompilers,
113
113
  runtimeSupport: normalizeRuntimeSupport({
@@ -62,11 +62,11 @@ export const executeTestPlan = async ({
62
62
  // skip full means file with 100% coverage won't appear in coverage reports (log and html)
63
63
  coverageSkipFull = false,
64
64
 
65
- compileServerProtocol,
66
- compileServerPrivateKey,
67
- compileServerCertificate,
68
- compileServerIp,
69
- compileServerPort,
65
+ protocol,
66
+ privateKey,
67
+ certificate,
68
+ ip,
69
+ port,
70
70
  compileServerCanReadFromFilesystem,
71
71
  compileServerCanWriteOnFilesystem,
72
72
  babelPluginMap,
@@ -133,6 +133,7 @@ export const executeTestPlan = async ({
133
133
  handleSIGINT,
134
134
 
135
135
  logger,
136
+ logLevel,
136
137
  compileServerLogLevel,
137
138
  launchAndExecuteLogLevel,
138
139
 
@@ -157,11 +158,11 @@ export const executeTestPlan = async ({
157
158
  coverageTempDirectoryRelativeUrl,
158
159
 
159
160
  jsenvDirectoryClean,
160
- compileServerProtocol,
161
- compileServerPrivateKey,
162
- compileServerCertificate,
163
- compileServerIp,
164
- compileServerPort,
161
+ protocol,
162
+ privateKey,
163
+ certificate,
164
+ ip,
165
+ port,
165
166
  compileServerCanReadFromFilesystem,
166
167
  compileServerCanWriteOnFilesystem,
167
168
  babelPluginMap,
@@ -127,8 +127,7 @@ const computeCompileReport = async ({
127
127
  logger.warn(`WARNING: meta.sources is empty for ${compiledFileUrl}`)
128
128
  }
129
129
 
130
- const metaIsValid = cacheValidity.meta.isValid
131
-
130
+ const metaIsValid = cacheValidity.meta ? cacheValidity.meta.isValid : false
132
131
  const [compileTiming, compileResult] = await timeFunction("compile", () =>
133
132
  callCompile({
134
133
  logger,
@@ -20,6 +20,11 @@ export const validateCache = async ({
20
20
  }) => {
21
21
  const validity = { isValid: true }
22
22
 
23
+ // disable cahce for html files so that we always parse the importmap file
24
+ if (compiledFileUrl.endsWith(".html")) {
25
+ return { isValid: false }
26
+ }
27
+
23
28
  const metaJsonFileUrl = `${compiledFileUrl}__asset__meta.json`
24
29
  const metaValidity = await validateMetaFile(metaJsonFileUrl)
25
30
  mergeValidity(validity, "meta", metaValidity)
@@ -5,6 +5,7 @@ import {
5
5
  bufferToEtag,
6
6
  urlIsInsideOf,
7
7
  } from "@jsenv/filesystem"
8
+ import { normalizeImportMap, resolveImport } from "@jsenv/importmap"
8
9
  import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js"
9
10
 
10
11
  import { getOrGenerateCompiledFile } from "./compile-directory/getOrGenerateCompiledFile.js"
@@ -19,6 +20,7 @@ export const compileFile = async ({
19
20
  projectFileRequestedCallback = () => {},
20
21
  request,
21
22
  pushResponse,
23
+ importmapInfos,
22
24
  compile,
23
25
  compileCacheStrategy,
24
26
  compileCacheSourcesValidation,
@@ -106,30 +108,34 @@ export const compileFile = async ({
106
108
  )
107
109
  })
108
110
 
109
- if (
110
- request.http2 &&
111
+ if (request.http2) {
111
112
  // js resolution is special, we cannot just do resolveUrl of the import specifier
112
113
  // because there can be importmap (bare specifier, import without extension, custom remapping, ...)
113
114
  // And we would push 404 to the browser
114
115
  // Until we implement import map resolution pushing ressource for js imports is disabled
115
- compileResult.contentType !== "application/javascript"
116
- ) {
117
- compileResult.dependencies.forEach((dependency) => {
118
- const requestUrl = resolveUrl(request.ressource, request.origin)
119
- const dependencyUrl = resolveUrl(dependency, requestUrl)
120
- if (!urlIsInsideOf(dependencyUrl, request.origin)) {
121
- // ignore external urls
122
- return
123
- }
124
- if (dependencyUrl.startsWith("data:")) {
125
- return
126
- }
127
- const dependencyRelativeUrl = urlToRelativeUrl(
128
- dependencyUrl,
129
- request.origin,
130
- )
131
- pushResponse({ path: `/${dependencyRelativeUrl}` })
116
+ const dependencyResolver = getDependencyResolver({
117
+ compileResult,
118
+ importmapInfos,
119
+ request,
120
+ projectDirectoryUrl,
132
121
  })
122
+ if (dependencyResolver) {
123
+ compileResult.dependencies.forEach((dependency) => {
124
+ const dependencyUrl = dependencyResolver.resolve(dependency)
125
+ if (!urlIsInsideOf(dependencyUrl, request.origin)) {
126
+ // ignore external urls
127
+ return
128
+ }
129
+ if (dependencyUrl.startsWith("data:")) {
130
+ return
131
+ }
132
+ const dependencyRelativeUrl = urlToRelativeUrl(
133
+ dependencyUrl,
134
+ request.origin,
135
+ )
136
+ pushResponse({ path: `/${dependencyRelativeUrl}` })
137
+ })
138
+ }
133
139
  }
134
140
 
135
141
  // when a compiled version of the source file was just created or updated
@@ -229,3 +235,62 @@ export const compileFile = async ({
229
235
  return convertFileSystemErrorToResponseProperties(error)
230
236
  }
231
237
  }
238
+
239
+ const getDependencyResolver = ({
240
+ compileResult,
241
+ importmapInfos,
242
+ request,
243
+ projectDirectoryUrl,
244
+ }) => {
245
+ const importmapKeys = Object.keys(importmapInfos)
246
+ const requestUrl = resolveUrl(request.ressource, request.origin)
247
+
248
+ if (
249
+ compileResult.contentType !== "application/javascript" ||
250
+ importmapKeys.length === 0
251
+ ) {
252
+ return {
253
+ type: "url_resolution",
254
+ resolve: (dependency) => {
255
+ const dependencyUrl = resolveUrl(dependency, requestUrl)
256
+ return dependencyUrl
257
+ },
258
+ }
259
+ }
260
+
261
+ const firstImportmapInfo = importmapInfos[importmapKeys[0]]
262
+ if (!firstImportmapInfo.text) {
263
+ return null
264
+ }
265
+
266
+ if (
267
+ // we are aware only of 1 importmap
268
+ importmapKeys.length === 1 ||
269
+ // all importmaps are the same
270
+ importmapKeys.slice(1).every(({ url }) => url === firstImportmapInfo.url)
271
+ ) {
272
+ const importMapBaseUrl = resolveUrl(
273
+ urlToRelativeUrl(firstImportmapInfo.url, projectDirectoryUrl),
274
+ `${request.origin}/`,
275
+ )
276
+ const importMap = normalizeImportMap(
277
+ JSON.parse(firstImportmapInfo.text),
278
+ importMapBaseUrl,
279
+ )
280
+ return {
281
+ type: "importmap_resolution",
282
+ resolve: (dependency) => {
283
+ const dependencyUrl = resolveImport({
284
+ specifier: dependency,
285
+ importer: requestUrl,
286
+ importMap,
287
+ })
288
+ return dependencyUrl
289
+ },
290
+ }
291
+ }
292
+
293
+ // there is more than 2 importmaps, we cannot know which one to pick
294
+ // (not supposed ot happen because during dev you usually use a single importmap)
295
+ return null
296
+ }
@@ -80,6 +80,8 @@ export const createCompiledFileService = ({
80
80
  projectDirectoryUrl,
81
81
  )
82
82
 
83
+ const importmapInfos = {}
84
+
83
85
  return (request, { pushResponse, redirectRequest }) => {
84
86
  const { origin, ressource } = request
85
87
  // we use "ressourceToPathname" to remove eventual query param from the url
@@ -167,6 +169,7 @@ export const createCompiledFileService = ({
167
169
  projectFileRequestedCallback,
168
170
  request,
169
171
  pushResponse,
172
+ importmapInfos,
170
173
  compile: ({ code }) => {
171
174
  return compiler({
172
175
  logger,
@@ -195,6 +198,9 @@ export const createCompiledFileService = ({
195
198
  sourcemapMethod,
196
199
  sourcemapExcludeSources,
197
200
  jsenvToolbarInjection,
201
+ onHtmlImportmapInfo: ({ htmlUrl, importmapInfo }) => {
202
+ importmapInfos[htmlUrl] = importmapInfo
203
+ },
198
204
  })
199
205
  },
200
206
  })
@@ -234,36 +240,40 @@ const getCompiler = ({ originalFileUrl, compileMeta }) => {
234
240
  // there is a custom compiler and potentially a jsenv compiler
235
241
  return async (params) => {
236
242
  // do custom compilation first
237
- const customResult = await customCompiler(params)
243
+ const customCompilerReturnValue = await customCompiler(params)
238
244
  // then check if jsenv compiler should apply
239
245
  // to the new result contentType
240
246
  const jsenvCompilerAfterCustomCompilation =
241
247
  getJsenvCompilerAfterCustomCompilation({
242
248
  url: originalFileUrl,
243
- contentType: customResult.contentType,
244
249
  compileMeta,
250
+ customCompilerReturnValue,
245
251
  })
246
252
  if (!jsenvCompilerAfterCustomCompilation) {
247
- return customResult
253
+ return customCompilerReturnValue
248
254
  }
249
- const jsenvResult = await jsenvCompilerAfterCustomCompilation({
255
+ const jsenvCompilerReturnValue = await jsenvCompilerAfterCustomCompilation({
250
256
  ...params,
251
- code: customResult.compiledSource,
252
- map: customResult.sourcemap,
257
+ code: customCompilerReturnValue.compiledSource,
258
+ map: customCompilerReturnValue.sourcemap,
253
259
  })
254
260
  return {
255
- ...customResult,
256
- ...jsenvResult,
261
+ ...customCompilerReturnValue,
262
+ ...jsenvCompilerReturnValue,
257
263
  }
258
264
  }
259
265
  }
260
266
 
261
267
  const getJsenvCompilerAfterCustomCompilation = ({
262
268
  url,
263
- contentType,
264
269
  compileMeta,
270
+ customCompilerReturnValue,
265
271
  }) => {
266
- const extensionToForce = contentTypeExtensions[contentType]
272
+ if (customCompilerReturnValue.isBuild) {
273
+ return null
274
+ }
275
+ const extensionToForce =
276
+ contentTypeExtensions[customCompilerReturnValue.contentType]
267
277
  const urlForcingExtension = extensionToForce
268
278
  ? setUrlExtension(url, extensionToForce)
269
279
  : url