@jsenv/core 38.1.1 → 38.2.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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
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. This tool makes a special effort to let you free to code as you want. 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.
4
4
 
5
- [Documentation for users](<https://github.com/jsenv/core/wiki/A)-Introduction>) · [Changelog](./CHANGELOG.md)
5
+ [Documentation](<https://github.com/jsenv/core/wiki/A)-Introduction>) · [Changelog](./CHANGELOG.md)
6
6
 
7
7
  See also
8
8
 
@@ -14797,214 +14797,6 @@ 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
-
15008
14800
  const jsenvPluginReferenceExpectedTypes = () => {
15009
14801
  const redirectJsReference = (reference) => {
15010
14802
  const urlObject = new URL(reference.url);
@@ -18353,6 +18145,104 @@ const jsenvPluginProtocolHttp = () => {
18353
18145
  };
18354
18146
  };
18355
18147
 
18148
+ const jsenvPluginInjections = (rawAssociations) => {
18149
+ let resolvedAssociations;
18150
+
18151
+ return {
18152
+ name: "jsenv:injections",
18153
+ appliesDuring: "*",
18154
+ init: (context) => {
18155
+ resolvedAssociations = URL_META.resolveAssociations(
18156
+ { injectionsGetter: rawAssociations },
18157
+ context.rootDirectoryUrl,
18158
+ );
18159
+ },
18160
+ transformUrlContent: async (urlInfo) => {
18161
+ const { injectionsGetter } = URL_META.applyAssociations({
18162
+ url: asUrlWithoutSearch(urlInfo.url),
18163
+ associations: resolvedAssociations,
18164
+ });
18165
+ if (!injectionsGetter) {
18166
+ return null;
18167
+ }
18168
+ if (typeof injectionsGetter !== "function") {
18169
+ throw new TypeError("injectionsGetter must be a function");
18170
+ }
18171
+ const injections = await injectionsGetter(urlInfo);
18172
+ if (!injections) {
18173
+ return null;
18174
+ }
18175
+ const keys = Object.keys(injections);
18176
+ if (keys.length === 0) {
18177
+ return null;
18178
+ }
18179
+ return replacePlaceholders(urlInfo.content, injections, urlInfo);
18180
+ },
18181
+ };
18182
+ };
18183
+
18184
+ const injectionSymbol = Symbol.for("jsenv_injection");
18185
+ const INJECTIONS = {
18186
+ optional: (value) => {
18187
+ return { [injectionSymbol]: "optional", value };
18188
+ },
18189
+ };
18190
+
18191
+ // we export this because it is imported by jsenv_plugin_placeholder.js and unit test
18192
+ const replacePlaceholders = (content, replacements, urlInfo) => {
18193
+ const magicSource = createMagicSource(content);
18194
+ for (const key of Object.keys(replacements)) {
18195
+ let index = content.indexOf(key);
18196
+ const replacement = replacements[key];
18197
+ let isOptional;
18198
+ let value;
18199
+ if (replacement && replacement[injectionSymbol]) {
18200
+ const valueBehindSymbol = replacement[injectionSymbol];
18201
+ isOptional = valueBehindSymbol === "optional";
18202
+ value = replacement.value;
18203
+ } else {
18204
+ value = replacement;
18205
+ }
18206
+ if (index === -1) {
18207
+ if (!isOptional) {
18208
+ urlInfo.context.logger.warn(
18209
+ `placeholder "${key}" not found in ${urlInfo.url}.
18210
+ --- suggestion a ---
18211
+ Add "${key}" in that file.
18212
+ --- suggestion b ---
18213
+ Fix eventual typo in "${key}"?
18214
+ --- suggestion c ---
18215
+ Mark injection as optional using INJECTIONS.optional()
18216
+
18217
+ import { INJECTIONS } from "@jsenv/core";
18218
+
18219
+ return {
18220
+ "${key}": INJECTIONS.optional(${JSON.stringify(value)}),
18221
+ }`,
18222
+ );
18223
+ }
18224
+ continue;
18225
+ }
18226
+
18227
+ while (index !== -1) {
18228
+ const start = index;
18229
+ const end = index + key.length;
18230
+ magicSource.replace({
18231
+ start,
18232
+ end,
18233
+ replacement:
18234
+ urlInfo.type === "js_classic" ||
18235
+ urlInfo.type === "js_module" ||
18236
+ urlInfo.type === "html"
18237
+ ? JSON.stringify(value, null, " ")
18238
+ : value,
18239
+ });
18240
+ index = content.indexOf(key, end);
18241
+ }
18242
+ }
18243
+ return magicSource.toContentAndSourcemap();
18244
+ };
18245
+
18356
18246
  const jsenvPluginInliningAsDataUrl = () => {
18357
18247
  return {
18358
18248
  name: "jsenv:inlining_as_data_url",
@@ -18885,9 +18775,9 @@ const babelPluginMetadataImportMetaScenarios = () => {
18885
18775
 
18886
18776
  /*
18887
18777
  * Source code can contain the following
18888
- * - __dev__
18889
- * - __build__
18890
- * A global will be injected with true/false when needed
18778
+ * - __DEV__
18779
+ * - __BUILD__
18780
+ * That will be replaced with true/false
18891
18781
  */
18892
18782
 
18893
18783
 
@@ -18896,8 +18786,8 @@ const jsenvPluginGlobalScenarios = () => {
18896
18786
  return replacePlaceholders(
18897
18787
  urlInfo.content,
18898
18788
  {
18899
- __DEV__: PLACEHOLDER.optional(urlInfo.context.dev),
18900
- __BUILD__: PLACEHOLDER.optional(urlInfo.context.build),
18789
+ __DEV__: INJECTIONS.optional(urlInfo.context.dev),
18790
+ __BUILD__: INJECTIONS.optional(urlInfo.context.build),
18901
18791
  },
18902
18792
  urlInfo,
18903
18793
  );
@@ -22982,4 +22872,4 @@ const createBuildFilesService = ({ buildDirectoryUrl, buildMainFilePath }) => {
22982
22872
 
22983
22873
  const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
22984
22874
 
22985
- export { build, startBuildServer, startDevServer };
22875
+ export { INJECTIONS, build, startBuildServer, startDevServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "38.1.1",
3
+ "version": "38.2.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -71,8 +71,7 @@
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",
75
- "@jsenv/plugin-bundling": "2.5.1",
74
+ "@jsenv/plugin-bundling": "2.5.2",
76
75
  "@jsenv/plugin-minification": "1.5.0",
77
76
  "@jsenv/plugin-transpilation": "1.3.2",
78
77
  "@jsenv/plugin-supervisor": "1.3.2",
@@ -99,6 +98,6 @@
99
98
  "eslint-plugin-react": "7.33.1",
100
99
  "open": "9.1.0",
101
100
  "playwright": "1.36.2",
102
- "prettier": "3.0.0"
101
+ "prettier": "3.0.1"
103
102
  }
104
103
  }
package/src/main.js CHANGED
@@ -3,3 +3,6 @@ export { startDevServer } from "./dev/start_dev_server.js";
3
3
  // build
4
4
  export { build } from "./build/build.js";
5
5
  export { startBuildServer } from "./build/start_build_server.js";
6
+
7
+ // others
8
+ export { INJECTIONS } from "./plugins/injections/jsenv_plugin_injections.js";
@@ -1,19 +1,22 @@
1
1
  /*
2
2
  * Source code can contain the following
3
- * - __dev__
4
- * - __build__
5
- * A global will be injected with true/false when needed
3
+ * - __DEV__
4
+ * - __BUILD__
5
+ * That will be replaced with true/false
6
6
  */
7
7
 
8
- import { replacePlaceholders, PLACEHOLDER } from "@jsenv/plugin-injections";
8
+ import {
9
+ replacePlaceholders,
10
+ INJECTIONS,
11
+ } from "../injections/jsenv_plugin_injections.js";
9
12
 
10
13
  export const jsenvPluginGlobalScenarios = () => {
11
14
  const transformIfNeeded = (urlInfo) => {
12
15
  return replacePlaceholders(
13
16
  urlInfo.content,
14
17
  {
15
- __DEV__: PLACEHOLDER.optional(urlInfo.context.dev),
16
- __BUILD__: PLACEHOLDER.optional(urlInfo.context.build),
18
+ __DEV__: INJECTIONS.optional(urlInfo.context.dev),
19
+ __BUILD__: INJECTIONS.optional(urlInfo.context.build),
17
20
  },
18
21
  urlInfo,
19
22
  );
@@ -0,0 +1,59 @@
1
+ import { createMagicSource } from "@jsenv/sourcemap";
2
+ import {
3
+ parseHtmlString,
4
+ injectHtmlNodeAsEarlyAsPossible,
5
+ createHtmlNode,
6
+ stringifyHtmlAst,
7
+ } from "@jsenv/ast";
8
+
9
+ export const injectGlobals = (content, globals, urlInfo) => {
10
+ if (urlInfo.type === "html") {
11
+ return globalInjectorOnHtml(content, globals, urlInfo);
12
+ }
13
+ if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
14
+ return globalsInjectorOnJs(content, globals, urlInfo);
15
+ }
16
+ throw new Error(`cannot inject globals into "${urlInfo.type}"`);
17
+ };
18
+
19
+ const globalInjectorOnHtml = (content, globals) => {
20
+ // ideally we would inject an importmap but browser support is too low
21
+ // (even worse for worker/service worker)
22
+ // so for now we inject code into entry points
23
+ const htmlAst = parseHtmlString(content, {
24
+ storeOriginalPositions: false,
25
+ });
26
+ const clientCode = generateClientCodeForGlobals(globals, {
27
+ isWebWorker: false,
28
+ });
29
+ injectHtmlNodeAsEarlyAsPossible(
30
+ htmlAst,
31
+ createHtmlNode({
32
+ tagName: "script",
33
+ textContent: clientCode,
34
+ }),
35
+ "jsenv:inject_globals",
36
+ );
37
+ return stringifyHtmlAst(htmlAst);
38
+ };
39
+
40
+ const globalsInjectorOnJs = (content, globals, urlInfo) => {
41
+ const clientCode = generateClientCodeForGlobals(globals, {
42
+ isWebWorker:
43
+ urlInfo.subtype === "worker" ||
44
+ urlInfo.subtype === "service_worker" ||
45
+ urlInfo.subtype === "shared_worker",
46
+ });
47
+ const magicSource = createMagicSource(content);
48
+ magicSource.prepend(clientCode);
49
+ return magicSource.toContentAndSourcemap();
50
+ };
51
+
52
+ const generateClientCodeForGlobals = (globals, { isWebWorker = false }) => {
53
+ const globalName = isWebWorker ? "self" : "window";
54
+ return `Object.assign(${globalName}, ${JSON.stringify(
55
+ globals,
56
+ null,
57
+ " ",
58
+ )});`;
59
+ };
@@ -0,0 +1,101 @@
1
+ import { URL_META } from "@jsenv/url-meta";
2
+ import { asUrlWithoutSearch } from "@jsenv/urls";
3
+ import { createMagicSource } from "@jsenv/sourcemap";
4
+
5
+ export const jsenvPluginInjections = (rawAssociations) => {
6
+ let resolvedAssociations;
7
+
8
+ return {
9
+ name: "jsenv:injections",
10
+ appliesDuring: "*",
11
+ init: (context) => {
12
+ resolvedAssociations = URL_META.resolveAssociations(
13
+ { injectionsGetter: rawAssociations },
14
+ context.rootDirectoryUrl,
15
+ );
16
+ },
17
+ transformUrlContent: async (urlInfo) => {
18
+ const { injectionsGetter } = URL_META.applyAssociations({
19
+ url: asUrlWithoutSearch(urlInfo.url),
20
+ associations: resolvedAssociations,
21
+ });
22
+ if (!injectionsGetter) {
23
+ return null;
24
+ }
25
+ if (typeof injectionsGetter !== "function") {
26
+ throw new TypeError("injectionsGetter must be a function");
27
+ }
28
+ const injections = await injectionsGetter(urlInfo);
29
+ if (!injections) {
30
+ return null;
31
+ }
32
+ const keys = Object.keys(injections);
33
+ if (keys.length === 0) {
34
+ return null;
35
+ }
36
+ return replacePlaceholders(urlInfo.content, injections, urlInfo);
37
+ },
38
+ };
39
+ };
40
+
41
+ const injectionSymbol = Symbol.for("jsenv_injection");
42
+ export const INJECTIONS = {
43
+ optional: (value) => {
44
+ return { [injectionSymbol]: "optional", value };
45
+ },
46
+ };
47
+
48
+ // we export this because it is imported by jsenv_plugin_placeholder.js and unit test
49
+ export const replacePlaceholders = (content, replacements, urlInfo) => {
50
+ const magicSource = createMagicSource(content);
51
+ for (const key of Object.keys(replacements)) {
52
+ let index = content.indexOf(key);
53
+ const replacement = replacements[key];
54
+ let isOptional;
55
+ let value;
56
+ if (replacement && replacement[injectionSymbol]) {
57
+ const valueBehindSymbol = replacement[injectionSymbol];
58
+ isOptional = valueBehindSymbol === "optional";
59
+ value = replacement.value;
60
+ } else {
61
+ value = replacement;
62
+ }
63
+ if (index === -1) {
64
+ if (!isOptional) {
65
+ urlInfo.context.logger.warn(
66
+ `placeholder "${key}" not found in ${urlInfo.url}.
67
+ --- suggestion a ---
68
+ Add "${key}" in that file.
69
+ --- suggestion b ---
70
+ Fix eventual typo in "${key}"?
71
+ --- suggestion c ---
72
+ Mark injection as optional using INJECTIONS.optional()
73
+
74
+ import { INJECTIONS } from "@jsenv/core";
75
+
76
+ return {
77
+ "${key}": INJECTIONS.optional(${JSON.stringify(value)}),
78
+ }`,
79
+ );
80
+ }
81
+ continue;
82
+ }
83
+
84
+ while (index !== -1) {
85
+ const start = index;
86
+ const end = index + key.length;
87
+ magicSource.replace({
88
+ start,
89
+ end,
90
+ replacement:
91
+ urlInfo.type === "js_classic" ||
92
+ urlInfo.type === "js_module" ||
93
+ urlInfo.type === "html"
94
+ ? JSON.stringify(value, null, " ")
95
+ : value,
96
+ });
97
+ index = content.indexOf(key, end);
98
+ }
99
+ }
100
+ return magicSource.toContentAndSourcemap();
101
+ };
@@ -1,5 +1,4 @@
1
1
  import { jsenvPluginSupervisor } from "@jsenv/plugin-supervisor";
2
- import { jsenvPluginInjections } from "@jsenv/plugin-injections";
3
2
  import { jsenvPluginTranspilation } from "@jsenv/plugin-transpilation";
4
3
 
5
4
  import { jsenvPluginReferenceAnalysis } from "./reference_analysis/jsenv_plugin_reference_analysis.js";
@@ -9,6 +8,7 @@ import { jsenvPluginWebResolution } from "./resolution_web/jsenv_plugin_web_reso
9
8
  import { jsenvPluginVersionSearchParam } from "./version_search_param/jsenv_plugin_version_search_param.js";
10
9
  import { jsenvPluginProtocolFile } from "./protocol_file/jsenv_plugin_protocol_file.js";
11
10
  import { jsenvPluginProtocolHttp } from "./protocol_http/jsenv_plugin_protocol_http.js";
11
+ import { jsenvPluginInjections } from "./injections/jsenv_plugin_injections.js";
12
12
  import { jsenvPluginInlining } from "./inlining/jsenv_plugin_inlining.js";
13
13
  import { jsenvPluginCommonJsGlobals } from "./commonjs_globals/jsenv_plugin_commonjs_globals.js";
14
14
  import { jsenvPluginImportMetaScenarios } from "./import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js";