@jsenv/core 38.0.5 → 38.1.0

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.
@@ -14797,6 +14797,214 @@ const createRepartitionMessage = ({ html, css, js, json, other, total }) => {
14797
14797
  - `)}`;
14798
14798
  };
14799
14799
 
14800
+ const placeholderSymbol = Symbol.for("jsenv_placeholder");
14801
+ const PLACEHOLDER = {
14802
+ optional: (value) => {
14803
+ return { [placeholderSymbol]: "optional", value };
14804
+ },
14805
+ };
14806
+
14807
+ const replacePlaceholders = (content, replacements, urlInfo) => {
14808
+ const magicSource = createMagicSource(content);
14809
+ for (const key of Object.keys(replacements)) {
14810
+ let index = content.indexOf(key);
14811
+ const replacement = replacements[key];
14812
+ let isOptional;
14813
+ let value;
14814
+ if (replacement && replacement[placeholderSymbol]) {
14815
+ const valueBehindSymbol = replacement[placeholderSymbol];
14816
+ isOptional = valueBehindSymbol === "optional";
14817
+ value = replacement.value;
14818
+ } else {
14819
+ value = replacement;
14820
+ }
14821
+ if (index === -1) {
14822
+ if (!isOptional) {
14823
+ urlInfo.context.logger.warn(
14824
+ `placeholder "${key}" not found in ${urlInfo.url}.
14825
+ --- suggestion a ---
14826
+ Add "${key}" in that file.
14827
+ --- suggestion b ---
14828
+ Fix eventual typo in "${key}"?
14829
+ --- suggestion c ---
14830
+ Mark injection as optional using PLACEHOLDER.optional()
14831
+
14832
+ import { PLACEHOLDER } from "@jsenv/core"
14833
+
14834
+ return {
14835
+ "${key}": PLACEHOLDER.optional(${JSON.stringify(value)})
14836
+ }`,
14837
+ );
14838
+ }
14839
+ continue;
14840
+ }
14841
+
14842
+ while (index !== -1) {
14843
+ const start = index;
14844
+ const end = index + key.length;
14845
+ magicSource.replace({
14846
+ start,
14847
+ end,
14848
+ replacement:
14849
+ urlInfo.type === "js_classic" ||
14850
+ urlInfo.type === "js_module" ||
14851
+ urlInfo.type === "html"
14852
+ ? JSON.stringify(value, null, " ")
14853
+ : value,
14854
+ });
14855
+ index = content.indexOf(key, end);
14856
+ }
14857
+ }
14858
+ return magicSource.toContentAndSourcemap();
14859
+ };
14860
+
14861
+ const injectGlobals = (content, globals, urlInfo) => {
14862
+ if (urlInfo.type === "html") {
14863
+ return globalInjectorOnHtml(content, globals);
14864
+ }
14865
+ if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
14866
+ return globalsInjectorOnJs(content, globals, urlInfo);
14867
+ }
14868
+ throw new Error(`cannot inject globals into "${urlInfo.type}"`);
14869
+ };
14870
+
14871
+ const globalInjectorOnHtml = (content, globals) => {
14872
+ // ideally we would inject an importmap but browser support is too low
14873
+ // (even worse for worker/service worker)
14874
+ // so for now we inject code into entry points
14875
+ const htmlAst = parseHtmlString(content, {
14876
+ storeOriginalPositions: false,
14877
+ });
14878
+ const clientCode = generateClientCodeForGlobals(globals, {
14879
+ isWebWorker: false,
14880
+ });
14881
+ injectHtmlNodeAsEarlyAsPossible(
14882
+ htmlAst,
14883
+ createHtmlNode({
14884
+ tagName: "script",
14885
+ textContent: clientCode,
14886
+ }),
14887
+ "jsenv:inject_globals",
14888
+ );
14889
+ return stringifyHtmlAst(htmlAst);
14890
+ };
14891
+
14892
+ const globalsInjectorOnJs = (content, globals, urlInfo) => {
14893
+ const clientCode = generateClientCodeForGlobals(globals, {
14894
+ isWebWorker:
14895
+ urlInfo.subtype === "worker" ||
14896
+ urlInfo.subtype === "service_worker" ||
14897
+ urlInfo.subtype === "shared_worker",
14898
+ });
14899
+ const magicSource = createMagicSource(content);
14900
+ magicSource.prepend(clientCode);
14901
+ return magicSource.toContentAndSourcemap();
14902
+ };
14903
+
14904
+ const generateClientCodeForGlobals = (globals, { isWebWorker = false }) => {
14905
+ const globalName = isWebWorker ? "self" : "window";
14906
+ return `Object.assign(${globalName}, ${JSON.stringify(
14907
+ globals,
14908
+ null,
14909
+ " ",
14910
+ )});`;
14911
+ };
14912
+
14913
+ const jsenvPluginInjections = (rawAssociations) => {
14914
+ let resolvedAssociations;
14915
+
14916
+ return {
14917
+ name: "jsenv:injections",
14918
+ appliesDuring: "*",
14919
+ init: (context) => {
14920
+ resolvedAssociations = URL_META.resolveAssociations(
14921
+ { injectionsGetter: rawAssociations },
14922
+ context.rootDirectoryUrl,
14923
+ );
14924
+ },
14925
+ transformUrlContent: async (urlInfo) => {
14926
+ const { injectionsGetter } = URL_META.applyAssociations({
14927
+ url: asUrlWithoutSearch(urlInfo.url),
14928
+ associations: resolvedAssociations,
14929
+ });
14930
+ if (!injectionsGetter) {
14931
+ return null;
14932
+ }
14933
+ if (typeof injectionsGetter !== "function") {
14934
+ throw new TypeError("injectionsGetter must be a function");
14935
+ }
14936
+ const injections = await injectionsGetter(urlInfo);
14937
+ if (!injections) {
14938
+ return null;
14939
+ }
14940
+ const keys = Object.keys(injections);
14941
+ if (keys.length === 0) {
14942
+ return null;
14943
+ }
14944
+ let someGlobal = false;
14945
+ let someReplacement = false;
14946
+ const globals = {};
14947
+ const replacements = {};
14948
+ for (const key of keys) {
14949
+ const { type, name, value } = createInjection(injections[key], key);
14950
+ if (type === "global") {
14951
+ globals[name] = value;
14952
+ someGlobal = true;
14953
+ } else {
14954
+ replacements[name] = value;
14955
+ someReplacement = true;
14956
+ }
14957
+ }
14958
+
14959
+ if (!someGlobal && !someReplacement) {
14960
+ return null;
14961
+ }
14962
+
14963
+ let content = urlInfo.content;
14964
+ let sourcemap;
14965
+ if (someGlobal) {
14966
+ const globalInjectionResult = injectGlobals(content, globals, urlInfo);
14967
+ content = globalInjectionResult.content;
14968
+ sourcemap = globalInjectionResult.sourcemap;
14969
+ }
14970
+ if (someReplacement) {
14971
+ const replacementResult = replacePlaceholders(
14972
+ content,
14973
+ replacements,
14974
+ urlInfo,
14975
+ );
14976
+ content = replacementResult.content;
14977
+ sourcemap = sourcemap
14978
+ ? composeTwoSourcemaps(sourcemap, replacementResult.sourcemap)
14979
+ : replacementResult.sourcemap;
14980
+ }
14981
+ return {
14982
+ content,
14983
+ sourcemap,
14984
+ };
14985
+ },
14986
+ };
14987
+ };
14988
+
14989
+ const wellKnowGlobalNames = ["window", "global", "globalThis", "self"];
14990
+ const createInjection = (value, key) => {
14991
+ for (const wellKnowGlobalName of wellKnowGlobalNames) {
14992
+ const prefix = `${wellKnowGlobalName}.`;
14993
+ if (key.startsWith(prefix)) {
14994
+ return {
14995
+ type: "global",
14996
+ name: key.slice(prefix.length),
14997
+ value,
14998
+ };
14999
+ }
15000
+ }
15001
+ return {
15002
+ type: "replacement",
15003
+ name: key,
15004
+ value,
15005
+ };
15006
+ };
15007
+
14800
15008
  const jsenvPluginReferenceExpectedTypes = () => {
14801
15009
  const redirectJsReference = (reference) => {
14802
15010
  const urlObject = new URL(reference.url);
@@ -18675,30 +18883,6 @@ const babelPluginMetadataImportMetaScenarios = () => {
18675
18883
  };
18676
18884
  };
18677
18885
 
18678
- const replacePlaceholders = (urlInfo, replacements) => {
18679
- const content = urlInfo.content;
18680
- const magicSource = createMagicSource(content);
18681
- Object.keys(replacements).forEach((key) => {
18682
- let index = content.indexOf(key);
18683
- while (index !== -1) {
18684
- const start = index;
18685
- const end = index + key.length;
18686
- magicSource.replace({
18687
- start,
18688
- end,
18689
- replacement:
18690
- urlInfo.type === "js_classic" ||
18691
- urlInfo.type === "js_module" ||
18692
- urlInfo.type === "html"
18693
- ? JSON.stringify(replacements[key], null, " ")
18694
- : replacements[key],
18695
- });
18696
- index = content.indexOf(key, end);
18697
- }
18698
- });
18699
- return magicSource.toContentAndSourcemap();
18700
- };
18701
-
18702
18886
  /*
18703
18887
  * Source code can contain the following
18704
18888
  * - __dev__
@@ -18709,10 +18893,14 @@ const replacePlaceholders = (urlInfo, replacements) => {
18709
18893
 
18710
18894
  const jsenvPluginGlobalScenarios = () => {
18711
18895
  const transformIfNeeded = (urlInfo) => {
18712
- return replacePlaceholders(urlInfo, {
18713
- __DEV__: urlInfo.context.dev,
18714
- __BUILD__: urlInfo.context.build,
18715
- });
18896
+ return replacePlaceholders(
18897
+ urlInfo.content,
18898
+ {
18899
+ __DEV__: PLACEHOLDER.optional(urlInfo.context.dev),
18900
+ __BUILD__: PLACEHOLDER.optional(urlInfo.context.build),
18901
+ },
18902
+ urlInfo,
18903
+ );
18716
18904
  };
18717
18905
 
18718
18906
  return {
@@ -19600,6 +19788,7 @@ const getCorePlugins = ({
19600
19788
  magicDirectoryIndex,
19601
19789
  directoryReferenceAllowed,
19602
19790
  supervisor,
19791
+ injections,
19603
19792
  transpilation = true,
19604
19793
  inlining = true,
19605
19794
 
@@ -19620,6 +19809,7 @@ const getCorePlugins = ({
19620
19809
 
19621
19810
  return [
19622
19811
  jsenvPluginReferenceAnalysis(referenceAnalysis),
19812
+ ...(injections ? [jsenvPluginInjections(injections)] : []),
19623
19813
  jsenvPluginTranspilation(transpilation),
19624
19814
  jsenvPluginImportmap(),
19625
19815
  ...(inlining ? [jsenvPluginInlining()] : []),
@@ -21228,6 +21418,7 @@ const build = async ({
21228
21418
  magicDirectoryIndex,
21229
21419
  directoryReferenceAllowed,
21230
21420
  scenarioPlaceholders,
21421
+ injections,
21231
21422
  transpilation = {},
21232
21423
  bundling = true,
21233
21424
  minification = !runtimeCompat.node,
@@ -21412,6 +21603,7 @@ build ${entryPointKeys.length} entry points`);
21412
21603
  magicExtensions,
21413
21604
  magicDirectoryIndex,
21414
21605
  directoryReferenceAllowed,
21606
+ injections,
21415
21607
  transpilation: {
21416
21608
  babelHelpersAsImport: !explicitJsModuleConversion,
21417
21609
  ...transpilation,
@@ -21965,6 +22157,7 @@ const createFileService = ({
21965
22157
  magicExtensions,
21966
22158
  magicDirectoryIndex,
21967
22159
  supervisor,
22160
+ injections,
21968
22161
  transpilation,
21969
22162
  clientAutoreload,
21970
22163
  cacheControl,
@@ -22046,6 +22239,7 @@ const createFileService = ({
22046
22239
  magicExtensions,
22047
22240
  magicDirectoryIndex,
22048
22241
  supervisor,
22242
+ injections,
22049
22243
  transpilation,
22050
22244
 
22051
22245
  clientAutoreload,
@@ -22382,6 +22576,7 @@ const startDevServer = async ({
22382
22576
  supervisor = true,
22383
22577
  magicExtensions,
22384
22578
  magicDirectoryIndex,
22579
+ injections,
22385
22580
  transpilation,
22386
22581
  cacheControl = true,
22387
22582
  ribbon = true,
@@ -22524,6 +22719,7 @@ const startDevServer = async ({
22524
22719
  magicExtensions,
22525
22720
  magicDirectoryIndex,
22526
22721
  supervisor,
22722
+ injections,
22527
22723
  transpilation,
22528
22724
  clientAutoreload,
22529
22725
  cacheControl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "38.0.5",
3
+ "version": "38.1.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -61,20 +61,21 @@
61
61
  "dependencies": {
62
62
  "@financial-times/polyfill-useragent-normaliser": "1.10.2",
63
63
  "@jsenv/abort": "4.2.4",
64
- "@jsenv/ast": "5.1.1",
64
+ "@jsenv/ast": "5.1.2",
65
65
  "@jsenv/filesystem": "4.2.6",
66
66
  "@jsenv/importmap": "1.2.1",
67
67
  "@jsenv/integrity": "0.0.1",
68
68
  "@jsenv/log": "3.4.0",
69
69
  "@jsenv/node-esm-resolution": "1.0.1",
70
- "@jsenv/js-module-fallback": "1.3.2",
70
+ "@jsenv/js-module-fallback": "1.3.3",
71
71
  "@jsenv/runtime-compat": "1.2.0",
72
72
  "@jsenv/server": "15.1.0",
73
73
  "@jsenv/sourcemap": "1.2.1",
74
+ "@jsenv/plugin-injections": "1.0.0",
74
75
  "@jsenv/plugin-bundling": "2.5.1",
75
76
  "@jsenv/plugin-minification": "1.5.0",
76
- "@jsenv/plugin-transpilation": "1.3.1",
77
- "@jsenv/plugin-supervisor": "1.3.1",
77
+ "@jsenv/plugin-transpilation": "1.3.2",
78
+ "@jsenv/plugin-supervisor": "1.3.2",
78
79
  "@jsenv/url-meta": "8.1.0",
79
80
  "@jsenv/urls": "2.2.1",
80
81
  "@jsenv/utils": "2.0.1"
@@ -90,14 +91,12 @@
90
91
  "@jsenv/https-local": "3.0.7",
91
92
  "@jsenv/package-workspace": "0.5.2",
92
93
  "@jsenv/performance-impact": "4.1.1",
93
- "@jsenv/plugin-globals": "./packages/related/plugin-globals/",
94
- "@jsenv/plugin-placeholders": "./packages/related/plugin-placeholders/",
95
94
  "@jsenv/plugin-as-js-classic": "./packages/related/plugin-as-js-classic/",
96
95
  "@jsenv/test": "./packages/related/test/",
97
- "eslint": "8.45.0",
96
+ "eslint": "8.46.0",
98
97
  "eslint-plugin-html": "7.1.0",
99
98
  "eslint-plugin-import": "2.28.0",
100
- "eslint-plugin-react": "7.33.0",
99
+ "eslint-plugin-react": "7.33.1",
101
100
  "open": "9.1.0",
102
101
  "playwright": "1.36.2",
103
102
  "prettier": "3.0.0"
@@ -107,6 +107,7 @@ export const build = async ({
107
107
  magicDirectoryIndex,
108
108
  directoryReferenceAllowed,
109
109
  scenarioPlaceholders,
110
+ injections,
110
111
  transpilation = {},
111
112
  bundling = true,
112
113
  minification = !runtimeCompat.node,
@@ -291,6 +292,7 @@ build ${entryPointKeys.length} entry points`);
291
292
  magicExtensions,
292
293
  magicDirectoryIndex,
293
294
  directoryReferenceAllowed,
295
+ injections,
294
296
  transpilation: {
295
297
  babelHelpersAsImport: !explicitJsModuleConversion,
296
298
  ...transpilation,
@@ -31,6 +31,7 @@ export const createFileService = ({
31
31
  magicExtensions,
32
32
  magicDirectoryIndex,
33
33
  supervisor,
34
+ injections,
34
35
  transpilation,
35
36
  clientAutoreload,
36
37
  cacheControl,
@@ -112,6 +113,7 @@ export const createFileService = ({
112
113
  magicExtensions,
113
114
  magicDirectoryIndex,
114
115
  supervisor,
116
+ injections,
115
117
  transpilation,
116
118
 
117
119
  clientAutoreload,
@@ -55,6 +55,7 @@ export const startDevServer = async ({
55
55
  supervisor = true,
56
56
  magicExtensions,
57
57
  magicDirectoryIndex,
58
+ injections,
58
59
  transpilation,
59
60
  cacheControl = true,
60
61
  ribbon = true,
@@ -197,6 +198,7 @@ export const startDevServer = async ({
197
198
  magicExtensions,
198
199
  magicDirectoryIndex,
199
200
  supervisor,
201
+ injections,
200
202
  transpilation,
201
203
  clientAutoreload,
202
204
  cacheControl,
@@ -5,14 +5,18 @@
5
5
  * A global will be injected with true/false when needed
6
6
  */
7
7
 
8
- import { replacePlaceholders } from "@jsenv/plugin-placeholders";
8
+ import { replacePlaceholders, PLACEHOLDER } from "@jsenv/plugin-injections";
9
9
 
10
10
  export const jsenvPluginGlobalScenarios = () => {
11
11
  const transformIfNeeded = (urlInfo) => {
12
- return replacePlaceholders(urlInfo, {
13
- __DEV__: urlInfo.context.dev,
14
- __BUILD__: urlInfo.context.build,
15
- });
12
+ return replacePlaceholders(
13
+ urlInfo.content,
14
+ {
15
+ __DEV__: PLACEHOLDER.optional(urlInfo.context.dev),
16
+ __BUILD__: PLACEHOLDER.optional(urlInfo.context.build),
17
+ },
18
+ urlInfo,
19
+ );
16
20
  };
17
21
 
18
22
  return {
@@ -1,4 +1,5 @@
1
1
  import { jsenvPluginSupervisor } from "@jsenv/plugin-supervisor";
2
+ import { jsenvPluginInjections } from "@jsenv/plugin-injections";
2
3
  import { jsenvPluginTranspilation } from "@jsenv/plugin-transpilation";
3
4
 
4
5
  import { jsenvPluginReferenceAnalysis } from "./reference_analysis/jsenv_plugin_reference_analysis.js";
@@ -31,6 +32,7 @@ export const getCorePlugins = ({
31
32
  magicDirectoryIndex,
32
33
  directoryReferenceAllowed,
33
34
  supervisor,
35
+ injections,
34
36
  transpilation = true,
35
37
  inlining = true,
36
38
 
@@ -51,6 +53,7 @@ export const getCorePlugins = ({
51
53
 
52
54
  return [
53
55
  jsenvPluginReferenceAnalysis(referenceAnalysis),
56
+ ...(injections ? [jsenvPluginInjections(injections)] : []),
54
57
  jsenvPluginTranspilation(transpilation),
55
58
  jsenvPluginImportmap(),
56
59
  ...(inlining ? [jsenvPluginInlining()] : []),