Skip to content

Commit f759e47

Browse files
authored
Merge pull request #533 from jsenv/confirm_and_list_scroll_up_infinite
Confirm and list scroll up infinite
2 parents 6396dad + 011d070 commit f759e47

66 files changed

Lines changed: 45015 additions & 38998 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dist/build/build.js

Lines changed: 111 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,11 +2364,21 @@ ${urlInfo.url}`,
23642364

23652365
const injectionSymbol = Symbol.for("jsenv_injection");
23662366
const INJECTIONS = {
2367+
/**
2368+
* Inject `Object.assign(window, { [key]: value })` at the top of the file
2369+
* (into a script for html, into the module itself for js) instead of
2370+
* replacing a placeholder: the value is read at runtime as a global.
2371+
*/
23672372
global: (value) => {
23682373
return { [injectionSymbol]: "global", value };
23692374
},
2375+
/**
2376+
* Replace the placeholder when the file contains it, stay silent when it does not
2377+
* (without this a missing placeholder is reported as a warning).
2378+
*/
23702379
optional: (value) => {
2371-
if (value && value[injectionSymbol] === "optional") {
2380+
if (value && value[injectionSymbol]) {
2381+
// a global injection is not a placeholder, it can't be missing from the file
23722382
return value;
23732383
}
23742384
return { [injectionSymbol]: "optional", value };
@@ -2473,19 +2483,27 @@ return {
24732483
magicSource.replace({
24742484
start,
24752485
end,
2476-
replacement:
2477-
urlInfo.type === "js_classic" ||
2478-
urlInfo.type === "js_module" ||
2479-
urlInfo.type === "html"
2480-
? JSON.stringify(value, null, " ")
2481-
: value,
2486+
replacement: asReplacement(value, urlInfo),
24822487
});
24832488
index = content.indexOf(key, end);
24842489
}
24852490
}
24862491
return magicSource.toContentAndSourcemap();
24872492
};
24882493

2494+
// In JS the placeholder stands for a value, so it must be substituted by a literal.
2495+
// Everywhere else (html attributes and text, css, ...) it stands for a piece of text
2496+
// and is substituted as-is, so it can be concatenated: href="__BACKEND_URL__/users/me"
2497+
const asReplacement = (value, urlInfo) => {
2498+
if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
2499+
return JSON.stringify(value, null, " ");
2500+
}
2501+
if (typeof value === "string") {
2502+
return value;
2503+
}
2504+
return JSON.stringify(value, null, " ");
2505+
};
2506+
24892507
const injectGlobals = (content, globals, urlInfo) => {
24902508
if (urlInfo.type === "html") {
24912509
return globalInjectorOnHtml(content, globals, urlInfo);
@@ -6560,7 +6578,9 @@ const jsenvPluginVersionSearchParam = () => {
65606578
* of them, the page switcher (cmd+K) opens one in the current tab. Whoever asks
65616579
* gets the same answer.
65626580
*
6563-
* Each page comes with what kind of page it is, read from where it sits and
6581+
* Each page comes with where its file is (so it can be opened in an editor as
6582+
* well as in the browser) and with what kind of page it is, read from where it
6583+
* sits and
65646584
* what it is called — the two conventions this repo already follows:
65656585
* - "experiment": something tried out, under a lab/ directory or named
65666586
* *_experiment.html;
@@ -6659,6 +6679,10 @@ const createHtmlPageLister = ({ rootDirectoryUrl }) => {
66596679
);
66606680
return {
66616681
url: `/${relativeUrl}`,
6682+
// Where the file actually is, so whoever wants to open it in an editor
6683+
// rather than in the browser has what GET /.internal/open_file/* asks
6684+
// for (a file url) without having to know the root directory.
6685+
fileUrl,
66626686
kind: readKind(meta),
66636687
// Relative to the root and without its trailing slash, which is how a
66646688
// tree names its own nodes.
@@ -7212,6 +7236,10 @@ const jsenvPluginFsRedirection = ({
72127236
}
72137237
const { requestedUrl, rootDirectoryUrl, mainFilePath } =
72147238
reference.ownerUrlInfo.context;
7239+
if (!requestedUrl) {
7240+
// the SPA fallback answers a request; during build there is none
7241+
return null;
7242+
}
72157243
const closestHtmlRootFile = getClosestHtmlRootFile(
72167244
requestedUrl,
72177245
rootDirectoryUrl,
@@ -7624,18 +7652,45 @@ const jsenvPluginInjections = (rawAssociations) => {
76247652
{ injectionsGetter: rawAssociations },
76257653
context.rootDirectoryUrl,
76267654
);
7627-
getInjections = (urlInfo) => {
7655+
const findInjectionsGetter = (urlInfo) => {
76287656
const { injectionsGetter } = URL_META.applyAssociations({
76297657
url: asUrlWithoutSearch(urlInfo.url),
76307658
associations: resolvedAssociations,
76317659
});
7632-
if (!injectionsGetter) {
7660+
if (injectionsGetter) {
7661+
return { injectionsGetter, isInherited: false };
7662+
}
7663+
if (urlInfo.isInline) {
7664+
// content inlined into a file (a <script> inside html) is authored in that
7665+
// file, so injections configured for the file must reach it too
7666+
const found = findInjectionsGetter(
7667+
urlInfo.firstReference.ownerUrlInfo,
7668+
);
7669+
if (found) {
7670+
return {
7671+
injectionsGetter: found.injectionsGetter,
7672+
isInherited: true,
7673+
};
7674+
}
7675+
}
7676+
return null;
7677+
};
7678+
getInjections = async (urlInfo) => {
7679+
const found = findInjectionsGetter(urlInfo);
7680+
if (!found) {
76337681
return null;
76347682
}
7683+
const { injectionsGetter, isInherited } = found;
76357684
if (typeof injectionsGetter !== "function") {
76367685
throw new TypeError("injectionsGetter must be a function");
76377686
}
7638-
return injectionsGetter(urlInfo);
7687+
const injections = await injectionsGetter(urlInfo);
7688+
if (!injections || !isInherited) {
7689+
return injections;
7690+
}
7691+
// the file holds several inline contents; a placeholder configured for the file
7692+
// is expected in one of them, not in each
7693+
return asOptionalInjections(injections);
76397694
};
76407695
}
76417696
},
@@ -7646,13 +7701,12 @@ const jsenvPluginInjections = (rawAssociations) => {
76467701
contentInjections: defaultInjections,
76477702
};
76487703
}
7649-
const injectionsResult = getInjections(urlInfo);
7650-
if (!injectionsResult) {
7704+
const injections = await getInjections(urlInfo);
7705+
if (!injections) {
76517706
return {
76527707
contentInjections: defaultInjections,
76537708
};
76547709
}
7655-
const injections = await injectionsResult;
76567710
return {
76577711
contentInjections: {
76587712
...defaultInjections,
@@ -7663,6 +7717,14 @@ const jsenvPluginInjections = (rawAssociations) => {
76637717
};
76647718
};
76657719

7720+
const asOptionalInjections = (injections) => {
7721+
const optionalInjections = {};
7722+
for (const key of Object.keys(injections)) {
7723+
optionalInjections[key] = INJECTIONS.optional(injections[key]);
7724+
}
7725+
return optionalInjections;
7726+
};
7727+
76667728
/*
76677729
* Some code uses globals specific to Node.js in code meant to run in browsers...
76687730
* This plugin will replace some node globals to things compatible with web:
@@ -11064,6 +11126,11 @@ const createBuildSpecifierManager = ({
1106411126
registerHtmlRefine((htmlAst, { registerHtmlMutation }) => {
1106511127
visitHtmlNodes(htmlAst, {
1106611128
link: (node) => {
11129+
if (getHtmlNodeAttribute(node, "jsenv-ignore") !== undefined) {
11130+
// reference analysis skipped this node, so it has no urlInfo in the graph
11131+
// and there is nothing to resync
11132+
return;
11133+
}
1106711134
const href = getHtmlNodeAttribute(node, "href");
1106811135
if (href === undefined || href.startsWith("data:")) {
1106911136
return;
@@ -11902,6 +11969,25 @@ const jsenvPluginMappings = (mappings) => {
1190211969
* How URLs are versioned for this entry point (defaults to "search_param")
1190311970
* @param {('none'|'inline'|'file'|'programmatic')} [entryPoint.sourcemaps]
1190411971
* Sourcemap generation strategy for this entry point (defaults to "none")
11972+
* @param {object} [entryPoint.injections]
11973+
* Values to inject into files, as { urlPattern: getInjections }.
11974+
* Keys are url patterns relative to sourceDirectoryUrl ("./index.html", "**\/*.js"),
11975+
* values are functions receiving urlInfo and returning (or resolving to)
11976+
* an object of placeholders to replace, named `__LIKE_THIS__` by convention:
11977+
*
11978+
* injections: {
11979+
* "./index.html": () => ({ __BACKEND_URL__: "https://api.example.com" }),
11980+
* }
11981+
*
11982+
* In JS files the value is injected as a JS literal (a string value brings its own quotes),
11983+
* everywhere else it is injected as-is, so it can be concatenated:
11984+
* `href="__BACKEND_URL__/users/me"`.
11985+
* An html url pattern also covers what is inlined in that html: an inline
11986+
* `<script>window.backendUrl = __BACKEND_URL__;</script>` gets the JS literal,
11987+
* which is how a value is shared with every js file of the page.
11988+
* Use INJECTIONS.optional(value) for a placeholder that may be absent from the file
11989+
* and INJECTIONS.global(value) to inject `Object.assign(window, { ... })` instead of
11990+
* replacing a placeholder.
1190511991
*
1190611992
* @return {Promise<Object>} buildReturnValue
1190711993
* @return {Promise<Object>} [buildReturnValue.buildInlineContents]
@@ -11939,6 +12025,17 @@ const build = async ({
1193912025
{
1194012026
const unexpectedParamNames = Object.keys(rest);
1194112027
if (unexpectedParamNames.length > 0) {
12028+
const entryPointParamNames = unexpectedParamNames.filter((name) =>
12029+
Object.hasOwn(entryPointDefaultParams, name),
12030+
);
12031+
if (entryPointParamNames.length > 0) {
12032+
throw new TypeError(
12033+
`${entryPointParamNames.join(",")}: param(s) configured per entry point, move them into entryPoints, as in:
12034+
entryPoints: {
12035+
"./index.html": { ${entryPointParamNames.map((name) => `${name}: ...`).join(", ")} },
12036+
}`,
12037+
);
12038+
}
1194212039
throw new TypeError(
1194312040
`${unexpectedParamNames.join(",")}: there is no such param`,
1194412041
);

0 commit comments

Comments
 (0)