@jsenv/core 40.7.2 → 40.8.1

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.
@@ -2134,6 +2134,9 @@ const INJECTIONS = {
2134
2134
  return { [injectionSymbol]: "global", value };
2135
2135
  },
2136
2136
  optional: (value) => {
2137
+ if (value && value[injectionSymbol] === "optional") {
2138
+ return value;
2139
+ }
2137
2140
  return { [injectionSymbol]: "optional", value };
2138
2141
  },
2139
2142
  };
@@ -2949,6 +2952,7 @@ const createKitchen = ({
2949
2952
  isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
2950
2953
  isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
2951
2954
  isPlaceholderInjection,
2955
+ INJECTIONS,
2952
2956
  getPluginMeta: null,
2953
2957
  sourcemaps,
2954
2958
  outDirectoryUrl,
@@ -7645,6 +7649,116 @@ const jsenvPluginNodeRuntime = ({ runtimeCompat }) => {
7645
7649
  };
7646
7650
  };
7647
7651
 
7652
+ /**
7653
+ * Inline CSS would force to write the following boilerplate all the time:
7654
+ * ```js
7655
+ * const css = `body { color: red; }`;
7656
+ * const stylesheet = new CSSStyleSheet();
7657
+ * stylesheet.replaceSync(css);
7658
+ * document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
7659
+ * if (import.meta.hot) {
7660
+ * import.meta.hot.dispose(() => {
7661
+ * document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
7662
+ * (s) => s !== stylesheet,
7663
+ * );
7664
+ * });
7665
+ * }
7666
+ * ```
7667
+ *
7668
+ * It would be nice to have a plugin that does this automatically with the following syntax
7669
+ *
7670
+ * ```js
7671
+ * const css = `body { color: red; }`;
7672
+ * import.meta.css = css;
7673
+ * ```
7674
+ *
7675
+ */
7676
+
7677
+
7678
+ const jsenvPluginImportMetaCss = () => {
7679
+ const importMetaCssClientFileUrl = import.meta.resolve(
7680
+ "../js/import_meta_css.js",
7681
+ );
7682
+
7683
+ return {
7684
+ name: "jsenv:import_meta_css",
7685
+ appliesDuring: "*",
7686
+ transformUrlContent: {
7687
+ js_module: async (urlInfo) => {
7688
+ if (!urlInfo.content.includes("import.meta.css")) {
7689
+ return null;
7690
+ }
7691
+ const { metadata } = await applyBabelPlugins({
7692
+ babelPlugins: [babelPluginMetadataUsesImportMetaCss],
7693
+ input: urlInfo.content,
7694
+ inputIsJsModule: true,
7695
+ inputUrl: urlInfo.originalUrl,
7696
+ outputUrl: urlInfo.generatedUrl,
7697
+ });
7698
+ const { usesImportMetaCss } = metadata;
7699
+ if (!usesImportMetaCss) {
7700
+ return null;
7701
+ }
7702
+ return injectImportMetaCss(urlInfo, importMetaCssClientFileUrl);
7703
+ },
7704
+ },
7705
+ };
7706
+ };
7707
+
7708
+ const injectImportMetaCss = (urlInfo, importMetaCssClientFileUrl) => {
7709
+ const importMetaCssClientFileReference = urlInfo.dependencies.inject({
7710
+ parentUrl: urlInfo.url,
7711
+ type: "js_import",
7712
+ expectedType: "js_module",
7713
+ specifier: importMetaCssClientFileUrl,
7714
+ });
7715
+ let content = urlInfo.content;
7716
+ let prelude = `import { installImportMetaCss } from ${importMetaCssClientFileReference.generatedSpecifier};
7717
+
7718
+ const remove = installImportMetaCss(import.meta);
7719
+ if (import.meta.hot) {
7720
+ import.meta.hot.dispose(() => {
7721
+ remove();
7722
+ });
7723
+ }
7724
+
7725
+ `;
7726
+ return {
7727
+ content: `${prelude.replace(/\n/g, "")}${content}`,
7728
+ };
7729
+ };
7730
+
7731
+ const babelPluginMetadataUsesImportMetaCss = () => {
7732
+ return {
7733
+ name: "metadata-uses-import-meta-css",
7734
+ visitor: {
7735
+ Program(programPath, state) {
7736
+ let usesImportMetaCss = false;
7737
+ programPath.traverse({
7738
+ MemberExpression(path) {
7739
+ const { node } = path;
7740
+ const { object } = node;
7741
+ if (object.type !== "MetaProperty") {
7742
+ return;
7743
+ }
7744
+ const { property: objectProperty } = object;
7745
+ if (objectProperty.name !== "meta") {
7746
+ return;
7747
+ }
7748
+ const { property } = node;
7749
+ const { name } = property;
7750
+ if (name === "css") {
7751
+ usesImportMetaCss = true;
7752
+ path.stop();
7753
+ }
7754
+ },
7755
+ });
7756
+ state.file.metadata.usesImportMetaCss = usesImportMetaCss;
7757
+ },
7758
+ },
7759
+ };
7760
+ };
7761
+
7648
7762
  // https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-stages-of-babel
7649
7763
  // https://github.com/cfware/babel-plugin-bundled-import-meta/blob/master/index.js
7650
7764
  // https://github.com/babel/babel/blob/f4edf62f6beeab8ae9f2b7f0b82f1b3b12a581af/packages/babel-helper-module-imports/src/index.js#L7
@@ -7656,13 +7770,13 @@ const babelPluginMetadataImportMetaHot = () => {
7656
7770
  Program(programPath, state) {
7657
7771
  Object.assign(
7658
7772
  state.file.metadata,
7659
- collectImportMetaProperties(programPath),
7773
+ collectImportMetaHotProperties(programPath),
7660
7774
  );
7661
7775
  },
7662
7776
  },
7663
7777
  };
7664
7778
  };
7665
- const collectImportMetaProperties = (programPath) => {
7779
+ const collectImportMetaHotProperties = (programPath) => {
7666
7780
  const importMetaHotPaths = [];
7667
7781
  let hotDecline = false;
7668
7782
  let hotAcceptSelf = false;
@@ -8942,6 +9056,7 @@ const getCorePlugins = ({
8942
9056
  ? [jsenvPluginAutoreloadOnServerRestart()]
8943
9057
  : []),
8944
9058
 
9059
+ jsenvPluginImportMetaCss(),
8945
9060
  jsenvPluginCommonJsGlobals(),
8946
9061
  jsenvPluginImportMetaScenarios(),
8947
9062
  ...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []),
@@ -2517,7 +2517,10 @@ const comparePathnames = (leftPathame, rightPathname) => {
2517
2517
  const rightPart = rightPartArray[i];
2518
2518
  i++;
2519
2519
  // local comparison comes first
2520
- const comparison = leftPart.localeCompare(rightPart);
2520
+ const comparison = leftPart.localeCompare(rightPart, undefined, {
2521
+ numeric: true,
2522
+ sensitivity: "base",
2523
+ });
2521
2524
  if (comparison !== 0) {
2522
2525
  return comparison;
2523
2526
  }
@@ -0,0 +1,38 @@
1
+ import "file:///Users/dmail/Documents/dev/jsenv/core/packages/internal/plugin-transpilation/src/babel/new_stylesheet/client/new_stylesheet.js";
2
+
3
+ const installImportMetaCss = importMeta => {
4
+ let cssText = "";
5
+ let stylesheet = new CSSStyleSheet();
6
+ let adopted = false;
7
+ const css = {
8
+ toString: () => cssText,
9
+ update: value => {
10
+ cssText = value;
11
+ stylesheet.replaceSync(cssText);
12
+ },
13
+ inject: () => {
14
+ if (!adopted) {
15
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
16
+ adopted = true;
17
+ }
18
+ },
19
+ remove: () => {
20
+ if (adopted) {
21
+ document.adoptedStyleSheets = document.adoptedStyleSheets.filter(s => s !== stylesheet);
22
+ adopted = false;
23
+ }
24
+ }
25
+ };
26
+ Object.defineProperty(importMeta, "css", {
27
+ get() {
28
+ return css;
29
+ },
30
+ set(value) {
31
+ css.update(value);
32
+ css.inject();
33
+ }
34
+ });
35
+ return css.remove;
36
+ };
37
+
38
+ export { installImportMetaCss };
@@ -7,6 +7,9 @@ const INJECTIONS = {
7
7
  return { [injectionSymbol]: "global", value };
8
8
  },
9
9
  optional: (value) => {
10
+ if (value && value[injectionSymbol] === "optional") {
11
+ return value;
12
+ }
10
13
  return { [injectionSymbol]: "optional", value };
11
14
  },
12
15
  };
@@ -1822,7 +1822,10 @@ const comparePathnames = (leftPathame, rightPathname) => {
1822
1822
  const rightPart = rightPartArray[i];
1823
1823
  i++;
1824
1824
  // local comparison comes first
1825
- const comparison = leftPart.localeCompare(rightPart);
1825
+ const comparison = leftPart.localeCompare(rightPart, undefined, {
1826
+ numeric: true,
1827
+ sensitivity: "base",
1828
+ });
1826
1829
  if (comparison !== 0) {
1827
1830
  return comparison;
1828
1831
  }
@@ -2174,6 +2174,9 @@ const INJECTIONS = {
2174
2174
  return { [injectionSymbol]: "global", value };
2175
2175
  },
2176
2176
  optional: (value) => {
2177
+ if (value && value[injectionSymbol] === "optional") {
2178
+ return value;
2179
+ }
2177
2180
  return { [injectionSymbol]: "optional", value };
2178
2181
  },
2179
2182
  };
@@ -2989,6 +2992,7 @@ const createKitchen = ({
2989
2992
  isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
2990
2993
  isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
2991
2994
  isPlaceholderInjection,
2995
+ INJECTIONS,
2992
2996
  getPluginMeta: null,
2993
2997
  sourcemaps,
2994
2998
  outDirectoryUrl,
@@ -7685,6 +7689,116 @@ const jsenvPluginNodeRuntime = ({ runtimeCompat }) => {
7685
7689
  };
7686
7690
  };
7687
7691
 
7692
+ /**
7693
+ * Inline CSS would force to write the following boilerplate all the time:
7694
+ * ```js
7695
+ * const css = `body { color: red; }`;
7696
+ * const stylesheet = new CSSStyleSheet();
7697
+ * stylesheet.replaceSync(css);
7698
+ * document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
7699
+ * if (import.meta.hot) {
7700
+ * import.meta.hot.dispose(() => {
7701
+ * document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
7702
+ * (s) => s !== stylesheet,
7703
+ * );
7704
+ * });
7705
+ * }
7706
+ * ```
7707
+ *
7708
+ * It would be nice to have a plugin that does this automatically with the following syntax
7709
+ *
7710
+ * ```js
7711
+ * const css = `body { color: red; }`;
7712
+ * import.meta.css = css;
7713
+ * ```
7714
+ *
7715
+ */
7716
+
7717
+
7718
+ const jsenvPluginImportMetaCss = () => {
7719
+ const importMetaCssClientFileUrl = import.meta.resolve(
7720
+ "../js/import_meta_css.js",
7721
+ );
7722
+
7723
+ return {
7724
+ name: "jsenv:import_meta_css",
7725
+ appliesDuring: "*",
7726
+ transformUrlContent: {
7727
+ js_module: async (urlInfo) => {
7728
+ if (!urlInfo.content.includes("import.meta.css")) {
7729
+ return null;
7730
+ }
7731
+ const { metadata } = await applyBabelPlugins({
7732
+ babelPlugins: [babelPluginMetadataUsesImportMetaCss],
7733
+ input: urlInfo.content,
7734
+ inputIsJsModule: true,
7735
+ inputUrl: urlInfo.originalUrl,
7736
+ outputUrl: urlInfo.generatedUrl,
7737
+ });
7738
+ const { usesImportMetaCss } = metadata;
7739
+ if (!usesImportMetaCss) {
7740
+ return null;
7741
+ }
7742
+ return injectImportMetaCss(urlInfo, importMetaCssClientFileUrl);
7743
+ },
7744
+ },
7745
+ };
7746
+ };
7747
+
7748
+ const injectImportMetaCss = (urlInfo, importMetaCssClientFileUrl) => {
7749
+ const importMetaCssClientFileReference = urlInfo.dependencies.inject({
7750
+ parentUrl: urlInfo.url,
7751
+ type: "js_import",
7752
+ expectedType: "js_module",
7753
+ specifier: importMetaCssClientFileUrl,
7754
+ });
7755
+ let content = urlInfo.content;
7756
+ let prelude = `import { installImportMetaCss } from ${importMetaCssClientFileReference.generatedSpecifier};
7757
+
7758
+ const remove = installImportMetaCss(import.meta);
7759
+ if (import.meta.hot) {
7760
+ import.meta.hot.dispose(() => {
7761
+ remove();
7762
+ });
7763
+ }
7764
+
7765
+ `;
7766
+ return {
7767
+ content: `${prelude.replace(/\n/g, "")}${content}`,
7768
+ };
7769
+ };
7770
+
7771
+ const babelPluginMetadataUsesImportMetaCss = () => {
7772
+ return {
7773
+ name: "metadata-uses-import-meta-css",
7774
+ visitor: {
7775
+ Program(programPath, state) {
7776
+ let usesImportMetaCss = false;
7777
+ programPath.traverse({
7778
+ MemberExpression(path) {
7779
+ const { node } = path;
7780
+ const { object } = node;
7781
+ if (object.type !== "MetaProperty") {
7782
+ return;
7783
+ }
7784
+ const { property: objectProperty } = object;
7785
+ if (objectProperty.name !== "meta") {
7786
+ return;
7787
+ }
7788
+ const { property } = node;
7789
+ const { name } = property;
7790
+ if (name === "css") {
7791
+ usesImportMetaCss = true;
7792
+ path.stop();
7793
+ }
7794
+ },
7795
+ });
7796
+ state.file.metadata.usesImportMetaCss = usesImportMetaCss;
7797
+ },
7798
+ },
7799
+ };
7800
+ };
7801
+
7688
7802
  // https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-stages-of-babel
7689
7803
  // https://github.com/cfware/babel-plugin-bundled-import-meta/blob/master/index.js
7690
7804
  // https://github.com/babel/babel/blob/f4edf62f6beeab8ae9f2b7f0b82f1b3b12a581af/packages/babel-helper-module-imports/src/index.js#L7
@@ -7696,13 +7810,13 @@ const babelPluginMetadataImportMetaHot = () => {
7696
7810
  Program(programPath, state) {
7697
7811
  Object.assign(
7698
7812
  state.file.metadata,
7699
- collectImportMetaProperties(programPath),
7813
+ collectImportMetaHotProperties(programPath),
7700
7814
  );
7701
7815
  },
7702
7816
  },
7703
7817
  };
7704
7818
  };
7705
- const collectImportMetaProperties = (programPath) => {
7819
+ const collectImportMetaHotProperties = (programPath) => {
7706
7820
  const importMetaHotPaths = [];
7707
7821
  let hotDecline = false;
7708
7822
  let hotAcceptSelf = false;
@@ -8982,6 +9096,7 @@ const getCorePlugins = ({
8982
9096
  ? [jsenvPluginAutoreloadOnServerRestart()]
8983
9097
  : []),
8984
9098
 
9099
+ jsenvPluginImportMetaCss(),
8985
9100
  jsenvPluginCommonJsGlobals(),
8986
9101
  jsenvPluginImportMetaScenarios(),
8987
9102
  ...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []),
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "40.7.2",
3
+ "version": "40.8.1",
4
4
  "description": "Tool to develop, test and build js projects",
5
- "license": "MIT",
6
- "author": {
7
- "name": "dmail",
8
- "email": "dmaillard06@gmail.com"
9
- },
10
5
  "repository": {
11
6
  "type": "git",
12
7
  "url": "https://github.com/jsenv/core"
13
8
  },
14
- "engines": {
15
- "node": ">=20.8.0"
16
- },
17
- "publishConfig": {
18
- "access": "public"
9
+ "license": "MIT",
10
+ "author": {
11
+ "name": "dmail",
12
+ "email": "dmaillard06@gmail.com"
19
13
  },
14
+ "sideEffects": [
15
+ "./src/kitchen/client/inline_content.js",
16
+ "./dist/client/new_stylesheet/new_stylesheet.js",
17
+ "./dist/client/inline_content/inline_content.js",
18
+ "./dist/client/directory_listing/jsenv_core_node_modules.js"
19
+ ],
20
20
  "type": "module",
21
21
  "imports": {},
22
22
  "exports": {
@@ -33,11 +33,6 @@
33
33
  "/dist/",
34
34
  "/src/"
35
35
  ],
36
- "volta": {
37
- "node": "23.11.0",
38
- "npm": "11.3.0"
39
- },
40
- "packageManager": "npm@11.3.0",
41
36
  "workspaces": [
42
37
  "./packages/backend/*",
43
38
  "./packages/frontend/*",
@@ -48,49 +43,44 @@
48
43
  "./packages/related/cli/*",
49
44
  "./packages/tooling/*"
50
45
  ],
51
- "sideEffects": [
52
- "./src/kitchen/client/inline_content.js",
53
- "./dist/client/new_stylesheet/new_stylesheet.js",
54
- "./dist/client/inline_content/inline_content.js",
55
- "./dist/client/directory_listing/jsenv_core_node_modules.js"
56
- ],
57
46
  "scripts": {
58
- "eslint": "npx eslint .",
59
- "test": "node --conditions=dev:jsenv ./scripts/test/test.mjs",
60
- "test:packages": "npm run test -- ./packages/",
61
47
  "build": "node --conditions=dev:jsenv ./scripts/build/build.mjs",
48
+ "build:file_size": "node ./scripts/build/build_file_size.mjs --log",
62
49
  "build:packages": "npm run build --workspaces --if-present --conditions=developement",
63
- "monorepo:sync_packages_versions": "node ./scripts/monorepo/sync_packages_versions.mjs",
50
+ "database:install": "npx @jsenv/database install",
51
+ "database:manage": "node --conditions=dev:jsenv ./packages/backend/database/src/cli/manage.js",
52
+ "database:setup": "npm run database:setup --workspaces --if-present --conditions=dev:jsenv",
53
+ "database:start": "npx @jsenv/database start",
54
+ "database:stop": "npx @jsenv/database stop",
55
+ "dev": "node --watch --conditions=dev:jsenv ./scripts/dev/dev.mjs",
56
+ "dev:route-inspector": "node --watch --conditions=dev:jsenv ./packages/backend/server/tests/route_inspector/start_server.js",
57
+ "eslint": "npx eslint .",
58
+ "https:setup": "npx @jsenv/https-local setup",
59
+ "md:build": "node ./docs/build.js",
60
+ "monorepo:node_modules_clear": "npx @jsenv/filesystem clear **/node_modules/",
64
61
  "monorepo:publish": "node ./scripts/monorepo/publish_packages.mjs",
62
+ "monorepo:sync_packages_versions": "node ./scripts/monorepo/sync_packages_versions.mjs",
65
63
  "monorepo:upgrade_versions": "node ./scripts/monorepo/upgrade_external_versions.mjs",
66
- "monorepo:node_modules_clear": "npx @jsenv/filesystem clear **/node_modules/",
67
- "md:build": "node ./docs/build.js",
64
+ "oto:start": "npm run start -w oto",
68
65
  "performances": "node --expose-gc ./scripts/performance/generate_performance_report.mjs --log --once",
69
- "build:file_size": "node ./scripts/build/build_file_size.mjs --log",
66
+ "playwright:install": "npx playwright install-deps && npx playwright install",
67
+ "prepublishOnly": "npm run build",
70
68
  "prettier": "prettier --write .",
71
- "test:snapshot_clear": "npx @jsenv/filesystem clear **/tests/**/side_effects/",
69
+ "test": "node --conditions=dev:jsenv ./scripts/test/test.mjs",
72
70
  "test:ci": "CI=1 npm run test",
73
- "test:packages:ci": "CI=1 npm run workspace:test",
74
71
  "test:only_dev_server_errors": "node --conditions=dev:jsenv ./tests/dev_server/errors/dev_errors_snapshots.test.mjs",
75
- "dev": "node --watch --conditions=dev:jsenv ./scripts/dev/dev.mjs",
76
- "dev:route-inspector": "node --watch --conditions=dev:jsenv ./packages/backend/server/tests/route_inspector/start_server.js",
77
- "playwright:install": "npx playwright install-deps && npx playwright install",
78
- "https:setup": "npx @jsenv/https-local setup",
79
- "prepublishOnly": "npm run build",
80
- "database:install": "npx @jsenv/database install",
81
- "database:start": "npx @jsenv/database start",
82
- "database:stop": "npx @jsenv/database stop",
83
- "database:setup": "npx @jsenv/database setup",
84
- "oto:start": "npm run start -w oto"
72
+ "test:packages": "npm run test -- ./packages/",
73
+ "test:packages:ci": "CI=1 npm run workspace:test",
74
+ "test:snapshot_clear": "npx @jsenv/filesystem clear **/tests/**/side_effects/"
85
75
  },
86
76
  "dependencies": {
87
77
  "@financial-times/polyfill-useragent-normaliser": "1.10.2",
88
- "@jsenv/ast": "6.7.6",
89
- "@jsenv/js-module-fallback": "1.4.18",
90
- "@jsenv/plugin-bundling": "2.9.8",
78
+ "@jsenv/ast": "6.7.7",
79
+ "@jsenv/js-module-fallback": "1.4.19",
80
+ "@jsenv/plugin-bundling": "2.9.9",
91
81
  "@jsenv/plugin-minification": "1.7.0",
92
- "@jsenv/plugin-supervisor": "1.7.3",
93
- "@jsenv/plugin-transpilation": "1.5.50",
82
+ "@jsenv/plugin-supervisor": "1.7.4",
83
+ "@jsenv/plugin-transpilation": "1.5.51",
94
84
  "@jsenv/server": "16.1.2",
95
85
  "@jsenv/sourcemap": "1.3.9",
96
86
  "react-table": "7.8.0"
@@ -137,7 +127,19 @@
137
127
  "prettier": "3.5.3",
138
128
  "prettier-plugin-embed": "0.5.0",
139
129
  "prettier-plugin-organize-imports": "4.1.0",
130
+ "prettier-plugin-packagejson": "2.5.15",
140
131
  "prettier-plugin-sql": "0.19.0",
141
132
  "strip-ansi": "7.1.0"
133
+ },
134
+ "packageManager": "npm@11.3.0",
135
+ "engines": {
136
+ "node": ">=20.8.0"
137
+ },
138
+ "volta": {
139
+ "node": "24.1.0",
140
+ "npm": "11.3.0"
141
+ },
142
+ "publishConfig": {
143
+ "access": "public"
142
144
  }
143
145
  }
@@ -17,7 +17,10 @@ import {
17
17
  determineSourcemapFileUrl,
18
18
  } from "./out_directory_url.js";
19
19
  import { createUrlGraph } from "./url_graph/url_graph.js";
20
- import { isPlaceholderInjection } from "./url_graph/url_info_injections.js";
20
+ import {
21
+ INJECTIONS,
22
+ isPlaceholderInjection,
23
+ } from "./url_graph/url_info_injections.js";
21
24
  import { createUrlInfoTransformer } from "./url_graph/url_info_transformations.js";
22
25
  import { urlSpecifierEncoding } from "./url_graph/url_specifier_encoding.js";
23
26
 
@@ -104,6 +107,7 @@ export const createKitchen = ({
104
107
  isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
105
108
  isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
106
109
  isPlaceholderInjection,
110
+ INJECTIONS,
107
111
  getPluginMeta: null,
108
112
  sourcemaps,
109
113
  outDirectoryUrl,
@@ -7,6 +7,9 @@ export const INJECTIONS = {
7
7
  return { [injectionSymbol]: "global", value };
8
8
  },
9
9
  optional: (value) => {
10
+ if (value && value[injectionSymbol] === "optional") {
11
+ return value;
12
+ }
10
13
  return { [injectionSymbol]: "optional", value };
11
14
  },
12
15
  };
@@ -0,0 +1,42 @@
1
+ export const installImportMetaCss = (importMeta) => {
2
+ let cssText = "";
3
+ let stylesheet = new CSSStyleSheet();
4
+ let adopted = false;
5
+
6
+ const css = {
7
+ toString: () => cssText,
8
+ update: (value) => {
9
+ cssText = value;
10
+ stylesheet.replaceSync(cssText);
11
+ },
12
+ inject: () => {
13
+ if (!adopted) {
14
+ document.adoptedStyleSheets = [
15
+ ...document.adoptedStyleSheets,
16
+ stylesheet,
17
+ ];
18
+ adopted = true;
19
+ }
20
+ },
21
+ remove: () => {
22
+ if (adopted) {
23
+ document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
24
+ (s) => s !== stylesheet,
25
+ );
26
+ adopted = false;
27
+ }
28
+ },
29
+ };
30
+
31
+ Object.defineProperty(importMeta, "css", {
32
+ get() {
33
+ return css;
34
+ },
35
+ set(value) {
36
+ css.update(value);
37
+ css.inject();
38
+ },
39
+ });
40
+
41
+ return css.remove;
42
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Inline CSS would force to write the following boilerplate all the time:
3
+ * ```js
4
+ * const css = `body { color: red; }`;
5
+ * const stylesheet = new CSSStyleSheet();
6
+ * stylesheet.replaceSync(css);
7
+ * document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
8
+ * if (import.meta.hot) {
9
+ * import.meta.hot.dispose(() => {
10
+ * document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
11
+ * (s) => s !== stylesheet,
12
+ * );
13
+ * });
14
+ * }
15
+ * ```
16
+ *
17
+ * It would be nice to have a plugin that does this automatically with the following syntax
18
+ *
19
+ * ```js
20
+ * const css = `body { color: red; }`;
21
+ * import.meta.css = css;
22
+ * ```
23
+ *
24
+ */
25
+
26
+ import { applyBabelPlugins } from "@jsenv/ast";
27
+
28
+ export const jsenvPluginImportMetaCss = () => {
29
+ const importMetaCssClientFileUrl = import.meta.resolve(
30
+ "./client/import_meta_css.js",
31
+ );
32
+
33
+ return {
34
+ name: "jsenv:import_meta_css",
35
+ appliesDuring: "*",
36
+ transformUrlContent: {
37
+ js_module: async (urlInfo) => {
38
+ if (!urlInfo.content.includes("import.meta.css")) {
39
+ return null;
40
+ }
41
+ const { metadata } = await applyBabelPlugins({
42
+ babelPlugins: [babelPluginMetadataUsesImportMetaCss],
43
+ input: urlInfo.content,
44
+ inputIsJsModule: true,
45
+ inputUrl: urlInfo.originalUrl,
46
+ outputUrl: urlInfo.generatedUrl,
47
+ });
48
+ const { usesImportMetaCss } = metadata;
49
+ if (!usesImportMetaCss) {
50
+ return null;
51
+ }
52
+ return injectImportMetaCss(urlInfo, importMetaCssClientFileUrl);
53
+ },
54
+ },
55
+ };
56
+ };
57
+
58
+ const injectImportMetaCss = (urlInfo, importMetaCssClientFileUrl) => {
59
+ const importMetaCssClientFileReference = urlInfo.dependencies.inject({
60
+ parentUrl: urlInfo.url,
61
+ type: "js_import",
62
+ expectedType: "js_module",
63
+ specifier: importMetaCssClientFileUrl,
64
+ });
65
+ let content = urlInfo.content;
66
+ let prelude = `import { installImportMetaCss } from ${importMetaCssClientFileReference.generatedSpecifier};
67
+
68
+ const remove = installImportMetaCss(import.meta);
69
+ if (import.meta.hot) {
70
+ import.meta.hot.dispose(() => {
71
+ remove();
72
+ });
73
+ }
74
+
75
+ `;
76
+ return {
77
+ content: `${prelude.replace(/\n/g, "")}${content}`,
78
+ };
79
+ };
80
+
81
+ const babelPluginMetadataUsesImportMetaCss = () => {
82
+ return {
83
+ name: "metadata-uses-import-meta-css",
84
+ visitor: {
85
+ Program(programPath, state) {
86
+ let usesImportMetaCss = false;
87
+ programPath.traverse({
88
+ MemberExpression(path) {
89
+ const { node } = path;
90
+ const { object } = node;
91
+ if (object.type !== "MetaProperty") {
92
+ return;
93
+ }
94
+ const { property: objectProperty } = object;
95
+ if (objectProperty.name !== "meta") {
96
+ return;
97
+ }
98
+ const { property } = node;
99
+ const { name } = property;
100
+ if (name === "css") {
101
+ usesImportMetaCss = true;
102
+ path.stop();
103
+ }
104
+ },
105
+ });
106
+ state.file.metadata.usesImportMetaCss = usesImportMetaCss;
107
+ },
108
+ },
109
+ };
110
+ };
@@ -9,13 +9,13 @@ export const babelPluginMetadataImportMetaHot = () => {
9
9
  Program(programPath, state) {
10
10
  Object.assign(
11
11
  state.file.metadata,
12
- collectImportMetaProperties(programPath),
12
+ collectImportMetaHotProperties(programPath),
13
13
  );
14
14
  },
15
15
  },
16
16
  };
17
17
  };
18
- const collectImportMetaProperties = (programPath) => {
18
+ const collectImportMetaHotProperties = (programPath) => {
19
19
  const importMetaHotPaths = [];
20
20
  let hotDecline = false;
21
21
  let hotAcceptSelf = false;
@@ -16,6 +16,7 @@ import { jsenvPluginCommonJsGlobals } from "./commonjs_globals/jsenv_plugin_comm
16
16
  import { jsenvPluginImportMetaScenarios } from "./import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js";
17
17
  import { jsenvPluginGlobalScenarios } from "./global_scenarios/jsenv_plugin_global_scenarios.js";
18
18
  import { jsenvPluginNodeRuntime } from "./node_runtime/jsenv_plugin_node_runtime.js";
19
+ import { jsenvPluginImportMetaCss } from "./import_meta_css/jsenv_plugin_import_meta_css.js";
19
20
  // autoreload
20
21
  import { jsenvPluginImportMetaHot } from "./import_meta_hot/jsenv_plugin_import_meta_hot.js";
21
22
  import { jsenvPluginAutoreload } from "./autoreload/jsenv_plugin_autoreload.js";
@@ -126,6 +127,7 @@ export const getCorePlugins = ({
126
127
  ? [jsenvPluginAutoreloadOnServerRestart()]
127
128
  : []),
128
129
 
130
+ jsenvPluginImportMetaCss(),
129
131
  jsenvPluginCommonJsGlobals(),
130
132
  jsenvPluginImportMetaScenarios(),
131
133
  ...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []),