Support tsconfig.json & jsconfig.json aliases (#1747)
* Resolve paths from tsconfig or jsconfig https://code.visualstudio.com/docs/languages/jsconfig https://nextjs.org/docs/advanced-features/module-path-aliases * edit: rename plugin to `@astrojs/vite-plugin-tsconfig-alias` Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> * edit: switch from `ps` to `path.posix` * edit: move sanitization of paths to loop * edit: rename `resolveConfigPaths` to `configAliasVitePlugin` * edit: update implementation based on feedback * prettier * edit: rename `matchTailingAsterisk` to `matchTrailingAsterisk` * edit: cleanup with comments * edit: spellcheck `condition` to `conditionally` * edit: refactor based on feedback * edit: Update README.md * edit: cleanup baseUrl transformation and add explainer comments * edit: cleanup resolutions and add commenting * yarn lint Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
parent
7acf762c12
commit
ba38147ccc
5 changed files with 154 additions and 3 deletions
|
@ -96,6 +96,7 @@
|
|||
"strip-indent": "^4.0.0",
|
||||
"supports-esm": "^1.0.0",
|
||||
"tiny-glob": "^0.2.8",
|
||||
"tsconfig-resolver": "^3.0.1",
|
||||
"vite": "^2.6.10",
|
||||
"yargs-parser": "^20.2.9",
|
||||
"zod": "^3.8.1"
|
||||
|
|
|
@ -6,6 +6,7 @@ import { fileURLToPath } from 'url';
|
|||
import vite from './vite.js';
|
||||
import astroVitePlugin from '../vite-plugin-astro/index.js';
|
||||
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
|
||||
import configAliasVitePlugin from '../vite-plugin-config-alias/index.js';
|
||||
import markdownVitePlugin from '../vite-plugin-markdown/index.js';
|
||||
import jsxVitePlugin from '../vite-plugin-jsx/index.js';
|
||||
import fetchVitePlugin from '../vite-plugin-fetch/index.js';
|
||||
|
@ -47,6 +48,7 @@ export async function createVite(inlineConfig: ViteConfigWithSSR, { astroConfig,
|
|||
entries: ['src/**/*'], // Try and scan a user’s project (won’t catch everything),
|
||||
},
|
||||
plugins: [
|
||||
configAliasVitePlugin({ config: astroConfig }),
|
||||
astroVitePlugin({ config: astroConfig, devServer }),
|
||||
markdownVitePlugin({ config: astroConfig, devServer }),
|
||||
jsxVitePlugin({ config: astroConfig, logging }),
|
||||
|
|
26
packages/astro/src/vite-plugin-config-alias/README.md
Normal file
26
packages/astro/src/vite-plugin-config-alias/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# vite-plugin-config-alias
|
||||
|
||||
This adds aliasing support to Vite from `tsconfig.json` or `jsconfig.json` files.
|
||||
|
||||
Consider the following example configuration:
|
||||
|
||||
```
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"components:*": ["components/*.astro"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With this configuration, the following imports would map to the same location.
|
||||
|
||||
```js
|
||||
import Test from '../components/Test.astro'
|
||||
|
||||
import Test from 'components/Test.astro'
|
||||
|
||||
import Test from 'components:Test'
|
||||
```
|
105
packages/astro/src/vite-plugin-config-alias/index.ts
Normal file
105
packages/astro/src/vite-plugin-config-alias/index.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import * as tsr from 'tsconfig-resolver';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
import type * as vite from 'vite';
|
||||
|
||||
/** Result of successfully parsed tsconfig.json or jsconfig.json. */
|
||||
export declare interface Alias {
|
||||
find: RegExp;
|
||||
replacement: string;
|
||||
}
|
||||
|
||||
/** Returns a path with its slashes replaced with posix slashes. */
|
||||
const normalize = (pathname: string) => String(pathname).split(path.sep).join(path.posix.sep);
|
||||
|
||||
/** Returns the results of a config file if it exists, otherwise null. */
|
||||
const getExistingConfig = (searchName: string, cwd: string | undefined): tsr.TsConfigResultSuccess | null => {
|
||||
const config = tsr.tsconfigResolverSync({ cwd, searchName });
|
||||
|
||||
return config.exists ? config : null;
|
||||
};
|
||||
|
||||
/** Returns a list of compiled aliases. */
|
||||
const getConfigAlias = (cwd: string | undefined): Alias[] | null => {
|
||||
/** Closest tsconfig.json or jsconfig.json */
|
||||
const config = getExistingConfig('tsconfig.json', cwd) || getExistingConfig('jsconfig.json', cwd);
|
||||
|
||||
// if no config was found, return null
|
||||
if (!config) return null;
|
||||
|
||||
/** Compiler options from tsconfig.json or jsconfig.json. */
|
||||
const compilerOptions = Object(config.config.compilerOptions);
|
||||
|
||||
// if no compilerOptions.baseUrl was defined, return null
|
||||
if (!compilerOptions.baseUrl) return null;
|
||||
|
||||
// resolve the base url from the configuration file directory
|
||||
const baseUrl = path.posix.resolve(path.posix.dirname(normalize(config.path)), normalize(compilerOptions.baseUrl));
|
||||
|
||||
/** List of compiled alias expressions. */
|
||||
const aliases: Alias[] = [];
|
||||
|
||||
// compile any alias expressions and push them to the list
|
||||
for (let [alias, values] of Object.entries(Object(compilerOptions.paths) as { [key: string]: string[] })) {
|
||||
values = [].concat(values as never);
|
||||
|
||||
/** 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 (let value of values) {
|
||||
/** String used to replace a matched path. */
|
||||
const replacement = [...path.posix.resolve(baseUrl, 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: /^(?!\.*\/)(.+)$/,
|
||||
replacement: `${[...baseUrl].map((segment) => (segment === '$' ? '$$' : segment)).join('')}/$1`,
|
||||
});
|
||||
|
||||
return aliases;
|
||||
};
|
||||
|
||||
/** Returns a Vite plugin used to alias pathes from tsconfig.json and jsconfig.json. */
|
||||
export default function configAliasVitePlugin(astroConfig: { projectRoot?: URL; [key: string]: unknown }): vite.PluginOption {
|
||||
/** Aliases from the tsconfig.json or jsconfig.json configuration. */
|
||||
const configAlias = getConfigAlias(astroConfig.projectRoot && url.fileURLToPath(astroConfig.projectRoot));
|
||||
|
||||
// if no config alias was found, bypass this plugin
|
||||
if (!configAlias) return {} as vite.PluginOption;
|
||||
|
||||
return {
|
||||
name: '@astrojs/vite-plugin-config-alias',
|
||||
enforce: 'pre',
|
||||
async resolveId(sourceId: string, importer, options) {
|
||||
/** Resolved ID conditionally handled by any other resolver. (this gives priority to all other resolvers) */
|
||||
const resolvedId = await this.resolve(sourceId, importer, { skipSelf: true, ...options });
|
||||
|
||||
// if any other resolver handles the file, return that resolution
|
||||
if (resolvedId) return resolvedId;
|
||||
|
||||
// conditionally resolve the source ID from any matching alias or baseUrl
|
||||
for (const alias of configAlias) {
|
||||
if (alias.find.test(sourceId)) {
|
||||
/** Processed Source ID with our alias applied. */
|
||||
const aliasedSourceId = sourceId.replace(alias.find, alias.replacement);
|
||||
|
||||
/** Resolved ID conditionally handled by any other resolver. (this also gives priority to all other resolvers) */
|
||||
const resolvedAliasedId = await this.resolve(aliasedSourceId, importer, { skipSelf: true, ...options });
|
||||
|
||||
// if the existing resolvers find the file, return that resolution
|
||||
if (resolvedAliasedId) return resolvedAliasedId;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
23
yarn.lock
23
yarn.lock
|
@ -1898,6 +1898,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
||||
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
|
||||
|
||||
"@types/json5@^0.0.30":
|
||||
version "0.0.30"
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.30.tgz#44cb52f32a809734ca562e685c6473b5754a7818"
|
||||
integrity sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==
|
||||
|
||||
"@types/mdast@^3.0.0", "@types/mdast@^3.0.3":
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
|
||||
|
@ -1990,7 +1995,7 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/resolve@^1.20.1":
|
||||
"@types/resolve@^1.17.0", "@types/resolve@^1.20.1":
|
||||
version "1.20.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.1.tgz#3727e48042fda81e374f5d5cf2fa92288bf698f8"
|
||||
integrity sha512-Ku5+GPFa12S3W26Uwtw+xyrtIpaZsGYHH6zxNbZlstmlvMYSZRzOwzwsXbxlVUbHyUucctSyuFtu6bNxwYomIw==
|
||||
|
@ -6458,7 +6463,7 @@ json5@^0.5.1:
|
|||
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
|
||||
integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
|
||||
|
||||
json5@^2.1.2:
|
||||
json5@^2.1.2, json5@^2.1.3:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
|
||||
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
|
||||
|
@ -9527,7 +9532,7 @@ resolve-path@^1.4.0:
|
|||
http-errors "~1.6.2"
|
||||
path-is-absolute "1.0.1"
|
||||
|
||||
resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.13.1, resolve@^1.20.0, resolve@^1.3.2:
|
||||
resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.3.2:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
||||
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
||||
|
@ -10608,6 +10613,18 @@ ts-morph@^12.0.0:
|
|||
"@ts-morph/common" "~0.11.0"
|
||||
code-block-writer "^10.1.1"
|
||||
|
||||
tsconfig-resolver@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tsconfig-resolver/-/tsconfig-resolver-3.0.1.tgz#c9e62e328ecfbeaae4a4f1131a92cdbed12350c4"
|
||||
integrity sha512-ZHqlstlQF449v8glscGRXzL6l2dZvASPCdXJRWG4gHEZlUVx2Jtmr+a2zeVG4LCsKhDXKRj5R3h0C/98UcVAQg==
|
||||
dependencies:
|
||||
"@types/json5" "^0.0.30"
|
||||
"@types/resolve" "^1.17.0"
|
||||
json5 "^2.1.3"
|
||||
resolve "^1.17.0"
|
||||
strip-bom "^4.0.0"
|
||||
type-fest "^0.13.1"
|
||||
|
||||
tslib@^1.8.1, tslib@^1.9.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
|
|
Loading…
Reference in a new issue