@jsenv/core 27.0.0-alpha.13 → 27.0.0-alpha.16

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 (27) hide show
  1. package/main.js +1 -1
  2. package/package.json +7 -8
  3. package/src/build/build.js +29 -17
  4. package/src/build/start_build_server.js +176 -0
  5. package/src/dev/start_dev_server.js +9 -20
  6. package/src/execute/execute.js +9 -2
  7. package/src/omega/kitchen.js +7 -3
  8. package/src/{dev/plugins/autoreload → plugins/autoreload/dev_sse}/client/autoreload_preference.js +0 -0
  9. package/src/{dev/plugins/autoreload → plugins/autoreload/dev_sse}/client/event_source_client.js +2 -2
  10. package/src/{dev/plugins/autoreload → plugins/autoreload/dev_sse}/client/reload.js +0 -0
  11. package/src/{dev/plugins/autoreload → plugins/autoreload/dev_sse}/client/url_helpers.js +0 -0
  12. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_client.js +41 -0
  13. package/src/{dev/plugins/autoreload/jsenv_plugin_autoreload.js → plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js} +23 -174
  14. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +25 -0
  15. package/src/plugins/autoreload/jsenv_plugin_hmr.js +35 -0
  16. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/babel_plugin_metadata_import_meta_hot.js +3 -4
  17. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/client/import_meta_hot.js +0 -0
  18. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/html_hot_dependencies.js +0 -0
  19. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +98 -0
  20. package/src/plugins/minification/jsenv_plugin_minification.js +2 -1
  21. package/src/plugins/plugins.js +23 -0
  22. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +13 -0
  23. package/src/test/execute_plan.js +6 -1
  24. package/src/test/execute_test_plan.js +4 -0
  25. package/src/dev/plugins/autoreload/client/event_source_connection.js +0 -195
  26. package/src/dev/plugins/autoreload/sse_service.js +0 -169
  27. package/src/preview/preview.js +0 -3
@@ -1,186 +1,35 @@
1
1
  import { urlToRelativeUrl } from "@jsenv/filesystem"
2
2
  import { createCallbackList } from "@jsenv/abort"
3
3
 
4
- import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
5
- import {
6
- parseHtmlString,
7
- stringifyHtmlAst,
8
- injectScriptAsEarlyAsPossible,
9
- createHtmlNode,
10
- } from "@jsenv/utils/html_ast/html_ast.js"
11
- import { applyBabelPlugins } from "@jsenv/utils/js_ast/apply_babel_plugins.js"
4
+ import { createSSEService } from "@jsenv/utils/event_source/sse_service.js"
12
5
 
13
- import { createSSEService } from "./sse_service.js"
14
- import { collectHotDataFromHtmlAst } from "./html_hot_dependencies.js"
15
- import { babelPluginMetadataImportMetaHot } from "./babel_plugin_metadata_import_meta_hot.js"
16
-
17
- export const jsenvPluginAutoreload = ({
18
- rootDirectoryUrl,
19
- urlGraph,
20
- autoreloadPatterns,
21
- cooldownBetweenFileEvents,
22
- }) => {
23
- return [
24
- jsenvPluginHmr(),
25
- jsenvPluginHot(),
26
- jsenvPluginHotSSE({
27
- rootDirectoryUrl,
28
- urlGraph,
29
- autoreloadPatterns,
30
- cooldownBetweenFileEvents,
31
- }),
32
- ]
33
- }
34
-
35
- const jsenvPluginHmr = () => {
36
- return {
37
- name: "jsenv:hmr",
38
- appliesDuring: { dev: true },
39
- normalizeUrl: (reference) => {
40
- const urlObject = new URL(reference.url)
41
- if (!urlObject.searchParams.has("hmr")) {
42
- reference.data.hmr = false
43
- return null
44
- }
45
- reference.data.hmr = true
46
- // "hmr" search param goal is to mark url as enabling hmr:
47
- // this goal is achieved when we reach this part of the code
48
- // We get rid of this params so that urlGraph and other parts of the code
49
- // recognize the url (it is not considered as a different url)
50
- urlObject.searchParams.delete("hmr")
51
- urlObject.searchParams.delete("v")
52
- return urlObject.href
53
- },
54
- transformUrlSearchParams: (reference, context) => {
55
- const parentUrlInfo = context.urlGraph.getUrlInfo(reference.parentUrl)
56
- if (!parentUrlInfo || !parentUrlInfo.data.hmr) {
57
- return null
58
- }
59
- const urlInfo = context.urlGraph.getUrlInfo(reference.url)
60
- if (!urlInfo.data.hmrTimestamp) {
61
- return null
62
- }
63
- return {
64
- hmr: "",
65
- v: urlInfo.data.hmrTimestamp,
66
- }
67
- },
68
- }
69
- }
70
-
71
- const jsenvPluginHot = () => {
72
- const eventSourceClientFileUrl = new URL(
73
- "./client/event_source_client.js",
74
- import.meta.url,
75
- ).href
76
- const importMetaHotClientFileUrl = new URL(
77
- "./client/import_meta_hot.js",
78
- import.meta.url,
79
- ).href
80
-
81
- return {
82
- name: "jsenv:hot",
83
- appliesDuring: { dev: true },
84
- transformUrlContent: {
85
- html: (htmlUrlInfo, context) => {
86
- const htmlAst = parseHtmlString(htmlUrlInfo.content)
87
- const { hotReferences } = collectHotDataFromHtmlAst(htmlAst)
88
- htmlUrlInfo.data.hotDecline = false
89
- htmlUrlInfo.data.hotAcceptSelf = false
90
- htmlUrlInfo.data.hotAcceptDependencies = hotReferences.map(
91
- ({ type, specifier }) => {
92
- const [reference] = context.referenceUtils.found({
93
- type,
94
- specifier,
95
- })
96
- return reference.url
97
- },
98
- )
99
- const [eventSourceClientReference] = context.referenceUtils.inject({
100
- type: "script_src",
101
- expectedType: "js_module",
102
- specifier: eventSourceClientFileUrl,
103
- })
104
- injectScriptAsEarlyAsPossible(
105
- htmlAst,
106
- createHtmlNode({
107
- "tagName": "script",
108
- "type": "module",
109
- "src": eventSourceClientReference.generatedSpecifier,
110
- "injected-by": "jsenv:hot",
111
- }),
112
- )
113
- const htmlModified = stringifyHtmlAst(htmlAst)
114
- return {
115
- content: htmlModified,
116
- }
117
- },
118
- css: (cssUrlInfo) => {
119
- cssUrlInfo.data.hotDecline = false
120
- cssUrlInfo.data.hotAcceptSelf = false
121
- cssUrlInfo.data.hotAcceptDependencies = []
122
- },
123
- js_module: async (urlInfo, context) => {
124
- const { metadata } = await applyBabelPlugins({
125
- babelPlugins: [babelPluginMetadataImportMetaHot],
126
- urlInfo,
127
- })
128
- const {
129
- importMetaHotDetected,
130
- hotDecline,
131
- hotAcceptSelf,
132
- hotAcceptDependencies,
133
- } = metadata
134
- urlInfo.data.hotDecline = hotDecline
135
- urlInfo.data.hotAcceptSelf = hotAcceptSelf
136
- urlInfo.data.hotAcceptDependencies = hotAcceptDependencies
137
- if (!importMetaHotDetected) {
138
- return null
139
- }
140
- // For some reason using magic source here produce
141
- // better sourcemap than doing the equivalent with babel
142
- // I suspect it's because I was doing injectAstAfterImport(programPath, ast.program.body[0])
143
- // which is likely not well supported by babel
144
- const [importMetaHotClientFileReference] =
145
- context.referenceUtils.inject({
146
- parentUrl: urlInfo.url,
147
- type: "js_import_export",
148
- expectedType: "js_module",
149
- specifier: importMetaHotClientFileUrl,
150
- })
151
- const magicSource = createMagicSource(urlInfo.content)
152
- magicSource.prepend(
153
- `import { createImportMetaHot } from ${importMetaHotClientFileReference.generatedSpecifier}
154
- import.meta.hot = createImportMetaHot(import.meta.url)
155
- `,
156
- )
157
- return magicSource.toContentAndSourcemap()
158
- },
159
- },
160
- }
161
- }
162
-
163
- const jsenvPluginHotSSE = ({
6
+ export const jsenvPluginDevSSEServer = ({
164
7
  rootDirectoryUrl,
165
8
  urlGraph,
166
- autoreloadPatterns,
9
+ watchedFilePatterns,
167
10
  cooldownBetweenFileEvents,
168
11
  }) => {
169
- const hotUpdateCallbackList = createCallbackList()
12
+ const serverEventCallbackList = createCallbackList()
170
13
  const notifyDeclined = ({ cause, reason, declinedBy }) => {
171
- hotUpdateCallbackList.notify({
172
- declined: true,
173
- cause,
174
- reason,
175
- declinedBy,
14
+ serverEventCallbackList.notify({
15
+ type: "reload",
16
+ data: JSON.stringify({
17
+ cause,
18
+ type: "full",
19
+ typeReason: reason,
20
+ declinedBy,
21
+ }),
176
22
  })
177
23
  }
178
24
  const notifyAccepted = ({ cause, reason, instructions }) => {
179
- hotUpdateCallbackList.notify({
180
- accepted: true,
181
- cause,
182
- reason,
183
- instructions,
25
+ serverEventCallbackList.notify({
26
+ type: "reload",
27
+ data: JSON.stringify({
28
+ cause,
29
+ type: "hot",
30
+ typeReason: reason,
31
+ hotInstructions: instructions,
32
+ }),
184
33
  })
185
34
  }
186
35
  const updateHmrTimestamp = (urlInfo, hmrTimestamp) => {
@@ -282,9 +131,9 @@ const jsenvPluginHotSSE = ({
282
131
  }
283
132
  const sseService = createSSEService({
284
133
  rootDirectoryUrl,
285
- autoreloadPatterns,
286
- hotUpdateCallbackList,
134
+ watchedFilePatterns,
287
135
  cooldownBetweenFileEvents,
136
+ serverEventCallbackList,
288
137
  onFileChange: ({ relativeUrl, event }) => {
289
138
  const url = new URL(relativeUrl, rootDirectoryUrl).href
290
139
  const urlInfo = urlGraph.urlInfos[url]
@@ -356,7 +205,7 @@ const jsenvPluginHotSSE = ({
356
205
  })
357
206
 
358
207
  return {
359
- name: "jsenv:hot_sse",
208
+ name: "jsenv:sse_server",
360
209
  appliesDuring: { dev: true },
361
210
  serve: (request, { urlGraph, rootDirectoryUrl }) => {
362
211
  if (request.ressource === "/__graph__") {
@@ -0,0 +1,25 @@
1
+ import { jsenvPluginHmr } from "./jsenv_plugin_hmr.js"
2
+ import { jsenvPluginDevSSEClient } from "./dev_sse/jsenv_plugin_dev_sse_client.js"
3
+ import { jsenvPluginDevSSEServer } from "./dev_sse/jsenv_plugin_dev_sse_server.js"
4
+
5
+ export const jsenvPluginAutoreload = ({
6
+ rootDirectoryUrl,
7
+ urlGraph,
8
+ scenario,
9
+ watchedFilePatterns,
10
+ cooldownBetweenFileEvents,
11
+ }) => {
12
+ if (scenario === "build") {
13
+ return []
14
+ }
15
+ return [
16
+ jsenvPluginHmr(),
17
+ jsenvPluginDevSSEClient(),
18
+ jsenvPluginDevSSEServer({
19
+ rootDirectoryUrl,
20
+ urlGraph,
21
+ watchedFilePatterns,
22
+ cooldownBetweenFileEvents,
23
+ }),
24
+ ]
25
+ }
@@ -0,0 +1,35 @@
1
+ export const jsenvPluginHmr = () => {
2
+ return {
3
+ name: "jsenv:hmr",
4
+ appliesDuring: { dev: true },
5
+ normalizeUrl: (reference) => {
6
+ const urlObject = new URL(reference.url)
7
+ if (!urlObject.searchParams.has("hmr")) {
8
+ reference.data.hmr = false
9
+ return null
10
+ }
11
+ reference.data.hmr = true
12
+ // "hmr" search param goal is to mark url as enabling hmr:
13
+ // this goal is achieved when we reach this part of the code
14
+ // We get rid of this params so that urlGraph and other parts of the code
15
+ // recognize the url (it is not considered as a different url)
16
+ urlObject.searchParams.delete("hmr")
17
+ urlObject.searchParams.delete("v")
18
+ return urlObject.href
19
+ },
20
+ transformUrlSearchParams: (reference, context) => {
21
+ const parentUrlInfo = context.urlGraph.getUrlInfo(reference.parentUrl)
22
+ if (!parentUrlInfo || !parentUrlInfo.data.hmr) {
23
+ return null
24
+ }
25
+ const urlInfo = context.urlGraph.getUrlInfo(reference.url)
26
+ if (!urlInfo.data.hmrTimestamp) {
27
+ return null
28
+ }
29
+ return {
30
+ hmr: "",
31
+ v: urlInfo.data.hmrTimestamp,
32
+ }
33
+ },
34
+ }
35
+ }
@@ -16,13 +16,12 @@ export const babelPluginMetadataImportMetaHot = () => {
16
16
  }
17
17
  }
18
18
  const collectImportMetaProperties = (programPath) => {
19
- let importMetaHotDetected = false
19
+ const importMetaHotPaths = []
20
20
  let hotDecline = false
21
21
  let hotAcceptSelf = false
22
22
  let hotAcceptDependencies = []
23
23
  programPath.traverse({
24
24
  MemberExpression(path) {
25
- if (importMetaHotDetected) return
26
25
  const { node } = path
27
26
  const { object } = node
28
27
  if (object.type !== "MetaProperty") {
@@ -35,7 +34,7 @@ const collectImportMetaProperties = (programPath) => {
35
34
  const { property } = node
36
35
  const { name } = property
37
36
  if (name === "hot") {
38
- importMetaHotDetected = true
37
+ importMetaHotPaths.push(path)
39
38
  }
40
39
  },
41
40
  CallExpression(path) {
@@ -79,7 +78,7 @@ const collectImportMetaProperties = (programPath) => {
79
78
  },
80
79
  })
81
80
  return {
82
- importMetaHotDetected,
81
+ importMetaHotPaths,
83
82
  hotDecline,
84
83
  hotAcceptSelf,
85
84
  hotAcceptDependencies,
@@ -0,0 +1,98 @@
1
+ import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
2
+ import { parseHtmlString } from "@jsenv/utils/html_ast/html_ast.js"
3
+ import { applyBabelPlugins } from "@jsenv/utils/js_ast/apply_babel_plugins.js"
4
+
5
+ import { collectHotDataFromHtmlAst } from "./html_hot_dependencies.js"
6
+ import { babelPluginMetadataImportMetaHot } from "./babel_plugin_metadata_import_meta_hot.js"
7
+
8
+ const importMetaHotClientFileUrl = new URL(
9
+ "./client/import_meta_hot.js",
10
+ import.meta.url,
11
+ ).href
12
+
13
+ export const jsenvPluginImportMetaHot = () => {
14
+ return {
15
+ name: "jsenv:import_meta_hot",
16
+ appliesDuring: "*",
17
+ transformUrlContent: {
18
+ html: (htmlUrlInfo, context) => {
19
+ // during build we don't really care to parse html hot dependencies
20
+ if (context.scenario === "build") {
21
+ return
22
+ }
23
+ const htmlAst = parseHtmlString(htmlUrlInfo.content)
24
+ const { hotReferences } = collectHotDataFromHtmlAst(htmlAst)
25
+ htmlUrlInfo.data.hotDecline = false
26
+ htmlUrlInfo.data.hotAcceptSelf = false
27
+ htmlUrlInfo.data.hotAcceptDependencies = hotReferences.map(
28
+ ({ type, specifier }) => {
29
+ const [reference] = context.referenceUtils.found({
30
+ type,
31
+ specifier,
32
+ })
33
+ return reference.url
34
+ },
35
+ )
36
+ },
37
+ css: (cssUrlInfo) => {
38
+ cssUrlInfo.data.hotDecline = false
39
+ cssUrlInfo.data.hotAcceptSelf = false
40
+ cssUrlInfo.data.hotAcceptDependencies = []
41
+ },
42
+ js_module: async (urlInfo, context) => {
43
+ const { metadata } = await applyBabelPlugins({
44
+ babelPlugins: [babelPluginMetadataImportMetaHot],
45
+ urlInfo,
46
+ })
47
+ const {
48
+ importMetaHotPaths,
49
+ hotDecline,
50
+ hotAcceptSelf,
51
+ hotAcceptDependencies,
52
+ } = metadata
53
+ urlInfo.data.hotDecline = hotDecline
54
+ urlInfo.data.hotAcceptSelf = hotAcceptSelf
55
+ urlInfo.data.hotAcceptDependencies = hotAcceptDependencies
56
+ if (importMetaHotPaths.length === 0) {
57
+ return null
58
+ }
59
+ if (context.scenario === "build") {
60
+ return removeImportMetaHots(urlInfo, importMetaHotPaths)
61
+ }
62
+ return injectImportMetaHot(urlInfo, context)
63
+ },
64
+ },
65
+ }
66
+ }
67
+
68
+ const removeImportMetaHots = (urlInfo, importMetaHotPaths) => {
69
+ const magicSource = createMagicSource(urlInfo.content)
70
+ importMetaHotPaths.forEach((path) => {
71
+ magicSource.replace({
72
+ start: path.node.start,
73
+ end: path.node.end,
74
+ replacement: "undefined",
75
+ })
76
+ })
77
+ return magicSource.toContentAndSourcemap()
78
+ }
79
+
80
+ // For some reason using magic source here produce
81
+ // better sourcemap than doing the equivalent with babel
82
+ // I suspect it's because I was doing injectAstAfterImport(programPath, ast.program.body[0])
83
+ // which is likely not well supported by babel
84
+ const injectImportMetaHot = (urlInfo, context) => {
85
+ const [importMetaHotClientFileReference] = context.referenceUtils.inject({
86
+ parentUrl: urlInfo.url,
87
+ type: "js_import_export",
88
+ expectedType: "js_module",
89
+ specifier: importMetaHotClientFileUrl,
90
+ })
91
+ const magicSource = createMagicSource(urlInfo.content)
92
+ magicSource.prepend(
93
+ `import { createImportMetaHot } from ${importMetaHotClientFileReference.generatedSpecifier}
94
+ import.meta.hot = createImportMetaHot(import.meta.url)
95
+ `,
96
+ )
97
+ return magicSource.toContentAndSourcemap()
98
+ }
@@ -8,7 +8,8 @@ export const jsenvPluginMinification = (minification) => {
8
8
  minification = {
9
9
  html: minification,
10
10
  css: minification,
11
- js: minification,
11
+ js_classic: minification,
12
+ js_module: minification,
12
13
  json: minification,
13
14
  svg: minification,
14
15
  }
@@ -14,8 +14,14 @@ import { jsenvPluginInjectGlobals } from "./inject_globals/jsenv_plugin_inject_g
14
14
  import { jsenvPluginTranspilation } from "./transpilation/jsenv_plugin_transpilation.js"
15
15
  import { jsenvPluginBundling } from "./bundling/jsenv_plugin_bundling.js"
16
16
  import { jsenvPluginMinification } from "./minification/jsenv_plugin_minification.js"
17
+ import { jsenvPluginImportMetaHot } from "./import_meta_hot/jsenv_plugin_import_meta_hot.js"
18
+ import { jsenvPluginAutoreload } from "./autoreload/jsenv_plugin_autoreload.js"
17
19
 
18
20
  export const getCorePlugins = ({
21
+ rootDirectoryUrl,
22
+ urlGraph,
23
+ scenario,
24
+
19
25
  htmlSupervisor,
20
26
  nodeEsmResolution,
21
27
  fileSystemMagicResolution,
@@ -23,10 +29,15 @@ export const getCorePlugins = ({
23
29
  transpilation = true,
24
30
  minification = false,
25
31
  bundling = false,
32
+
33
+ autoreload = false,
26
34
  } = {}) => {
27
35
  if (htmlSupervisor === true) {
28
36
  htmlSupervisor = {}
29
37
  }
38
+ if (autoreload === true) {
39
+ autoreload = {}
40
+ }
30
41
  return [
31
42
  jsenvPluginTranspilation(transpilation),
32
43
  ...(htmlSupervisor ? [jsenvPluginHtmlSupervisor(htmlSupervisor)] : []), // before inline as it turns inline <script> into <script src>
@@ -46,5 +57,17 @@ export const getCorePlugins = ({
46
57
 
47
58
  jsenvPluginBundling(bundling),
48
59
  jsenvPluginMinification(minification),
60
+
61
+ jsenvPluginImportMetaHot(),
62
+ ...(autoreload
63
+ ? [
64
+ jsenvPluginAutoreload({
65
+ rootDirectoryUrl,
66
+ urlGraph,
67
+ scenario,
68
+ ...autoreload,
69
+ }),
70
+ ]
71
+ : []),
49
72
  ]
50
73
  }
@@ -1,3 +1,16 @@
1
+ /*
2
+ * Something to keep in mind:
3
+ * When systemjs format is used by babel, it will generated UID based on
4
+ * the import specifier:
5
+ * https://github.com/babel/babel/blob/97d1967826077f15e766778c0d64711399e9a72a/packages/babel-plugin-transform-modules-systemjs/src/index.ts#L498
6
+ * But at this stage import specifier are absolute file urls
7
+ * So without minification these specifier are long and dependent
8
+ * on where the files are on the filesystem.
9
+ * This is mitigated by minification that will shorten them
10
+ * But ideally babel should not generate this in the first place
11
+ * and prefer to unique identifier based solely on the specifier basename for instance
12
+ */
13
+
1
14
  import { createRequire } from "node:module"
2
15
  import { readFileSync, urlToFilename } from "@jsenv/filesystem"
3
16
 
@@ -65,8 +65,10 @@ export const executePlan = async (
65
65
  scenario,
66
66
  sourcemaps,
67
67
  plugins,
68
- transpilation,
69
68
  injectedGlobals,
69
+ nodeEsmResolution,
70
+ fileSystemMagicResolution,
71
+ transpilation,
70
72
 
71
73
  protocol,
72
74
  privateKey,
@@ -140,7 +142,10 @@ export const executePlan = async (
140
142
  plugins: [
141
143
  ...plugins,
142
144
  ...getCorePlugins({
145
+ scenario,
143
146
  htmlSupervisor: true,
147
+ nodeEsmResolution,
148
+ fileSystemMagicResolution,
144
149
  injectedGlobals,
145
150
  transpilation: {
146
151
  ...transpilation,
@@ -83,6 +83,8 @@ export const executeTestPlan = async ({
83
83
  sourcemaps = "inline",
84
84
  plugins = [],
85
85
  injectedGlobals,
86
+ nodeEsmResolution,
87
+ fileSystemMagicResolution,
86
88
 
87
89
  protocol,
88
90
  privateKey,
@@ -167,6 +169,8 @@ export const executeTestPlan = async ({
167
169
  sourcemaps,
168
170
  plugins,
169
171
  injectedGlobals,
172
+ nodeEsmResolution,
173
+ fileSystemMagicResolution,
170
174
 
171
175
  protocol,
172
176
  privateKey,