@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 +2 -2
- package/dist/jsenv_core.js +104 -214
- package/package.json +3 -4
- package/src/main.js +3 -0
- package/src/plugins/global_scenarios/jsenv_plugin_global_scenarios.js +9 -6
- package/src/plugins/injections/internal/inject_globals.js +59 -0
- package/src/plugins/injections/jsenv_plugin_injections.js +101 -0
- package/src/plugins/plugins.js +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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.
|
|
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
|
|
5
|
+
[Documentation](<https://github.com/jsenv/core/wiki/A)-Introduction>) · [Changelog](./CHANGELOG.md)
|
|
6
6
|
|
|
7
7
|
See also
|
|
8
8
|
|
package/dist/jsenv_core.js
CHANGED
|
@@ -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
|
-
* -
|
|
18889
|
-
* -
|
|
18890
|
-
*
|
|
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__:
|
|
18900
|
-
__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.
|
|
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-
|
|
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.
|
|
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
|
-
* -
|
|
4
|
-
* -
|
|
5
|
-
*
|
|
3
|
+
* - __DEV__
|
|
4
|
+
* - __BUILD__
|
|
5
|
+
* That will be replaced with true/false
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
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__:
|
|
16
|
-
__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
|
+
};
|
package/src/plugins/plugins.js
CHANGED
|
@@ -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";
|