Skip to content

Commit 5c52c3b

Browse files
committed
update
1 parent 4498d0a commit 5c52c3b

File tree

6 files changed

+69
-79
lines changed

6 files changed

+69
-79
lines changed

lib/TsconfigPathsUtils.js

Lines changed: 45 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,55 @@ const path = require("path");
2525
*/
2626

2727
/**
28-
* Convert TypeScript paths configuration to AliasPlugin aliases
28+
* @param {string} pattern Path pattern
29+
* @returns {number} Length of the prefix
30+
*/
31+
function getPrefixLength(pattern) {
32+
const prefixLength = pattern.indexOf("*");
33+
if (prefixLength === -1) {
34+
return pattern.length;
35+
}
36+
return pattern.slice(0, Math.max(0, prefixLength)).length;
37+
}
38+
39+
/**
40+
* Sort path patterns.
41+
* If a module name can be matched with multiple patterns then pattern with the longest prefix will be picked.
42+
* @param {string[]} arr Array of path patterns
43+
* @returns {string[]} Array of path patterns sorted by longest prefix
44+
*/
45+
function sortByLongestPrefix(arr) {
46+
return [...arr].sort((a, b) => getPrefixLength(b) - getPrefixLength(a));
47+
}
48+
49+
/**
50+
* Converts an absolute baseUrl and paths to an array of absolute mapping entries.
51+
* The array is sorted by longest prefix.
52+
* Having an array with entries allows us to keep a sorting order rather than
53+
* sort by keys each time we use the mappings.
2954
* @param {{[key: string]: string[]}} paths TypeScript paths mapping
3055
* @param {string} baseUrl Base URL for resolving paths
3156
* @returns {AliasOption[]} Array of alias options
3257
*/
33-
function convertPathsToAliases(paths, baseUrl) {
58+
function getAbsoluteMappingEntries(paths, baseUrl) {
59+
/** @type {string[]} */
60+
const sortedKeys = sortByLongestPrefix(Object.keys(paths));
3461
/** @type {AliasOption[]} */
35-
const aliases = [];
36-
37-
for (const [pattern, mappings] of Object.entries(paths)) {
38-
// Handle exact matches (no wildcards)
39-
if (!pattern.includes("*")) {
40-
if (mappings.length > 0) {
41-
const targetPath = path.join(baseUrl, mappings[0]);
42-
aliases.push({ name: pattern, alias: targetPath });
43-
}
44-
} else {
45-
// Handle wildcard patterns by mapping the directory
46-
const aliasName = pattern.replace(/\/\*$/, "");
47-
// Convert targets like "dir/*" -> "dir"
48-
const aliasTargets = mappings.map((mapping) =>
49-
path.join(baseUrl, mapping.replace(/\/\*$/, "")),
50-
);
51-
if (aliasTargets.length > 0) {
52-
aliases.push({ name: aliasName, alias: aliasTargets[0] });
53-
}
62+
const absolutePaths = [];
63+
64+
for (const pattern of sortedKeys) {
65+
const mappings = paths[pattern];
66+
const aliasName = pattern.replace(/\/\*$/, "");
67+
// Convert targets like "dir/*" -> "dir"
68+
const aliasTargets = mappings.map((mapping) =>
69+
path.join(baseUrl, mapping.replace(/\/\*$/, "")),
70+
);
71+
if (aliasTargets.length > 0) {
72+
absolutePaths.push({ name: aliasName, alias: aliasTargets });
5473
}
5574
}
5675

57-
return aliases;
76+
return absolutePaths;
5877
}
5978

6079
/**
@@ -65,9 +84,8 @@ function convertPathsToAliases(paths, baseUrl) {
6584
*/
6685
async function readTsconfigCompilerOptions(fileSystem, absTsconfigPath) {
6786
try {
68-
const fs = fileSystem;
6987
const data = await new Promise((resolve, reject) => {
70-
fs.readFile(absTsconfigPath, "utf8", (err, data) => {
88+
fileSystem.readFile(absTsconfigPath, "utf8", (err, data) => {
7189
if (err) reject(err);
7290
resolve(data);
7391
});
@@ -112,63 +130,12 @@ async function loadTsconfigPaths(fileSystem, configFile) {
112130
const fileDependencies = new Set();
113131

114132
aliases.push(
115-
...convertPathsToAliases(
133+
...getAbsoluteMappingEntries(
116134
mainOptions.paths,
117135
/** @type {string} */ (mainOptions.baseUrl),
118136
),
119137
);
120138

121-
// Collect references from the main tsconfig.json
122-
const tsconfigJson = await new Promise((resolve, reject) => {
123-
fs.readFile(configPath, "utf8", (err, data) => {
124-
if (err) reject(err);
125-
resolve(JSON.parse(/** @type {string} */ (data)));
126-
});
127-
});
128-
fileDependencies.add(configPath);
129-
130-
const references = Array.isArray(tsconfigJson.references)
131-
? tsconfigJson.references
132-
: [];
133-
134-
for (const ref of references) {
135-
/** @type {string} */
136-
// TypeScript allows string or object with path
137-
const refPathLike = typeof ref === "string" ? ref : ref && ref.path;
138-
if (!refPathLike) continue;
139-
let refPath = path.isAbsolute(refPathLike)
140-
? refPathLike
141-
: path.join(path.dirname(configPath), refPathLike);
142-
// If reference points to a directory, append tsconfig.json
143-
try {
144-
const stat = await new Promise((resolve, reject) => {
145-
fs.stat(refPath, (err, stat) => {
146-
if (err) reject(err);
147-
resolve(stat);
148-
});
149-
});
150-
if (stat.isDirectory()) {
151-
refPath = path.join(refPath, "tsconfig.json");
152-
}
153-
} catch (_e) {
154-
// if it doesn't exist as directory/file, try adding tsconfig.json
155-
if (!/\.json$/i.test(refPath)) {
156-
refPath = path.join(refPath, "tsconfig.json");
157-
}
158-
}
159-
160-
const refOptions = await readTsconfigCompilerOptions(fs, refPath);
161-
if (!refOptions) continue;
162-
fileDependencies.add(refPath);
163-
164-
aliases.push(
165-
...convertPathsToAliases(
166-
refOptions.paths,
167-
/** @type {string} */ (refOptions.baseUrl),
168-
),
169-
);
170-
}
171-
172139
return {
173140
aliases,
174141
fileDependencies,
@@ -187,7 +154,7 @@ function tsconfigPathsToResolveOptions(aliases) {
187154
const modules = [];
188155
for (const opt of aliases) {
189156
if (opt.name === "*") {
190-
modules.push(/** @type {string} */ (opt.alias));
157+
modules.push(.../** @type {Array<string>} */ (opt.alias));
191158
} else {
192159
alias.push(opt);
193160
}
@@ -218,7 +185,7 @@ async function loadTsconfigFile(fileSystem, configFile) {
218185
}
219186
}
220187

221-
module.exports.convertPathsToAliases = convertPathsToAliases;
188+
module.exports.getAbsoluteMappingEntries = getAbsoluteMappingEntries;
222189
module.exports.loadTsconfigFile = loadTsconfigFile;
223190
module.exports.loadTsconfigPaths = loadTsconfigPaths;
224191
module.exports.readTsconfigCompilerOptions = readTsconfigCompilerOptions;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const a = 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const a = 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const a = 1

test/fixtures/tsconfig-paths/example/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"foo": ["./src/mapped/foo"],
99
"bar/*": ["./src/mapped/bar/*"],
1010
"refs/*": ["./src/refs/*"],
11-
"*": ["./src/mapped/star/*"]
11+
"*": ["./src/mapped/longest/one.ts", "./src/mapped/star/*"],
12+
"longest/*": ["./src/mapped/longest/two.ts"]
1213
},
1314
"composite": true
1415
}

test/tsconfig-paths.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,25 @@ describe("TsconfigPathsPlugin", () => {
109109
});
110110
});
111111

112+
it("when multiple patterns match a module specifier, the pattern with the longest matching prefix before any * token is used:", (done) => {
113+
const resolver = ResolverFactory.createResolver({
114+
fileSystem,
115+
extensions: [".ts", ".tsx"],
116+
mainFields: ["browser", "main"],
117+
mainFiles: ["index"],
118+
tsconfig: path.join(exampleDir, "tsconfig.json"),
119+
});
120+
121+
resolver.resolve({}, exampleDir, "longest/bar", {}, (err, result) => {
122+
if (err) return done(err);
123+
if (!result) return done(new Error("No result"));
124+
expect(result).toEqual(
125+
path.join(exampleDir, "src", "mapped", "longest", "two.ts"),
126+
);
127+
done();
128+
});
129+
});
130+
112131
it("resolves 'foo' using references from referenceExample project", (done) => {
113132
const resolver = ResolverFactory.createResolver({
114133
fileSystem,

0 commit comments

Comments
 (0)