@jsenv/core 27.0.0-alpha.36 → 27.0.0-alpha.37

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.
package/main.js CHANGED
@@ -1,7 +1,10 @@
1
1
  // dev
2
2
  export { startDevServer } from "./src/dev/start_dev_server.js"
3
3
  // test
4
- export { executeTestPlan } from "./src/test/execute_test_plan.js"
4
+ export {
5
+ executeTestPlan,
6
+ defaultCoverageConfig,
7
+ } from "./src/test/execute_test_plan.js"
5
8
  export {
6
9
  chromium,
7
10
  chromiumIsolatedTab,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "27.0.0-alpha.36",
3
+ "version": "27.0.0-alpha.37",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,7 +11,8 @@
11
11
  "node": ">=16.13.0"
12
12
  },
13
13
  "publishConfig": {
14
- "access": "public"
14
+ "access": "public",
15
+ "registry": "https://registry.npmjs.org"
15
16
  },
16
17
  "type": "module",
17
18
  "imports": {},
@@ -31,19 +32,20 @@
31
32
  "./packages/*"
32
33
  ],
33
34
  "scripts": {
34
- "eslint": "npx eslint ./test/dev_server/manual/main/importmap_inline/ --ext=.js,.mjs,.cjs,.html",
35
+ "eslint": "npx eslint . --ext=.js,.mjs,.cjs,.html",
36
+ "dev": "node ./script/dev/dev.mjs",
35
37
  "build": "node ./script/build/build.mjs",
36
38
  "test": "node ./script/test/test.mjs",
37
- "test-with-coverage": "npm run test -- --coverage",
38
- "dev": "node ./script/dev/dev.mjs",
39
+ "test-packages": "npm run test --workspaces --if-present",
39
40
  "start_file_server": "node ./script/dev/start_file_server.mjs",
41
+ "workspace-versions": "node ./script/publish/workspace_versions.mjs",
42
+ "workspace-publish": "node ./script/publish/workspace_publish.mjs",
40
43
  "performances": "node --expose-gc ./script/performance/generate_performance_report.mjs --log --once",
41
44
  "file-size": "node ./script/file_size/file_size.mjs --log",
42
45
  "prettier": "prettier --write .",
43
46
  "playwright-install": "npx playwright install-deps && npx playwright install",
44
47
  "certificate-install": "node ./script/dev/install_certificate_authority.mjs",
45
- "workspace-versions": "node ./script/publish/workspace_versions.mjs",
46
- "workspace-publish": "node ./script/publish/workspace_publish.mjs",
48
+ "test-with-coverage": "npm run test -- --coverage",
47
49
  "prepublishOnly": "npm run build"
48
50
  },
49
51
  "optionalDependencies": {
@@ -63,10 +65,10 @@
63
65
  "@jsenv/integrity": "0.0.1",
64
66
  "@jsenv/log": "1.5.2",
65
67
  "@jsenv/logger": "4.0.1",
66
- "@jsenv/node-esm-resolution": "0.0.4",
68
+ "@jsenv/node-esm-resolution": "0.0.5",
67
69
  "@jsenv/server": "12.6.1",
68
70
  "@jsenv/uneval": "1.6.0",
69
- "@jsenv/utils": "1.4.4",
71
+ "@jsenv/utils": "1.5.0",
70
72
  "construct-style-sheets-polyfill": "3.1.0",
71
73
  "cssnano": "5.1.7",
72
74
  "cssnano-preset-default": "5.2.7",
@@ -93,7 +95,7 @@
93
95
  "@jsenv/file-size-impact": "12.1.11",
94
96
  "@jsenv/https-local": "1.0.8",
95
97
  "@jsenv/importmap-node-module": "5.1.3",
96
- "@jsenv/package-workspace": "0.0.5",
98
+ "@jsenv/package-workspace": "0.1.1",
97
99
  "@jsenv/performance-impact": "2.2.10",
98
100
  "@jsenv/pwa": "5.0.0",
99
101
  "eslint": "8.13.0",
@@ -102,11 +104,8 @@
102
104
  "eslint-plugin-react": "7.29.4",
103
105
  "playwright": "1.20.2",
104
106
  "postcss-import": "14.1.0",
105
- "preact": "10.7.1",
106
107
  "prettier": "2.6.2",
107
- "react": "17.0.2",
108
- "react-dom": "17.0.2",
109
108
  "redux": "4.1.2",
110
109
  "rollup": "2.70.1"
111
110
  }
112
- }
111
+ }
@@ -30,6 +30,7 @@ import {
30
30
  stringifyHtmlAst,
31
31
  } from "@jsenv/utils/html_ast/html_ast.js"
32
32
 
33
+ import { jsenvPluginUrlReferences } from "../plugins/url_references/jsenv_plugin_url_references.js"
33
34
  import { jsenvPluginInline } from "../plugins/inline/jsenv_plugin_inline.js"
34
35
  import { jsenvPluginAsJsClassic } from "../plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js"
35
36
  import { createUrlGraph } from "../omega/url_graph.js"
@@ -330,6 +331,7 @@ ${Object.keys(rawGraph.urlInfos).join("\n")}`,
330
331
  sourcemaps,
331
332
  runtimeCompat,
332
333
  plugins: [
334
+ jsenvPluginUrlReferences(),
333
335
  jsenvPluginAsJsClassic({
334
336
  systemJsInjection: true,
335
337
  }),
@@ -852,6 +854,7 @@ const applyUrlVersioning = async ({
852
854
  sourcemaps,
853
855
  runtimeCompat,
854
856
  plugins: [
857
+ jsenvPluginUrlReferences(),
855
858
  jsenvPluginInline({
856
859
  fetchInlineUrls: false,
857
860
  analyzeConvertedScripts: true, // to be able to version their urls
@@ -28,8 +28,9 @@ export const createBuilUrlsGenerator = ({ buildDirectoryUrl }) => {
28
28
  const urlObject = new URL(url)
29
29
  let { search, hash } = urlObject
30
30
  let name = getUrlName(url, urlInfo)
31
- const [basename, extension] = splitFileExtension(name)
32
- let nameCandidate = name
31
+ let [basename, extension] = splitFileExtension(name)
32
+ extension = extensionMappings[extension] || extension
33
+ let nameCandidate = `${basename}${extension}` // reconstruct name in case extension was normalized
33
34
  let integer = 1
34
35
  // eslint-disable-next-line no-constant-condition
35
36
  while (true) {
@@ -48,6 +49,18 @@ export const createBuilUrlsGenerator = ({ buildDirectoryUrl }) => {
48
49
  }
49
50
  }
50
51
 
52
+ // It's best to generate files with an extension representing what is inside the file
53
+ // and after build js files contains solely js (js or typescript is gone).
54
+ // This way a static file server is already configured to server the correct content-type
55
+ // (otherwise one would have to configure that ".jsx" is "text/javascript")
56
+ // To keep in mind: if you have "user.jsx" and "user.js" AND both file are not bundled
57
+ // you end up with "dist/js/user.js" and "dist/js/user2.js"
58
+ const extensionMappings = {
59
+ ".ts": ".js",
60
+ ".jsx": ".js",
61
+ ".tsx": ".js",
62
+ }
63
+
51
64
  const splitFileExtension = (filename) => {
52
65
  const dotLastIndex = filename.lastIndexOf(".")
53
66
  if (dotLastIndex === -1) {
@@ -21,6 +21,7 @@ import {
21
21
  pluginCORS,
22
22
  fetchFileSystem,
23
23
  } from "@jsenv/server"
24
+ import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
24
25
  import { createLogger } from "@jsenv/logger"
25
26
  import { Abort } from "@jsenv/abort"
26
27
 
@@ -50,6 +51,9 @@ export const startBuildServer = async ({
50
51
  cooldownBetweenFileEvents,
51
52
  autorestart,
52
53
  }) => {
54
+ rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
55
+ buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl)
56
+
53
57
  const autorestartProcess = await initProcessAutorestart({
54
58
  signal,
55
59
  handleSIGINT,
@@ -13,7 +13,6 @@ import { CONTENT_TYPE } from "@jsenv/utils/content_type/content_type.js"
13
13
  import { setUrlFilename } from "@jsenv/utils/urls/url_utils.js"
14
14
 
15
15
  import { createPluginController } from "../plugins/plugin_controller.js"
16
- import { jsenvPluginUrlReferences } from "../plugins/url_references/jsenv_plugin_url_references.js"
17
16
  import { createUrlInfoTransformer } from "./url_graph/url_info_transformations.js"
18
17
  import { RUNTIME_COMPAT } from "./compat/runtime_compat.js"
19
18
  import { defaultRuntimeCompat } from "./compat/default_runtime_compat.js"
@@ -50,7 +49,7 @@ export const createKitchen = ({
50
49
  writeOnFileSystem = true,
51
50
  }) => {
52
51
  const pluginController = createPluginController({
53
- plugins: [jsenvPluginUrlReferences(), ...plugins],
52
+ plugins,
54
53
  scenario,
55
54
  })
56
55
  const jsenvDirectoryUrl = new URL(".jsenv/", rootDirectoryUrl).href
@@ -119,6 +118,7 @@ export const createKitchen = ({
119
118
  // for inline ressources the reference contains the content
120
119
  content,
121
120
  contentType,
121
+ timing: {},
122
122
  }
123
123
  }
124
124
  const mutateReference = (reference, newReference) => {
@@ -208,6 +208,7 @@ export const createKitchen = ({
208
208
  })
209
209
  }
210
210
  }
211
+ baseContext.resolveReference = resolveReference
211
212
  const urlInfoTransformer = createUrlInfoTransformer({
212
213
  logger,
213
214
  urlGraph,
@@ -651,6 +652,7 @@ export const createKitchen = ({
651
652
 
652
653
  const applyReferenceEffectsOnUrlInfo = (reference, urlInfo, context) => {
653
654
  Object.assign(urlInfo.data, reference.data)
655
+ Object.assign(urlInfo.timing, reference.timing)
654
656
  if (reference.injected) {
655
657
  urlInfo.data.injected = true
656
658
  }
@@ -60,11 +60,11 @@ export const createFileService = ({
60
60
  [runtimeName]: runtimeVersion,
61
61
  },
62
62
  })
63
- const { response, contentType, content } = urlInfo
63
+ let { response, contentType, content } = urlInfo
64
64
  if (response) {
65
65
  return response
66
66
  }
67
- return {
67
+ response = {
68
68
  url: reference.url,
69
69
  status: 200,
70
70
  headers: {
@@ -73,7 +73,17 @@ export const createFileService = ({
73
73
  "cache-control": `private,max-age=0,must-revalidate`,
74
74
  },
75
75
  body: content,
76
+ timing: urlInfo.timing,
76
77
  }
78
+ kitchen.pluginController.callHooks(
79
+ "augmentResponse",
80
+ { reference, urlInfo },
81
+ {},
82
+ (returnValue) => {
83
+ response = composeTwoResponses(response, returnValue)
84
+ },
85
+ )
86
+ return response
77
87
  } catch (e) {
78
88
  const code = e.code
79
89
  if (code === "PARSE_ERROR") {
@@ -125,16 +135,7 @@ export const createFileService = ({
125
135
  }
126
136
  return async (request) => {
127
137
  let response = await getResponse(request)
128
- if (response.url) {
129
- kitchen.pluginController.callHooks(
130
- "augmentResponse",
131
- response,
132
- {},
133
- (returnValue) => {
134
- response = composeTwoResponses(response, returnValue)
135
- },
136
- )
137
- }
138
+
138
139
  return response
139
140
  }
140
141
  }
@@ -23,7 +23,12 @@ export const createUrlInfoTransformer = ({
23
23
  // for inline content (<script> insdide html)
24
24
  // chrome won't be able to fetch the file as it does not exists
25
25
  // so sourcemap must contain sources
26
- urlInfo.isInline || sourcemapsSources
26
+ sourcemapsSources ||
27
+ urlInfo.isInline ||
28
+ (sourcemap.sources &&
29
+ sourcemap.sources.some(
30
+ (source) => !source || !source.startsWith("file:"),
31
+ ))
27
32
  if (sourcemap.sources && sourcemap.sources.length > 1) {
28
33
  sourcemap.sources = sourcemap.sources.map(
29
34
  (source) => new URL(source, urlInfo.data.rawUrl || urlInfo.url).href,
@@ -150,5 +150,6 @@ const createUrlInfo = (url) => {
150
150
  content: undefined,
151
151
  sourcemap: null,
152
152
  sourcemapReference: null,
153
+ timing: {},
153
154
  }
154
155
  }
@@ -12,8 +12,14 @@ import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
12
12
 
13
13
  export const jsenvPluginCommonJsGlobals = () => {
14
14
  const transformCommonJsGlobals = async (urlInfo, { scenario }) => {
15
+ if (
16
+ !urlInfo.content.includes("process.env.NODE_ENV") &&
17
+ !urlInfo.content.includes("__filename") &&
18
+ !urlInfo.content.includes("__dirname")
19
+ ) {
20
+ return null
21
+ }
15
22
  const isJsModule = urlInfo.type === "js_module"
16
-
17
23
  const replaceMap = {
18
24
  "process.env.NODE_ENV": `("${
19
25
  scenario === "dev" || scenario === "test" ? "dev" : "prod"
@@ -6,7 +6,7 @@ import { urlToExtension } from "@jsenv/filesystem"
6
6
  import { applyFileSystemMagicResolution } from "@jsenv/node-esm-resolution"
7
7
 
8
8
  export const jsenvPluginFileSystemMagic = ({
9
- magicExtensions = ["inherit"],
9
+ magicExtensions = ["inherit", ".js"],
10
10
  magicDirectoryIndex = true,
11
11
  preservesSymlink = true,
12
12
  } = {}) => {
@@ -40,6 +40,9 @@ export const jsenvPluginImportMetaHot = () => {
40
40
  cssUrlInfo.data.hotAcceptDependencies = []
41
41
  },
42
42
  js_module: async (urlInfo, context) => {
43
+ if (!urlInfo.content.includes("import.meta.hot")) {
44
+ return null
45
+ }
43
46
  const { metadata } = await applyBabelPlugins({
44
47
  babelPlugins: [babelPluginMetadataImportMetaHot],
45
48
  urlInfo,
@@ -18,6 +18,13 @@ export const jsenvPluginImportMetaScenarios = () => {
18
18
  appliesDuring: "*",
19
19
  transformUrlContent: {
20
20
  js_module: async (urlInfo, { scenario }) => {
21
+ if (
22
+ !urlInfo.content.includes("import.meta.dev") &&
23
+ !urlInfo.content.includes("import.meta.test") &&
24
+ !urlInfo.content.includes("import.meta.build")
25
+ ) {
26
+ return null
27
+ }
21
28
  const { metadata } = await applyBabelPlugins({
22
29
  babelPlugins: [babelPluginMetadataImportMetaScenarios],
23
30
  urlInfo,
@@ -1,3 +1,9 @@
1
+ import {
2
+ parseHtmlString,
3
+ injectScriptAsEarlyAsPossible,
4
+ createHtmlNode,
5
+ stringifyHtmlAst,
6
+ } from "@jsenv/utils/html_ast/html_ast.js"
1
7
  import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
2
8
  import { isWebWorkerUrlInfo } from "@jsenv/core/src/omega/web_workers.js"
3
9
 
@@ -5,11 +11,41 @@ export const jsenvPluginInjectGlobals = (globals = {}) => {
5
11
  if (Object.keys(globals).length === 0) {
6
12
  return []
7
13
  }
8
- const injectGlobals = (urlInfo) => {
14
+
15
+ const globalInjectorOnHtmlEntryPoint = (urlInfo) => {
16
+ if (!urlInfo.data.isEntryPoint) {
17
+ return null
18
+ }
19
+ // ideally we would inject an importmap but browser support is too low
20
+ // (even worse for worker/service worker)
21
+ // so for now we inject code into entry points
22
+ const htmlAst = parseHtmlString(urlInfo.content, {
23
+ storeOriginalPositions: false,
24
+ })
25
+ injectScriptAsEarlyAsPossible(
26
+ htmlAst,
27
+ createHtmlNode({
28
+ "tagName": "script",
29
+ "textContent": generateClientCodeForGlobals({
30
+ globals,
31
+ isWebWorker: false,
32
+ }),
33
+ "injected-by": "jsenv:inject_globals",
34
+ }),
35
+ )
36
+ return stringifyHtmlAst(htmlAst)
37
+ }
38
+
39
+ const globalsInjectorOnJsEntryPoints = (urlInfo) => {
40
+ if (!urlInfo.data.isEntryPoint && !urlInfo.data.isWebWorkerEntryPoint) {
41
+ return null
42
+ }
9
43
  const magicSource = createMagicSource(urlInfo.content)
10
- const globalName = isWebWorkerUrlInfo(urlInfo) ? "self" : "window"
11
- magicSource.prepend(
12
- `Object.assign(${globalName}, ${JSON.stringify(globals, null, " ")});`,
44
+ magicSource.append(
45
+ generateClientCodeForGlobals({
46
+ globals,
47
+ isWebWorker: isWebWorkerUrlInfo(urlInfo),
48
+ }),
13
49
  )
14
50
  return magicSource.toContentAndSourcemap()
15
51
  }
@@ -18,8 +54,14 @@ export const jsenvPluginInjectGlobals = (globals = {}) => {
18
54
  name: "jsenv:inject_globals",
19
55
  appliesDuring: "*",
20
56
  transformUrlContent: {
21
- js_classic: injectGlobals,
22
- js_module: injectGlobals,
57
+ html: globalInjectorOnHtmlEntryPoint,
58
+ js_classic: globalsInjectorOnJsEntryPoints,
59
+ js_module: globalsInjectorOnJsEntryPoints,
23
60
  },
24
61
  }
25
62
  }
63
+
64
+ const generateClientCodeForGlobals = ({ isWebWorker = false, globals }) => {
65
+ const globalName = isWebWorker ? "self" : "window"
66
+ return `Object.assign(${globalName}, ${JSON.stringify(globals, null, " ")});`
67
+ }
@@ -7,6 +7,13 @@ import { generateInlineContentUrl } from "@jsenv/utils/urls/inline_content_url_g
7
7
 
8
8
  export const jsenvPluginJsInlineContent = ({ allowEscapeForVersioning }) => {
9
9
  const parseAndTransformInlineContentCalls = async (urlInfo, context) => {
10
+ if (
11
+ !urlInfo.content.includes("InlineContent(") &&
12
+ !urlInfo.content.includes("new Blob(") &&
13
+ !urlInfo.content.includes("JSON.parse(")
14
+ ) {
15
+ return null
16
+ }
10
17
  const { metadata } = await applyBabelPlugins({
11
18
  babelPlugins: [babelPluginMetadataInlineContentCalls],
12
19
  urlInfo,
@@ -1,3 +1,5 @@
1
+ import { timeStart } from "@jsenv/server"
2
+
1
3
  export const createPluginController = ({
2
4
  plugins,
3
5
  scenario,
@@ -47,7 +49,13 @@ export const createPluginController = ({
47
49
  }
48
50
  currentPlugin = hook.plugin
49
51
  currentHookName = hook.hookName
52
+ const timeEnd = timeStart(
53
+ `${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
54
+ )
50
55
  let valueReturned = hookFn(info, context)
56
+ if (info.timing) {
57
+ Object.assign(info.timing, timeEnd())
58
+ }
51
59
  valueReturned = assertAndNormalizeReturnValue(hook.hookName, valueReturned)
52
60
  currentPlugin = null
53
61
  currentHookName = null
@@ -60,7 +68,13 @@ export const createPluginController = ({
60
68
  }
61
69
  currentPlugin = hook.plugin
62
70
  currentHookName = hook.hookName
71
+ const timeEnd = timeStart(
72
+ `${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
73
+ )
63
74
  let valueReturned = await hookFn(info, context)
75
+ if (info.timing) {
76
+ Object.assign(info.timing, timeEnd())
77
+ }
64
78
  valueReturned = assertAndNormalizeReturnValue(hook.hookName, valueReturned)
65
79
  currentPlugin = null
66
80
  currentHookName = null
@@ -145,12 +159,18 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
145
159
  const { appliesDuring } = pluginEntry
146
160
  if (appliesDuring === undefined) {
147
161
  console.warn(`"appliesDuring" is undefined on ${pluginEntry.name}`)
162
+ return
148
163
  }
149
164
  if (appliesDuring === "*") {
150
165
  plugins.push(pluginEntry)
151
166
  return
152
167
  }
153
- if (appliesDuring && appliesDuring[scenario]) {
168
+ if (typeof appliesDuring !== "object") {
169
+ throw new Error(
170
+ `"appliesDuring" must be an object or "*", got ${appliesDuring}`,
171
+ )
172
+ }
173
+ if (appliesDuring[scenario]) {
154
174
  plugins.push(pluginEntry)
155
175
  return
156
176
  }
@@ -1,3 +1,4 @@
1
+ import { jsenvPluginUrlReferences } from "../plugins/url_references/jsenv_plugin_url_references.js"
1
2
  import { jsenvPluginLeadingSlash } from "./leading_slash/jsenv_plugin_leading_slash.js"
2
3
  import { jsenvPluginImportmap } from "./importmap/jsenv_plugin_importmap.js"
3
4
  import { jsenvPluginUrlResolution } from "./url_resolution/jsenv_plugin_url_resolution.js"
@@ -39,6 +40,7 @@ export const getCorePlugins = ({
39
40
  autoreload = {}
40
41
  }
41
42
  return [
43
+ jsenvPluginUrlReferences(),
42
44
  jsenvPluginTranspilation(transpilation),
43
45
  ...(htmlSupervisor ? [jsenvPluginHtmlSupervisor(htmlSupervisor)] : []), // before inline as it turns inline <script> into <script src>
44
46
  jsenvPluginInline(), // before "file urls" to resolve and load inline urls
@@ -14,6 +14,10 @@ export const jsenvPluginImportAssertions = () => {
14
14
  appliesDuring: "*",
15
15
  transformUrlContent: {
16
16
  js_module: async (urlInfo, context) => {
17
+ // "usesImportAssertion" is set by "jsenv:imports_analysis"
18
+ if (!urlInfo.data.usesImportAssertion) {
19
+ return null
20
+ }
17
21
  const importTypesToTranspile = getImportTypesToTranspile(context)
18
22
  if (importTypesToTranspile.length === 0) {
19
23
  return null
@@ -8,10 +8,11 @@ export const jsenvPluginTopLevelAwait = () => {
8
8
  appliesDuring: "*",
9
9
  transformUrlContent: {
10
10
  js_module: async (urlInfo, context) => {
11
- if (!urlInfo.data.usesTopLevelAwait) {
11
+ if (context.isSupportedOnCurrentClients("top_level_await")) {
12
12
  return null
13
13
  }
14
- if (context.isSupportedOnCurrentClients("top_level_await")) {
14
+ const usesTLA = await usesTopLevelAwait(urlInfo)
15
+ if (!usesTLA) {
15
16
  return null
16
17
  }
17
18
  const { code, map } = await applyBabelPlugins({
@@ -35,3 +36,35 @@ export const jsenvPluginTopLevelAwait = () => {
35
36
  },
36
37
  }
37
38
  }
39
+
40
+ const usesTopLevelAwait = async (urlInfo) => {
41
+ if (!urlInfo.content.includes("await ")) {
42
+ return false
43
+ }
44
+ const { metadata } = await applyBabelPlugins({
45
+ urlInfo,
46
+ babelPlugins: [babelPluginMetadataUsesTopLevelAwait],
47
+ })
48
+ return metadata.usesTopLevelAwait
49
+ }
50
+
51
+ const babelPluginMetadataUsesTopLevelAwait = () => {
52
+ return {
53
+ name: "metadata-uses-top-level-await",
54
+ visitor: {
55
+ Program: (programPath, state) => {
56
+ let usesTopLevelAwait = false
57
+ programPath.traverse({
58
+ AwaitExpression: (path) => {
59
+ const closestFunction = path.getFunctionParent()
60
+ if (!closestFunction) {
61
+ usesTopLevelAwait = true
62
+ path.stop()
63
+ }
64
+ },
65
+ })
66
+ state.file.metadata.usesTopLevelAwait = usesTopLevelAwait
67
+ },
68
+ },
69
+ }
70
+ }
@@ -1,8 +1,5 @@
1
1
  import { applyBabelPlugins } from "@jsenv/utils/js_ast/apply_babel_plugins.js"
2
2
  import {
3
- analyzeImportCall,
4
- isImportCall,
5
- analyzeImportExportDeclaration,
6
3
  analyzeNewUrlCall,
7
4
  analyzeNewWorkerOrNewSharedWorker,
8
5
  analyzeImportScriptCalls,
@@ -14,19 +11,7 @@ import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
14
11
  import { isWebWorkerUrlInfo } from "@jsenv/core/src/omega/web_workers.js"
15
12
 
16
13
  export const parseAndTransformJsUrls = async (urlInfo, context) => {
17
- const isJsModule = urlInfo.type === "js_module"
18
- const isWebWorker = isWebWorkerUrlInfo(urlInfo)
19
- const { metadata } = await applyBabelPlugins({
20
- babelPlugins: [
21
- [babelPluginMetadataJsUrlMentions, { isJsModule, isWebWorker }],
22
- ],
23
- urlInfo,
24
- })
25
- const { jsMentions, usesTopLevelAwait, usesImport, usesExport } = metadata
26
- urlInfo.data.usesImport = usesImport
27
- urlInfo.data.usesExport = usesExport
28
- urlInfo.data.usesTopLevelAwait = usesTopLevelAwait
29
-
14
+ const jsMentions = await performJsUrlsStaticAnalysis(urlInfo)
30
15
  const { rootDirectoryUrl, referenceUtils } = context
31
16
  const actions = []
32
17
  const magicSource = createMagicSource(urlInfo.content)
@@ -58,6 +43,51 @@ export const parseAndTransformJsUrls = async (urlInfo, context) => {
58
43
  return magicSource.toContentAndSourcemap()
59
44
  }
60
45
 
46
+ const performJsUrlsStaticAnalysis = async (urlInfo) => {
47
+ const isJsModule = urlInfo.type === "js_module"
48
+ const isWebWorker = isWebWorkerUrlInfo(urlInfo)
49
+ if (canSkipStaticAnalysis(urlInfo, { isJsModule, isWebWorker })) {
50
+ return []
51
+ }
52
+ const { metadata } = await applyBabelPlugins({
53
+ babelPlugins: [
54
+ [babelPluginMetadataJsUrlMentions, { isJsModule, isWebWorker }],
55
+ ],
56
+ urlInfo,
57
+ })
58
+ const { jsMentions } = metadata
59
+ return jsMentions
60
+ }
61
+
62
+ const canSkipStaticAnalysis = (urlInfo, { isJsModule, isWebWorker }) => {
63
+ const js = urlInfo.content
64
+ if (isJsModule) {
65
+ if (
66
+ js.includes("new URL(") ||
67
+ js.includes("new Worker(") ||
68
+ js.includes("new SharedWorker(") ||
69
+ js.includes("serviceWorker.register(")
70
+ ) {
71
+ return false
72
+ }
73
+ }
74
+ if (!isJsModule) {
75
+ if (
76
+ js.includes("System.") ||
77
+ js.includes("new URL(") ||
78
+ js.includes("new Worker(") ||
79
+ js.includes("new SharedWorker(") ||
80
+ js.includes("serviceWorker.register(")
81
+ ) {
82
+ return false
83
+ }
84
+ }
85
+ if (isWebWorker && js.includes("importScripts(")) {
86
+ return false
87
+ }
88
+ return true
89
+ }
90
+
61
91
  /*
62
92
  * see also
63
93
  * https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md
@@ -70,10 +100,6 @@ const babelPluginMetadataJsUrlMentions = (_, { isJsModule, isWebWorker }) => {
70
100
  visitor: {
71
101
  Program(programPath, state) {
72
102
  const jsMentions = []
73
- let usesImport = false
74
- let usesExport = false
75
- let usesTopLevelAwait = false
76
-
77
103
  const callOneStaticAnalyzer = (path, analyzer) => {
78
104
  const returnValue = analyzer(path)
79
105
  if (returnValue === null) {
@@ -96,14 +122,7 @@ const babelPluginMetadataJsUrlMentions = (_, { isJsModule, isWebWorker }) => {
96
122
  }
97
123
  }
98
124
  }
99
-
100
125
  const visitors = {
101
- AwaitExpression: (path) => {
102
- const closestFunction = path.getFunctionParent()
103
- if (!closestFunction) {
104
- usesTopLevelAwait = true
105
- }
106
- },
107
126
  NewExpression: (path) => {
108
127
  callStaticAnalyzers(path, [
109
128
  (path) => analyzeNewWorkerOrNewSharedWorker(path, { isJsModule }),
@@ -113,50 +132,16 @@ const babelPluginMetadataJsUrlMentions = (_, { isJsModule, isWebWorker }) => {
113
132
  }
114
133
  const callExpressionStaticAnalysers = [
115
134
  ...(isJsModule
116
- ? [analyzeImportCall]
135
+ ? []
117
136
  : [analyzeSystemRegisterCall, analyzeSystemImportCall]),
118
137
  ...(isWebWorker ? [analyzeImportScriptCalls] : []),
119
138
  analyzeServiceWorkerRegisterCall,
120
139
  ]
121
140
  visitors.CallExpression = (path) => {
122
- if (isJsModule && !usesImport && isImportCall(path.node)) {
123
- usesImport = true
124
- }
125
141
  callStaticAnalyzers(path, callExpressionStaticAnalysers)
126
142
  }
127
-
128
- if (isJsModule) {
129
- Object.assign(visitors, {
130
- ExportNamedDeclaration: (path) => {
131
- if (!usesImport && path.node.source) {
132
- usesImport = true
133
- }
134
- usesExport = true
135
- callStaticAnalyzers(path, [analyzeImportExportDeclaration])
136
- },
137
- ExportAllDeclaration: (path) => {
138
- usesImport = true
139
- usesExport = true
140
- callStaticAnalyzers(path, [analyzeImportExportDeclaration])
141
- },
142
- ExportDefaultDeclaration: (path) => {
143
- if (!usesImport && path.node.source) {
144
- usesImport = true
145
- }
146
- usesExport = true
147
- callStaticAnalyzers(path, [analyzeImportExportDeclaration])
148
- },
149
- ImportDeclaration: (path) => {
150
- usesImport = true
151
- callStaticAnalyzers(path, [analyzeImportExportDeclaration])
152
- },
153
- })
154
- }
155
143
  programPath.traverse(visitors)
156
144
  state.file.metadata.jsMentions = jsMentions
157
- state.file.metadata.usesImport = usesImport
158
- state.file.metadata.usesExport = usesExport
159
- state.file.metadata.usesTopLevelAwait = usesTopLevelAwait
160
145
  },
161
146
  },
162
147
  }
@@ -0,0 +1,46 @@
1
+ import { parseJsModuleImports } from "@jsenv/utils/js_ast/parse_js_module_imports.js"
2
+ import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
3
+
4
+ export const jsenvPluginImportsAnalysis = () => {
5
+ return {
6
+ name: "jsenv:imports_analysis",
7
+ appliesDuring: "*",
8
+ transformUrlContent: {
9
+ js_module: async (urlInfo, context) => {
10
+ const [imports, exports] = await parseJsModuleImports(
11
+ urlInfo.content,
12
+ (urlInfo.data && urlInfo.data.rawUrl) || urlInfo.url,
13
+ )
14
+ const actions = []
15
+ const magicSource = createMagicSource(urlInfo.content)
16
+ urlInfo.data.usesImport = imports.length > 0
17
+ urlInfo.data.usesExport = exports.length > 0
18
+ urlInfo.data.usesImportAssertion = imports.some(
19
+ (importInfo) => importInfo.usesAssert,
20
+ )
21
+ imports.forEach((importInfo) => {
22
+ const [reference] = context.referenceUtils.found({
23
+ type: "js_import_export",
24
+ subtype: importInfo.subtype,
25
+ expectedType: "js_module",
26
+ expectedSubtype: urlInfo.subtype,
27
+ line: importInfo.line,
28
+ column: importInfo.column,
29
+ specifier: importInfo.specifier,
30
+ })
31
+ actions.push(async () => {
32
+ magicSource.replace({
33
+ start: importInfo.start,
34
+ end: importInfo.end,
35
+ replacement: await context.referenceUtils.readGeneratedSpecifier(
36
+ reference,
37
+ ),
38
+ })
39
+ })
40
+ })
41
+ await Promise.all(actions.map((action) => action()))
42
+ return magicSource.toContentAndSourcemap()
43
+ },
44
+ },
45
+ }
46
+ }
@@ -0,0 +1,18 @@
1
+ import { parseAndTransformHtmlUrls } from "./html/html_urls.js"
2
+ import { parseAndTransformCssUrls } from "./css/css_urls.js"
3
+ import { parseAndTransformJsUrls } from "./js/js_urls.js"
4
+ import { parseAndTransformWebmanifestUrls } from "./webmanifest/webmanifest_urls.js"
5
+
6
+ export const jsenvPluginUrlAnalysis = () => {
7
+ return {
8
+ name: "jsenv:url_analysis",
9
+ appliesDuring: "*",
10
+ transformUrlContent: {
11
+ html: parseAndTransformHtmlUrls,
12
+ css: parseAndTransformCssUrls,
13
+ js_classic: parseAndTransformJsUrls,
14
+ js_module: parseAndTransformJsUrls,
15
+ webmanifest: parseAndTransformWebmanifestUrls,
16
+ },
17
+ }
18
+ }
@@ -1,18 +1,6 @@
1
- import { parseAndTransformHtmlUrls } from "./html/html_urls.js"
2
- import { parseAndTransformCssUrls } from "./css/css_urls.js"
3
- import { parseAndTransformJsUrls } from "./js/js_urls.js"
4
- import { parseAndTransformWebmanifestUrls } from "./webmanifest/webmanifest_urls.js"
1
+ import { jsenvPluginImportsAnalysis } from "./jsenv_plugin_imports_analysis.js"
2
+ import { jsenvPluginUrlAnalysis } from "./jsenv_plugin_url_analysis.js"
5
3
 
6
4
  export const jsenvPluginUrlReferences = () => {
7
- return {
8
- name: "jsenv:url_references",
9
- appliesDuring: "*",
10
- transformUrlContent: {
11
- html: parseAndTransformHtmlUrls,
12
- css: parseAndTransformCssUrls,
13
- js_classic: parseAndTransformJsUrls,
14
- js_module: parseAndTransformJsUrls,
15
- webmanifest: parseAndTransformWebmanifestUrls,
16
- },
17
- }
5
+ return [jsenvPluginImportsAnalysis(), jsenvPluginUrlAnalysis()]
18
6
  }
@@ -1,6 +1,4 @@
1
- export const jsenvPluginUrlVersion = ({
2
- longTermCache = false, // will become true once things get more stable
3
- } = {}) => {
1
+ export const jsenvPluginUrlVersion = ({ longTermCache = true } = {}) => {
4
2
  return {
5
3
  name: "jsenv:url_version",
6
4
  appliesDuring: "*", // maybe only during dev?
@@ -15,7 +13,7 @@ export const jsenvPluginUrlVersion = ({
15
13
  urlObject.searchParams.delete("v")
16
14
  return urlObject.href
17
15
  },
18
- transformUrl: (reference) => {
16
+ transformUrlSearchParams: (reference) => {
19
17
  if (!reference.data.version) {
20
18
  return null
21
19
  }
@@ -26,15 +24,14 @@ export const jsenvPluginUrlVersion = ({
26
24
  v: reference.data.version,
27
25
  }
28
26
  },
29
- augmentResponse: ({ url }) => {
27
+ augmentResponse: ({ reference }) => {
30
28
  if (!longTermCache) {
31
29
  return null
32
30
  }
33
- const urlObject = new URL(url)
34
- if (!urlObject.searchParams.has("v")) {
31
+ if (!reference.searchParams.has("v")) {
35
32
  return null
36
33
  }
37
- if (urlObject.searchParams.has("hmr")) {
34
+ if (reference.searchParams.has("hmr")) {
38
35
  return null
39
36
  }
40
37
  // When url is versioned put it in browser cache for 30 days
@@ -13,6 +13,14 @@ import { generateCoverageHtmlDirectory } from "@jsenv/utils/coverage/coverage_re
13
13
  import { generateCoverageTextLog } from "@jsenv/utils/coverage/coverage_reporter_text_log.js"
14
14
  import { executePlan } from "./execute_plan.js"
15
15
 
16
+ export const defaultCoverageConfig = {
17
+ "./index.js": true,
18
+ "./main.js": true,
19
+ "./src/**/*.js": true,
20
+ "./**/*.test.*": false, // contains .test. -> nope
21
+ "./**/test/": false, // inside a test folder -> nope,
22
+ }
23
+
16
24
  /**
17
25
  * Execute a list of files and log how it goes
18
26
  * @param {Object} testPlanParameters
@@ -57,13 +65,7 @@ export const executeTestPlan = async ({
57
65
  coverage = process.argv.includes("--cover") ||
58
66
  process.argv.includes("--coverage"),
59
67
  coverageTempDirectoryRelativeUrl = "./.coverage/tmp/",
60
- coverageConfig = {
61
- "./index.js": true,
62
- "./main.js": true,
63
- "./src/**/*.js": true,
64
- "./**/*.test.*": false, // contains .test. -> nope
65
- "./**/test/": false, // inside a test folder -> nope,
66
- },
68
+ coverageConfig = defaultCoverageConfig,
67
69
  coverageIncludeMissing = true,
68
70
  coverageAndExecutionAllowed = false,
69
71
  coverageForceIstanbul = false,