source: frontend/node_modules/enhanced-resolve/lib/ExportsFieldPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 7.4 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Ivan Kopeykin @vankop
4*/
5
6"use strict";
7
8const DescriptionFileUtils = require("./DescriptionFileUtils");
9const forEachBail = require("./forEachBail");
10const { processExportsField } = require("./util/entrypoints");
11const { parseIdentifier } = require("./util/identifier");
12const {
13 deprecatedInvalidSegmentRegEx,
14 invalidSegmentRegEx,
15} = require("./util/path");
16
17/** @typedef {import("./Resolver")} Resolver */
18/** @typedef {import("./Resolver").JsonObject} JsonObject */
19/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
20/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
21/** @typedef {import("./util/entrypoints").ExportsField} ExportsField */
22/** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
23
24module.exports = class ExportsFieldPlugin {
25 /**
26 * @param {string | ResolveStepHook} source source
27 * @param {Set<string>} conditionNames condition names
28 * @param {string | string[]} fieldNamePath name path
29 * @param {string | ResolveStepHook} target target
30 */
31 constructor(source, conditionNames, fieldNamePath, target) {
32 this.source = source;
33 this.target = target;
34 this.conditionNames = conditionNames;
35 this.fieldName = fieldNamePath;
36 // `null` is cached for description files that have no exports field,
37 // so subsequent resolves against the same package.json skip the
38 // `DescriptionFileUtils.getField` walk entirely.
39 /** @type {WeakMap<JsonObject, FieldProcessor | null>} */
40 this._fieldProcessorCache = new WeakMap();
41 }
42
43 /**
44 * @param {Resolver} resolver the resolver
45 * @returns {void}
46 */
47 apply(resolver) {
48 const target = resolver.ensureHook(this.target);
49 resolver
50 .getHook(this.source)
51 .tapAsync("ExportsFieldPlugin", (request, resolveContext, callback) => {
52 // When there is no description file, abort
53 if (!request.descriptionFileData) return callback();
54 if (
55 // When the description file is inherited from parent, abort
56 // (There is no description file inside of this package)
57 request.relativePath !== "." ||
58 request.request === undefined
59 ) {
60 return callback();
61 }
62
63 const { descriptionFileData } = request;
64 const remainingRequest =
65 request.query || request.fragment
66 ? (request.request === "." ? "./" : request.request) +
67 request.query +
68 request.fragment
69 : request.request;
70
71 /** @type {string[]} */
72 let paths;
73 /** @type {string | null} */
74 let usedField;
75
76 try {
77 // Look up the cached processor first. On a cache hit we
78 // avoid re-walking the description file for the exports
79 // field — and `null` is cached for description files that
80 // have no exports field at all, so those skip the read
81 // entirely. `processExportsField` can throw on a malformed
82 // `exports` map (e.g. a key without a leading `.`), so
83 // building the processor must stay inside this try/catch.
84 let fieldProcessor =
85 this._fieldProcessorCache.get(descriptionFileData);
86 if (
87 fieldProcessor === undefined &&
88 !this._fieldProcessorCache.has(descriptionFileData)
89 ) {
90 const exportsField =
91 /** @type {ExportsField | null | undefined} */
92 (
93 DescriptionFileUtils.getField(
94 descriptionFileData,
95 this.fieldName,
96 )
97 );
98 fieldProcessor = exportsField
99 ? processExportsField(exportsField)
100 : null;
101 this._fieldProcessorCache.set(descriptionFileData, fieldProcessor);
102 }
103 if (!fieldProcessor) return callback();
104
105 if (request.directory) {
106 return callback(
107 new Error(
108 `Resolving to directories is not possible with the exports field (request was ${remainingRequest}/)`,
109 ),
110 );
111 }
112
113 [paths, usedField] = fieldProcessor(
114 remainingRequest,
115 this.conditionNames,
116 );
117 } catch (/** @type {unknown} */ err) {
118 if (resolveContext.log) {
119 resolveContext.log(
120 `Exports field in ${request.descriptionFilePath} can't be processed: ${err}`,
121 );
122 }
123 return callback(/** @type {Error} */ (err));
124 }
125
126 if (paths.length === 0) {
127 const conditions = [...this.conditionNames];
128 const conditionsStr =
129 conditions.length === 1
130 ? `the condition "${conditions[0]}"`
131 : `the conditions ${JSON.stringify(conditions)}`;
132 return callback(
133 new Error(
134 `"${remainingRequest}" is not exported under ${conditionsStr} from package ${request.descriptionFileRoot} (see exports field in ${request.descriptionFilePath})`,
135 ),
136 );
137 }
138
139 forEachBail(
140 paths,
141 /**
142 * @param {string} path path
143 * @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
144 * @param {number} i index
145 * @returns {void}
146 */
147 (path, callback, i) => {
148 const parsedIdentifier = parseIdentifier(path);
149
150 if (!parsedIdentifier) return callback();
151
152 const [relativePath, query, fragment] = parsedIdentifier;
153
154 if (!relativePath.startsWith("./")) {
155 if (paths.length === i) {
156 return callback(
157 new Error(
158 `Invalid "exports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`,
159 ),
160 );
161 }
162
163 return callback();
164 }
165
166 const withoutDotSlash = relativePath.slice(2);
167 if (
168 invalidSegmentRegEx.test(withoutDotSlash) &&
169 deprecatedInvalidSegmentRegEx.test(withoutDotSlash)
170 ) {
171 if (paths.length === i) {
172 return callback(
173 new Error(
174 `Invalid "exports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`,
175 ),
176 );
177 }
178
179 return callback();
180 }
181
182 /** @type {ResolveRequest} */
183 const obj = {
184 ...request,
185 request: undefined,
186 path: resolver.join(
187 /** @type {string} */ (request.descriptionFileRoot),
188 relativePath,
189 ),
190 relativePath,
191 query,
192 fragment,
193 };
194
195 resolver.doResolve(
196 target,
197 obj,
198 `using exports field: ${path}`,
199 resolveContext,
200 (err, result) => {
201 if (err) return callback(err);
202 // Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
203 if (result === undefined) return callback(null, null);
204 callback(null, result);
205 },
206 );
207 },
208 /**
209 * @param {(null | Error)=} err error
210 * @param {(null | ResolveRequest)=} result result
211 * @returns {void}
212 */
213 (err, result) => {
214 if (err) return callback(err);
215 // When an exports field match was found but the target file doesn't exist,
216 // return an error to prevent fallback to parent node_modules directories.
217 // Per the Node.js ESM spec, a matched exports entry that fails to resolve
218 // is a hard error, not a signal to continue searching up the directory tree.
219 // See: https://github.com/webpack/enhanced-resolve/issues/399
220 if (!result) {
221 return callback(
222 new Error(
223 `Package path ${remainingRequest} is exported from package ${request.descriptionFileRoot}, but no valid target file was found (see exports field in ${request.descriptionFilePath})`,
224 ),
225 );
226 }
227 callback(null, result);
228 },
229 );
230 });
231 }
232};
Note: See TracBrowser for help on using the repository browser.