@jsenv/core 41.4.16 → 41.4.18
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/dist/build/build.js +573 -469
- package/dist/client/directory_listing/js/directory_listing.js +23 -2
- package/dist/start_dev_server/start_dev_server.js +780 -612
- package/package.json +7 -7
- package/src/dev/dev_server_plugins/dev_server_plugin_serve_source_files.js +116 -68
- package/src/dev/start_dev_server.js +24 -4
- package/src/helpers/watch_source_files.js +25 -20
- package/src/kitchen/url_graph/references.js +30 -13
- package/src/kitchen/url_graph/url_graph.js +0 -1
- package/src/plugins/import_meta_css/jsenv_plugin_import_meta_css.js +8 -40
- package/src/plugins/import_meta_hot/import_meta_hot_analysis.js +70 -0
- package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +35 -20
- package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +22 -56
- package/src/plugins/modulepreload/jsenv_plugin_modulepreload.js +128 -0
- package/src/plugins/plugins.js +3 -0
- package/src/plugins/protocol_file/client/directory_listing.jsx +27 -1
- package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +20 -0
- package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +32 -15
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +11 -2
- package/src/plugins/resolution_node_esm/node_esm_resolver.js +0 -5
- package/src/plugins/import_meta_hot/babel_plugin_metadata_import_meta_hot.js +0 -97
package/dist/build/build.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { parseHtml, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, stringifyHtmlAst, applyBabelPlugins, generateUrlForInlineContent, injectJsenvScript, parseJsWithAcorn, visitHtmlNodes, analyzeScriptNode, getHtmlNodeText, getHtmlNodeAttribute, getHtmlNodePosition, getUrlForContentInsideHtml, setHtmlNodeAttributes, setHtmlNodeText, parseCssUrls, getHtmlNodeAttributePosition, parseSrcSet, removeHtmlNodeText, parseJsUrls, getUrlForContentInsideJs, analyzeLinkNode, findHtmlNode, removeHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
|
|
1
|
+
import { parseHtml, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, stringifyHtmlAst, applyBabelPlugins, generateUrlForInlineContent, injectJsenvScript, parseJsWithAcorn, visitHtmlNodes, analyzeScriptNode, getHtmlNodeText, getHtmlNodeAttribute, getHtmlNodePosition, getUrlForContentInsideHtml, setHtmlNodeAttributes, setHtmlNodeText, parseCssUrls, getHtmlNodeAttributePosition, parseSrcSet, removeHtmlNodeText, parseJsUrls, getUrlForContentInsideJs, visitJsAst, getImportMetaPropertyName, visitJsAstUntil, analyzeLinkNode, findHtmlNode, removeHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
|
|
2
2
|
import { bundleJsModules, jsenvPluginBundling } from "@jsenv/plugin-bundling";
|
|
3
3
|
import { jsenvPluginMinification } from "@jsenv/plugin-minification";
|
|
4
4
|
import { jsenvPluginTranspilation, jsenvPluginJsModuleFallback } from "@jsenv/plugin-transpilation";
|
|
5
5
|
import { memoryUsage } from "node:process";
|
|
6
|
-
import { readFileSync, existsSync, readdirSync, lstatSync,
|
|
7
|
-
import { lookupPackageDirectory, registerDirectoryLifecycle, urlToRelativeUrl, createDetailedMessage, stringifyUrlSite, generateContentFrame, validateResponseIntegrity,
|
|
6
|
+
import { readFileSync, existsSync, realpathSync, readdirSync, lstatSync, statSync } from "node:fs";
|
|
7
|
+
import { lookupPackageDirectory, urlIsOrIsInsideOf, registerDirectoryLifecycle, urlToRelativeUrl, createDetailedMessage, stringifyUrlSite, generateContentFrame, validateResponseIntegrity, ensureWindowsDriveLetter, setUrlFilename, moveUrl, getCallerPosition, urlToBasename, urlToExtension, asSpecifierWithoutSearch, asUrlWithoutSearch, injectQueryParamsIntoSpecifier, bufferToEtag, isFileSystemPath, urlToPathname, setUrlBasename, urlToFileSystemPath, writeFileSync, createLogger, URL_META, applyNodeEsmResolution, normalizeUrl, ANSI, RUNTIME_COMPAT, CONTENT_TYPE, readPackageAtOrNull, urlToFilename, DATA_URL, errorToHTML, normalizeImportMap, composeTwoImportMaps, resolveImport, JS_QUOTES, readCustomConditionsFromProcessArgs, collectFiles, readEntryStatSync, applyFileSystemMagicResolution, getExtensionsToTry, ensurePathnameTrailingSlash, compareFileUrls, setUrlExtension, isSpecifierForNodeBuiltin, injectQueryParams, renderDetails, humanizeDuration, humanizeFileSize, renderTable, renderBigSection, distributePercentages, humanizeMemory, comparePathnames, UNICODE, escapeRegexpSpecialChars, injectQueryParamIntoSpecifierWithoutEncoding, renderUrlOrRelativeUrlFilename, assertAndNormalizeDirectoryUrl, Abort, raceProcessTeardownEvents, startMonitoringCpuUsage, startMonitoringMemoryUsage, inferRuntimeCompatFromClosestPackage, browserDefaultRuntimeCompat, nodeDefaultRuntimeCompat, clearDirectorySync, createTaskLog, createLookupPackageDirectory, ensureEmptyDirectory, updateJsonFileSync, createDynamicLog } from "./jsenv_core_packages.js";
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
9
|
import { generateSourcemapFileUrl, createMagicSource, composeTwoSourcemaps, generateSourcemapDataUrl, SOURCEMAP } from "@jsenv/sourcemap";
|
|
10
10
|
import { createPluginsController } from "@jsenv/server/src/plugins_controller.js";
|
|
@@ -20,18 +20,21 @@ import "node:util";
|
|
|
20
20
|
const getDirectoryWatchPatterns = (
|
|
21
21
|
directoryUrl,
|
|
22
22
|
watchedDirectoryUrl,
|
|
23
|
-
{ sourceFilesConfig },
|
|
23
|
+
{ sourceFilesConfig, defaultPatterns = true },
|
|
24
24
|
) => {
|
|
25
25
|
const directoryUrlRelativeToWatchedDirectory = urlToRelativeUrl(
|
|
26
26
|
directoryUrl,
|
|
27
27
|
watchedDirectoryUrl,
|
|
28
28
|
);
|
|
29
|
-
const watchPatterns = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
const watchPatterns = {};
|
|
30
|
+
if (defaultPatterns) {
|
|
31
|
+
Object.assign(watchPatterns, {
|
|
32
|
+
[`${directoryUrlRelativeToWatchedDirectory}**/*`]: true, // by default watch everything inside the source directory
|
|
33
|
+
[`${directoryUrlRelativeToWatchedDirectory}**/.*`]: false, // file starting with a dot -> do not watch
|
|
34
|
+
[`${directoryUrlRelativeToWatchedDirectory}**/.*/`]: false, // directory starting with a dot -> do not watch
|
|
35
|
+
[`${directoryUrlRelativeToWatchedDirectory}**/node_modules/`]: false, // node_modules directory -> do not watch
|
|
36
|
+
});
|
|
37
|
+
}
|
|
35
38
|
for (const key of Object.keys(sourceFilesConfig)) {
|
|
36
39
|
watchPatterns[`${directoryUrlRelativeToWatchedDirectory}${key}`] =
|
|
37
40
|
sourceFilesConfig[key];
|
|
@@ -52,11 +55,12 @@ const watchSourceFiles = (
|
|
|
52
55
|
// And jsenv should not consider these as source files and watch them (to not hurt performances)
|
|
53
56
|
const watchPatterns = {};
|
|
54
57
|
let watchedDirectoryUrl = "";
|
|
55
|
-
const addDirectoryToWatch = (directoryUrl) => {
|
|
58
|
+
const addDirectoryToWatch = (directoryUrl, { defaultPatterns } = {}) => {
|
|
56
59
|
Object.assign(
|
|
57
60
|
watchPatterns,
|
|
58
61
|
getDirectoryWatchPatterns(directoryUrl, watchedDirectoryUrl, {
|
|
59
62
|
sourceFilesConfig,
|
|
63
|
+
defaultPatterns,
|
|
60
64
|
}),
|
|
61
65
|
);
|
|
62
66
|
};
|
|
@@ -88,7 +92,6 @@ const watchSourceFiles = (
|
|
|
88
92
|
},
|
|
89
93
|
},
|
|
90
94
|
);
|
|
91
|
-
stopWatchingSourceFiles.watchPatterns = watchPatterns;
|
|
92
95
|
return stopWatchingSourceFiles;
|
|
93
96
|
};
|
|
94
97
|
|
|
@@ -108,16 +111,18 @@ const watchSourceFiles = (
|
|
|
108
111
|
}
|
|
109
112
|
watchedDirectoryUrl = packageDirectoryUrl;
|
|
110
113
|
for (const workspace of workspaces) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
const workspaceDirectoryUrl = workspace.endsWith("*")
|
|
115
|
+
? new URL(workspace.slice(0, -1), packageDirectoryUrl)
|
|
116
|
+
: new URL(workspace, packageDirectoryUrl);
|
|
117
|
+
addDirectoryToWatch(workspaceDirectoryUrl, {
|
|
118
|
+
// The source directory patterns already cover a workspace inside it.
|
|
119
|
+
// Every pattern is tested against every file found while watching, so
|
|
120
|
+
// the same patterns rooted at each workspace only multiply that work.
|
|
121
|
+
defaultPatterns: !urlIsOrIsInsideOf(
|
|
122
|
+
workspaceDirectoryUrl,
|
|
123
|
+
sourceDirectoryUrl,
|
|
124
|
+
),
|
|
125
|
+
});
|
|
121
126
|
}
|
|
122
127
|
// we are updating the root directory
|
|
123
128
|
// we must make the patterns relative to source directory relative to the new root directory
|
|
@@ -1578,20 +1583,37 @@ const checkForDependencyRemovalEffects = (reference) => {
|
|
|
1578
1583
|
};
|
|
1579
1584
|
|
|
1580
1585
|
const traceFromUrlSite = (urlSite) => {
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
: ""
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1586
|
+
const { url, line, column, content } = urlSite;
|
|
1587
|
+
const trace = { url, line, column };
|
|
1588
|
+
// A file references many urls and an error on one of them is rare: the code
|
|
1589
|
+
// frame (and the message embedding it) is built the first time it is read,
|
|
1590
|
+
// not for every reference found.
|
|
1591
|
+
defineLazyProperty(trace, "codeFrame", () =>
|
|
1592
|
+
content ? generateContentFrame({ content, line, column }) : "",
|
|
1593
|
+
);
|
|
1594
|
+
defineLazyProperty(trace, "message", () => stringifyUrlSite(urlSite));
|
|
1595
|
+
return trace;
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
const defineLazyProperty = (object, property, compute) => {
|
|
1599
|
+
const setValue = (value) => {
|
|
1600
|
+
Object.defineProperty(object, property, {
|
|
1601
|
+
enumerable: true,
|
|
1602
|
+
configurable: true,
|
|
1603
|
+
writable: true,
|
|
1604
|
+
value,
|
|
1605
|
+
});
|
|
1594
1606
|
};
|
|
1607
|
+
Object.defineProperty(object, property, {
|
|
1608
|
+
enumerable: true,
|
|
1609
|
+
configurable: true,
|
|
1610
|
+
get: () => {
|
|
1611
|
+
const value = compute();
|
|
1612
|
+
setValue(value);
|
|
1613
|
+
return value;
|
|
1614
|
+
},
|
|
1615
|
+
set: setValue,
|
|
1616
|
+
});
|
|
1595
1617
|
};
|
|
1596
1618
|
|
|
1597
1619
|
const adjustUrlSite = (urlInfo, { url, line, column }) => {
|
|
@@ -2090,7 +2112,6 @@ const createUrlInfo = (url, context) => {
|
|
|
2090
2112
|
dereferencedTimestamp: 0,
|
|
2091
2113
|
originalContentEtag: null,
|
|
2092
2114
|
contentEtag: null,
|
|
2093
|
-
isWatched: false,
|
|
2094
2115
|
isValid: () => false,
|
|
2095
2116
|
data: {}, // plugins can put whatever they want here
|
|
2096
2117
|
referenceToOthersSet: new Set(),
|
|
@@ -6413,11 +6434,6 @@ const addRelationshipWithPackageJson = ({
|
|
|
6413
6434
|
String(packageJsonContentAsBuffer),
|
|
6414
6435
|
);
|
|
6415
6436
|
}
|
|
6416
|
-
// Checked on disk at every validation rather than trusted to the watcher:
|
|
6417
|
-
// what this file decides (the package version, hence the ?v= importers
|
|
6418
|
-
// embed) ends up in the browser's immutable cache, so a request racing the
|
|
6419
|
-
// watcher must never be answered from a stale package.json.
|
|
6420
|
-
packageJsonReference.urlInfo.revalidateOnFileSystem = true;
|
|
6421
6437
|
};
|
|
6422
6438
|
|
|
6423
6439
|
const createResolverWithFallbackOnError = (mainResolver, fallbackResolver) => {
|
|
@@ -6759,6 +6775,240 @@ const createHtmlPageLister = ({ rootDirectoryUrl }) => {
|
|
|
6759
6775
|
};
|
|
6760
6776
|
};
|
|
6761
6777
|
|
|
6778
|
+
const jsenvPluginFsRedirection = ({
|
|
6779
|
+
spa,
|
|
6780
|
+
directoryContentMagicName,
|
|
6781
|
+
magicExtensions = ["inherit", ".js"],
|
|
6782
|
+
magicDirectoryIndex = true,
|
|
6783
|
+
preserveSymlinks = false,
|
|
6784
|
+
}) => {
|
|
6785
|
+
return {
|
|
6786
|
+
name: "jsenv:fs_redirection",
|
|
6787
|
+
appliesDuring: "*",
|
|
6788
|
+
redirectReference: (reference) => {
|
|
6789
|
+
if (reference.url === "file:///") {
|
|
6790
|
+
return `ignore:file:///`;
|
|
6791
|
+
}
|
|
6792
|
+
if (reference.url === "file://") {
|
|
6793
|
+
return `ignore:file://`;
|
|
6794
|
+
}
|
|
6795
|
+
// ignore all new URL second arg
|
|
6796
|
+
if (reference.subtype === "new_url_second_arg") {
|
|
6797
|
+
if (reference.original) {
|
|
6798
|
+
return `ignore:${reference.original.specifier}`;
|
|
6799
|
+
}
|
|
6800
|
+
return `ignore:${reference.specifier}`;
|
|
6801
|
+
}
|
|
6802
|
+
// http, https, data, about, ...
|
|
6803
|
+
if (!reference.url.startsWith("file:")) {
|
|
6804
|
+
return null;
|
|
6805
|
+
}
|
|
6806
|
+
if (reference.original && !reference.original.url.startsWith("file:")) {
|
|
6807
|
+
return null;
|
|
6808
|
+
}
|
|
6809
|
+
if (reference.isInline) {
|
|
6810
|
+
return null;
|
|
6811
|
+
}
|
|
6812
|
+
|
|
6813
|
+
if (
|
|
6814
|
+
reference.specifierPathname.endsWith(`/${directoryContentMagicName}`)
|
|
6815
|
+
) {
|
|
6816
|
+
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
6817
|
+
const directoryUrl = new URL(
|
|
6818
|
+
reference.specifierPathname
|
|
6819
|
+
.replace(`/${directoryContentMagicName}`, "/")
|
|
6820
|
+
.slice(1),
|
|
6821
|
+
rootDirectoryUrl,
|
|
6822
|
+
).href;
|
|
6823
|
+
return directoryUrl;
|
|
6824
|
+
}
|
|
6825
|
+
// ignore "./" on new URL("./")
|
|
6826
|
+
// if (
|
|
6827
|
+
// reference.subtype === "new_url_first_arg" &&
|
|
6828
|
+
// reference.specifier === "./"
|
|
6829
|
+
// ) {
|
|
6830
|
+
// return `ignore:${reference.url}`;
|
|
6831
|
+
// }
|
|
6832
|
+
const urlObject = new URL(reference.url);
|
|
6833
|
+
let fsStat = readEntryStatSync(urlObject, { nullIfNotFound: true });
|
|
6834
|
+
reference.fsStat = fsStat;
|
|
6835
|
+
const { search, hash } = urlObject;
|
|
6836
|
+
urlObject.search = "";
|
|
6837
|
+
urlObject.hash = "";
|
|
6838
|
+
// must be read before applyFsStatEffectsOnUrlObject which forces the
|
|
6839
|
+
// trailing slash on directories
|
|
6840
|
+
const specifierUsesTrailingSlash = urlObject.pathname.endsWith("/");
|
|
6841
|
+
applyFsStatEffectsOnUrlObject(urlObject, fsStat);
|
|
6842
|
+
const shouldApplyFilesystemMagicResolution =
|
|
6843
|
+
reference.type === "js_import";
|
|
6844
|
+
if (shouldApplyFilesystemMagicResolution) {
|
|
6845
|
+
const filesystemResolution = applyFileSystemMagicResolution(
|
|
6846
|
+
urlObject.href,
|
|
6847
|
+
{
|
|
6848
|
+
fileStat: fsStat,
|
|
6849
|
+
magicDirectoryIndex,
|
|
6850
|
+
magicExtensions: getExtensionsToTry(
|
|
6851
|
+
magicExtensions,
|
|
6852
|
+
reference.ownerUrlInfo.url,
|
|
6853
|
+
),
|
|
6854
|
+
},
|
|
6855
|
+
);
|
|
6856
|
+
if (filesystemResolution.stat) {
|
|
6857
|
+
fsStat = filesystemResolution.stat;
|
|
6858
|
+
reference.fsStat = fsStat;
|
|
6859
|
+
urlObject.href = filesystemResolution.url;
|
|
6860
|
+
applyFsStatEffectsOnUrlObject(urlObject, fsStat);
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
if (spa) {
|
|
6864
|
+
// for SPA we want to serve the root HTML file most of the time
|
|
6865
|
+
if (!fsStat) {
|
|
6866
|
+
if (urlToExtension(urlObject)) {
|
|
6867
|
+
// url has an extension, we assume it's a file request -> let 404 happen
|
|
6868
|
+
return null;
|
|
6869
|
+
}
|
|
6870
|
+
if (specifierUsesTrailingSlash) {
|
|
6871
|
+
// the trailing slash asks for a directory and there is none here
|
|
6872
|
+
// -> let 404 happen (same reasoning as the extension above)
|
|
6873
|
+
return null;
|
|
6874
|
+
}
|
|
6875
|
+
const spaFallbackUrl = getSpaFallbackUrl(reference);
|
|
6876
|
+
if (spaFallbackUrl) {
|
|
6877
|
+
return spaFallbackUrl;
|
|
6878
|
+
}
|
|
6879
|
+
return null;
|
|
6880
|
+
}
|
|
6881
|
+
if (fsStat.isDirectory()) {
|
|
6882
|
+
// When requesting a directory, check if we have an HTML entry file for that directory
|
|
6883
|
+
const directoryEntryFileUrl = getDirectoryEntryFileUrl(urlObject);
|
|
6884
|
+
if (directoryEntryFileUrl) {
|
|
6885
|
+
reference.fsStat = readEntryStatSync(directoryEntryFileUrl);
|
|
6886
|
+
return directoryEntryFileUrl;
|
|
6887
|
+
}
|
|
6888
|
+
if (!specifierUsesTrailingSlash) {
|
|
6889
|
+
// the trailing slash is what tells a directory apart from a route:
|
|
6890
|
+
// "/join/" is the directory, "/join" is a route owned by the SPA
|
|
6891
|
+
// even when "join/" exists in the source files.
|
|
6892
|
+
// Without this a source directory would shadow the route having
|
|
6893
|
+
// the same name and the SPA would be unreachable in dev while
|
|
6894
|
+
// being perfectly fine once built
|
|
6895
|
+
const spaFallbackUrl = getSpaFallbackUrl(reference);
|
|
6896
|
+
if (spaFallbackUrl) {
|
|
6897
|
+
reference.fsStat = readEntryStatSync(spaFallbackUrl, {
|
|
6898
|
+
nullIfNotFound: true,
|
|
6899
|
+
});
|
|
6900
|
+
return spaFallbackUrl;
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
if (!fsStat) {
|
|
6906
|
+
return null;
|
|
6907
|
+
}
|
|
6908
|
+
const urlBeforeSymlinkResolution = urlObject.href;
|
|
6909
|
+
if (preserveSymlinks) {
|
|
6910
|
+
return `${urlBeforeSymlinkResolution}${search}${hash}`;
|
|
6911
|
+
}
|
|
6912
|
+
const urlAfterSymlinkResolution = resolveSymlink(
|
|
6913
|
+
urlBeforeSymlinkResolution,
|
|
6914
|
+
);
|
|
6915
|
+
if (urlAfterSymlinkResolution !== urlBeforeSymlinkResolution) {
|
|
6916
|
+
reference.leadsToASymlink = true;
|
|
6917
|
+
// reference.baseUrl = urlBeforeSymlinkResolution;
|
|
6918
|
+
}
|
|
6919
|
+
const resolvedUrl = `${urlAfterSymlinkResolution}${search}${hash}`;
|
|
6920
|
+
return resolvedUrl;
|
|
6921
|
+
},
|
|
6922
|
+
};
|
|
6923
|
+
};
|
|
6924
|
+
|
|
6925
|
+
const applyFsStatEffectsOnUrlObject = (urlObject, fsStat) => {
|
|
6926
|
+
if (!fsStat) {
|
|
6927
|
+
return;
|
|
6928
|
+
}
|
|
6929
|
+
const { pathname } = urlObject;
|
|
6930
|
+
const pathnameUsesTrailingSlash = pathname.endsWith("/");
|
|
6931
|
+
// force trailing slash on directories
|
|
6932
|
+
if (fsStat.isDirectory()) {
|
|
6933
|
+
if (!pathnameUsesTrailingSlash) {
|
|
6934
|
+
urlObject.pathname = `${pathname}/`;
|
|
6935
|
+
}
|
|
6936
|
+
} else if (pathnameUsesTrailingSlash) {
|
|
6937
|
+
// otherwise remove trailing slash if any
|
|
6938
|
+
// a warning here? (because it's strange to reference a file with a trailing slash)
|
|
6939
|
+
urlObject.pathname = pathname.slice(0, -1);
|
|
6940
|
+
}
|
|
6941
|
+
};
|
|
6942
|
+
|
|
6943
|
+
const resolveSymlink = (fileUrl) => {
|
|
6944
|
+
const urlObject = new URL(fileUrl);
|
|
6945
|
+
const realpath = realpathSync(urlObject);
|
|
6946
|
+
const realUrlObject = pathToFileURL(realpath);
|
|
6947
|
+
if (urlObject.pathname.endsWith("/")) {
|
|
6948
|
+
realUrlObject.pathname += `/`;
|
|
6949
|
+
}
|
|
6950
|
+
return realUrlObject.href;
|
|
6951
|
+
};
|
|
6952
|
+
|
|
6953
|
+
const getDirectoryEntryFileUrl = (directoryUrl) => {
|
|
6954
|
+
const indexHtmlFileUrl = new URL(`index.html`, directoryUrl);
|
|
6955
|
+
if (existsSync(indexHtmlFileUrl)) {
|
|
6956
|
+
return indexHtmlFileUrl.href;
|
|
6957
|
+
}
|
|
6958
|
+
const filename = urlToFilename(directoryUrl);
|
|
6959
|
+
const htmlFileUrlCandidate = new URL(`${filename}.html`, directoryUrl);
|
|
6960
|
+
if (existsSync(htmlFileUrlCandidate)) {
|
|
6961
|
+
return htmlFileUrlCandidate.href;
|
|
6962
|
+
}
|
|
6963
|
+
return null;
|
|
6964
|
+
};
|
|
6965
|
+
const getSpaFallbackUrl = (reference) => {
|
|
6966
|
+
const { requestedUrl, rootDirectoryUrl, mainFilePath } =
|
|
6967
|
+
reference.ownerUrlInfo.context;
|
|
6968
|
+
if (!requestedUrl) {
|
|
6969
|
+
// the SPA fallback answers a request; during build there is none
|
|
6970
|
+
return null;
|
|
6971
|
+
}
|
|
6972
|
+
const spaFallbackFileUrls = listSpaFallbackFileUrls(requestedUrl, {
|
|
6973
|
+
rootDirectoryUrl,
|
|
6974
|
+
mainFilePath,
|
|
6975
|
+
});
|
|
6976
|
+
for (const spaFallbackFileUrl of spaFallbackFileUrls) {
|
|
6977
|
+
if (existsSync(new URL(spaFallbackFileUrl))) {
|
|
6978
|
+
return spaFallbackFileUrl;
|
|
6979
|
+
}
|
|
6980
|
+
}
|
|
6981
|
+
// none exists: the main file it is, and the 404 answering for it lists
|
|
6982
|
+
// what was tried (see directory listing)
|
|
6983
|
+
return new URL(mainFilePath, rootDirectoryUrl).href;
|
|
6984
|
+
};
|
|
6985
|
+
// The html files that can answer a route, closest first: the entry file of
|
|
6986
|
+
// the route's own directory ("index.html", then "<dirname>.html"), then of
|
|
6987
|
+
// each directory above it up to the server root, then the main file.
|
|
6988
|
+
const listSpaFallbackFileUrls = (
|
|
6989
|
+
requestedUrl,
|
|
6990
|
+
{ rootDirectoryUrl, mainFilePath },
|
|
6991
|
+
) => {
|
|
6992
|
+
const fileUrls = [];
|
|
6993
|
+
let directoryUrl = new URL("./", requestedUrl).href;
|
|
6994
|
+
while (urlIsOrIsInsideOf(directoryUrl, rootDirectoryUrl)) {
|
|
6995
|
+
fileUrls.push(new URL("index.html", directoryUrl).href);
|
|
6996
|
+
const filename = urlToFilename(directoryUrl);
|
|
6997
|
+
if (filename) {
|
|
6998
|
+
fileUrls.push(new URL(`${filename}.html`, directoryUrl).href);
|
|
6999
|
+
}
|
|
7000
|
+
if (directoryUrl === String(rootDirectoryUrl)) {
|
|
7001
|
+
break;
|
|
7002
|
+
}
|
|
7003
|
+
directoryUrl = new URL("../", directoryUrl).href;
|
|
7004
|
+
}
|
|
7005
|
+
const mainFileUrl = new URL(mainFilePath, rootDirectoryUrl).href;
|
|
7006
|
+
if (!fileUrls.includes(mainFileUrl)) {
|
|
7007
|
+
fileUrls.push(mainFileUrl);
|
|
7008
|
+
}
|
|
7009
|
+
return fileUrls;
|
|
7010
|
+
};
|
|
7011
|
+
|
|
6762
7012
|
/*
|
|
6763
7013
|
* NICE TO HAVE:
|
|
6764
7014
|
*
|
|
@@ -7136,6 +7386,24 @@ const generateDirectoryListingInjection = (
|
|
|
7136
7386
|
filePathExisting: `/${filePathExisting}`,
|
|
7137
7387
|
filePathNotFound,
|
|
7138
7388
|
});
|
|
7389
|
+
// a url without extension nor trailing slash is a route: in spa mode it
|
|
7390
|
+
// was answered with the closest html file, the 404 means none was found
|
|
7391
|
+
const urlNotFoundObject = new URL(urlNotFound);
|
|
7392
|
+
if (
|
|
7393
|
+
spa &&
|
|
7394
|
+
!urlToExtension(urlNotFoundObject) &&
|
|
7395
|
+
!urlNotFoundObject.pathname.endsWith("/")
|
|
7396
|
+
) {
|
|
7397
|
+
enoentDetails.spaFallbackFilePaths = listSpaFallbackFileUrls(
|
|
7398
|
+
urlNotFound,
|
|
7399
|
+
{ rootDirectoryUrl: serverRootDirectoryUrl, mainFilePath },
|
|
7400
|
+
).map((fileUrl) =>
|
|
7401
|
+
FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
7402
|
+
fileUrl,
|
|
7403
|
+
serverRootDirectoryUrl,
|
|
7404
|
+
),
|
|
7405
|
+
);
|
|
7406
|
+
}
|
|
7139
7407
|
}
|
|
7140
7408
|
|
|
7141
7409
|
return {
|
|
@@ -7207,223 +7475,6 @@ const getDirectoryContentItems = ({
|
|
|
7207
7475
|
return items;
|
|
7208
7476
|
};
|
|
7209
7477
|
|
|
7210
|
-
const jsenvPluginFsRedirection = ({
|
|
7211
|
-
spa,
|
|
7212
|
-
directoryContentMagicName,
|
|
7213
|
-
magicExtensions = ["inherit", ".js"],
|
|
7214
|
-
magicDirectoryIndex = true,
|
|
7215
|
-
preserveSymlinks = false,
|
|
7216
|
-
}) => {
|
|
7217
|
-
return {
|
|
7218
|
-
name: "jsenv:fs_redirection",
|
|
7219
|
-
appliesDuring: "*",
|
|
7220
|
-
redirectReference: (reference) => {
|
|
7221
|
-
if (reference.url === "file:///") {
|
|
7222
|
-
return `ignore:file:///`;
|
|
7223
|
-
}
|
|
7224
|
-
if (reference.url === "file://") {
|
|
7225
|
-
return `ignore:file://`;
|
|
7226
|
-
}
|
|
7227
|
-
// ignore all new URL second arg
|
|
7228
|
-
if (reference.subtype === "new_url_second_arg") {
|
|
7229
|
-
if (reference.original) {
|
|
7230
|
-
return `ignore:${reference.original.specifier}`;
|
|
7231
|
-
}
|
|
7232
|
-
return `ignore:${reference.specifier}`;
|
|
7233
|
-
}
|
|
7234
|
-
// http, https, data, about, ...
|
|
7235
|
-
if (!reference.url.startsWith("file:")) {
|
|
7236
|
-
return null;
|
|
7237
|
-
}
|
|
7238
|
-
if (reference.original && !reference.original.url.startsWith("file:")) {
|
|
7239
|
-
return null;
|
|
7240
|
-
}
|
|
7241
|
-
if (reference.isInline) {
|
|
7242
|
-
return null;
|
|
7243
|
-
}
|
|
7244
|
-
|
|
7245
|
-
if (
|
|
7246
|
-
reference.specifierPathname.endsWith(`/${directoryContentMagicName}`)
|
|
7247
|
-
) {
|
|
7248
|
-
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
7249
|
-
const directoryUrl = new URL(
|
|
7250
|
-
reference.specifierPathname
|
|
7251
|
-
.replace(`/${directoryContentMagicName}`, "/")
|
|
7252
|
-
.slice(1),
|
|
7253
|
-
rootDirectoryUrl,
|
|
7254
|
-
).href;
|
|
7255
|
-
return directoryUrl;
|
|
7256
|
-
}
|
|
7257
|
-
// ignore "./" on new URL("./")
|
|
7258
|
-
// if (
|
|
7259
|
-
// reference.subtype === "new_url_first_arg" &&
|
|
7260
|
-
// reference.specifier === "./"
|
|
7261
|
-
// ) {
|
|
7262
|
-
// return `ignore:${reference.url}`;
|
|
7263
|
-
// }
|
|
7264
|
-
const urlObject = new URL(reference.url);
|
|
7265
|
-
let fsStat = readEntryStatSync(urlObject, { nullIfNotFound: true });
|
|
7266
|
-
reference.fsStat = fsStat;
|
|
7267
|
-
const { search, hash } = urlObject;
|
|
7268
|
-
urlObject.search = "";
|
|
7269
|
-
urlObject.hash = "";
|
|
7270
|
-
// must be read before applyFsStatEffectsOnUrlObject which forces the
|
|
7271
|
-
// trailing slash on directories
|
|
7272
|
-
const specifierUsesTrailingSlash = urlObject.pathname.endsWith("/");
|
|
7273
|
-
applyFsStatEffectsOnUrlObject(urlObject, fsStat);
|
|
7274
|
-
const shouldApplyFilesystemMagicResolution =
|
|
7275
|
-
reference.type === "js_import";
|
|
7276
|
-
if (shouldApplyFilesystemMagicResolution) {
|
|
7277
|
-
const filesystemResolution = applyFileSystemMagicResolution(
|
|
7278
|
-
urlObject.href,
|
|
7279
|
-
{
|
|
7280
|
-
fileStat: fsStat,
|
|
7281
|
-
magicDirectoryIndex,
|
|
7282
|
-
magicExtensions: getExtensionsToTry(
|
|
7283
|
-
magicExtensions,
|
|
7284
|
-
reference.ownerUrlInfo.url,
|
|
7285
|
-
),
|
|
7286
|
-
},
|
|
7287
|
-
);
|
|
7288
|
-
if (filesystemResolution.stat) {
|
|
7289
|
-
fsStat = filesystemResolution.stat;
|
|
7290
|
-
reference.fsStat = fsStat;
|
|
7291
|
-
urlObject.href = filesystemResolution.url;
|
|
7292
|
-
applyFsStatEffectsOnUrlObject(urlObject, fsStat);
|
|
7293
|
-
}
|
|
7294
|
-
}
|
|
7295
|
-
if (spa) {
|
|
7296
|
-
// for SPA we want to serve the root HTML file most of the time
|
|
7297
|
-
if (!fsStat) {
|
|
7298
|
-
if (urlToExtension(urlObject)) {
|
|
7299
|
-
// url has an extension, we assume it's a file request -> let 404 happen
|
|
7300
|
-
return null;
|
|
7301
|
-
}
|
|
7302
|
-
if (specifierUsesTrailingSlash) {
|
|
7303
|
-
// the trailing slash asks for a directory and there is none here
|
|
7304
|
-
// -> let 404 happen (same reasoning as the extension above)
|
|
7305
|
-
return null;
|
|
7306
|
-
}
|
|
7307
|
-
const spaFallbackUrl = getSpaFallbackUrl(reference);
|
|
7308
|
-
if (spaFallbackUrl) {
|
|
7309
|
-
return spaFallbackUrl;
|
|
7310
|
-
}
|
|
7311
|
-
return null;
|
|
7312
|
-
}
|
|
7313
|
-
if (fsStat.isDirectory()) {
|
|
7314
|
-
// When requesting a directory, check if we have an HTML entry file for that directory
|
|
7315
|
-
const directoryEntryFileUrl = getDirectoryEntryFileUrl(urlObject);
|
|
7316
|
-
if (directoryEntryFileUrl) {
|
|
7317
|
-
reference.fsStat = readEntryStatSync(directoryEntryFileUrl);
|
|
7318
|
-
return directoryEntryFileUrl;
|
|
7319
|
-
}
|
|
7320
|
-
if (!specifierUsesTrailingSlash) {
|
|
7321
|
-
// the trailing slash is what tells a directory apart from a route:
|
|
7322
|
-
// "/join/" is the directory, "/join" is a route owned by the SPA
|
|
7323
|
-
// even when "join/" exists in the source files.
|
|
7324
|
-
// Without this a source directory would shadow the route having
|
|
7325
|
-
// the same name and the SPA would be unreachable in dev while
|
|
7326
|
-
// being perfectly fine once built
|
|
7327
|
-
const spaFallbackUrl = getSpaFallbackUrl(reference);
|
|
7328
|
-
if (spaFallbackUrl) {
|
|
7329
|
-
reference.fsStat = readEntryStatSync(spaFallbackUrl, {
|
|
7330
|
-
nullIfNotFound: true,
|
|
7331
|
-
});
|
|
7332
|
-
return spaFallbackUrl;
|
|
7333
|
-
}
|
|
7334
|
-
}
|
|
7335
|
-
}
|
|
7336
|
-
}
|
|
7337
|
-
if (!fsStat) {
|
|
7338
|
-
return null;
|
|
7339
|
-
}
|
|
7340
|
-
const urlBeforeSymlinkResolution = urlObject.href;
|
|
7341
|
-
if (preserveSymlinks) {
|
|
7342
|
-
return `${urlBeforeSymlinkResolution}${search}${hash}`;
|
|
7343
|
-
}
|
|
7344
|
-
const urlAfterSymlinkResolution = resolveSymlink(
|
|
7345
|
-
urlBeforeSymlinkResolution,
|
|
7346
|
-
);
|
|
7347
|
-
if (urlAfterSymlinkResolution !== urlBeforeSymlinkResolution) {
|
|
7348
|
-
reference.leadsToASymlink = true;
|
|
7349
|
-
// reference.baseUrl = urlBeforeSymlinkResolution;
|
|
7350
|
-
}
|
|
7351
|
-
const resolvedUrl = `${urlAfterSymlinkResolution}${search}${hash}`;
|
|
7352
|
-
return resolvedUrl;
|
|
7353
|
-
},
|
|
7354
|
-
};
|
|
7355
|
-
};
|
|
7356
|
-
|
|
7357
|
-
const applyFsStatEffectsOnUrlObject = (urlObject, fsStat) => {
|
|
7358
|
-
if (!fsStat) {
|
|
7359
|
-
return;
|
|
7360
|
-
}
|
|
7361
|
-
const { pathname } = urlObject;
|
|
7362
|
-
const pathnameUsesTrailingSlash = pathname.endsWith("/");
|
|
7363
|
-
// force trailing slash on directories
|
|
7364
|
-
if (fsStat.isDirectory()) {
|
|
7365
|
-
if (!pathnameUsesTrailingSlash) {
|
|
7366
|
-
urlObject.pathname = `${pathname}/`;
|
|
7367
|
-
}
|
|
7368
|
-
} else if (pathnameUsesTrailingSlash) {
|
|
7369
|
-
// otherwise remove trailing slash if any
|
|
7370
|
-
// a warning here? (because it's strange to reference a file with a trailing slash)
|
|
7371
|
-
urlObject.pathname = pathname.slice(0, -1);
|
|
7372
|
-
}
|
|
7373
|
-
};
|
|
7374
|
-
|
|
7375
|
-
const resolveSymlink = (fileUrl) => {
|
|
7376
|
-
const urlObject = new URL(fileUrl);
|
|
7377
|
-
const realpath = realpathSync(urlObject);
|
|
7378
|
-
const realUrlObject = pathToFileURL(realpath);
|
|
7379
|
-
if (urlObject.pathname.endsWith("/")) {
|
|
7380
|
-
realUrlObject.pathname += `/`;
|
|
7381
|
-
}
|
|
7382
|
-
return realUrlObject.href;
|
|
7383
|
-
};
|
|
7384
|
-
|
|
7385
|
-
const getDirectoryEntryFileUrl = (directoryUrl) => {
|
|
7386
|
-
const indexHtmlFileUrl = new URL(`index.html`, directoryUrl);
|
|
7387
|
-
if (existsSync(indexHtmlFileUrl)) {
|
|
7388
|
-
return indexHtmlFileUrl.href;
|
|
7389
|
-
}
|
|
7390
|
-
const filename = urlToFilename(directoryUrl);
|
|
7391
|
-
const htmlFileUrlCandidate = new URL(`${filename}.html`, directoryUrl);
|
|
7392
|
-
if (existsSync(htmlFileUrlCandidate)) {
|
|
7393
|
-
return htmlFileUrlCandidate.href;
|
|
7394
|
-
}
|
|
7395
|
-
return null;
|
|
7396
|
-
};
|
|
7397
|
-
const getSpaFallbackUrl = (reference) => {
|
|
7398
|
-
const { requestedUrl, rootDirectoryUrl, mainFilePath } =
|
|
7399
|
-
reference.ownerUrlInfo.context;
|
|
7400
|
-
if (!requestedUrl) {
|
|
7401
|
-
// the SPA fallback answers a request; during build there is none
|
|
7402
|
-
return null;
|
|
7403
|
-
}
|
|
7404
|
-
const closestHtmlRootFile = getClosestHtmlRootFile(
|
|
7405
|
-
requestedUrl,
|
|
7406
|
-
rootDirectoryUrl,
|
|
7407
|
-
);
|
|
7408
|
-
if (closestHtmlRootFile) {
|
|
7409
|
-
return closestHtmlRootFile;
|
|
7410
|
-
}
|
|
7411
|
-
return String(new URL(mainFilePath, rootDirectoryUrl));
|
|
7412
|
-
};
|
|
7413
|
-
const getClosestHtmlRootFile = (requestedUrl, serverRootDirectoryUrl) => {
|
|
7414
|
-
let directoryUrl = new URL("./", requestedUrl);
|
|
7415
|
-
while (true) {
|
|
7416
|
-
const directoryEntryFileUrl = getDirectoryEntryFileUrl(directoryUrl);
|
|
7417
|
-
if (directoryEntryFileUrl) {
|
|
7418
|
-
return directoryEntryFileUrl;
|
|
7419
|
-
}
|
|
7420
|
-
if (!urlIsOrIsInsideOf(directoryUrl, serverRootDirectoryUrl)) {
|
|
7421
|
-
return null;
|
|
7422
|
-
}
|
|
7423
|
-
directoryUrl = new URL("../", directoryUrl);
|
|
7424
|
-
}
|
|
7425
|
-
};
|
|
7426
|
-
|
|
7427
7478
|
const directoryContentMagicName = "...";
|
|
7428
7479
|
|
|
7429
7480
|
const jsenvPluginProtocolFile = ({
|
|
@@ -7577,7 +7628,16 @@ const jsenvPluginProtocolFile = ({
|
|
|
7577
7628
|
}
|
|
7578
7629
|
const serveFile = (url) => {
|
|
7579
7630
|
const contentType = CONTENT_TYPE.fromUrlExtension(url);
|
|
7580
|
-
const
|
|
7631
|
+
const urlObject = new URL(url);
|
|
7632
|
+
// taken before the read: a write landing in between moves the
|
|
7633
|
+
// stat past this one, so the dev server sees the content as
|
|
7634
|
+
// outdated (see isValid in the dev server) rather than the reverse
|
|
7635
|
+
const fileStat = statSync(urlObject);
|
|
7636
|
+
const fileBuffer = readFileSync(urlObject);
|
|
7637
|
+
urlInfo.data.fileStat = {
|
|
7638
|
+
mtimeMs: fileStat.mtimeMs,
|
|
7639
|
+
size: fileStat.size,
|
|
7640
|
+
};
|
|
7581
7641
|
const content = CONTENT_TYPE.isTextual(contentType)
|
|
7582
7642
|
? String(fileBuffer)
|
|
7583
7643
|
: fileBuffer;
|
|
@@ -8084,7 +8144,7 @@ const jsenvPluginImportMetaScenarios = () => {
|
|
|
8084
8144
|
name: "jsenv:import_meta_scenario",
|
|
8085
8145
|
appliesDuring: "*",
|
|
8086
8146
|
transformUrlContent: {
|
|
8087
|
-
js_module:
|
|
8147
|
+
js_module: (urlInfo) => {
|
|
8088
8148
|
// Do not scan node modules for import.meta.dev/import.meta.build
|
|
8089
8149
|
// - node modules won't have this in their code
|
|
8090
8150
|
// - ;or should use other an other technic as this one won't be available
|
|
@@ -8100,40 +8160,42 @@ const jsenvPluginImportMetaScenarios = () => {
|
|
|
8100
8160
|
) {
|
|
8101
8161
|
return null;
|
|
8102
8162
|
}
|
|
8103
|
-
const {
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8163
|
+
const importMetaScenarioNodes = { dev: [], build: [] };
|
|
8164
|
+
visitJsAst(urlInfo.contentAst, {
|
|
8165
|
+
MemberExpression: (node) => {
|
|
8166
|
+
const name = getImportMetaPropertyName(node);
|
|
8167
|
+
if (name === "dev" || name === "build") {
|
|
8168
|
+
importMetaScenarioNodes[name].push(node);
|
|
8169
|
+
}
|
|
8170
|
+
},
|
|
8109
8171
|
});
|
|
8110
|
-
const { dev
|
|
8172
|
+
const { dev, build } = importMetaScenarioNodes;
|
|
8111
8173
|
const replacements = [];
|
|
8112
|
-
const replace = (
|
|
8113
|
-
replacements.push({
|
|
8174
|
+
const replace = (node, value) => {
|
|
8175
|
+
replacements.push({ node, value });
|
|
8114
8176
|
};
|
|
8115
8177
|
if (urlInfo.context.build) {
|
|
8116
8178
|
// during build ensure replacement for tree-shaking
|
|
8117
|
-
dev.forEach((
|
|
8118
|
-
replace(
|
|
8179
|
+
dev.forEach((node) => {
|
|
8180
|
+
replace(node, "undefined");
|
|
8119
8181
|
});
|
|
8120
|
-
build.forEach((
|
|
8121
|
-
replace(
|
|
8182
|
+
build.forEach((node) => {
|
|
8183
|
+
replace(node, "true");
|
|
8122
8184
|
});
|
|
8123
8185
|
} else {
|
|
8124
8186
|
// during dev we can let "import.meta.build" untouched
|
|
8125
8187
|
// it will be evaluated to undefined.
|
|
8126
8188
|
// Moreover it can be surprising to see some "undefined"
|
|
8127
8189
|
// when source file contains "import.meta.build"
|
|
8128
|
-
dev.forEach((
|
|
8129
|
-
replace(
|
|
8190
|
+
dev.forEach((node) => {
|
|
8191
|
+
replace(node, "true");
|
|
8130
8192
|
});
|
|
8131
8193
|
}
|
|
8132
8194
|
const magicSource = createMagicSource(urlInfo.content);
|
|
8133
|
-
replacements.forEach(({
|
|
8195
|
+
replacements.forEach(({ node, value }) => {
|
|
8134
8196
|
magicSource.replace({
|
|
8135
|
-
start:
|
|
8136
|
-
end:
|
|
8197
|
+
start: node.start,
|
|
8198
|
+
end: node.end,
|
|
8137
8199
|
replacement: value,
|
|
8138
8200
|
});
|
|
8139
8201
|
});
|
|
@@ -8143,42 +8205,6 @@ const jsenvPluginImportMetaScenarios = () => {
|
|
|
8143
8205
|
};
|
|
8144
8206
|
};
|
|
8145
8207
|
|
|
8146
|
-
const babelPluginMetadataImportMetaScenarios = () => {
|
|
8147
|
-
return {
|
|
8148
|
-
name: "metadata-import-meta-scenarios",
|
|
8149
|
-
visitor: {
|
|
8150
|
-
Program(programPath, state) {
|
|
8151
|
-
const importMetas = {};
|
|
8152
|
-
programPath.traverse({
|
|
8153
|
-
MemberExpression(path) {
|
|
8154
|
-
const { node } = path;
|
|
8155
|
-
const { object } = node;
|
|
8156
|
-
if (object.type !== "MetaProperty") {
|
|
8157
|
-
return;
|
|
8158
|
-
}
|
|
8159
|
-
const { property: objectProperty } = object;
|
|
8160
|
-
if (objectProperty.name !== "meta") {
|
|
8161
|
-
return;
|
|
8162
|
-
}
|
|
8163
|
-
const { property } = node;
|
|
8164
|
-
const { name } = property;
|
|
8165
|
-
const importMetaPaths = importMetas[name];
|
|
8166
|
-
if (importMetaPaths) {
|
|
8167
|
-
importMetaPaths.push(path);
|
|
8168
|
-
} else {
|
|
8169
|
-
importMetas[name] = [path];
|
|
8170
|
-
}
|
|
8171
|
-
},
|
|
8172
|
-
});
|
|
8173
|
-
state.file.metadata.importMetaScenarios = {
|
|
8174
|
-
dev: importMetas.dev,
|
|
8175
|
-
build: importMetas.build,
|
|
8176
|
-
};
|
|
8177
|
-
},
|
|
8178
|
-
},
|
|
8179
|
-
};
|
|
8180
|
-
};
|
|
8181
|
-
|
|
8182
8208
|
/*
|
|
8183
8209
|
* Source code can contain the following
|
|
8184
8210
|
* - __DEV__
|
|
@@ -8277,15 +8303,10 @@ const jsenvPluginImportMetaCss = () => {
|
|
|
8277
8303
|
if (!urlInfo.content.includes("import.meta.css")) {
|
|
8278
8304
|
return null;
|
|
8279
8305
|
}
|
|
8280
|
-
const
|
|
8281
|
-
|
|
8282
|
-
input: urlInfo.content,
|
|
8283
|
-
inputIsJsModule: true,
|
|
8284
|
-
inputUrl: urlInfo.originalUrl,
|
|
8285
|
-
outputUrl: urlInfo.generatedUrl,
|
|
8306
|
+
const importMetaCssNode = visitJsAstUntil(urlInfo.contentAst, {
|
|
8307
|
+
MemberExpression: (node) => getImportMetaPropertyName(node) === "css",
|
|
8286
8308
|
});
|
|
8287
|
-
|
|
8288
|
-
if (!usesImportMetaCss) {
|
|
8309
|
+
if (!importMetaCssNode) {
|
|
8289
8310
|
return null;
|
|
8290
8311
|
}
|
|
8291
8312
|
if (urlInfo.context.build) {
|
|
@@ -8365,37 +8386,6 @@ const babelPluginRewriteImportMetaCssAssignment = (
|
|
|
8365
8386
|
};
|
|
8366
8387
|
};
|
|
8367
8388
|
|
|
8368
|
-
const babelPluginMetadataUsesImportMetaCss = () => {
|
|
8369
|
-
return {
|
|
8370
|
-
name: "metadata-uses-import-meta-css",
|
|
8371
|
-
visitor: {
|
|
8372
|
-
Program(programPath, state) {
|
|
8373
|
-
let usesImportMetaCss = false;
|
|
8374
|
-
programPath.traverse({
|
|
8375
|
-
MemberExpression(path) {
|
|
8376
|
-
const { node } = path;
|
|
8377
|
-
const { object } = node;
|
|
8378
|
-
if (object.type !== "MetaProperty") {
|
|
8379
|
-
return;
|
|
8380
|
-
}
|
|
8381
|
-
const { property: objectProperty } = object;
|
|
8382
|
-
if (objectProperty.name !== "meta") {
|
|
8383
|
-
return;
|
|
8384
|
-
}
|
|
8385
|
-
const { property } = node;
|
|
8386
|
-
const { name } = property;
|
|
8387
|
-
if (name === "css") {
|
|
8388
|
-
usesImportMetaCss = true;
|
|
8389
|
-
path.stop();
|
|
8390
|
-
}
|
|
8391
|
-
},
|
|
8392
|
-
});
|
|
8393
|
-
state.file.metadata.usesImportMetaCss = usesImportMetaCss;
|
|
8394
|
-
},
|
|
8395
|
-
},
|
|
8396
|
-
};
|
|
8397
|
-
};
|
|
8398
|
-
|
|
8399
8389
|
const injectImportMetaCss = (
|
|
8400
8390
|
urlInfo,
|
|
8401
8391
|
{ content, importFrom, importName, importAs, hot },
|
|
@@ -8438,104 +8428,6 @@ ${importVariableName}(import.meta);
|
|
|
8438
8428
|
};
|
|
8439
8429
|
};
|
|
8440
8430
|
|
|
8441
|
-
// https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-stages-of-babel
|
|
8442
|
-
// https://github.com/cfware/babel-plugin-bundled-import-meta/blob/master/index.js
|
|
8443
|
-
// https://github.com/babel/babel/blob/f4edf62f6beeab8ae9f2b7f0b82f1b3b12a581af/packages/babel-helper-module-imports/src/index.js#L7
|
|
8444
|
-
|
|
8445
|
-
const babelPluginMetadataImportMetaHot = () => {
|
|
8446
|
-
return {
|
|
8447
|
-
name: "metadata-import-meta-hot",
|
|
8448
|
-
visitor: {
|
|
8449
|
-
Program(programPath, state) {
|
|
8450
|
-
Object.assign(
|
|
8451
|
-
state.file.metadata,
|
|
8452
|
-
collectImportMetaHotProperties(programPath),
|
|
8453
|
-
);
|
|
8454
|
-
},
|
|
8455
|
-
},
|
|
8456
|
-
};
|
|
8457
|
-
};
|
|
8458
|
-
const collectImportMetaHotProperties = (programPath) => {
|
|
8459
|
-
const importMetaHotPaths = [];
|
|
8460
|
-
let hotDecline = false;
|
|
8461
|
-
let hotAcceptSelf = false;
|
|
8462
|
-
let hotAcceptDependencies = [];
|
|
8463
|
-
programPath.traverse({
|
|
8464
|
-
MemberExpression(path) {
|
|
8465
|
-
const { node } = path;
|
|
8466
|
-
const { object } = node;
|
|
8467
|
-
if (object.type !== "MetaProperty") {
|
|
8468
|
-
return;
|
|
8469
|
-
}
|
|
8470
|
-
const { property: objectProperty } = object;
|
|
8471
|
-
if (objectProperty.name !== "meta") {
|
|
8472
|
-
return;
|
|
8473
|
-
}
|
|
8474
|
-
const { property } = node;
|
|
8475
|
-
const { name } = property;
|
|
8476
|
-
if (name === "hot") {
|
|
8477
|
-
importMetaHotPaths.push(path);
|
|
8478
|
-
}
|
|
8479
|
-
},
|
|
8480
|
-
CallExpression(path) {
|
|
8481
|
-
if (isImportMetaHotMethodCall(path, "accept")) {
|
|
8482
|
-
const callNode = path.node;
|
|
8483
|
-
const args = callNode.arguments;
|
|
8484
|
-
if (args.length === 0) {
|
|
8485
|
-
hotAcceptSelf = true;
|
|
8486
|
-
return;
|
|
8487
|
-
}
|
|
8488
|
-
const firstArg = args[0];
|
|
8489
|
-
if (firstArg.type === "StringLiteral") {
|
|
8490
|
-
hotAcceptDependencies = [
|
|
8491
|
-
{
|
|
8492
|
-
specifierPath: path.get("arguments")[0],
|
|
8493
|
-
},
|
|
8494
|
-
];
|
|
8495
|
-
return;
|
|
8496
|
-
}
|
|
8497
|
-
if (firstArg.type === "ArrayExpression") {
|
|
8498
|
-
const firstArgPath = path.get("arguments")[0];
|
|
8499
|
-
hotAcceptDependencies = firstArg.elements.map((arrayNode, index) => {
|
|
8500
|
-
if (arrayNode.type !== "StringLiteral") {
|
|
8501
|
-
throw new Error(
|
|
8502
|
-
`all array elements must be strings in "import.meta.hot.accept(array)"`,
|
|
8503
|
-
);
|
|
8504
|
-
}
|
|
8505
|
-
return {
|
|
8506
|
-
specifierPath: firstArgPath.get(String(index)),
|
|
8507
|
-
};
|
|
8508
|
-
});
|
|
8509
|
-
return;
|
|
8510
|
-
}
|
|
8511
|
-
// accept first arg can be "anything" such as
|
|
8512
|
-
// `const cb = () => {}; import.meta.accept(cb)`
|
|
8513
|
-
hotAcceptSelf = true;
|
|
8514
|
-
}
|
|
8515
|
-
if (isImportMetaHotMethodCall(path, "decline")) {
|
|
8516
|
-
hotDecline = true;
|
|
8517
|
-
}
|
|
8518
|
-
},
|
|
8519
|
-
});
|
|
8520
|
-
return {
|
|
8521
|
-
importMetaHotPaths,
|
|
8522
|
-
hotDecline,
|
|
8523
|
-
hotAcceptSelf,
|
|
8524
|
-
hotAcceptDependencies,
|
|
8525
|
-
};
|
|
8526
|
-
};
|
|
8527
|
-
const isImportMetaHotMethodCall = (path, methodName) => {
|
|
8528
|
-
const { property, object } = path.node.callee;
|
|
8529
|
-
return (
|
|
8530
|
-
property &&
|
|
8531
|
-
property.name === methodName &&
|
|
8532
|
-
object &&
|
|
8533
|
-
object.property &&
|
|
8534
|
-
object.property.name === "hot" &&
|
|
8535
|
-
object.object.type === "MetaProperty"
|
|
8536
|
-
);
|
|
8537
|
-
};
|
|
8538
|
-
|
|
8539
8431
|
// Some "smart" default applied to decide what should hot reload / fullreload:
|
|
8540
8432
|
// By default:
|
|
8541
8433
|
// - hot reload on <img src="./image.png" />
|
|
@@ -8683,6 +8575,75 @@ const htmlNodeCanHotReload = (node) => {
|
|
|
8683
8575
|
].includes(node.nodeName);
|
|
8684
8576
|
};
|
|
8685
8577
|
|
|
8578
|
+
const analyzeImportMetaHot = (ast) => {
|
|
8579
|
+
const importMetaHotNodes = [];
|
|
8580
|
+
let hotDecline = false;
|
|
8581
|
+
let hotAcceptSelf = false;
|
|
8582
|
+
let hotAcceptSpecifiers = [];
|
|
8583
|
+
visitJsAst(ast, {
|
|
8584
|
+
MemberExpression: (node) => {
|
|
8585
|
+
if (getImportMetaPropertyName(node) === "hot") {
|
|
8586
|
+
importMetaHotNodes.push(node);
|
|
8587
|
+
}
|
|
8588
|
+
},
|
|
8589
|
+
CallExpression: (node) => {
|
|
8590
|
+
const methodName = getImportMetaHotMethodName(node);
|
|
8591
|
+
if (methodName === "accept") {
|
|
8592
|
+
const args = node.arguments;
|
|
8593
|
+
if (args.length === 0) {
|
|
8594
|
+
hotAcceptSelf = true;
|
|
8595
|
+
return;
|
|
8596
|
+
}
|
|
8597
|
+
const [firstArg] = args;
|
|
8598
|
+
if (isStringLiteral(firstArg)) {
|
|
8599
|
+
hotAcceptSpecifiers = [firstArg.value];
|
|
8600
|
+
return;
|
|
8601
|
+
}
|
|
8602
|
+
if (firstArg.type === "ArrayExpression") {
|
|
8603
|
+
hotAcceptSpecifiers = firstArg.elements.map((element) => {
|
|
8604
|
+
if (!isStringLiteral(element)) {
|
|
8605
|
+
throw new Error(
|
|
8606
|
+
`all array elements must be strings in "import.meta.hot.accept(array)"`,
|
|
8607
|
+
);
|
|
8608
|
+
}
|
|
8609
|
+
return element.value;
|
|
8610
|
+
});
|
|
8611
|
+
return;
|
|
8612
|
+
}
|
|
8613
|
+
// accept first arg can be "anything" such as
|
|
8614
|
+
// `const cb = () => {}; import.meta.hot.accept(cb)`
|
|
8615
|
+
hotAcceptSelf = true;
|
|
8616
|
+
return;
|
|
8617
|
+
}
|
|
8618
|
+
if (methodName === "decline") {
|
|
8619
|
+
hotDecline = true;
|
|
8620
|
+
}
|
|
8621
|
+
},
|
|
8622
|
+
});
|
|
8623
|
+
return {
|
|
8624
|
+
importMetaHotNodes,
|
|
8625
|
+
hotDecline,
|
|
8626
|
+
hotAcceptSelf,
|
|
8627
|
+
hotAcceptSpecifiers,
|
|
8628
|
+
};
|
|
8629
|
+
};
|
|
8630
|
+
|
|
8631
|
+
// "import.meta.hot.<method>(...)"
|
|
8632
|
+
const getImportMetaHotMethodName = (callNode) => {
|
|
8633
|
+
const { callee } = callNode;
|
|
8634
|
+
if (callee.type !== "MemberExpression" || callee.computed) {
|
|
8635
|
+
return null;
|
|
8636
|
+
}
|
|
8637
|
+
if (getImportMetaPropertyName(callee.object) !== "hot") {
|
|
8638
|
+
return null;
|
|
8639
|
+
}
|
|
8640
|
+
return callee.property.name;
|
|
8641
|
+
};
|
|
8642
|
+
|
|
8643
|
+
const isStringLiteral = (node) => {
|
|
8644
|
+
return node.type === "Literal" && typeof node.value === "string";
|
|
8645
|
+
};
|
|
8646
|
+
|
|
8686
8647
|
const jsenvPluginImportMetaHot = () => {
|
|
8687
8648
|
const importMetaHotClientFileUrl = import.meta
|
|
8688
8649
|
.resolve("../client/import_meta_hot/import_meta_hot.js");
|
|
@@ -8731,7 +8692,7 @@ const jsenvPluginImportMetaHot = () => {
|
|
|
8731
8692
|
cssUrlInfo.data.hotAcceptSelf = false;
|
|
8732
8693
|
cssUrlInfo.data.hotAcceptDependencies = [];
|
|
8733
8694
|
},
|
|
8734
|
-
js_module:
|
|
8695
|
+
js_module: (urlInfo) => {
|
|
8735
8696
|
// Do not scan node modules for import.meta.hot
|
|
8736
8697
|
// - unlikely to be there
|
|
8737
8698
|
// - we don't watch node modules (too expensive)
|
|
@@ -8743,27 +8704,22 @@ const jsenvPluginImportMetaHot = () => {
|
|
|
8743
8704
|
if (!urlInfo.content.includes("import.meta.hot")) {
|
|
8744
8705
|
return null;
|
|
8745
8706
|
}
|
|
8746
|
-
const { metadata } = await applyBabelPlugins({
|
|
8747
|
-
babelPlugins: [babelPluginMetadataImportMetaHot],
|
|
8748
|
-
input: urlInfo.content,
|
|
8749
|
-
inputIsJsModule: true,
|
|
8750
|
-
inputUrl: urlInfo.originalUrl,
|
|
8751
|
-
outputUrl: urlInfo.generatedUrl,
|
|
8752
|
-
});
|
|
8753
8707
|
const {
|
|
8754
|
-
|
|
8708
|
+
importMetaHotNodes,
|
|
8755
8709
|
hotDecline,
|
|
8756
8710
|
hotAcceptSelf,
|
|
8757
|
-
|
|
8758
|
-
} =
|
|
8711
|
+
hotAcceptSpecifiers,
|
|
8712
|
+
} = analyzeImportMetaHot(urlInfo.contentAst);
|
|
8759
8713
|
urlInfo.data.hotDecline = hotDecline;
|
|
8760
8714
|
urlInfo.data.hotAcceptSelf = hotAcceptSelf;
|
|
8761
|
-
urlInfo.data.hotAcceptDependencies =
|
|
8762
|
-
|
|
8715
|
+
urlInfo.data.hotAcceptDependencies = hotAcceptSpecifiers.map(
|
|
8716
|
+
(specifier) => resolveHotAcceptSpecifier(urlInfo, specifier),
|
|
8717
|
+
);
|
|
8718
|
+
if (importMetaHotNodes.length === 0) {
|
|
8763
8719
|
return null;
|
|
8764
8720
|
}
|
|
8765
8721
|
if (urlInfo.context.build) {
|
|
8766
|
-
return removeImportMetaHots(urlInfo,
|
|
8722
|
+
return removeImportMetaHots(urlInfo, importMetaHotNodes);
|
|
8767
8723
|
}
|
|
8768
8724
|
return injectImportMetaHot(urlInfo, importMetaHotClientFileUrl);
|
|
8769
8725
|
},
|
|
@@ -8771,12 +8727,32 @@ const jsenvPluginImportMetaHot = () => {
|
|
|
8771
8727
|
};
|
|
8772
8728
|
};
|
|
8773
8729
|
|
|
8774
|
-
|
|
8730
|
+
// The specifier given to import.meta.hot.accept() is one the file imports,
|
|
8731
|
+
// so its url is known from that import (autoreload compares urls). A
|
|
8732
|
+
// specifier the file does not import accepts nothing: it is kept as a plain
|
|
8733
|
+
// url, never resolved as a dependency (that could throw on a bare specifier).
|
|
8734
|
+
const resolveHotAcceptSpecifier = (urlInfo, specifier) => {
|
|
8735
|
+
for (const referenceToOther of urlInfo.referenceToOthersSet) {
|
|
8736
|
+
if (
|
|
8737
|
+
referenceToOther.type === "js_import" &&
|
|
8738
|
+
referenceToOther.specifier === specifier
|
|
8739
|
+
) {
|
|
8740
|
+
return referenceToOther.url;
|
|
8741
|
+
}
|
|
8742
|
+
}
|
|
8743
|
+
try {
|
|
8744
|
+
return new URL(specifier, urlInfo.url).href;
|
|
8745
|
+
} catch {
|
|
8746
|
+
return specifier;
|
|
8747
|
+
}
|
|
8748
|
+
};
|
|
8749
|
+
|
|
8750
|
+
const removeImportMetaHots = (urlInfo, importMetaHotNodes) => {
|
|
8775
8751
|
const magicSource = createMagicSource(urlInfo.content);
|
|
8776
|
-
|
|
8752
|
+
importMetaHotNodes.forEach((node) => {
|
|
8777
8753
|
magicSource.replace({
|
|
8778
|
-
start:
|
|
8779
|
-
end:
|
|
8754
|
+
start: node.start,
|
|
8755
|
+
end: node.end,
|
|
8780
8756
|
replacement: "undefined",
|
|
8781
8757
|
});
|
|
8782
8758
|
});
|
|
@@ -9305,6 +9281,132 @@ const jsenvPluginAutoreload = ({
|
|
|
9305
9281
|
];
|
|
9306
9282
|
};
|
|
9307
9283
|
|
|
9284
|
+
/*
|
|
9285
|
+
* Tells the browser the static import graph of a page's module scripts, as
|
|
9286
|
+
* `Link: <url>; rel=modulepreload` response headers, so it can fetch every
|
|
9287
|
+
* module as soon as the page's headers arrive, without waiting to discover
|
|
9288
|
+
* them level by level (each level costing a round trip and, the first time,
|
|
9289
|
+
* a cook).
|
|
9290
|
+
*
|
|
9291
|
+
* Only what the graph already knows is listed: a module never cooked has no
|
|
9292
|
+
* known imports. The header is computed at each response from the graph as it
|
|
9293
|
+
* is then: a page gets its full list from its second load on, the first load
|
|
9294
|
+
* keeps the plain waterfall.
|
|
9295
|
+
*
|
|
9296
|
+
* Headers rather than <link> elements written into the page: the page content
|
|
9297
|
+
* stays what it is (served from memory, revalidated by etag) and the list can
|
|
9298
|
+
* grow without cooking the page again. And not references either: a reference
|
|
9299
|
+
* from the html to every module would make the html a dependent of each of
|
|
9300
|
+
* them, and a hot update propagating up the importers would reach the html
|
|
9301
|
+
* (which declines) instead of stopping at the module accepting it.
|
|
9302
|
+
*
|
|
9303
|
+
* OFF BY DEFAULT, to be enabled once the dev server speaks http/2 or http/3.
|
|
9304
|
+
* Over http/1.1 it makes pages slower — measured on a 500 modules page: first
|
|
9305
|
+
* paint 32ms -> 868ms, load 626ms -> 985ms. Knowing the urls earlier gives the
|
|
9306
|
+
* browser no extra resource: what it has is 6 connections per origin, and
|
|
9307
|
+
* every request goes through them in the order it was learned.
|
|
9308
|
+
* - Without the header, requests come in execution order: the parser asks
|
|
9309
|
+
* for the render-blocking supervisor script first and gets a connection at
|
|
9310
|
+
* once; transfer and execution overlap, a module executing and discovering
|
|
9311
|
+
* its imports while the others download. On localhost the server answers
|
|
9312
|
+
* from memory in 0.1ms, so the 6 connections are busy from the start.
|
|
9313
|
+
* - With the header, hundreds of requests take the 6 connections before the
|
|
9314
|
+
* parser asks for the supervisor script, which then waits for a free
|
|
9315
|
+
* connection behind 1MB+ transfers (520ms measured, 2s with 20ms of RTT).
|
|
9316
|
+
* Meanwhile nothing renders and nothing executes: transfer, then execution,
|
|
9317
|
+
* serialized. http/1.1 cannot reprioritize a queued request, and a
|
|
9318
|
+
* modulepreload has the same priority as a script.
|
|
9319
|
+
* - And there is nothing to hide: the discovery latency a preload removes is
|
|
9320
|
+
* ~1ms per graph level on localhost; with a real RTT a wide graph (more
|
|
9321
|
+
* than 6 modules pending at any time) keeps the 6 connections saturated
|
|
9322
|
+
* anyway, and the load takes requests / 6 * RTT whatever the order.
|
|
9323
|
+
* It pays off for a deep and narrow graph (connections idle, waiting for a
|
|
9324
|
+
* response to learn the next level), and over http/2 or http/3: no
|
|
9325
|
+
* connection limit, and the browser prioritizes the blocking script.
|
|
9326
|
+
*/
|
|
9327
|
+
|
|
9328
|
+
|
|
9329
|
+
const jsenvPluginModulepreload = () => {
|
|
9330
|
+
return {
|
|
9331
|
+
name: "jsenv:modulepreload",
|
|
9332
|
+
appliesDuring: "dev",
|
|
9333
|
+
augmentResponse: ({ urlInfo }) => {
|
|
9334
|
+
if (urlInfo.type !== "html") {
|
|
9335
|
+
return null;
|
|
9336
|
+
}
|
|
9337
|
+
const hrefs = collectStaticImportHrefs(urlInfo);
|
|
9338
|
+
if (hrefs.length === 0) {
|
|
9339
|
+
return null;
|
|
9340
|
+
}
|
|
9341
|
+
return {
|
|
9342
|
+
headers: {
|
|
9343
|
+
link: hrefs.map((href) => `<${href}>; rel=modulepreload`).join(", "),
|
|
9344
|
+
},
|
|
9345
|
+
};
|
|
9346
|
+
},
|
|
9347
|
+
};
|
|
9348
|
+
};
|
|
9349
|
+
|
|
9350
|
+
const collectStaticImportHrefs = (htmlUrlInfo) => {
|
|
9351
|
+
const hrefSet = new Set();
|
|
9352
|
+
const visitedSet = new Set();
|
|
9353
|
+
const addHref = (reference) => {
|
|
9354
|
+
const specifier = urlSpecifierEncoding.decode(reference);
|
|
9355
|
+
if (typeof specifier !== "string") {
|
|
9356
|
+
return;
|
|
9357
|
+
}
|
|
9358
|
+
// "?hot" belongs to the request that cooked the importer, the page loading
|
|
9359
|
+
// now imports the url without it
|
|
9360
|
+
hrefSet.add(injectQueryParamsIntoSpecifier(specifier, { hot: undefined }));
|
|
9361
|
+
};
|
|
9362
|
+
const visitJsModule = (jsModuleUrlInfo) => {
|
|
9363
|
+
if (visitedSet.has(jsModuleUrlInfo)) {
|
|
9364
|
+
return;
|
|
9365
|
+
}
|
|
9366
|
+
visitedSet.add(jsModuleUrlInfo);
|
|
9367
|
+
for (const reference of jsModuleUrlInfo.referenceToOthersSet) {
|
|
9368
|
+
if (
|
|
9369
|
+
reference.type !== "js_import" ||
|
|
9370
|
+
reference.isWeak ||
|
|
9371
|
+
reference.isImplicit ||
|
|
9372
|
+
!STATIC_IMPORT_SUBTYPES.includes(reference.subtype) ||
|
|
9373
|
+
!reference.url.startsWith("file:")
|
|
9374
|
+
) {
|
|
9375
|
+
continue;
|
|
9376
|
+
}
|
|
9377
|
+
const importedUrlInfo = reference.urlInfo;
|
|
9378
|
+
// never cooked: its content, hence its own imports, are unknown
|
|
9379
|
+
if (importedUrlInfo.type !== "js_module") {
|
|
9380
|
+
continue;
|
|
9381
|
+
}
|
|
9382
|
+
addHref(reference);
|
|
9383
|
+
visitJsModule(importedUrlInfo);
|
|
9384
|
+
}
|
|
9385
|
+
};
|
|
9386
|
+
for (const reference of htmlUrlInfo.referenceToOthersSet) {
|
|
9387
|
+
if (
|
|
9388
|
+
reference.type !== "script" ||
|
|
9389
|
+
reference.expectedType !== "js_module" ||
|
|
9390
|
+
// the scripts jsenv adds to every page are few, small and in memory
|
|
9391
|
+
reference.injected ||
|
|
9392
|
+
reference.isWeak
|
|
9393
|
+
) {
|
|
9394
|
+
continue;
|
|
9395
|
+
}
|
|
9396
|
+
const scriptUrlInfo = reference.urlInfo;
|
|
9397
|
+
if (scriptUrlInfo.type !== "js_module") {
|
|
9398
|
+
continue;
|
|
9399
|
+
}
|
|
9400
|
+
if (!reference.isInline && reference.url.startsWith("file:")) {
|
|
9401
|
+
addHref(reference);
|
|
9402
|
+
}
|
|
9403
|
+
visitJsModule(scriptUrlInfo);
|
|
9404
|
+
}
|
|
9405
|
+
return Array.from(hrefSet);
|
|
9406
|
+
};
|
|
9407
|
+
|
|
9408
|
+
const STATIC_IMPORT_SUBTYPES = ["import_static", "export_named", "export_all"];
|
|
9409
|
+
|
|
9308
9410
|
/*
|
|
9309
9411
|
* Tells the browser about the dependencies declared in package.json that
|
|
9310
9412
|
* node_modules does not match, so the page can say it is running with something
|
|
@@ -9946,6 +10048,7 @@ const getCorePlugins = ({
|
|
|
9946
10048
|
|
|
9947
10049
|
clientAutoreload,
|
|
9948
10050
|
clientAutoreloadOnServerRestart,
|
|
10051
|
+
modulepreload = false,
|
|
9949
10052
|
dependencyStatus,
|
|
9950
10053
|
cacheControl,
|
|
9951
10054
|
scenarioPlaceholders = true,
|
|
@@ -10047,6 +10150,7 @@ const getCorePlugins = ({
|
|
|
10047
10150
|
...(clientAutoreload && clientAutoreload.enabled
|
|
10048
10151
|
? [jsenvPluginAutoreload(clientAutoreload)]
|
|
10049
10152
|
: []),
|
|
10153
|
+
...(modulepreload ? [jsenvPluginModulepreload()] : []),
|
|
10050
10154
|
...(dependencyStatus
|
|
10051
10155
|
? [jsenvPluginDependencyStatus(dependencyStatus)]
|
|
10052
10156
|
: []),
|