@jsenv/core 27.7.0 → 28.0.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.
Files changed (36) hide show
  1. package/dist/js/server_events_client.js +1 -1
  2. package/dist/main.js +504 -457
  3. package/package.json +2 -2
  4. package/src/build/build.js +4 -5
  5. package/src/build/start_build_server.js +2 -2
  6. package/src/dev/start_dev_server.js +3 -3
  7. package/src/execute/execute.js +14 -52
  8. package/src/execute/runtimes/browsers/from_playwright.js +19 -8
  9. package/src/main.js +3 -0
  10. package/src/omega/kitchen.js +9 -14
  11. package/src/omega/omega_server.js +4 -4
  12. package/src/omega/server/file_service.js +11 -11
  13. package/src/ping_server.js +30 -0
  14. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +0 -4
  15. package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +1 -1
  16. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +1 -1
  17. package/src/plugins/autoreload/jsenv_plugin_hmr.js +1 -1
  18. package/src/plugins/bundling/jsenv_plugin_bundling.js +1 -3
  19. package/src/plugins/cache_control/jsenv_plugin_cache_control.js +2 -5
  20. package/src/plugins/commonjs_globals/jsenv_plugin_commonjs_globals.js +2 -2
  21. package/src/plugins/file_urls/jsenv_plugin_file_urls.js +4 -8
  22. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +1 -4
  23. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +2 -2
  24. package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +12 -26
  25. package/src/plugins/importmap/jsenv_plugin_importmap.js +1 -1
  26. package/src/plugins/minification/jsenv_plugin_minification.js +1 -3
  27. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +9 -7
  28. package/src/plugins/plugin_controller.js +17 -6
  29. package/src/plugins/plugins.js +0 -2
  30. package/src/plugins/server_events/client/server_events_client.js +1 -1
  31. package/src/plugins/toolbar/jsenv_plugin_toolbar.js +1 -3
  32. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +13 -0
  33. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +1 -1
  34. package/src/plugins/url_analysis/html/html_urls.js +2 -2
  35. package/src/test/execute_plan.js +15 -68
  36. package/src/test/execute_test_plan.js +4 -26
@@ -1,11 +1,10 @@
1
1
  /*
2
2
  * Source code can contain the following
3
3
  * - import.meta.dev
4
- * - import.meta.test
5
4
  * - import.meta.build
6
5
  * They are either:
7
6
  * - replaced by true: When scenario matches (import.meta.dev and it's the dev server)
8
- * - left as is to be evaluated to undefined (import.meta.test but it's the dev server)
7
+ * - left as is to be evaluated to undefined (import.meta.build but it's the dev server)
9
8
  * - replaced by undefined (import.meta.dev but it's build; the goal is to ensure it's tree-shaked)
10
9
  */
11
10
 
@@ -17,7 +16,7 @@ export const jsenvPluginImportMetaScenarios = () => {
17
16
  name: "jsenv:import_meta_scenario",
18
17
  appliesDuring: "*",
19
18
  transformUrlContent: {
20
- js_module: async (urlInfo, { scenario }) => {
19
+ js_module: async (urlInfo, context) => {
21
20
  if (
22
21
  !urlInfo.content.includes("import.meta.dev") &&
23
22
  !urlInfo.content.includes("import.meta.test") &&
@@ -29,37 +28,25 @@ export const jsenvPluginImportMetaScenarios = () => {
29
28
  babelPlugins: [babelPluginMetadataImportMetaScenarios],
30
29
  urlInfo,
31
30
  })
32
- const { dev = [], test = [], build = [] } = metadata.importMetaScenarios
31
+ const { dev = [], build = [] } = metadata.importMetaScenarios
33
32
  const replacements = []
34
33
  const replace = (path, value) => {
35
34
  replacements.push({ path, value })
36
35
  }
37
- if (scenario === "dev") {
36
+ if (context.scenarios.build) {
37
+ // during build ensure replacement for tree-shaking
38
38
  dev.forEach((path) => {
39
- replace(path, "true")
40
- })
41
- } else if (scenario === "test") {
42
- // test is also considered a dev environment
43
- // just like the dev server can be used to debug test files
44
- // without this people would have to write
45
- // if (import.meta.dev || import.meta.test) or if (!import.meta.build)
46
- dev.forEach((path) => {
47
- replace(path, "true")
39
+ replace(path, "undefined")
48
40
  })
49
- test.forEach((path) => {
41
+ build.forEach((path) => {
50
42
  replace(path, "true")
51
43
  })
52
- } else if (scenario === "build") {
53
- // replacing by undefined might not be required
54
- // as I suppose rollup would consider them as undefined
55
- // but let's make it explicit to ensure code is properly tree-shaked
44
+ } else {
45
+ // during dev we can let "import.meta.build" untouched
46
+ // it will be evaluated to undefined.
47
+ // Moreover it can be surprising to see some "undefined"
48
+ // when source file contains "import.meta.build"
56
49
  dev.forEach((path) => {
57
- replace(path, "undefined")
58
- })
59
- test.forEach((path) => {
60
- replace(path, "undefined")
61
- })
62
- build.forEach((path) => {
63
50
  replace(path, "true")
64
51
  })
65
52
  }
@@ -106,7 +93,6 @@ const babelPluginMetadataImportMetaScenarios = () => {
106
93
  })
107
94
  state.file.metadata.importMetaScenarios = {
108
95
  dev: importMetas.dev,
109
- test: importMetas.test,
110
96
  build: importMetas.build,
111
97
  }
112
98
  },
@@ -202,7 +202,7 @@ export const jsenvPluginImportmap = () => {
202
202
  // by "formatReferencedUrl" making the importmap presence useless.
203
203
  // In dev/test we keep importmap into the HTML to see it even if useless
204
204
  // Duing build we get rid of it
205
- if (context.scenario === "build") {
205
+ if (context.scenarios.build) {
206
206
  removeHtmlNode(importmap)
207
207
  }
208
208
  return {
@@ -40,9 +40,7 @@ export const jsenvPluginMinification = (minification) => {
40
40
 
41
41
  return {
42
42
  name: "jsenv:minification",
43
- appliesDuring: {
44
- build: true,
45
- },
43
+ appliesDuring: "build",
46
44
  optimizeUrlContent: {
47
45
  html: htmlOptimizer,
48
46
  svg: htmlOptimizer,
@@ -27,8 +27,10 @@ export const jsenvPluginNodeEsmResolution = ({
27
27
  return {
28
28
  name: "jsenv:node_esm_resolution",
29
29
  appliesDuring: "*",
30
- init: ({ rootDirectoryUrl, scenario, runtimeCompat, urlGraph }) => {
31
- const nodeRuntimeEnabled = Object.keys(runtimeCompat).includes("node")
30
+ init: (context) => {
31
+ const nodeRuntimeEnabled = Object.keys(context.runtimeCompat).includes(
32
+ "node",
33
+ )
32
34
  // https://nodejs.org/api/esm.html#resolver-algorithm-specification
33
35
  packageConditions = packageConditions || [
34
36
  ...readCustomConditionsFromProcessArgs(),
@@ -57,19 +59,19 @@ export const jsenvPluginNodeEsmResolution = ({
57
59
  return packageJson
58
60
  }
59
61
 
60
- if (scenario === "dev") {
62
+ if (context.scenarios.dev) {
61
63
  const onFileChange = () => {
62
64
  packageScopesCache.clear()
63
65
  packageJsonsCache.clear()
64
- urlGraph.urlInfoMap.forEach((urlInfo) => {
66
+ context.urlGraph.urlInfoMap.forEach((urlInfo) => {
65
67
  if (urlInfo.dependsOnPackageJson) {
66
- urlGraph.considerModified(urlInfo)
68
+ context.urlGraph.considerModified(urlInfo)
67
69
  }
68
70
  })
69
71
  }
70
72
  filesInvalidatingCache.forEach((file) => {
71
73
  const unregister = registerFileLifecycle(
72
- new URL(file, rootDirectoryUrl),
74
+ new URL(file, context.rootDirectoryUrl),
73
75
  {
74
76
  added: () => {
75
77
  onFileChange()
@@ -131,7 +133,7 @@ export const jsenvPluginNodeEsmResolution = ({
131
133
  return null
132
134
  },
133
135
  transformUrlSearchParams: (reference, context) => {
134
- if (context.scenario === "build") {
136
+ if (context.scenarios.build) {
135
137
  return null
136
138
  }
137
139
  if (!reference.url.startsWith("file:")) {
@@ -17,8 +17,8 @@ const HOOK_NAMES = [
17
17
  "destroy",
18
18
  ]
19
19
 
20
- export const createPluginController = ({ plugins, scenario }) => {
21
- const flatPlugins = flattenAndFilterPlugins(plugins, { scenario })
20
+ export const createPluginController = ({ plugins, scenarios }) => {
21
+ const flatPlugins = flattenAndFilterPlugins(plugins, { scenarios })
22
22
  // precompute a list of hooks per hookName for one major reason:
23
23
  // - When debugging, there is less iteration
24
24
  // also it should increase perf as there is less work to do
@@ -187,7 +187,7 @@ export const createPluginController = ({ plugins, scenario }) => {
187
187
  }
188
188
  }
189
189
 
190
- const flattenAndFilterPlugins = (plugins, { scenario }) => {
190
+ const flattenAndFilterPlugins = (plugins, { scenarios }) => {
191
191
  const flatPlugins = []
192
192
  const visitPluginEntry = (pluginEntry) => {
193
193
  if (Array.isArray(pluginEntry)) {
@@ -208,13 +208,14 @@ const flattenAndFilterPlugins = (plugins, { scenario }) => {
208
208
  return
209
209
  }
210
210
  if (typeof appliesDuring === "string") {
211
- if (!["dev", "build", "test"].includes(appliesDuring)) {
211
+ if (!["dev", "test", "build"].includes(appliesDuring)) {
212
212
  throw new Error(
213
213
  `"appliesDuring" must be "dev", "test" or "build", got ${appliesDuring}`,
214
214
  )
215
215
  }
216
- if (appliesDuring === scenario) {
216
+ if (scenarios[appliesDuring]) {
217
217
  flatPlugins.push(pluginEntry)
218
+ return
218
219
  }
219
220
  return
220
221
  }
@@ -223,7 +224,17 @@ const flattenAndFilterPlugins = (plugins, { scenario }) => {
223
224
  `"appliesDuring" must be an object or a string, got ${appliesDuring}`,
224
225
  )
225
226
  }
226
- if (appliesDuring[scenario]) {
227
+ let applies
228
+ for (const key of Object.keys(appliesDuring)) {
229
+ if (!appliesDuring[key] && scenarios[key]) {
230
+ applies = false
231
+ break
232
+ }
233
+ if (appliesDuring[key] && scenarios[key]) {
234
+ applies = true
235
+ }
236
+ }
237
+ if (applies) {
227
238
  flatPlugins.push(pluginEntry)
228
239
  return
229
240
  }
@@ -24,7 +24,6 @@ import { jsenvPluginExplorer } from "./explorer/jsenv_plugin_explorer.js"
24
24
 
25
25
  export const getCorePlugins = ({
26
26
  rootDirectoryUrl,
27
- scenario,
28
27
  runtimeCompat,
29
28
 
30
29
  urlAnalysis = {},
@@ -84,7 +83,6 @@ export const getCorePlugins = ({
84
83
  ? [
85
84
  jsenvPluginAutoreload({
86
85
  ...clientAutoreload,
87
- scenario,
88
86
  clientFileChangeCallbackList,
89
87
  clientFilesPruneCallbackList,
90
88
  }),
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { createWebSocketConnection } from "./web_socket_connection.js"
4
4
 
5
- const websocketScheme = self.location.protocol === "https" ? "wss" : "ws"
5
+ const websocketScheme = self.location.protocol === "https:" ? "wss" : "ws"
6
6
  const websocketUrl = `${websocketScheme}://${self.location.host}${self.location.pathname}${self.location.search}`
7
7
  const websocketConnection = createWebSocketConnection(websocketUrl, {
8
8
  retry: true,
@@ -17,9 +17,7 @@ export const jsenvPluginToolbar = ({ logs = false } = {}) => {
17
17
 
18
18
  return {
19
19
  name: "jsenv:toolbar",
20
- appliesDuring: {
21
- dev: true,
22
- },
20
+ appliesDuring: { dev: true, test: false },
23
21
  transformUrlContent: {
24
22
  html: ({ url, content }, { referenceUtils }) => {
25
23
  if (url === toolbarHtmlClientFileUrl) {
@@ -1,5 +1,6 @@
1
1
  import { applyBabelPlugins } from "@jsenv/ast"
2
2
 
3
+ import { babelPluginInstrument } from "@jsenv/core/src/test/coverage/babel_plugin_instrument.js"
3
4
  import { RUNTIME_COMPAT } from "@jsenv/core/src/omega/compat/runtime_compat.js"
4
5
  import { getBaseBabelPluginStructure } from "./helpers/babel_plugin_structure.js"
5
6
  import { babelPluginBabelHelpersAsJsenvImports } from "./helpers/babel_plugin_babel_helpers_as_jsenv_imports.js"
@@ -54,6 +55,18 @@ export const jsenvPluginBabel = ({
54
55
  isJsModule,
55
56
  getImportSpecifier,
56
57
  })
58
+ if (context.scenarios.dev) {
59
+ const requestHeaders = context.request.headers
60
+ if (requestHeaders["x-coverage-instanbul"]) {
61
+ babelPluginStructure["transform-instrument"] = [
62
+ babelPluginInstrument,
63
+ {
64
+ rootDirectoryUrl: context.rootDirectoryUrl,
65
+ coverageConfig: JSON.parse(requestHeaders["x-coverage-instanbul"]),
66
+ },
67
+ ]
68
+ }
69
+ }
57
70
  if (getCustomBabelPlugins) {
58
71
  Object.assign(babelPluginStructure, getCustomBabelPlugins(context))
59
72
  }
@@ -43,7 +43,7 @@ export const jsenvPluginImportAssertions = ({
43
43
  // We would have to tell rollup to ignore import with assertion
44
44
  // - means rollup can bundle more js file together
45
45
  // - means url versioning can work for css inlined in js
46
- if (context.scenario === "build") {
46
+ if (context.scenarios.build) {
47
47
  json = true
48
48
  css = true
49
49
  text = true
@@ -13,9 +13,9 @@ import {
13
13
  export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
14
14
  const url = urlInfo.originalUrl
15
15
  const content = urlInfo.content
16
- const { scenario, referenceUtils } = context
16
+ const { scenarios, referenceUtils } = context
17
17
  const htmlAst = parseHtmlString(content, {
18
- storeOriginalPositions: scenario !== "build",
18
+ storeOriginalPositions: scenarios.dev,
19
19
  })
20
20
  const actions = []
21
21
  visitHtmlUrls({
@@ -14,11 +14,10 @@ import {
14
14
  import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
15
15
  import { ensureEmptyDirectory, writeFileSync } from "@jsenv/filesystem"
16
16
 
17
- import { babelPluginInstrument } from "./coverage/babel_plugin_instrument.js"
18
17
  import { reportToCoverage } from "./coverage/report_to_coverage.js"
19
- import { startOmegaServer } from "@jsenv/core/src/omega/omega_server.js"
20
18
  import { run } from "@jsenv/core/src/execute/run.js"
21
19
 
20
+ import { pingServer } from "../ping_server.js"
22
21
  import { ensureGlobalGc } from "./gc.js"
23
22
  import { generateExecutionSteps } from "./execution_steps.js"
24
23
  import { createExecutionLog, createSummaryLog } from "./logs_file_execution.js"
@@ -37,10 +36,10 @@ export const executePlan = async (
37
36
  logFileRelativeUrl,
38
37
  completedExecutionLogMerging,
39
38
  completedExecutionLogAbbreviation,
40
-
41
39
  rootDirectoryUrl,
40
+ devServerOrigin,
41
+
42
42
  keepRunning,
43
- services,
44
43
  defaultMsAllocatedPerExecution,
45
44
  maxExecutionsInParallel,
46
45
  failFast,
@@ -55,20 +54,6 @@ export const executePlan = async (
55
54
  coverageV8ConflictWarning,
56
55
  coverageTempDirectoryRelativeUrl,
57
56
 
58
- scenario,
59
- sourcemaps,
60
- plugins,
61
- nodeEsmResolution,
62
- fileSystemMagicResolution,
63
- transpilation,
64
- writeGeneratedFiles,
65
-
66
- protocol,
67
- privateKey,
68
- certificate,
69
- host,
70
- port,
71
-
72
57
  beforeExecutionCallback = () => {},
73
58
  afterExecutionCallback = () => {},
74
59
  } = {},
@@ -88,7 +73,7 @@ export const executePlan = async (
88
73
  const { runtime } = executionConfig
89
74
  if (runtime) {
90
75
  runtimes[runtime.name] = runtime.version
91
- if (runtime.needsServer) {
76
+ if (runtime.type === "browser") {
92
77
  someNeedsServer = true
93
78
  }
94
79
  if (runtime.type === "node") {
@@ -189,6 +174,7 @@ export const executePlan = async (
189
174
 
190
175
  let runtimeParams = {
191
176
  rootDirectoryUrl,
177
+ devServerOrigin,
192
178
  coverageEnabled,
193
179
  coverageConfig,
194
180
  coverageMethodForBrowsers,
@@ -196,55 +182,16 @@ export const executePlan = async (
196
182
  stopAfterAllSignal,
197
183
  }
198
184
  if (someNeedsServer) {
199
- const server = await startOmegaServer({
200
- signal: multipleExecutionsOperation.signal,
201
- logLevel: "warn",
202
- keepProcessAlive: false,
203
- port,
204
- host,
205
- protocol,
206
- certificate,
207
- privateKey,
208
- services,
209
-
210
- rootDirectoryUrl,
211
- scenario,
212
- runtimeCompat: runtimes,
213
-
214
- plugins,
215
- htmlSupervisor: true,
216
- nodeEsmResolution,
217
- fileSystemMagicResolution,
218
- transpilation: {
219
- ...transpilation,
220
- getCustomBabelPlugins: ({ clientRuntimeCompat }) => {
221
- if (
222
- coverageEnabled &&
223
- (coverageMethodForBrowsers !== "playwright_api" ||
224
- Object.keys(clientRuntimeCompat)[0] !== "chrome")
225
- ) {
226
- return {
227
- "transform-instrument": [
228
- babelPluginInstrument,
229
- {
230
- rootDirectoryUrl,
231
- coverageConfig,
232
- },
233
- ],
234
- }
235
- }
236
- return {}
237
- },
238
- },
239
- sourcemaps,
240
- writeGeneratedFiles,
241
- })
242
- multipleExecutionsOperation.addEndCallback(async () => {
243
- await server.stop()
244
- })
245
- runtimeParams = {
246
- ...runtimeParams,
247
- server,
185
+ if (!devServerOrigin) {
186
+ throw new TypeError(
187
+ `devServerOrigin is required when running tests on browser(s)`,
188
+ )
189
+ }
190
+ const devServerStarted = await pingServer(devServerOrigin)
191
+ if (!devServerStarted) {
192
+ throw new Error(
193
+ `dev server not started at ${devServerOrigin}. It is required to run tests`,
194
+ )
248
195
  }
249
196
  }
250
197
 
@@ -17,9 +17,10 @@ import { generateCoverageTextLog } from "./coverage/coverage_reporter_text_log.j
17
17
  import { executePlan } from "./execute_plan.js"
18
18
 
19
19
  /**
20
- * Execute a list of files and log how it goes
20
+ * Execute a list of files and log how it goes.
21
21
  * @param {Object} testPlanParameters
22
22
  * @param {string|url} testPlanParameters.rootDirectoryUrl Root directory of the project
23
+ * @param {string|url} [testPlanParameters.serverOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
23
24
  * @param {Object} testPlanParameters.testPlan Object associating patterns leading to files to runtimes where they should be executed
24
25
  * @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
25
26
  * @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
@@ -45,6 +46,7 @@ export const executeTestPlan = async ({
45
46
  completedExecutionLogAbbreviation = false,
46
47
  completedExecutionLogMerging = false,
47
48
  rootDirectoryUrl,
49
+ devServerOrigin,
48
50
 
49
51
  testPlan,
50
52
  updateProcessExitCode = true,
@@ -79,18 +81,6 @@ export const executeTestPlan = async ({
79
81
  coverageReportTextLog = true,
80
82
  coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
81
83
  coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
82
-
83
- sourcemaps = "inline",
84
- plugins = [],
85
- nodeEsmResolution,
86
- fileSystemMagicResolution,
87
- writeGeneratedFiles = false,
88
-
89
- protocol,
90
- privateKey,
91
- certificate,
92
- host,
93
- port,
94
84
  }) => {
95
85
  const logger = createLogger({ logLevel })
96
86
  rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
@@ -150,6 +140,7 @@ export const executeTestPlan = async ({
150
140
  completedExecutionLogMerging,
151
141
  completedExecutionLogAbbreviation,
152
142
  rootDirectoryUrl,
143
+ devServerOrigin,
153
144
 
154
145
  maxExecutionsInParallel,
155
146
  defaultMsAllocatedPerExecution,
@@ -165,19 +156,6 @@ export const executeTestPlan = async ({
165
156
  coverageMethodForNodeJs,
166
157
  coverageV8ConflictWarning,
167
158
  coverageTempDirectoryRelativeUrl,
168
-
169
- scenario: "test",
170
- sourcemaps,
171
- plugins,
172
- nodeEsmResolution,
173
- fileSystemMagicResolution,
174
- writeGeneratedFiles,
175
-
176
- protocol,
177
- privateKey,
178
- certificate,
179
- host,
180
- port,
181
159
  })
182
160
  if (
183
161
  updateProcessExitCode &&