@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
package/main.js CHANGED
@@ -16,8 +16,8 @@ export {
16
16
  } from "./src/execute/runtimes/browsers/webkit.js"
17
17
  export { nodeProcess } from "./src/execute/runtimes/node/node_process.js"
18
18
  // build
19
- export { preview } from "./src/preview/preview.js"
20
19
  export { build } from "./src/build/build.js"
20
+ export { startBuildServer } from "./src/build/start_build_server.js"
21
21
 
22
22
  // advanced
23
23
  export { execute } from "./src/execute/execute.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "27.0.0-alpha.13",
3
+ "version": "27.0.0-alpha.16",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -65,9 +65,9 @@
65
65
  "@jsenv/log": "1.5.1",
66
66
  "@jsenv/logger": "4.0.1",
67
67
  "@jsenv/node-esm-resolution": "0.0.2",
68
- "@jsenv/server": "12.6.0",
68
+ "@jsenv/server": "12.6.1",
69
69
  "@jsenv/uneval": "1.6.0",
70
- "@jsenv/utils": "1.1.0",
70
+ "@jsenv/utils": "1.2.0",
71
71
  "construct-style-sheets-polyfill": "3.1.0",
72
72
  "cssnano": "5.1.7",
73
73
  "cssnano-preset-default": "5.2.7",
@@ -89,15 +89,14 @@
89
89
  "devDependencies": {
90
90
  "@babel/eslint-parser": "7.17.0",
91
91
  "@babel/plugin-syntax-import-assertions": "7.16.7",
92
- "@jsenv/assert": "2.5.3",
92
+ "@jsenv/assert": "2.5.4",
93
93
  "@jsenv/eslint-config": "16.0.9",
94
- "@jsenv/file-size-impact": "12.1.10",
95
- "@jsenv/github-release-package": "1.3.5",
94
+ "@jsenv/file-size-impact": "12.1.11",
95
+ "@jsenv/github-release-package": "1.3.6",
96
96
  "@jsenv/https-local": "1.0.8",
97
97
  "@jsenv/importmap-node-module": "5.1.3",
98
- "@jsenv/package-publish": "1.7.2",
99
98
  "@jsenv/package-workspace": "0.0.5",
100
- "@jsenv/performance-impact": "2.2.9",
99
+ "@jsenv/performance-impact": "2.2.10",
101
100
  "@jsenv/pwa": "4.0.1",
102
101
  "eslint": "8.13.0",
103
102
  "eslint-plugin-html": "6.2.0",
@@ -66,7 +66,6 @@ export const build = async ({
66
66
  transpilation = {},
67
67
  bundling = true,
68
68
  minification = true,
69
-
70
69
  versioning = true,
71
70
  versioningMethod = "search_param", // "filename", "search_param"
72
71
  lineBreakNormalization = process.platform === "win32",
@@ -582,13 +581,27 @@ ${Object.keys(finalGraph.urlInfos).join("\n")}`,
582
581
  if (!urlInfo.data.isEntryPoint && urlInfo.dependents.size === 0) {
583
582
  return
584
583
  }
584
+
585
+ const urlContent =
586
+ urlInfo.type === "html"
587
+ ? stringifyHtmlAst(
588
+ parseHtmlString(urlInfo.content, {
589
+ storeOriginalPositions: false,
590
+ }),
591
+ { removeOriginalPositionAttributes: true },
592
+ )
593
+ : urlInfo.content
585
594
  const versionGenerator = createVersionGenerator()
586
595
  versionGenerator.augmentWithContent({
587
- content: urlInfo.content,
596
+ content: urlContent,
588
597
  contentType: urlInfo.contentType,
589
598
  lineBreakNormalization,
590
599
  })
591
600
  urlInfo.dependencies.forEach((dependencyUrl) => {
601
+ // this dependency is inline (data:) or remote (http://, https://)
602
+ if (!dependencyUrl.startsWith("file:")) {
603
+ return
604
+ }
592
605
  const dependencyUrlInfo = finalGraph.getUrlInfo(dependencyUrl)
593
606
  if (
594
607
  // this content is part of the file, no need to take into account twice
@@ -615,6 +628,7 @@ ${Object.keys(finalGraph.urlInfos).join("\n")}`,
615
628
  }
616
629
  })
617
630
  urlInfo.data.version = versionGenerator.generate()
631
+
618
632
  urlInfo.data.versionedUrl = injectVersionIntoBuildUrl({
619
633
  buildUrl: urlInfo.url,
620
634
  version: urlInfo.data.version,
@@ -717,18 +731,6 @@ ${Object.keys(finalGraph.urlInfos).join("\n")}`,
717
731
  }
718
732
  return versionedUrlInfo
719
733
  },
720
- transformUrlContent: {
721
- html: (urlInfo) => {
722
- const htmlAst = parseHtmlString(urlInfo.content, {
723
- storeOriginalPositions: false,
724
- })
725
- return {
726
- content: stringifyHtmlAst(htmlAst, {
727
- removeOriginalPositionAttributes: true,
728
- }),
729
- }
730
- },
731
- },
732
734
  },
733
735
  ],
734
736
  })
@@ -761,8 +763,6 @@ ${Object.keys(finalGraph.urlInfos).join("\n")}`,
761
763
  throw e
762
764
  }
763
765
  versioningTask.done()
764
- } else {
765
- // TODO: remove html attributes such as original-src-position
766
766
  }
767
767
 
768
768
  GRAPH.forEach(finalGraph, (urlInfo) => {
@@ -772,6 +772,14 @@ ${Object.keys(finalGraph.urlInfos).join("\n")}`,
772
772
  if (urlInfo.external) {
773
773
  return
774
774
  }
775
+ if (urlInfo.type === "html") {
776
+ const htmlAst = parseHtmlString(urlInfo.content, {
777
+ storeOriginalPositions: false,
778
+ })
779
+ urlInfo.content = stringifyHtmlAst(htmlAst, {
780
+ removeOriginalPositionAttributes: true,
781
+ })
782
+ }
775
783
  const version = urlInfo.data.version
776
784
  const useVersionedUrl = version && canUseVersionedUrl(urlInfo, finalGraph)
777
785
  const buildUrl = useVersionedUrl ? urlInfo.data.versionedUrl : urlInfo.url
@@ -795,7 +803,11 @@ ${Object.keys(finalGraph.urlInfos).join("\n")}`,
795
803
  // nothing uses this url anymore
796
804
  // - versioning update inline content
797
805
  // - file converted for import assertion of js_classic conversion
798
- if (!urlInfo.data.isEntryPoint && urlInfo.dependents.size === 0) {
806
+ if (
807
+ !urlInfo.data.isEntryPoint &&
808
+ urlInfo.type !== "sourcemap" &&
809
+ urlInfo.dependents.size === 0
810
+ ) {
799
811
  cleanupActions.push(() => {
800
812
  delete finalGraph.urlInfos[urlInfo.url]
801
813
  })
@@ -0,0 +1,176 @@
1
+ /*
2
+ * startBuildServer is mean to interact with the build files;
3
+ * files that will be deployed to production server(s).
4
+ * We want to be as close as possible from the production in order to:
5
+ * - run lighthouse
6
+ * - run an automated test tool such as cypress, playwright
7
+ * - see exactly how build file behaves (debug, measure perf, etc)
8
+ * For these reasons "startBuildServer" must be as close as possible from a static file server.
9
+ * It is not meant to provide a nice developper experience: this is the role "startDevServer".
10
+ *
11
+ * Conclusion:
12
+ * "startBuildServer" must be as close as possible from a static file server because
13
+ * we want to be in the user shoes and we should not alter build files.
14
+ */
15
+
16
+ import {
17
+ jsenvAccessControlAllowedHeaders,
18
+ startServer,
19
+ pluginServerTiming,
20
+ pluginRequestWaitingCheck,
21
+ pluginCORS,
22
+ fetchFileSystem,
23
+ } from "@jsenv/server"
24
+ import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
25
+ import { createLogger } from "@jsenv/logger"
26
+
27
+ import { createTaskLog } from "@jsenv/utils/logs/task_log.js"
28
+ import { watchFiles } from "@jsenv/utils/file_watcher/file_watcher.js"
29
+ import { executeCommand } from "@jsenv/utils/command/command.js"
30
+
31
+ export const startBuildServer = async ({
32
+ signal = new AbortController().signal,
33
+ handleSIGINT = true,
34
+ logLevel,
35
+ buildCommandLogLevel = "warn",
36
+ protocol,
37
+ http2,
38
+ certificate,
39
+ privateKey,
40
+ listenAnyIp,
41
+ ip,
42
+ port,
43
+
44
+ rootDirectoryUrl,
45
+ buildDirectoryUrl,
46
+ buildCommand,
47
+ watchedFilePatterns,
48
+ cooldownBetweenFileEvents,
49
+ }) => {
50
+ const operation = Abort.startOperation()
51
+ operation.addAbortSignal(signal)
52
+ if (handleSIGINT) {
53
+ operation.addAbortSource((abort) => {
54
+ return raceProcessTeardownEvents(
55
+ {
56
+ SIGINT: true,
57
+ },
58
+ abort,
59
+ )
60
+ })
61
+ }
62
+ const logger = createLogger({ logLevel })
63
+
64
+ let buildPromise
65
+ let abortController
66
+ const runBuild = async () => {
67
+ const buildTask = createTaskLog(logger, `execute build command`)
68
+ abortController = new AbortController()
69
+ operation.addAbortCallback(() => {
70
+ abortController.abort()
71
+ })
72
+ buildPromise = executeCommand(buildCommand, {
73
+ cwd: rootDirectoryUrl,
74
+ logLevel: buildCommandLogLevel,
75
+ signal: abortController.signal,
76
+ })
77
+ try {
78
+ await buildPromise
79
+ buildTask.done()
80
+ } catch (e) {
81
+ if (e.code === "ABORT_ERR") {
82
+ buildTask.fail(`execute build command aborted`)
83
+ } else {
84
+ buildTask.fail()
85
+ throw e
86
+ }
87
+ }
88
+ }
89
+
90
+ const startServerTask = createTaskLog(logger, "start build server")
91
+ const server = await startServer({
92
+ signal,
93
+ stopOnExit: false,
94
+ stopOnSIGINT: false,
95
+ stopOnInternalError: false,
96
+ keepProcessAlive: true,
97
+ logLevel,
98
+ startLog: false,
99
+
100
+ protocol,
101
+ http2,
102
+ certificate,
103
+ privateKey,
104
+ listenAnyIp,
105
+ ip,
106
+ port,
107
+ plugins: {
108
+ ...pluginCORS({
109
+ accessControlAllowRequestOrigin: true,
110
+ accessControlAllowRequestMethod: true,
111
+ accessControlAllowRequestHeaders: true,
112
+ accessControlAllowedRequestHeaders: [
113
+ ...jsenvAccessControlAllowedHeaders,
114
+ "x-jsenv-execution-id",
115
+ ],
116
+ accessControlAllowCredentials: true,
117
+ }),
118
+ ...pluginServerTiming(),
119
+ ...pluginRequestWaitingCheck({
120
+ requestWaitingMs: 60 * 1000,
121
+ }),
122
+ },
123
+ sendErrorDetails: true,
124
+ requestToResponse: async (request) => {
125
+ await buildPromise
126
+ const urlIsVersioned = new URL(
127
+ request.ressource,
128
+ request.origin,
129
+ ).searchParams.has("v")
130
+
131
+ return fetchFileSystem(
132
+ new URL(request.ressource.slice(1), buildDirectoryUrl),
133
+ {
134
+ headers: request.headers,
135
+ cacheControl: urlIsVersioned
136
+ ? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
137
+ : "private,max-age=0,must-revalidate",
138
+ etagEnabled: true,
139
+ compressionEnabled: !request.pathname.endsWith(".mp4"),
140
+ rootDirectoryUrl: buildDirectoryUrl,
141
+ canReadDirectory: true,
142
+ },
143
+ )
144
+ },
145
+ })
146
+ startServerTask.done()
147
+ logger.info(``)
148
+ Object.keys(server.origins).forEach((key) => {
149
+ logger.info(`- ${server.origins[key]}`)
150
+ })
151
+ logger.info(``)
152
+
153
+ const unregisterDirectoryLifecyle = watchFiles({
154
+ rootDirectoryUrl,
155
+ watchedFilePatterns,
156
+ cooldownBetweenFileEvents,
157
+ fileChangeCallback: ({ relativeUrl, event }) => {
158
+ abortController.abort()
159
+ // setTimeout is to ensure the abortController.abort() above
160
+ // is properly taken into account so that logs about abort comes first
161
+ // then logs about re-running the build happens
162
+ setTimeout(() => {
163
+ logger.info(`${relativeUrl} ${event} -> rebuild`)
164
+ runBuild()
165
+ })
166
+ },
167
+ })
168
+ operation.addAbortCallback(() => {
169
+ unregisterDirectoryLifecyle()
170
+ })
171
+ runBuild()
172
+
173
+ return server
174
+ }
175
+
176
+ const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30
@@ -7,7 +7,6 @@ import { createUrlGraph } from "@jsenv/core/src/omega/url_graph.js"
7
7
  import { createKitchen } from "@jsenv/core/src/omega/kitchen.js"
8
8
  import { startOmegaServer } from "@jsenv/core/src/omega/omega_server.js"
9
9
 
10
- import { jsenvPluginAutoreload } from "./plugins/autoreload/jsenv_plugin_autoreload.js"
11
10
  import { jsenvPluginExplorer } from "./plugins/explorer/jsenv_plugin_explorer.js"
12
11
  import { jsenvPluginToolbar } from "./plugins/toolbar/jsenv_plugin_toolbar.js"
13
12
 
@@ -29,16 +28,9 @@ export const startDevServer = async ({
29
28
  plugins = [],
30
29
  htmlSupervisor = true,
31
30
  injectedGlobals,
32
-
31
+ nodeEsmResolution,
32
+ fileSystemMagicResolution,
33
33
  autoreload = true,
34
- autoreloadPatterns = {
35
- "./**": true,
36
- "./**/.*/": false, // any folder starting with a dot is ignored (includes .git for instance)
37
- "./dist/": false,
38
- "./**/node_modules/": false,
39
- },
40
- cooldownBetweenFileEvents = 0,
41
-
42
34
  explorerGroups = {
43
35
  source: {
44
36
  "./*.html": true,
@@ -65,19 +57,16 @@ export const startDevServer = async ({
65
57
  plugins: [
66
58
  ...plugins,
67
59
  ...getCorePlugins({
60
+ rootDirectoryUrl,
61
+ urlGraph,
62
+ scenario: "dev",
63
+
68
64
  htmlSupervisor,
69
65
  injectedGlobals,
66
+ nodeEsmResolution,
67
+ fileSystemMagicResolution,
68
+ autoreload,
70
69
  }),
71
- ...(autoreload
72
- ? [
73
- jsenvPluginAutoreload({
74
- rootDirectoryUrl,
75
- urlGraph,
76
- autoreloadPatterns,
77
- cooldownBetweenFileEvents,
78
- }),
79
- ]
80
- : []),
81
70
  jsenvPluginExplorer({
82
71
  groups: explorerGroups,
83
72
  }),
@@ -26,10 +26,13 @@ export const execute = async ({
26
26
  runtime,
27
27
  runtimeParams,
28
28
 
29
- injectedGlobals,
30
- plugins = [],
31
29
  scenario = "dev",
32
30
  sourcemaps = "inline",
31
+ plugins = [],
32
+ nodeEsmResolution,
33
+ fileSystemMagicResolution,
34
+ injectedGlobals,
35
+ transpilation,
33
36
 
34
37
  port,
35
38
  protocol,
@@ -72,7 +75,11 @@ export const execute = async ({
72
75
  plugins: [
73
76
  ...plugins,
74
77
  ...getCorePlugins({
78
+ scenario,
79
+ nodeEsmResolution,
80
+ fileSystemMagicResolution,
75
81
  injectedGlobals,
82
+ transpilation,
76
83
  }),
77
84
  ],
78
85
  })
@@ -38,9 +38,13 @@ export const createKitchen = ({
38
38
  test: "inline",
39
39
  build: "none",
40
40
  }[scenario],
41
- // we don't need sources in sourcemap as long as the url in the
42
- // sourcemap uses file:/// (chrome will understand and read from filesystem)
43
- sourcemapsSources = false,
41
+ sourcemapsSources = {
42
+ // during dev/test, chrome is able to find the sourcemap sources
43
+ // as long as they use file:// protocol in the sourcemap files
44
+ dev: false,
45
+ test: false,
46
+ build: true,
47
+ }[scenario],
44
48
  runtimeCompat = defaultRuntimeCompat,
45
49
  writeOnFileSystem = true,
46
50
  }) => {
@@ -1,4 +1,5 @@
1
- import { createEventSourceConnection } from "./event_source_connection.js"
1
+ import { createEventSourceConnection } from "@jsenv/utils/event_source/event_source.js"
2
+ import { urlHotMetas } from "../../../import_meta_hot/client/import_meta_hot.js"
2
3
  import {
3
4
  isAutoreloadEnabled,
4
5
  setAutoreloadPreference,
@@ -9,7 +10,6 @@ import {
9
10
  reloadDOMNodesUsingUrl,
10
11
  reloadJsImport,
11
12
  } from "./reload.js"
12
- import { urlHotMetas } from "./import_meta_hot.js"
13
13
 
14
14
  const reloadMessages = []
15
15
  const reloadMessagesSignal = { onchange: () => {} }
@@ -0,0 +1,41 @@
1
+ import {
2
+ parseHtmlString,
3
+ stringifyHtmlAst,
4
+ injectScriptAsEarlyAsPossible,
5
+ createHtmlNode,
6
+ } from "@jsenv/utils/html_ast/html_ast.js"
7
+
8
+ export const jsenvPluginDevSSEClient = () => {
9
+ const eventSourceClientFileUrl = new URL(
10
+ "./client/event_source_client.js",
11
+ import.meta.url,
12
+ ).href
13
+
14
+ return {
15
+ name: "jsenv:dev_sse_client",
16
+ appliesDuring: { dev: true },
17
+ transformUrlContent: {
18
+ html: (htmlUrlInfo, context) => {
19
+ const htmlAst = parseHtmlString(htmlUrlInfo.content)
20
+ const [eventSourceClientReference] = context.referenceUtils.inject({
21
+ type: "script_src",
22
+ expectedType: "js_module",
23
+ specifier: eventSourceClientFileUrl,
24
+ })
25
+ injectScriptAsEarlyAsPossible(
26
+ htmlAst,
27
+ createHtmlNode({
28
+ "tagName": "script",
29
+ "type": "module",
30
+ "src": eventSourceClientReference.generatedSpecifier,
31
+ "injected-by": "jsenv:dev_sse_client",
32
+ }),
33
+ )
34
+ const htmlModified = stringifyHtmlAst(htmlAst)
35
+ return {
36
+ content: htmlModified,
37
+ }
38
+ },
39
+ },
40
+ }
41
+ }