Skip to content

Commit f4037d3

Browse files
Merge pull request #4 from yakovlev-alexey/match-tsconfig
Should resolve tsconfig.json like Parcel does
2 parents f745466 + 7fbddb2 commit f4037d3

File tree

18 files changed

+122
-58
lines changed

18 files changed

+122
-58
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Simply add `parcel-resolver-ts-base-url` to your `.parcelrc`:
3434
}
3535
```
3636

37-
> At the moment `parcel` doesn't provide a way to specify `tsconfig.json` location I know of. `parcel-resolver-ts-base-url` will use `tsconfig.json` from the project root.
37+
> At the moment `parcel` doesn't provide a way to specify `tsconfig.json` location I know of. `parcel-resolver-ts-base-url` will look for `tsconfig.json` recursively from the import location to the project root (`.parcelrc` location) like Parcel itself does.
3838
3939
Read more about `parcel` configuration in [official docs](https://parceljs.org/features/plugins/).
4040

run-tests.sh

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@ RED='\033[0;31m'
44
GREEN='\033[0;32m'
55
NC='\033[0m'
66

7-
# parcel would not allow to use multiple .tsconfig.json files in the same npm project
8-
# to combat this we move npm and parcel files around test directories
9-
# so that we do not have to duplicate files and install deps multiple times
10-
files_to_move=("node_modules" ".parcelrc" "package.json" "yarn.lock")
11-
tests=("base-url" "paths" "base-url-paths" "base-url-resource" "paths-resource" "base-url-paths-resource" "paths-invalid-match")
12-
negative_tests=("paths-invalid-match")
7+
tests=("base-url" "paths" "base-url-paths" "base-url-resource" "paths-resource" "base-url-paths-resource" "paths-invalid-match" "no-tsconfig" "invalid-tsconfig" "js-import")
8+
negative_tests=("paths-invalid-match" "invalid-tsconfig")
139

1410
contains_element() {
1511
local e match="$1"
@@ -27,37 +23,25 @@ is_negative_test(){
2723
fi
2824
}
2925

30-
move_files() {
31-
for file in "${files_to_move[@]}";
32-
do
33-
mv "$1$file" "$2$file"
34-
done
35-
}
36-
3726
cleanup(){
38-
move_files "" "../"
39-
4027
rm -rf .parcel-cache index*
4128

4229
printf "${RED}TEST \"$1\" FAILED!!!${NC}\n"
4330

44-
cd ..
4531
exit 1
4632
}
4733

4834
run_test() {
49-
cd $1
5035
printf "\n======================\n"
5136
printf "Running test \"$1\"...\n"
5237
if is_negative_test $1;
5338
then
54-
yarn parcel build src/main.ts &> /dev/null && cleanup $1
39+
yarn parcel build $1/src/main.ts &> /dev/null && cleanup $1
5540
else
56-
yarn parcel build src/main.ts &> /dev/null || cleanup $1
41+
yarn parcel build $1/src/main.ts &> /dev/null || cleanup $1
5742
fi
5843
rm -rf .parcel-cache index*
5944
printf "${GREEN}TEST \"$1\" SUCCESSFUL${NC}\n"
60-
cd ..
6145
}
6246

6347
cd tests
@@ -67,7 +51,6 @@ yarn install &> /dev/null || (printf "${RED}FAILED TO INSTALL DEPS${NC}\n" && ex
6751
cp ../index.js node_modules/parcel-resolver-ts-base-url/index.js
6852
printf "Deps installed\n"
6953

70-
move_files "" "${tests[0]}/"
7154
run_test ${tests[0]}
7255

7356
for index in "${!tests[@]}";
@@ -77,9 +60,7 @@ do
7760
continue
7861
fi
7962

80-
move_files "${tests[$index-1]}/" "${tests[$index]}/"
8163
run_test ${tests[$index]}
8264
done
8365

84-
move_files "${tests[${#tests[@]} - 1]}/" ""
8566

src/constants.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
const POSSIBLE_EXTENSIONS = ["js", "jsx", "ts", "tsx", "cjs", "mjs"];
22

3-
export { POSSIBLE_EXTENSIONS };
3+
const POSSIBLE_EXTENSION_REGEX = /[.](([cm]js)|([jt]sx?))$/;
4+
5+
export { POSSIBLE_EXTENSIONS, POSSIBLE_EXTENSION_REGEX };

src/index.js

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,45 @@ import { parsePaths } from "./steps/parse-paths";
99
import { resolveBase } from "./steps/resolve-base";
1010
import { resolvePath } from "./steps/resolve-path";
1111

12+
import { POSSIBLE_EXTENSION_REGEX } from "./constants";
13+
1214
export default new Resolver({
1315
async resolve({ specifier, dependency, options }) {
14-
const isTypescriptImport = /\.tsx?$/g.test(
16+
const isJavascriptImport = POSSIBLE_EXTENSION_REGEX.test(
1517
dependency.resolveFrom || ""
1618
);
1719

18-
if (!isTypescriptImport) {
20+
if (!isJavascriptImport) {
1921
return null;
2022
}
2123

2224
try {
23-
const tsConfig = await getTsConfig(
24-
options.projectRoot,
25-
options.inputFS
25+
const { tsConfig, path: sourcePath } = await getTsConfig(
26+
options.inputFS,
27+
dependency.resolveFrom
28+
? path.dirname(dependency.resolveFrom)
29+
: options.projectRoot,
30+
options.projectRoot
2631
);
2732

28-
const rawBaseUrl = tsConfig?.compilerOptions?.baseUrl || ".";
29-
const rawPaths = tsConfig?.compilerOptions?.paths;
33+
const rawBaseUrl = tsConfig.compilerOptions?.baseUrl;
34+
const rawPaths = tsConfig.compilerOptions?.paths;
3035

3136
if (!rawBaseUrl && !rawPaths) {
3237
return null;
3338
}
3439

35-
const baseUrl = path.resolve(options.projectRoot, rawBaseUrl);
40+
const baseUrl = path.resolve(sourcePath, rawBaseUrl || ".");
3641
const paths = parsePaths(rawPaths);
3742

3843
const matchedPath = matchPath(specifier, paths);
3944

4045
if (matchedPath !== null) {
4146
const resolved = await resolvePath(
47+
options.inputFS,
4248
specifier,
4349
matchedPath,
44-
baseUrl,
45-
options.inputFS
50+
baseUrl
4651
);
4752

4853
if (!resolved) {
@@ -56,9 +61,9 @@ export default new Resolver({
5661
}
5762

5863
const resolvedFromBase = await resolveBase(
64+
options.inputFS,
5965
specifier,
60-
baseUrl,
61-
options.inputFS
66+
baseUrl
6267
);
6368

6469
if (resolvedFromBase !== null) {
@@ -67,7 +72,21 @@ export default new Resolver({
6772
invalidateOnFileChange: [resolvedFromBase],
6873
};
6974
}
70-
} catch (err) {}
75+
} catch (err) {
76+
return {
77+
diagnostics: [
78+
{
79+
message:
80+
err instanceof Error
81+
? err.message
82+
: "Unknown error",
83+
hints: [
84+
"Check if a tsconfig.json file exists and has valid configuration in it",
85+
],
86+
},
87+
],
88+
};
89+
}
7190

7291
return null;
7392
},

src/steps/resolve-base.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import path from "path";
33
import { matchFile } from "../utils/match-file";
44

55
/**
6+
* @param {import('@parcel/fs').FileSystem} fs
67
* @param {string} specifier
78
* @param {string} baseUrl
8-
* @param {import('@parcel/fs').FileSystem} fs
99
* @returns {Promise<string | null>}
1010
*/
11-
const resolveBase = async (specifier, baseUrl, fs) => {
11+
const resolveBase = async (fs, specifier, baseUrl) => {
1212
const filePath = path.resolve(baseUrl, specifier);
1313

14-
return await matchFile(filePath, fs);
14+
return await matchFile(fs, filePath);
1515
};
1616

1717
export { resolveBase };

src/steps/resolve-path.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import path from "path";
33
import { matchFile } from "../utils/match-file";
44

55
/**
6+
* @param {import('@parcel/fs').FileSystem} fs
67
* @param {string} specifier
78
* @param {import('./parse-paths').Path} matchedPath
89
* @param {string} baseUrl
9-
* @param {import('@parcel/fs').FileSystem} fs
1010
* @returns {Promise<string | null>}
1111
*/
12-
const resolvePath = async (specifier, matchedPath, baseUrl, fs) => {
12+
const resolvePath = async (fs, specifier, matchedPath, baseUrl) => {
1313
const strippedMatch = matchedPath.match.replace("*", "");
1414

1515
const wildcard = specifier.replace(strippedMatch, "");
@@ -22,7 +22,7 @@ const resolvePath = async (specifier, matchedPath, baseUrl, fs) => {
2222
for (const resolve of resolves) {
2323
const filePath = path.resolve(baseUrl, resolve.replace("*", wildcard));
2424

25-
const matchedFile = await matchFile(filePath, fs);
25+
const matchedFile = await matchFile(fs, filePath);
2626

2727
if (matchedFile) {
2828
return matchedFile;

src/utils/get-package.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { createMemo } from "./memo";
44

55
/**
66
* @param {import('./memo').Memo<Record<string, unknown>>} memo
7-
* @param {string} folder
87
* @param {import('@parcel/fs').FileSystem} fs
8+
* @param {string} folder
99
* @returns {Promise<Record<string, unknown> | null>}
1010
*/
11-
const getPackage = async (memo, folder, fs) => {
11+
const getPackage = async (memo, fs, folder) => {
1212
if (memo[folder]) {
1313
return memo[folder];
1414
}

src/utils/get-ts-config.js

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,51 @@ import { createMemo } from "./memo";
44

55
/**
66
* @param {import('./memo').Memo<Record<string, unknown>>} memo
7-
* @param {string} projectRoot
87
* @param {import('@parcel/fs').FileSystem} fs
9-
* @returns {Promise<Record<string, any>>}
8+
* @param {string} sourcePath
9+
* @param {string} projectRoot
10+
* @returns {Promise<{ tsConfig: Record<string, any>, path: string }>}
1011
*/
11-
const getTsConfig = async (memo, projectRoot, fs) => {
12-
if (memo[projectRoot]) {
13-
return memo[projectRoot];
14-
}
12+
const getTsConfig = async (memo, fs, sourcePath, projectRoot) => {
13+
while (true) {
14+
if (memo[sourcePath]) {
15+
return { tsConfig: memo[sourcePath], path: sourcePath };
16+
}
17+
18+
const tsConfigPath = path.join(sourcePath, "tsconfig.json");
1519

16-
const tsConfigPath = path.join(projectRoot, "tsconfig.json");
17-
const tsConfigContent = await fs.readFile(tsConfigPath, "utf-8");
20+
try {
21+
if (await fs.exists(tsConfigPath)) {
22+
const tsConfigContent = await fs.readFile(
23+
tsConfigPath,
24+
"utf-8"
25+
);
1826

19-
return (memo[projectRoot] = JSON.parse(tsConfigContent));
27+
return {
28+
tsConfig: (memo[sourcePath] = JSON.parse(tsConfigContent)),
29+
path: sourcePath,
30+
};
31+
}
32+
} catch (err) {
33+
throw new Error(
34+
`Unexpected exception when reading ${path.relative(
35+
projectRoot,
36+
tsConfigPath
37+
)}: ${err instanceof Error ? err.message : err}`
38+
);
39+
}
40+
41+
if (sourcePath === projectRoot) {
42+
throw new Error(
43+
`tsconfig.json not found in ${path.relative(
44+
projectRoot,
45+
sourcePath
46+
)}`
47+
);
48+
}
49+
50+
sourcePath = path.join(sourcePath, "..");
51+
}
2052
};
2153

2254
const memoizedGetTsConfig = createMemo(getTsConfig);

src/utils/match-file.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import { POSSIBLE_EXTENSIONS } from "../constants";
55
import { getPackage } from "./get-package";
66

77
/**
8-
* @param {string} matchedPath
98
* @param {import('@parcel/fs').FileSystem} fs
9+
* @param {string} matchedPath
1010
* @param {string[]} extensions
11+
* @returns {Promise<string | null>}
1112
*/
12-
const matchFile = async (matchedPath, fs, extensions = POSSIBLE_EXTENSIONS) => {
13+
const matchFile = async (fs, matchedPath, extensions = POSSIBLE_EXTENSIONS) => {
1314
const exists = await fs.exists(matchedPath);
1415

1516
const stat = exists && (await fs.stat(matchedPath));
@@ -20,7 +21,7 @@ const matchFile = async (matchedPath, fs, extensions = POSSIBLE_EXTENSIONS) => {
2021
return matchedPath;
2122
}
2223

23-
const pkg = await getPackage(matchedPath, fs);
24+
const pkg = await getPackage(fs, matchedPath);
2425

2526
const main = pkg && (pkg.main || pkg.module);
2627
if (typeof main === "string") {

tests/invalid-tsconfig/src/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { test } from "utils/test";
2+
3+
test();

0 commit comments

Comments
 (0)