-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
index.ts
142 lines (121 loc) · 4.46 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import path from 'path';
import { normalizePath, type Plugin as VitePlugin, type ResolvedConfig } from 'vite';
import type { AstroSettings } from '../@types/astro';
type Alias = {
find: RegExp;
replacement: string;
};
/** Returns a list of compiled aliases. */
const getConfigAlias = (settings: AstroSettings): Alias[] | null => {
const { tsConfig, tsConfigPath } = settings;
if (!tsConfig || !tsConfigPath || !tsConfig.compilerOptions) return null;
const { baseUrl, paths } = tsConfig.compilerOptions;
if (!baseUrl) return null;
// resolve the base url from the configuration file directory
const resolvedBaseUrl = path.resolve(path.dirname(tsConfigPath), baseUrl);
const aliases: Alias[] = [];
// compile any alias expressions and push them to the list
if (paths) {
for (const [alias, values] of Object.entries(paths)) {
/** Regular Expression used to match a given path. */
const find = new RegExp(
`^${[...alias]
.map((segment) =>
segment === '*' ? '(.+)' : segment.replace(/[\\^$*+?.()|[\]{}]/, '\\$&')
)
.join('')}$`
);
/** Internal index used to calculate the matching id in a replacement. */
let matchId = 0;
for (const value of values) {
/** String used to replace a matched path. */
const replacement = [...normalizePath(path.resolve(resolvedBaseUrl, value))]
.map((segment) => (segment === '*' ? `$${++matchId}` : segment === '$' ? '$$' : segment))
.join('');
aliases.push({ find, replacement });
}
}
}
// compile the baseUrl expression and push it to the list
// - `baseUrl` changes the way non-relative specifiers are resolved
// - if `baseUrl` exists then all non-relative specifiers are resolved relative to it
aliases.push({
find: /^(?!\.*\/|\.*$|\w:)(.+)$/,
replacement: `${[...normalizePath(resolvedBaseUrl)]
.map((segment) => (segment === '$' ? '$$' : segment))
.join('')}/$1`,
});
return aliases;
};
/** Returns a Vite plugin used to alias paths from tsconfig.json and jsconfig.json. */
export default function configAliasVitePlugin({
settings,
}: {
settings: AstroSettings;
}): VitePlugin | null {
const configAlias = getConfigAlias(settings);
if (!configAlias) return null;
const plugin: VitePlugin = {
name: 'astro:tsconfig-alias',
enforce: 'pre',
configResolved(config) {
patchCreateResolver(config, plugin);
},
async resolveId(id, importer, options) {
if (isVirtualId(id)) return;
// Handle aliases found from `compilerOptions.paths`. Unlike Vite aliases, tsconfig aliases
// are best effort only, so we have to manually replace them here, instead of using `vite.resolve.alias`
for (const alias of configAlias) {
if (alias.find.test(id)) {
const updatedId = id.replace(alias.find, alias.replacement);
const resolved = await this.resolve(updatedId, importer, { skipSelf: true, ...options });
if (resolved) return resolved;
}
}
},
};
return plugin;
}
/**
* Vite's `createResolver` is used to resolve various things, including CSS `@import`.
* However, there's no way to extend this resolver, besides patching it. This function
* patches and adds a Vite plugin whose `resolveId` will be used to resolve before the
* internal plugins in `createResolver`.
*
* Vite may simplify this soon: https://github.com/vitejs/vite/pull/10555
*/
function patchCreateResolver(config: ResolvedConfig, prePlugin: VitePlugin) {
const _createResolver = config.createResolver;
// @ts-expect-error override readonly property intentionally
config.createResolver = function (...args1: any) {
const resolver = _createResolver.apply(config, args1);
return async function (...args2: any) {
const id: string = args2[0];
const importer: string | undefined = args2[1];
const ssr: boolean | undefined = args2[3];
// fast path so we don't run this extensive logic in prebundling
if (importer?.includes('node_modules')) {
return resolver.apply(_createResolver, args2);
}
const fakePluginContext = {
resolve: (_id: string, _importer?: string) => resolver(_id, _importer, false, ssr),
};
const fakeResolveIdOpts = {
assertions: {},
isEntry: false,
ssr,
};
// @ts-expect-error resolveId exists
const resolved = await prePlugin.resolveId.apply(fakePluginContext, [
id,
importer,
fakeResolveIdOpts,
]);
if (resolved) return resolved;
return resolver.apply(_createResolver, args2);
};
};
}
function isVirtualId(id: string) {
return id.includes('\0') || id.startsWith('virtual:') || id.startsWith('astro:');
}