Skip to content

Commit effa185

Browse files
vankopsokra
authored andcommitted
add ImportsFieldPlugin
1 parent 6166645 commit effa185

18 files changed

+445
-10
lines changed

lib/ExportsFieldPlugin.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = class ExportsFieldPlugin {
2929
this.conditionNames = conditionNames;
3030
this.fieldName = fieldNamePath;
3131
/** @type {WeakMap<any, FieldProcessor>} */
32-
this.exportsFieldProcessorCache = new WeakMap();
32+
this.fieldProcessorCache = new WeakMap();
3333
}
3434

3535
/**
@@ -78,17 +78,17 @@ module.exports = class ExportsFieldPlugin {
7878
// We attach the cache to the description file instead of the exportsField value
7979
// because we use a WeakMap and the exportsField could be a string too.
8080
// Description file is always an object when exports field can be accessed.
81-
let exportFieldProcessor = this.exportsFieldProcessorCache.get(
81+
let fieldProcessor = this.fieldProcessorCache.get(
8282
request.descriptionFileData
8383
);
84-
if (exportFieldProcessor === undefined) {
85-
exportFieldProcessor = processExportsField(exportsField);
86-
this.exportsFieldProcessorCache.set(
84+
if (fieldProcessor === undefined) {
85+
fieldProcessor = processExportsField(exportsField);
86+
this.fieldProcessorCache.set(
8787
request.descriptionFileData,
88-
exportFieldProcessor
88+
fieldProcessor
8989
);
9090
}
91-
paths = exportFieldProcessor(remainingRequest, this.conditionNames);
91+
paths = fieldProcessor(remainingRequest, this.conditionNames);
9292
} catch (err) {
9393
if (resolveContext.log) {
9494
resolveContext.log(

lib/ImportsFieldPlugin.js

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Ivan Kopeykin @vankop
4+
*/
5+
6+
"use strict";
7+
8+
const path = require("path");
9+
const DescriptionFileUtils = require("./DescriptionFileUtils");
10+
const forEachBail = require("./forEachBail");
11+
const { processImportsField } = require("./util/entrypoints");
12+
13+
/** @typedef {import("./Resolver")} Resolver */
14+
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
15+
/** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
16+
/** @typedef {import("./util/entrypoints").ImportsField} ImportsField */
17+
18+
const dotCode = ".".charCodeAt(0);
19+
const slashCode = "/".charCodeAt(0);
20+
21+
module.exports = class ExportsFieldPlugin {
22+
/**
23+
* @param {string | ResolveStepHook} source source
24+
* @param {Set<string>} conditionNames condition names
25+
* @param {string | string[]} fieldNamePath name path
26+
* @param {string | ResolveStepHook} targetFile target file
27+
* @param {string | ResolveStepHook} targetPackage target package
28+
*/
29+
constructor(
30+
source,
31+
conditionNames,
32+
fieldNamePath,
33+
targetFile,
34+
targetPackage
35+
) {
36+
this.source = source;
37+
this.targetFile = targetFile;
38+
this.targetPackage = targetPackage;
39+
this.conditionNames = conditionNames;
40+
this.fieldName = fieldNamePath;
41+
/** @type {WeakMap<any, FieldProcessor>} */
42+
this.fieldProcessorCache = new WeakMap();
43+
}
44+
45+
/**
46+
* @param {Resolver} resolver the resolver
47+
* @returns {void}
48+
*/
49+
apply(resolver) {
50+
const targetFile = resolver.ensureHook(this.targetFile);
51+
const targetPackage = resolver.ensureHook(this.targetPackage);
52+
53+
resolver
54+
.getHook(this.source)
55+
.tapAsync("ImportsFieldPlugin", (request, resolveContext, callback) => {
56+
// When there is no description file, abort
57+
if (!request.descriptionFilePath) return callback();
58+
59+
if (
60+
// When the description file is inherited from parent, abort
61+
// (There is no description file inside of this package)
62+
request.relativePath !== "." ||
63+
request.request === undefined
64+
)
65+
return callback();
66+
67+
// request should start with "#" only
68+
if (request.query || request.request || !request.fragment)
69+
return callback();
70+
71+
const remainingRequest = request.fragment;
72+
/** @type {ImportsField|null} */
73+
const importsField = DescriptionFileUtils.getField(
74+
request.descriptionFileData,
75+
this.fieldName
76+
);
77+
if (!importsField) return callback();
78+
79+
if (request.directory) {
80+
return callback(
81+
new Error(
82+
`Resolving to directories is not possible with the exports field (request was ${remainingRequest}/)`
83+
)
84+
);
85+
}
86+
87+
let paths;
88+
89+
try {
90+
// We attach the cache to the description file instead of the importsField value
91+
// because we use a WeakMap and the importsField could be a string too.
92+
// Description file is always an object when exports field can be accessed.
93+
let fieldProcessor = this.fieldProcessorCache.get(
94+
request.descriptionFileData
95+
);
96+
if (fieldProcessor === undefined) {
97+
fieldProcessor = processImportsField(importsField);
98+
this.fieldProcessorCache.set(
99+
request.descriptionFileData,
100+
fieldProcessor
101+
);
102+
}
103+
paths = fieldProcessor(remainingRequest, this.conditionNames);
104+
} catch (err) {
105+
if (resolveContext.log) {
106+
resolveContext.log(
107+
`Imports field in ${request.descriptionFilePath} can't be processed: ${err}`
108+
);
109+
}
110+
return callback(err);
111+
}
112+
113+
if (paths.length === 0) {
114+
return callback(
115+
new Error(
116+
`Package import ${remainingRequest} is not imported from package ${request.descriptionFileRoot} (see imports field in ${request.descriptionFilePath})`
117+
)
118+
);
119+
}
120+
121+
forEachBail(
122+
paths,
123+
(p, callback) => {
124+
const match = /^([^?#]*)(\?[^#]*)?(#.*)?$/.exec(p);
125+
126+
if (!match) return callback();
127+
128+
const [, path_, query, fragment] = match;
129+
130+
switch (path_.charCodeAt(0)) {
131+
// should be relative
132+
case dotCode: {
133+
const obj = {
134+
...request,
135+
request: undefined,
136+
path: path.join(
137+
/** @type {string} */ (request.descriptionFileRoot),
138+
path_
139+
),
140+
relativePath: path_,
141+
query: query || "",
142+
fragment: fragment || ""
143+
};
144+
145+
resolver.doResolve(
146+
targetFile,
147+
obj,
148+
"using imports field: " + p,
149+
resolveContext,
150+
callback
151+
);
152+
break;
153+
}
154+
// should be absolute
155+
case slashCode: {
156+
const obj = {
157+
...request,
158+
request: undefined,
159+
path: path_,
160+
relativePath: path_,
161+
query: query || "",
162+
fragment: fragment || ""
163+
};
164+
165+
resolver.doResolve(
166+
targetFile,
167+
obj,
168+
"using imports field: " + p,
169+
resolveContext,
170+
callback
171+
);
172+
break;
173+
}
174+
// package resolving
175+
default: {
176+
const obj = {
177+
...request,
178+
request: path_,
179+
relativePath: path_,
180+
fullySpecified: true,
181+
query: query || "",
182+
fragment: fragment || ""
183+
};
184+
185+
resolver.doResolve(
186+
targetPackage,
187+
obj,
188+
"using imports field: " + p,
189+
resolveContext,
190+
callback
191+
);
192+
}
193+
}
194+
},
195+
(err, result) => callback(err, result || null)
196+
);
197+
});
198+
}
199+
};

lib/Resolver.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const {
7070
* @property {boolean} directory
7171
* @property {boolean} module
7272
* @property {boolean} file
73+
* @property {boolean} internal
7374
*/
7475

7576
/**
@@ -410,7 +411,8 @@ class Resolver {
410411
fragment: "",
411412
module: false,
412413
directory: false,
413-
file: false
414+
file: false,
415+
internal: false
414416
};
415417

416418
const match = /^([^?#]*)(\?[^#]*)?(#.*)?$/.exec(identifier);
@@ -421,20 +423,27 @@ class Resolver {
421423
part.query = match[2] || "";
422424
part.fragment = match[3] || "";
423425

424-
if (part.request) {
426+
if (part.request.length > 0) {
425427
part.module = this.isModule(part.request);
426428
part.directory = this.isDirectory(part.request);
427429
if (part.directory) {
428430
part.request = part.request.substr(0, part.request.length - 1);
429431
}
432+
} else if (part.fragment.length > 0) {
433+
part.internal = this.isPrivate(identifier);
430434
}
435+
431436
return part;
432437
}
433438

434439
isModule(path) {
435440
return getType(path) === PathType.Normal;
436441
}
437442

443+
isPrivate(path) {
444+
return getType(path) === PathType.Internal;
445+
}
446+
438447
/**
439448
* @param {string} path a path
440449
* @returns {boolean} true, if the path is a directory path

lib/ResolverFactory.js

+28
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const DescriptionFilePlugin = require("./DescriptionFilePlugin");
1818
const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
1919
const ExportsFieldPlugin = require("./ExportsFieldPlugin");
2020
const FileExistsPlugin = require("./FileExistsPlugin");
21+
const ImportsFieldPlugin = require("./ImportsFieldPlugin");
2122
const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
2223
const JoinRequestPlugin = require("./JoinRequestPlugin");
2324
const MainFieldPlugin = require("./MainFieldPlugin");
@@ -54,6 +55,7 @@ const UseFilePlugin = require("./UseFilePlugin");
5455
* @property {string[]=} conditionNames A list of exports field condition names.
5556
* @property {boolean=} enforceExtension Enforce that a extension from extensions must be used
5657
* @property {(string | string[])[]=} exportsFields A list of exports fields in description files
58+
* @property {(string | string[])[]=} importsFields A list of imports fields in description files
5759
* @property {string[]=} extensions A list of extensions which should be tried for files
5860
* @property {FileSystem} fileSystem The file system which should be used
5961
* @property {(Object | boolean)=} unsafeCache Use this cache object to unsafely cache the successful requests
@@ -81,6 +83,7 @@ const UseFilePlugin = require("./UseFilePlugin");
8183
* @property {string[]} descriptionFiles
8284
* @property {boolean} enforceExtension
8385
* @property {Set<string | string[]>} exportsFields
86+
* @property {Set<string | string[]>} importsFields
8487
* @property {Set<string>} extensions
8588
* @property {FileSystem} fileSystem
8689
* @property {Object | false} unsafeCache
@@ -158,6 +161,7 @@ function createOptions(options) {
158161
? options.cacheWithContext
159162
: true,
160163
exportsFields: new Set(options.exportsFields || ["exports"]),
164+
importsFields: new Set(options.importsFields || ["imports"]),
161165
conditionNames: new Set(options.conditionNames),
162166
descriptionFiles: Array.from(
163167
new Set(options.descriptionFiles || ["package.json"])
@@ -211,6 +215,7 @@ exports.createResolver = function(options) {
211215
descriptionFiles,
212216
enforceExtension,
213217
exportsFields,
218+
importsFields,
214219
extensions,
215220
fileSystem,
216221
fullySpecified,
@@ -240,6 +245,7 @@ exports.createResolver = function(options) {
240245
resolver.ensureHook("newInteralResolve");
241246
resolver.ensureHook("parsedResolve");
242247
resolver.ensureHook("describedResolve");
248+
resolver.ensureHook("internal");
243249
resolver.ensureHook("rawModule");
244250
resolver.ensureHook("module");
245251
resolver.ensureHook("resolveAsModule");
@@ -311,11 +317,33 @@ exports.createResolver = function(options) {
311317
"raw-module"
312318
)
313319
);
320+
plugins.push(
321+
new ConditionalPlugin(
322+
"after-described-resolve",
323+
{ internal: true },
324+
"resolve as internal import",
325+
false,
326+
"internal"
327+
)
328+
);
314329
if (roots.size > 0) {
315330
plugins.push(new RootsPlugin("after-described-resolve", roots, "relative"));
316331
}
317332
plugins.push(new JoinRequestPlugin("after-described-resolve", "relative"));
318333

334+
// internal
335+
importsFields.forEach(importsField => {
336+
plugins.push(
337+
new ImportsFieldPlugin(
338+
"internal",
339+
conditionNames,
340+
importsField,
341+
"final-file",
342+
"internal-resolve"
343+
)
344+
);
345+
});
346+
319347
// raw-module
320348
exportsFields.forEach(exportsField => {
321349
plugins.push(

0 commit comments

Comments
 (0)