@jsenv/core 38.2.3 → 38.2.5
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/README.md +91 -4
- package/dist/jsenv_core.js +54 -15
- package/package.json +2 -2
- package/src/kitchen/url_graph/url_info_transformations.js +1 -0
package/README.md
CHANGED
|
@@ -1,9 +1,96 @@
|
|
|
1
1
|
# @jsenv/core [](https://www.npmjs.com/package/@jsenv/core)
|
|
2
2
|
|
|
3
|
-
Jsenv is a tool to develop test and build projects using JavaScript.
|
|
4
|
-
Jsenv is simple, easy to understand and well documented.
|
|
3
|
+
Jsenv is a tool to develop test and build projects using JavaScript. Jsenv is simple, easy to understand and well [documented](<https://github.com/jsenv/core/wiki/A)-directory-structure>).
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Jsenv cares a lot about the developper experience, especially when it comes to tests.
|
|
6
|
+
|
|
7
|
+
The pillars of jsenv are:
|
|
8
|
+
|
|
9
|
+
1. A dev server serving source files with autoreload
|
|
10
|
+
2. A build generating an optimized version of source files into a directory
|
|
11
|
+
3. A build server serving build files
|
|
12
|
+
4. A test runner executing test files in web browser(s)
|
|
13
|
+
|
|
14
|
+
# The best parts
|
|
15
|
+
|
|
16
|
+
## Reduce cognitive load inside test files
|
|
17
|
+
|
|
18
|
+
When coding, we spend most of our time working on source files. At some point we switch from source files to test files. Suddenly things are different:
|
|
19
|
+
|
|
20
|
+
- code does not execute as it would in source files
|
|
21
|
+
- some tools are used differently in test files, some cannot be used at all
|
|
22
|
+
- you are forced to code in a certain way that is completely different from the one in source files
|
|
23
|
+
|
|
24
|
+
This huge gap between source files and test files creates a context switching costing a lot of cognitive energy.
|
|
25
|
+
Jsenv makes a special effort to [provide a solution](<https://github.com/jsenv/core/wiki/D)-Test>) where switching from source files to test files is easy.
|
|
26
|
+
|
|
27
|
+
It also means tools used on source files can be reused on test files: ESlint, VSCode debugger, etc. No need to maintain separate tools and/or configurations.
|
|
28
|
+
|
|
29
|
+
## Other good parts
|
|
30
|
+
|
|
31
|
+
- A [large browser support during dev](<https://github.com/jsenv/core/wiki/B)-Dev#21-browser-support>). Because some people might be happy to use an other browser than the latest chrome during dev. Moreover it is useful to reproduce bug specific to certain browsers.
|
|
32
|
+
- A [large browser support after build](<https://github.com/jsenv/core/wiki/C)-Build#211-maximal-browser-support>). Because some product still needs to support old versions of Firefox, Chrome and Safari.
|
|
33
|
+
- A [single set of files during build](<https://github.com/jsenv/core/wiki/C)-Build#212-same-build-for-all-browsers>). Because a single one is simpler to properly support in every aspects.
|
|
34
|
+
- Versioning during build is robust and <a href="https://bundlers.tooling.report/hashing/avoid-cascade/" target="_blank">avoids cascading hash changes</a><sup>↗</sup>
|
|
35
|
+
- Ability to [execute tests in multiple browsers](<https://github.com/jsenv/core/wiki/D)-Test#32-executing-on-more-browsers>): Chrome, Safari, Firefox
|
|
36
|
+
- An advanced support of top level await, allowing to use it everywhere
|
|
37
|
+
- An advanced support of web workers including worker type module
|
|
38
|
+
- Unlock js module features on a regular `<script>` when needed. If you need the behaviour of `<script>` which is to block other `<script>` tag in the page, you'll be happy to still have the power of js modules, like imports, at your disposal.
|
|
39
|
+
|
|
40
|
+
# Demos
|
|
41
|
+
|
|
42
|
+
A demo is a project pre-configured with jsenv.
|
|
43
|
+
|
|
44
|
+
The following command can be used to install and try a demo:
|
|
45
|
+
|
|
46
|
+
```console
|
|
47
|
+
npm create jsenv@latest
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
It will prompt to choose one of the available demo:
|
|
51
|
+
|
|
52
|
+
```console
|
|
53
|
+
? Select a demo: › - Use arrow-keys. Return to submit.
|
|
54
|
+
❯ web
|
|
55
|
+
web-react
|
|
56
|
+
web-preact
|
|
57
|
+
node-package
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Selecting "web" will copy [create-jsenv/demo-web](https://github.com/jsenv/core/tree/bc7fb0aa2c8ced1db4d7583a2ea1858be464c23b/packages/related/create-jsenv/demo-web)<sup>↗</sup> files into a directory:
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
✔ Select a demo: › web
|
|
64
|
+
✔ copy demo files into "[...]jsenv-demo-web/" (done in 0.1 second)
|
|
65
|
+
----- commands to run -----
|
|
66
|
+
cd jsenv-demo-web
|
|
67
|
+
npm install
|
|
68
|
+
npm start
|
|
69
|
+
---------------------------
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
After running the suggested commands the demo is ready.
|
|
73
|
+
|
|
74
|
+
> **Info**
|
|
75
|
+
> "npm install" can take time because tests are runned in headless browsers that needs to be installed first.
|
|
76
|
+
|
|
77
|
+
The demo contains preconfigured scripts:
|
|
78
|
+
|
|
79
|
+
- `npm run dev`: starts a server for source files; Documented in [B) Dev](<https://github.com/jsenv/core/wiki/B)-Dev>).
|
|
80
|
+
- `npm run build`: generate files optimized for production; Documented in [C) Build](<https://github.com/jsenv/core/wiki/C)-Build>).
|
|
81
|
+
- `npm run build:serve`: start a server for build files; Documented in [C) Build#how-to-serve-build-files](<https://github.com/jsenv/core/wiki/C)-Build#3-how-to-serve-build-files>).
|
|
82
|
+
- `npm run test`: execute test files on browsers(s); Documented in [D) Test](<https://github.com/jsenv/core/wiki/D)-Test>).
|
|
83
|
+
|
|
84
|
+
<!--
|
|
85
|
+
The following commands can be used to skip the prompt
|
|
86
|
+
|
|
87
|
+
| Command |
|
|
88
|
+
| ------------------------------------------- |
|
|
89
|
+
| `npm create jsenv@latest -- --web` |
|
|
90
|
+
| `npm create jsenv@latest -- --web-preact` |
|
|
91
|
+
| `npm create jsenv@latest -- --web-react` |
|
|
92
|
+
| `npm create jsenv@latest -- --node-package` |
|
|
93
|
+
-->
|
|
7
94
|
|
|
8
95
|
<!-- # Installation
|
|
9
96
|
|
|
@@ -11,5 +98,5 @@ Jsenv is simple, easy to understand and well documented.
|
|
|
11
98
|
npm install --save-dev @jsenv/core
|
|
12
99
|
```
|
|
13
100
|
|
|
14
|
-
_@jsenv/core_ is tested on Mac, Windows, Linux with Node.js
|
|
101
|
+
_@jsenv/core_ is tested on Mac, Windows, Linux with Node.js 20.
|
|
15
102
|
Other operating systems and Node.js versions are not tested. -->
|
package/dist/jsenv_core.js
CHANGED
|
@@ -2509,7 +2509,9 @@ const createWatcher = (sourcePath, options) => {
|
|
|
2509
2509
|
if (e.code === "ENOENT") {
|
|
2510
2510
|
return;
|
|
2511
2511
|
}
|
|
2512
|
-
console.error(
|
|
2512
|
+
console.error(
|
|
2513
|
+
`error while trying to get rid of windows EPERM: ${e.stack}`,
|
|
2514
|
+
);
|
|
2513
2515
|
throw error;
|
|
2514
2516
|
}
|
|
2515
2517
|
} else {
|
|
@@ -2543,7 +2545,8 @@ callback: ${callback}`);
|
|
|
2543
2545
|
return { registerCleanupCallback, cleanup };
|
|
2544
2546
|
};
|
|
2545
2547
|
|
|
2546
|
-
const
|
|
2548
|
+
const isLinux = process.platform === "linux";
|
|
2549
|
+
const fsWatchSupportsRecursive = !isLinux;
|
|
2547
2550
|
|
|
2548
2551
|
const registerDirectoryLifecycle = (
|
|
2549
2552
|
source,
|
|
@@ -2626,8 +2629,8 @@ const registerDirectoryLifecycle = (
|
|
|
2626
2629
|
try {
|
|
2627
2630
|
const relativeUrl = urlToRelativeUrl(url, source);
|
|
2628
2631
|
const previousInfo = infoMap.get(relativeUrl);
|
|
2629
|
-
const
|
|
2630
|
-
const type = statsToType(
|
|
2632
|
+
const stat = statSync(new URL(url));
|
|
2633
|
+
const type = statsToType(stat);
|
|
2631
2634
|
const patternValue = previousInfo
|
|
2632
2635
|
? previousInfo.patternValue
|
|
2633
2636
|
: getWatchPatternValue({ url, type });
|
|
@@ -2636,13 +2639,15 @@ const registerDirectoryLifecycle = (
|
|
|
2636
2639
|
url,
|
|
2637
2640
|
relativeUrl,
|
|
2638
2641
|
type,
|
|
2639
|
-
|
|
2640
|
-
mtimeMs: stats.mtimeMs,
|
|
2642
|
+
stat,
|
|
2641
2643
|
patternValue,
|
|
2642
2644
|
};
|
|
2643
2645
|
} catch (e) {
|
|
2644
2646
|
if (e.code === "ENOENT") {
|
|
2645
|
-
return
|
|
2647
|
+
return {
|
|
2648
|
+
type: null,
|
|
2649
|
+
stat: null,
|
|
2650
|
+
};
|
|
2646
2651
|
}
|
|
2647
2652
|
throw e;
|
|
2648
2653
|
}
|
|
@@ -2715,7 +2720,7 @@ const registerDirectoryLifecycle = (
|
|
|
2715
2720
|
const handleChange = (relativeUrl) => {
|
|
2716
2721
|
const entryUrl = new URL(relativeUrl, sourceUrl).href;
|
|
2717
2722
|
const entryInfo = readEntryInfo(entryUrl);
|
|
2718
|
-
if (
|
|
2723
|
+
if (entryInfo.type === null) {
|
|
2719
2724
|
const previousEntryInfo = infoMap.get(relativeUrl);
|
|
2720
2725
|
if (!previousEntryInfo) {
|
|
2721
2726
|
// on MacOS it's possible to receive a "rename" event for
|
|
@@ -2775,17 +2780,36 @@ const registerDirectoryLifecycle = (
|
|
|
2775
2780
|
readdirSync(new URL(directoryUrl)).forEach((entryName) => {
|
|
2776
2781
|
const childEntryUrl = new URL(entryName, directoryUrl).href;
|
|
2777
2782
|
const childEntryInfo = readEntryInfo(childEntryUrl);
|
|
2778
|
-
if (childEntryInfo && childEntryInfo.patternValue) {
|
|
2783
|
+
if (childEntryInfo.type !== null && childEntryInfo.patternValue) {
|
|
2779
2784
|
handleEntryFound(childEntryInfo, { notify });
|
|
2780
2785
|
}
|
|
2781
2786
|
});
|
|
2787
|
+
// we must watch manually every directory we find
|
|
2788
|
+
if (!fsWatchSupportsRecursive) {
|
|
2789
|
+
const watcher = createWatcher(urlToFileSystemPath(entryInfo.url), {
|
|
2790
|
+
persistent: keepProcessAlive,
|
|
2791
|
+
});
|
|
2792
|
+
tracker.registerCleanupCallback(() => {
|
|
2793
|
+
watcher.close();
|
|
2794
|
+
});
|
|
2795
|
+
watcher.on("change", (eventType, filename) => {
|
|
2796
|
+
handleDirectoryEvent({
|
|
2797
|
+
directoryRelativeUrl: entryInfo.relativeUrl,
|
|
2798
|
+
filename: filename
|
|
2799
|
+
? // replace back slashes with slashes
|
|
2800
|
+
filename.replace(/\\/g, "/")
|
|
2801
|
+
: "",
|
|
2802
|
+
eventType,
|
|
2803
|
+
});
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2782
2806
|
}
|
|
2783
2807
|
if (added && entryInfo.patternValue && notify) {
|
|
2784
2808
|
added({
|
|
2785
2809
|
relativeUrl: entryInfo.relativeUrl,
|
|
2786
2810
|
type: entryInfo.type,
|
|
2787
2811
|
patternValue: entryInfo.patternValue,
|
|
2788
|
-
mtime: entryInfo.mtimeMs,
|
|
2812
|
+
mtime: entryInfo.stat.mtimeMs,
|
|
2789
2813
|
});
|
|
2790
2814
|
}
|
|
2791
2815
|
};
|
|
@@ -2796,19 +2820,19 @@ const registerDirectoryLifecycle = (
|
|
|
2796
2820
|
relativeUrl: entryInfo.relativeUrl,
|
|
2797
2821
|
type: entryInfo.type,
|
|
2798
2822
|
patternValue: entryInfo.patternValue,
|
|
2799
|
-
mtime: entryInfo.mtimeMs,
|
|
2823
|
+
mtime: entryInfo.stat.mtimeMs,
|
|
2800
2824
|
});
|
|
2801
2825
|
}
|
|
2802
2826
|
};
|
|
2803
2827
|
const handleEntryUpdated = (entryInfo) => {
|
|
2804
2828
|
infoMap.set(entryInfo.relativeUrl, entryInfo);
|
|
2805
|
-
if (updated && entryInfo.patternValue) {
|
|
2829
|
+
if (updated && entryInfo.patternValue && shouldCallUpdated(entryInfo)) {
|
|
2806
2830
|
updated({
|
|
2807
2831
|
relativeUrl: entryInfo.relativeUrl,
|
|
2808
2832
|
type: entryInfo.type,
|
|
2809
2833
|
patternValue: entryInfo.patternValue,
|
|
2810
|
-
mtime: entryInfo.mtimeMs,
|
|
2811
|
-
previousMtime: entryInfo.previousInfo.mtimeMs,
|
|
2834
|
+
mtime: entryInfo.stat.mtimeMs,
|
|
2835
|
+
previousMtime: entryInfo.previousInfo.stat.mtimeMs,
|
|
2812
2836
|
});
|
|
2813
2837
|
}
|
|
2814
2838
|
};
|
|
@@ -2816,7 +2840,7 @@ const registerDirectoryLifecycle = (
|
|
|
2816
2840
|
readdirSync(new URL(sourceUrl)).forEach((entry) => {
|
|
2817
2841
|
const entryUrl = new URL(entry, sourceUrl).href;
|
|
2818
2842
|
const entryInfo = readEntryInfo(entryUrl);
|
|
2819
|
-
if (entryInfo && entryInfo.patternValue) {
|
|
2843
|
+
if (entryInfo.type !== null && entryInfo.patternValue) {
|
|
2820
2844
|
handleEntryFound(entryInfo, {
|
|
2821
2845
|
notify: notifyExistent,
|
|
2822
2846
|
});
|
|
@@ -2850,6 +2874,20 @@ ${relativeUrls.join("\n")}`,
|
|
|
2850
2874
|
return tracker.cleanup;
|
|
2851
2875
|
};
|
|
2852
2876
|
|
|
2877
|
+
const shouldCallUpdated = (entryInfo) => {
|
|
2878
|
+
const { stat, previousInfo } = entryInfo;
|
|
2879
|
+
if (!stat.atimeMs) {
|
|
2880
|
+
return true;
|
|
2881
|
+
}
|
|
2882
|
+
if (stat.atimeMs < stat.mtimeMs) {
|
|
2883
|
+
return true;
|
|
2884
|
+
}
|
|
2885
|
+
if (isLinux && stat.mtimeMs <= previousInfo.stat.mtimeMs) {
|
|
2886
|
+
return false;
|
|
2887
|
+
}
|
|
2888
|
+
return true;
|
|
2889
|
+
};
|
|
2890
|
+
|
|
2853
2891
|
const undefinedOrFunction = (value) => {
|
|
2854
2892
|
return typeof value === "undefined" || typeof value === "function";
|
|
2855
2893
|
};
|
|
@@ -13214,6 +13252,7 @@ const createUrlInfoTransformer = ({
|
|
|
13214
13252
|
generatedUrlObject.searchParams.delete("as_json_module");
|
|
13215
13253
|
generatedUrlObject.searchParams.delete("as_text_module");
|
|
13216
13254
|
generatedUrlObject.searchParams.delete("dynamic_import");
|
|
13255
|
+
generatedUrlObject.searchParams.delete("cjs_as_js_module");
|
|
13217
13256
|
const urlForSourcemap = generatedUrlObject.href;
|
|
13218
13257
|
urlInfo.sourcemapGeneratedUrl = generateSourcemapFileUrl(urlForSourcemap);
|
|
13219
13258
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "38.2.
|
|
3
|
+
"version": "38.2.5",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@financial-times/polyfill-useragent-normaliser": "1.10.2",
|
|
63
63
|
"@jsenv/abort": "4.2.4",
|
|
64
64
|
"@jsenv/ast": "5.1.4",
|
|
65
|
-
"@jsenv/filesystem": "4.3.
|
|
65
|
+
"@jsenv/filesystem": "4.3.2",
|
|
66
66
|
"@jsenv/importmap": "1.2.1",
|
|
67
67
|
"@jsenv/integrity": "0.0.1",
|
|
68
68
|
"@jsenv/log": "3.4.0",
|
|
@@ -149,6 +149,7 @@ export const createUrlInfoTransformer = ({
|
|
|
149
149
|
generatedUrlObject.searchParams.delete("as_json_module");
|
|
150
150
|
generatedUrlObject.searchParams.delete("as_text_module");
|
|
151
151
|
generatedUrlObject.searchParams.delete("dynamic_import");
|
|
152
|
+
generatedUrlObject.searchParams.delete("cjs_as_js_module");
|
|
152
153
|
const urlForSourcemap = generatedUrlObject.href;
|
|
153
154
|
urlInfo.sourcemapGeneratedUrl = generateSourcemapFileUrl(urlForSourcemap);
|
|
154
155
|
|