@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 CHANGED
@@ -1,9 +1,96 @@
1
1
  # @jsenv/core [![npm package](https://img.shields.io/npm/v/@jsenv/core.svg?logo=npm&label=package)](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
- [Documentation](<https://github.com/jsenv/core/wiki/A)-Introduction>)
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 18.
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. -->
@@ -2509,7 +2509,9 @@ const createWatcher = (sourcePath, options) => {
2509
2509
  if (e.code === "ENOENT") {
2510
2510
  return;
2511
2511
  }
2512
- console.error(`error while fixing windows eperm: ${e.stack}`);
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 fsWatchSupportsRecursive = true;
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 stats = statSync(new URL(url));
2630
- const type = statsToType(stats);
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
- atimeMs: stats.atimeMs,
2640
- mtimeMs: stats.mtimeMs,
2642
+ stat,
2641
2643
  patternValue,
2642
2644
  };
2643
2645
  } catch (e) {
2644
2646
  if (e.code === "ENOENT") {
2645
- return null;
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 (!entryInfo) {
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",
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.0",
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