@jsenv/core 27.0.3 → 27.2.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 (37) hide show
  1. package/dist/controllable_child_process.mjs +139 -0
  2. package/dist/controllable_worker_thread.mjs +103 -0
  3. package/dist/js/execute_using_dynamic_import.js +169 -0
  4. package/dist/js/v8_coverage.js +539 -0
  5. package/dist/main.js +683 -818
  6. package/package.json +9 -8
  7. package/src/build/build.js +9 -12
  8. package/src/build/build_urls_generator.js +1 -1
  9. package/src/build/inject_global_version_mappings.js +3 -2
  10. package/src/build/inject_service_worker_urls.js +1 -2
  11. package/src/execute/run.js +50 -68
  12. package/src/execute/runtimes/browsers/chromium.js +1 -1
  13. package/src/execute/runtimes/browsers/firefox.js +1 -1
  14. package/src/execute/runtimes/browsers/from_playwright.js +13 -8
  15. package/src/execute/runtimes/browsers/webkit.js +1 -1
  16. package/src/execute/runtimes/node/{controllable_file.mjs → controllable_child_process.mjs} +18 -50
  17. package/src/execute/runtimes/node/controllable_worker_thread.mjs +103 -0
  18. package/src/execute/runtimes/node/execute_using_dynamic_import.js +49 -0
  19. package/src/execute/runtimes/node/exit_codes.js +9 -0
  20. package/src/execute/runtimes/node/{node_process.js → node_child_process.js} +56 -50
  21. package/src/execute/runtimes/node/node_worker_thread.js +268 -25
  22. package/src/execute/runtimes/node/profiler_v8_coverage.js +56 -0
  23. package/src/main.js +3 -1
  24. package/src/omega/kitchen.js +19 -6
  25. package/src/omega/server/file_service.js +2 -2
  26. package/src/omega/url_graph/url_graph_load.js +0 -1
  27. package/src/omega/url_graph.js +1 -0
  28. package/src/plugins/bundling/js_module/bundle_js_module.js +2 -5
  29. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +18 -15
  30. package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +2 -1
  31. package/src/test/coverage/report_to_coverage.js +16 -19
  32. package/src/test/coverage/v8_coverage.js +26 -0
  33. package/src/test/coverage/{v8_coverage_from_directory.js → v8_coverage_node_directory.js} +22 -26
  34. package/src/test/execute_plan.js +98 -91
  35. package/src/test/execute_test_plan.js +19 -13
  36. package/src/test/logs_file_execution.js +90 -13
  37. package/dist/js/controllable_file.mjs +0 -227
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "27.0.3",
3
+ "version": "27.2.1",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -43,9 +43,9 @@
43
43
  "eslint": "npx eslint . --ext=.js,.mjs,.cjs,.html",
44
44
  "dev": "node --conditions=development ./scripts/dev/dev.mjs",
45
45
  "test": "node --conditions=development ./scripts/test/test.mjs",
46
- "test:coverage": "npm run test -- --coverage",
46
+ "test:coverage": "cross-env NODE_V8_COVERAGE=.coverage/node npm run test -- --coverage",
47
+ "test:workspace": "npm run test --workspaces --if-present -- --workspace",
47
48
  "build": "node --conditions=development ./scripts/build/build.mjs",
48
- "workspace:test": "npm run test --workspaces --if-present -- --workspace",
49
49
  "workspace:versions": "node ./scripts/publish/workspace_versions.mjs",
50
50
  "workspace:publish": "node ./scripts/publish/workspace_publish.mjs",
51
51
  "performances": "node --expose-gc ./scripts/performance/generate_performance_report.mjs --log --once",
@@ -65,20 +65,20 @@
65
65
  "@babel/plugin-transform-modules-umd": "7.18.6",
66
66
  "@c88/v8-coverage": "0.1.1",
67
67
  "@financial-times/polyfill-useragent-normaliser": "2.0.1",
68
- "@jsenv/ast": "1.1.1",
69
68
  "@jsenv/abort": "4.2.3",
69
+ "@jsenv/ast": "1.1.1",
70
+ "@jsenv/babel-plugins": "1.0.5",
70
71
  "@jsenv/filesystem": "4.1.0",
71
72
  "@jsenv/importmap": "1.2.1",
72
73
  "@jsenv/integrity": "0.0.1",
74
+ "@jsenv/log": "3.1.0",
73
75
  "@jsenv/node-esm-resolution": "0.1.0",
74
- "@jsenv/server": "12.7.4",
76
+ "@jsenv/server": "12.7.5",
77
+ "@jsenv/sourcemap": "1.0.1",
75
78
  "@jsenv/uneval": "1.6.0",
76
79
  "@jsenv/url-meta": "7.0.0",
77
80
  "@jsenv/urls": "1.2.6",
78
81
  "@jsenv/utils": "2.0.1",
79
- "@jsenv/babel-plugins": "1.0.5",
80
- "@jsenv/log": "3.0.2",
81
- "@jsenv/sourcemap": "1.0.1",
82
82
  "acorn-import-assertions": "1.8.0",
83
83
  "cuid": "2.1.8",
84
84
  "html-minifier": "4.0.0",
@@ -103,6 +103,7 @@
103
103
  "@jsenv/https-local": "2.1.0",
104
104
  "@jsenv/package-workspace": "0.4.1",
105
105
  "@jsenv/performance-impact": "3.0.1",
106
+ "cross-env": "7.0.3",
106
107
  "eslint": "8.19.0",
107
108
  "eslint-plugin-html": "6.2.0",
108
109
  "eslint-plugin-import": "2.26.0",
@@ -287,7 +287,7 @@ build ${entryPointKeys.length} entry points`)
287
287
  }
288
288
  }
289
289
  GRAPH.forEach(rawGraph, (rawUrlInfo) => {
290
- if (rawUrlInfo.data.isEntryPoint) {
290
+ if (rawUrlInfo.isEntryPoint) {
291
291
  addToBundlerIfAny(rawUrlInfo)
292
292
  if (rawUrlInfo.type === "html") {
293
293
  rawUrlInfo.dependencies.forEach((dependencyUrl) => {
@@ -370,6 +370,7 @@ build ${entryPointKeys.length} entry points`)
370
370
  const bundleUrlInfo = {
371
371
  type,
372
372
  subtype: rawUrlInfo ? rawUrlInfo.subtype : undefined,
373
+ isEntryPoint: rawUrlInfo ? rawUrlInfo.isEntryPoint : undefined,
373
374
  filename: rawUrlInfo ? rawUrlInfo.filename : undefined,
374
375
  originalUrl: rawUrlInfo ? rawUrlInfo.originalUrl : undefined,
375
376
  originalContent: rawUrlInfo
@@ -516,11 +517,10 @@ build ${entryPointKeys.length} entry points`)
516
517
  // Here we just want to reserve an url for that file
517
518
  const buildUrl = buildUrlsGenerator.generate(rawUrl, {
518
519
  urlInfo: {
519
- data: {
520
- ...reference.data,
521
- isWebWorkerEntryPoint:
522
- isWebWorkerEntryPointReference(reference),
523
- },
520
+ data: reference.data,
521
+ isEntryPoint:
522
+ reference.isEntryPoint ||
523
+ isWebWorkerEntryPointReference(reference),
524
524
  type: reference.expectedType,
525
525
  subtype: reference.expectedSubtype,
526
526
  filename: reference.filename,
@@ -801,7 +801,7 @@ ${Object.keys(finalGraph.urlInfos).join("\n")}`,
801
801
  // - versioning update inline content
802
802
  // - file converted for import assertion of js_classic conversion
803
803
  if (
804
- !urlInfo.data.isEntryPoint &&
804
+ !urlInfo.isEntryPoint &&
805
805
  urlInfo.type !== "sourcemap" &&
806
806
  urlInfo.dependents.size === 0
807
807
  ) {
@@ -998,7 +998,7 @@ const applyUrlVersioning = async ({
998
998
  if (!urlInfo.shouldHandle) {
999
999
  return
1000
1000
  }
1001
- if (!urlInfo.data.isEntryPoint && urlInfo.dependents.size === 0) {
1001
+ if (!urlInfo.isEntryPoint && urlInfo.dependents.size === 0) {
1002
1002
  return
1003
1003
  }
1004
1004
 
@@ -1255,14 +1255,11 @@ const assertEntryPoints = ({ entryPoints }) => {
1255
1255
  }
1256
1256
 
1257
1257
  const canUseVersionedUrl = (urlInfo) => {
1258
- if (urlInfo.data.isEntryPoint) {
1258
+ if (urlInfo.isEntryPoint) {
1259
1259
  return false
1260
1260
  }
1261
1261
  if (urlInfo.type === "webmanifest") {
1262
1262
  return false
1263
1263
  }
1264
- if (urlInfo.subtype === "service_worker") {
1265
- return !urlInfo.data.isWebWorkerEntryPoint
1266
- }
1267
1264
  return true
1268
1265
  }
@@ -91,7 +91,7 @@ const determineDirectoryPath = ({
91
91
  })
92
92
  return parentDirectoryPath
93
93
  }
94
- if (urlInfo.data.isEntryPoint || urlInfo.data.isWebWorkerEntryPoint) {
94
+ if (urlInfo.isEntryPoint) {
95
95
  return ""
96
96
  }
97
97
  if (urlInfo.type === "importmap") {
@@ -8,6 +8,7 @@ import {
8
8
  stringifyHtmlAst,
9
9
  } from "@jsenv/ast"
10
10
 
11
+ import { isWebWorkerUrlInfo } from "@jsenv/core/src/omega/web_workers.js"
11
12
  import { GRAPH } from "./graph_utils.js"
12
13
 
13
14
  export const injectGlobalVersionMapping = async ({
@@ -17,7 +18,7 @@ export const injectGlobalVersionMapping = async ({
17
18
  }) => {
18
19
  await Promise.all(
19
20
  GRAPH.map(finalGraph, async (urlInfo) => {
20
- if (urlInfo.data.isEntryPoint || urlInfo.data.isWebWorkerEntryPoint) {
21
+ if (urlInfo.isEntryPoint) {
21
22
  await injectVersionMappings({
22
23
  urlInfo,
23
24
  kitchen: finalGraphKitchen,
@@ -43,7 +44,7 @@ const jsInjector = (urlInfo, { versionMappings }) => {
43
44
  const magicSource = createMagicSource(urlInfo.content)
44
45
  magicSource.prepend(
45
46
  generateClientCodeForVersionMappings(versionMappings, {
46
- globalName: urlInfo.data.isWebWorkerEntryPoint ? "self" : "window",
47
+ globalName: isWebWorkerUrlInfo(urlInfo) ? "self" : "window",
47
48
  }),
48
49
  )
49
50
  return magicSource.toContentAndSourcemap()
@@ -12,8 +12,7 @@ export const injectServiceWorkerUrls = async ({
12
12
  finalGraph,
13
13
  (finalUrlInfo) => {
14
14
  return (
15
- finalUrlInfo.subtype === "service_worker" &&
16
- finalUrlInfo.data.isWebWorkerEntryPoint
15
+ finalUrlInfo.subtype === "service_worker" && finalUrlInfo.isEntryPoint
17
16
  )
18
17
  },
19
18
  )
@@ -1,7 +1,6 @@
1
1
  import cuid from "cuid"
2
2
  import { Abort, raceCallbacks } from "@jsenv/abort"
3
- import { resolveUrl } from "@jsenv/urls"
4
- import { writeFile } from "@jsenv/filesystem"
3
+ import { writeFileSync } from "@jsenv/filesystem"
5
4
 
6
5
  export const run = async ({
7
6
  signal = new AbortController().signal,
@@ -10,17 +9,18 @@ export const run = async ({
10
9
  keepRunning = false,
11
10
  mirrorConsole = false,
12
11
  collectConsole = false,
13
- collectCoverage = false,
12
+ coverageEnabled = false,
14
13
  coverageTempDirectoryUrl,
15
14
  collectPerformance = false,
16
15
 
17
16
  runtime,
18
17
  runtimeParams,
19
18
  }) => {
19
+ let result = {}
20
+ const callbacks = []
21
+
20
22
  const onConsoleRef = { current: () => {} }
21
23
  const stopSignal = { notify: () => {} }
22
-
23
- let resultTransformer = (result) => result
24
24
  const runtimeLabel = `${runtime.name}/${runtime.version}`
25
25
 
26
26
  const runOperation = Abort.startOperation()
@@ -33,22 +33,24 @@ export const run = async ({
33
33
  allocatedMs !== Infinity
34
34
  ) {
35
35
  const timeoutAbortSource = runOperation.timeout(allocatedMs)
36
- resultTransformer = composeTransformer(resultTransformer, (result) => {
36
+ callbacks.push(() => {
37
37
  if (
38
38
  result.status === "errored" &&
39
39
  Abort.isAbortError(result.error) &&
40
40
  timeoutAbortSource.signal.aborted
41
41
  ) {
42
- return createTimedoutResult()
42
+ result = {
43
+ status: "timedout",
44
+ }
43
45
  }
44
- return result
45
46
  })
46
47
  }
47
- resultTransformer = composeTransformer(resultTransformer, (result) => {
48
+ callbacks.push(() => {
48
49
  if (result.status === "errored" && Abort.isAbortError(result.error)) {
49
- return createAbortedResult()
50
+ result = {
51
+ status: "aborted",
52
+ }
50
53
  }
51
- return result
52
54
  })
53
55
  const consoleCalls = []
54
56
  onConsoleRef.current = ({ type, text }) => {
@@ -64,45 +66,14 @@ export const run = async ({
64
66
  }
65
67
  }
66
68
  if (collectConsole) {
67
- resultTransformer = composeTransformer(resultTransformer, (result) => {
69
+ callbacks.push(() => {
68
70
  result.consoleCalls = consoleCalls
69
- return result
70
- })
71
- }
72
- if (collectCoverage) {
73
- resultTransformer = composeTransformer(
74
- resultTransformer,
75
- async (result) => {
76
- // we do not keep coverage in memory, it can grow very big
77
- // instead we store it on the filesystem
78
- // and they can be read later at "coverageFileUrl"
79
- const { coverage } = result
80
- if (coverage) {
81
- const coverageFileUrl = resolveUrl(
82
- `./${runtime.name}/${cuid()}`,
83
- coverageTempDirectoryUrl,
84
- )
85
- await writeFile(coverageFileUrl, JSON.stringify(coverage, null, " "))
86
- result.coverageFileUrl = coverageFileUrl
87
- delete result.coverage
88
- }
89
- return result
90
- },
91
- )
92
- } else {
93
- resultTransformer = composeTransformer(resultTransformer, (result) => {
94
- // as collectCoverage is disabled
95
- // executionResult.coverage is undefined or {}
96
- // we delete it just to have a cleaner object
97
- delete result.coverage
98
- return result
99
71
  })
100
72
  }
101
73
 
102
74
  const startMs = Date.now()
103
- resultTransformer = composeTransformer(resultTransformer, (result) => {
75
+ callbacks.push(() => {
104
76
  result.duration = Date.now() - startMs
105
- return result
106
77
  })
107
78
 
108
79
  try {
@@ -119,7 +90,7 @@ export const run = async ({
119
90
  },
120
91
  runned: async (cb) => {
121
92
  try {
122
- const result = await runtime.run({
93
+ const runResult = await runtime.run({
123
94
  signal: runOperation.signal,
124
95
  logger,
125
96
  ...runtimeParams,
@@ -128,7 +99,7 @@ export const run = async ({
128
99
  stopSignal,
129
100
  onConsole: (log) => onConsoleRef.current(log),
130
101
  })
131
- cb(result)
102
+ cb(runResult)
132
103
  } catch (e) {
133
104
  cb({
134
105
  status: "errored",
@@ -144,35 +115,46 @@ export const run = async ({
144
115
  if (winner.name === "aborted") {
145
116
  runOperation.throwIfAborted()
146
117
  }
147
- let result = winner.data
148
- result = await resultTransformer(result)
118
+
119
+ const { status, namespace, error, performance, coverage } = winner.data
120
+ result.status = status
121
+ if (status === "errored") {
122
+ result.error = error
123
+ } else {
124
+ result.namespace = namespace
125
+ }
126
+ if (collectPerformance) {
127
+ result.performance = performance
128
+ }
129
+ if (coverageEnabled) {
130
+ if (coverage) {
131
+ // we do not keep coverage in memory, it can grow very big
132
+ // instead we store it on the filesystem
133
+ // and they can be read later at "coverageFileUrl"
134
+ const coverageFileUrl = new URL(
135
+ `./${runtime.name}/${cuid()}.json`,
136
+ coverageTempDirectoryUrl,
137
+ )
138
+ writeFileSync(coverageFileUrl, JSON.stringify(coverage, null, " "))
139
+ result.coverageFileUrl = coverageFileUrl.href
140
+ } else {
141
+ // will eventually log a warning in report_to_coverage.js
142
+ }
143
+ }
144
+ callbacks.forEach((callback) => {
145
+ callback()
146
+ })
149
147
  return result
150
148
  } catch (e) {
151
- let result = {
149
+ result = {
152
150
  status: "errored",
153
151
  error: e,
154
152
  }
155
- result = await resultTransformer(result)
153
+ callbacks.forEach((callback) => {
154
+ callback()
155
+ })
156
156
  return result
157
157
  } finally {
158
158
  await runOperation.end()
159
159
  }
160
160
  }
161
-
162
- const composeTransformer = (previousTransformer, transformer) => {
163
- return async (value) => {
164
- const transformedValue = await previousTransformer(value)
165
- return transformer(transformedValue)
166
- }
167
- }
168
-
169
- const createAbortedResult = () => {
170
- return {
171
- status: "aborted",
172
- }
173
- }
174
- const createTimedoutResult = () => {
175
- return {
176
- status: "timedout",
177
- }
178
- }
@@ -2,7 +2,7 @@ import { createRuntimeFromPlaywright } from "./from_playwright.js"
2
2
 
3
3
  export const chromium = createRuntimeFromPlaywright({
4
4
  browserName: "chromium",
5
- browserVersion: "97.0.4666.0",
5
+ browserVersion: "104.0.5112.20", // to update, check https://github.com/microsoft/playwright/releases
6
6
  coveragePlaywrightAPIAvailable: true,
7
7
  })
8
8
  export const chromiumIsolatedTab = chromium.isolatedTab
@@ -2,6 +2,6 @@ import { createRuntimeFromPlaywright } from "./from_playwright.js"
2
2
 
3
3
  export const firefox = createRuntimeFromPlaywright({
4
4
  browserName: "firefox",
5
- browserVersion: "93.0",
5
+ browserVersion: "100.0.2", // to update, check https://github.com/microsoft/playwright/releases
6
6
  })
7
7
  export const firefoxIsolatedTab = firefox.isolatedTab
@@ -11,7 +11,7 @@ import { moveUrl } from "@jsenv/urls"
11
11
  import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
12
12
  import { escapeRegexpSpecialChars } from "@jsenv/utils/src/string/escape_regexp_special_chars.js"
13
13
 
14
- import { filterV8Coverage } from "@jsenv/core/src/test/coverage/v8_coverage_from_directory.js"
14
+ import { filterV8Coverage } from "@jsenv/core/src/test/coverage/v8_coverage.js"
15
15
  import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/core/src/test/coverage/istanbul_coverage_composition.js"
16
16
 
17
17
  export const createRuntimeFromPlaywright = ({
@@ -23,6 +23,7 @@ export const createRuntimeFromPlaywright = ({
23
23
  isolatedTab = false,
24
24
  }) => {
25
25
  const runtime = {
26
+ type: "browser",
26
27
  name: browserName,
27
28
  version: browserVersion,
28
29
  needsServer: true,
@@ -37,9 +38,9 @@ export const createRuntimeFromPlaywright = ({
37
38
 
38
39
  // measurePerformance,
39
40
  collectPerformance,
40
- collectCoverage = false,
41
- coverageForceIstanbul,
42
- urlShouldBeCovered,
41
+ coverageEnabled = false,
42
+ coverageConfig,
43
+ coverageMethodForBrowsers,
43
44
 
44
45
  stopAfterAllSignal,
45
46
  stopSignal,
@@ -108,8 +109,11 @@ export const createRuntimeFromPlaywright = ({
108
109
  }
109
110
 
110
111
  let resultTransformer = (result) => result
111
- if (collectCoverage) {
112
- if (coveragePlaywrightAPIAvailable && !coverageForceIstanbul) {
112
+ if (coverageEnabled) {
113
+ if (
114
+ coveragePlaywrightAPIAvailable &&
115
+ coverageMethodForBrowsers === "playwright_api"
116
+ ) {
113
117
  await page.coverage.startJSCoverage({
114
118
  // reportAnonymousScripts: true,
115
119
  })
@@ -133,10 +137,11 @@ export const createRuntimeFromPlaywright = ({
133
137
  }
134
138
  },
135
139
  )
136
- const coverage = filterV8Coverage(
140
+ const coverage = await filterV8Coverage(
137
141
  { result: v8CoveragesWithFsUrls },
138
142
  {
139
- urlShouldBeCovered,
143
+ rootDirectoryUrl,
144
+ coverageConfig,
140
145
  },
141
146
  )
142
147
  return {
@@ -2,7 +2,7 @@ import { createRuntimeFromPlaywright } from "./from_playwright.js"
2
2
 
3
3
  export const webkit = createRuntimeFromPlaywright({
4
4
  browserName: "webkit",
5
- browserVersion: "15.4",
5
+ browserVersion: "15.4", // to update, check https://github.com/microsoft/playwright/releases
6
6
  ignoreErrorHook: (error) => {
7
7
  // we catch error during execution but safari throw unhandled rejection
8
8
  // in a non-deterministic way.
@@ -1,48 +1,23 @@
1
- import v8 from "node:v8"
2
1
  import { uneval } from "@jsenv/uneval"
3
- import { startObservingPerformances } from "./node_execution_performance.js"
2
+
3
+ import { executeUsingDynamicImport } from "./execute_using_dynamic_import.js"
4
4
 
5
5
  const ACTIONS_AVAILABLE = {
6
- "execute-using-dynamic-import": async ({ fileUrl, collectPerformance }) => {
7
- const getNamespace = async () => {
8
- const namespace = await import(fileUrl)
9
- const namespaceResolved = {}
10
- await Promise.all([
11
- ...Object.keys(namespace).map(async (key) => {
12
- const value = await namespace[key]
13
- namespaceResolved[key] = value
14
- }),
15
- ])
16
- return namespaceResolved
17
- }
18
- if (collectPerformance) {
19
- const getPerformance = startObservingPerformances()
20
- const namespace = await getNamespace()
21
- const performance = await getPerformance()
22
- return {
23
- namespace,
24
- performance,
25
- }
26
- }
27
- const namespace = await getNamespace()
28
- return {
29
- namespace,
30
- }
31
- },
6
+ "execute-using-dynamic-import": executeUsingDynamicImport,
32
7
  "execute-using-require": async ({ fileUrl }) => {
33
- const { createRequire } = await import("module")
34
- const { fileURLToPath } = await import("url")
8
+ const { createRequire } = await import("node:module")
9
+ const { fileURLToPath } = await import("node:url")
35
10
  const filePath = fileURLToPath(fileUrl)
36
11
  const require = createRequire(fileUrl)
37
12
  // eslint-disable-next-line import/no-dynamic-require
38
13
  const namespace = require(filePath)
39
14
  const namespaceResolved = {}
40
- await Promise.all([
41
- ...Object.keys(namespace).map(async (key) => {
15
+ await Promise.all(
16
+ Object.keys(namespace).map(async (key) => {
42
17
  const value = await namespace[key]
43
18
  namespaceResolved[key] = value
44
19
  }),
45
- ])
20
+ )
46
21
  return namespaceResolved
47
22
  },
48
23
  }
@@ -94,18 +69,17 @@ const sendToParent = (type, data) => {
94
69
  // It means node process may stay alive longer than expected
95
70
  // the time to send the data to the parent.
96
71
  process.send({
72
+ jsenv: true,
97
73
  type,
98
74
  data,
99
75
  })
100
76
  }
101
77
 
102
- const onceProcessMessage = (type, callback) => {
103
- const listener = (event) => {
104
- if (event.type === type) {
105
- // commenting line below keep this process alive
106
- removeListener()
107
- // eslint-disable-next-line no-eval
108
- callback(eval(`(${event.data})`))
78
+ const onceParentMessage = (type, callback) => {
79
+ const listener = (message) => {
80
+ if (message && message.jsenv && message.type === type) {
81
+ removeListener() // commenting this line keep this process alive
82
+ callback(message.data)
109
83
  }
110
84
  }
111
85
  const removeListener = () => {
@@ -115,7 +89,7 @@ const onceProcessMessage = (type, callback) => {
115
89
  return removeListener
116
90
  }
117
91
 
118
- const removeActionRequestListener = onceProcessMessage(
92
+ const removeActionRequestListener = onceParentMessage(
119
93
  ACTION_REQUEST_EVENT_NAME,
120
94
  async ({ actionType, actionParams }) => {
121
95
  const action = ACTIONS_AVAILABLE[actionType]
@@ -133,13 +107,6 @@ const removeActionRequestListener = onceProcessMessage(
133
107
  value = e
134
108
  }
135
109
 
136
- if (process.env.NODE_V8_COVERAGE) {
137
- v8.takeCoverage()
138
- // if (actionParams.stopCoverageAfterExecution) {
139
- // v8.stopCoverage()
140
- // }
141
- }
142
-
143
110
  // setTimeout(() => {}, 100)
144
111
 
145
112
  if (failed) {
@@ -151,7 +118,6 @@ const removeActionRequestListener = onceProcessMessage(
151
118
  // removeActionRequestListener()
152
119
  if (actionParams.exitAfterAction) {
153
120
  removeActionRequestListener()
154
-
155
121
  // for some reason this fixes v8 coverage directory sometimes empty on Ubuntu
156
122
  // process.exit()
157
123
  }
@@ -164,4 +130,6 @@ const removeActionRequestListener = onceProcessMessage(
164
130
  // process.once("SIGTERM", removeActionRequestListener)
165
131
  // process.once("SIGINT", removeActionRequestListener)
166
132
 
167
- setTimeout(() => sendToParent("ready"))
133
+ setTimeout(() => {
134
+ sendToParent("ready")
135
+ })
@@ -0,0 +1,103 @@
1
+ import { parentPort } from "node:worker_threads"
2
+ import { uneval } from "@jsenv/uneval"
3
+
4
+ import { executeUsingDynamicImport } from "./execute_using_dynamic_import.js"
5
+
6
+ const ACTIONS_AVAILABLE = {
7
+ "execute-using-dynamic-import": executeUsingDynamicImport,
8
+ }
9
+ const ACTION_REQUEST_EVENT_NAME = "action"
10
+ const ACTION_RESPONSE_EVENT_NAME = "action-result"
11
+ const ACTION_RESPONSE_STATUS_FAILED = "action-failed"
12
+ const ACTION_RESPONSE_STATUS_COMPLETED = "action-completed"
13
+
14
+ const sendActionFailed = (error) => {
15
+ if (error.hasOwnProperty("toString")) {
16
+ delete error.toString
17
+ }
18
+ sendToParent(
19
+ ACTION_RESPONSE_EVENT_NAME,
20
+ // process.send algorithm does not send non enumerable values
21
+ // so use @jsenv/uneval
22
+ uneval(
23
+ {
24
+ status: ACTION_RESPONSE_STATUS_FAILED,
25
+ value: error,
26
+ },
27
+ { ignoreSymbols: true },
28
+ ),
29
+ )
30
+ }
31
+
32
+ const sendActionCompleted = (value) => {
33
+ sendToParent(
34
+ ACTION_RESPONSE_EVENT_NAME,
35
+ // here we use JSON.stringify because we should not
36
+ // have non enumerable value (unlike there is on Error objects)
37
+ // otherwise uneval is quite slow to turn a giant object
38
+ // into a string (and value can be giant when using coverage)
39
+ JSON.stringify({
40
+ status: ACTION_RESPONSE_STATUS_COMPLETED,
41
+ value,
42
+ }),
43
+ )
44
+ }
45
+
46
+ const sendToParent = (type, data) => {
47
+ // this can keep process alive longer than expected
48
+ // when source is a long string.
49
+ // It means node process may stay alive longer than expected
50
+ // the time to send the data to the parent.
51
+ parentPort.postMessage({
52
+ jsenv: true,
53
+ type,
54
+ data,
55
+ })
56
+ }
57
+
58
+ const onceParentMessage = (type, callback) => {
59
+ const listener = (message) => {
60
+ if (message && message.jsenv && message.type === type) {
61
+ removeListener() // commenting this line keep this worker alive
62
+ callback(message.data)
63
+ }
64
+ }
65
+ const removeListener = () => {
66
+ parentPort.removeListener("message", listener)
67
+ }
68
+ parentPort.on("message", listener)
69
+ return removeListener
70
+ }
71
+
72
+ const removeActionRequestListener = onceParentMessage(
73
+ ACTION_REQUEST_EVENT_NAME,
74
+ async ({ actionType, actionParams }) => {
75
+ const action = ACTIONS_AVAILABLE[actionType]
76
+ if (!action) {
77
+ sendActionFailed(new Error(`unknown action ${actionType}`))
78
+ return
79
+ }
80
+
81
+ let value
82
+ let failed = false
83
+ try {
84
+ value = await action(actionParams)
85
+ } catch (e) {
86
+ failed = true
87
+ value = e
88
+ }
89
+
90
+ if (failed) {
91
+ sendActionFailed(value)
92
+ } else {
93
+ sendActionCompleted(value)
94
+ }
95
+ if (actionParams.exitAfterAction) {
96
+ removeActionRequestListener()
97
+ }
98
+ },
99
+ )
100
+
101
+ setTimeout(() => {
102
+ sendToParent("ready")
103
+ })