| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | /** @typedef {import("../Dependency")} Dependency */
|
|---|
| 9 | /** @typedef {import("../Module")} Module */
|
|---|
| 10 | /** @typedef {import("../ModuleGraph")} ModuleGraph */
|
|---|
| 11 | /** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
|---|
| 12 |
|
|---|
| 13 | /** @typedef {Range[]} IdRanges */
|
|---|
| 14 |
|
|---|
| 15 | /**
|
|---|
| 16 | * @summary Get the subset of ids and their corresponding range in an id chain that should be re-rendered by webpack.
|
|---|
| 17 | * Only those in the chain that are actually referring to namespaces or imports should be re-rendered.
|
|---|
| 18 | * Deeper member accessors on the imported object should not be re-rendered. If deeper member accessors are re-rendered,
|
|---|
| 19 | * there is a potential loss of meaning with rendering a quoted accessor as an unquoted accessor, or vice versa,
|
|---|
| 20 | * because minifiers treat quoted accessors differently. e.g. import { a } from "./module"; a["b"] vs a.b
|
|---|
| 21 | * @param {string[]} untrimmedIds chained ids
|
|---|
| 22 | * @param {Range} untrimmedRange range encompassing allIds
|
|---|
| 23 | * @param {IdRanges | undefined} ranges cumulative range of ids for each of allIds
|
|---|
| 24 | * @param {ModuleGraph} moduleGraph moduleGraph
|
|---|
| 25 | * @param {Dependency} dependency dependency
|
|---|
| 26 | * @returns {{ trimmedIds: string[], trimmedRange: Range }} computed trimmed ids and cumulative range of those ids
|
|---|
| 27 | */
|
|---|
| 28 | module.exports.getTrimmedIdsAndRange = (
|
|---|
| 29 | untrimmedIds,
|
|---|
| 30 | untrimmedRange,
|
|---|
| 31 | ranges,
|
|---|
| 32 | moduleGraph,
|
|---|
| 33 | dependency
|
|---|
| 34 | ) => {
|
|---|
| 35 | let trimmedIds = trimIdsToThoseImported(
|
|---|
| 36 | untrimmedIds,
|
|---|
| 37 | moduleGraph,
|
|---|
| 38 | dependency
|
|---|
| 39 | );
|
|---|
| 40 | let trimmedRange = untrimmedRange;
|
|---|
| 41 | if (trimmedIds.length !== untrimmedIds.length) {
|
|---|
| 42 | // The array returned from dep.idRanges is right-aligned with the array returned from dep.names.
|
|---|
| 43 | // Meaning, the two arrays may not always have the same number of elements, but the last element of
|
|---|
| 44 | // dep.idRanges corresponds to [the expression fragment to the left of] the last element of dep.names.
|
|---|
| 45 | // Use this to find the correct replacement range based on the number of ids that were trimmed.
|
|---|
| 46 | const idx =
|
|---|
| 47 | ranges === undefined
|
|---|
| 48 | ? -1 /* trigger failure case below */
|
|---|
| 49 | : ranges.length + (trimmedIds.length - untrimmedIds.length);
|
|---|
| 50 | if (idx < 0 || idx >= /** @type {Range[]} */ (ranges).length) {
|
|---|
| 51 | // cspell:ignore minifiers
|
|---|
| 52 | // Should not happen but we can't throw an error here because of backward compatibility with
|
|---|
| 53 | // external plugins in wp5. Instead, we just disable trimming for now. This may break some minifiers.
|
|---|
| 54 | trimmedIds = untrimmedIds;
|
|---|
| 55 | // TODO webpack 6 remove the "trimmedIds = ids" above and uncomment the following line instead.
|
|---|
| 56 | // throw new Error("Missing range starts data for id replacement trimming.");
|
|---|
| 57 | } else {
|
|---|
| 58 | trimmedRange = /** @type {Range[]} */ (ranges)[idx];
|
|---|
| 59 | }
|
|---|
| 60 | }
|
|---|
| 61 |
|
|---|
| 62 | return { trimmedIds, trimmedRange };
|
|---|
| 63 | };
|
|---|
| 64 |
|
|---|
| 65 | /**
|
|---|
| 66 | * @summary Determine which IDs in the id chain are actually referring to namespaces or imports,
|
|---|
| 67 | * and which are deeper member accessors on the imported object.
|
|---|
| 68 | * @param {string[]} ids untrimmed ids
|
|---|
| 69 | * @param {ModuleGraph} moduleGraph moduleGraph
|
|---|
| 70 | * @param {Dependency} dependency dependency
|
|---|
| 71 | * @returns {string[]} trimmed ids
|
|---|
| 72 | */
|
|---|
| 73 | function trimIdsToThoseImported(ids, moduleGraph, dependency) {
|
|---|
| 74 | /** @type {string[]} */
|
|---|
| 75 | let trimmedIds = [];
|
|---|
| 76 | let currentExportsInfo = moduleGraph.getExportsInfo(
|
|---|
| 77 | /** @type {Module} */ (moduleGraph.getModule(dependency))
|
|---|
| 78 | );
|
|---|
| 79 | for (let i = 0; i < ids.length; i++) {
|
|---|
| 80 | if (i === 0 && ids[i] === "default") {
|
|---|
| 81 | continue; // ExportInfo for the next level under default is still at the root ExportsInfo, so don't advance currentExportsInfo
|
|---|
| 82 | }
|
|---|
| 83 | const exportInfo = currentExportsInfo.getExportInfo(ids[i]);
|
|---|
| 84 | if (exportInfo.provided === false) {
|
|---|
| 85 | // json imports have nested ExportInfo for elements that things that are not actually exported, so check .provided
|
|---|
| 86 | trimmedIds = ids.slice(0, i);
|
|---|
| 87 | break;
|
|---|
| 88 | }
|
|---|
| 89 | const nestedInfo = exportInfo.getNestedExportsInfo();
|
|---|
| 90 | if (!nestedInfo) {
|
|---|
| 91 | // once all nested exports are traversed, the next item is the actual import so stop there
|
|---|
| 92 | trimmedIds = ids.slice(0, i + 1);
|
|---|
| 93 | break;
|
|---|
| 94 | }
|
|---|
| 95 | currentExportsInfo = nestedInfo;
|
|---|
| 96 | }
|
|---|
| 97 | // Never trim to nothing. This can happen for invalid imports (e.g. import { notThere } from "./module", or import { anything } from "./missingModule")
|
|---|
| 98 | return trimmedIds.length ? trimmedIds : ids;
|
|---|
| 99 | }
|
|---|