Skip to content

Commit

Permalink
Support tsconfig.json & jsconfig.json aliases (#1747)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Jonathan Neal and natemoo-re committed Nov 9, 2021
1 parent 7acf762 commit ba38147
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 }),
Expand Down
26 changes: 26 additions & 0 deletions packages/astro/src/vite-plugin-config-alias/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 105 additions & 0 deletions packages/astro/src/vite-plugin-config-alias/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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==
Expand Down Expand Up @@ -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==
Expand Down Expand Up @@ -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==
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit ba38147

Please sign in to comment.