Skip to content

Commit

Permalink
Support tsconfig aliases in styles (#6566)
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy committed Mar 16, 2023
1 parent 6c465e9 commit ea9b3dd
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 80 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-ravens-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Support tsconfig aliases in styles
116 changes: 42 additions & 74 deletions packages/astro/src/vite-plugin-config-alias/index.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,20 @@
import * as path from 'path';
import fs from 'fs';
import path from 'path';
import type { AstroSettings } from '../@types/astro';

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);
import type { Alias, Plugin as VitePlugin } from 'vite';
import type { TsConfigJson } from 'tsconfig-resolver';
import slash from 'slash';

/** Returns a list of compiled aliases. */
const getConfigAlias = (settings: AstroSettings): Alias[] | null => {
/** Closest tsconfig.json or jsconfig.json */
const config = settings.tsConfig;
const configPath = settings.tsConfigPath;

// if no config was found, return null
if (!config || !configPath) return null;

/** Compiler options from tsconfig.json or jsconfig.json. */
const compilerOptions = Object(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(configPath).replace(/^\/?/, '/')),
normalize(compilerOptions.baseUrl)
);

/** List of compiled alias expressions. */
const getConfigAlias = (
paths: NonNullable<TsConfigJson.CompilerOptions['paths']>,
baseUrl: NonNullable<TsConfigJson.CompilerOptions['baseUrl']>
): Alias[] => {
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);

for (const [alias, values] of Object.entries(paths)) {
/** Regular Expression used to match a given path. */
const find = new RegExp(
`^${[...alias]
Expand All @@ -54,7 +27,7 @@ const getConfigAlias = (settings: AstroSettings): Alias[] | null => {
/** Internal index used to calculate the matching id in a replacement. */
let matchId = 0;

for (let value of values) {
for (const value of values) {
/** String used to replace a matched path. */
const replacement = [...path.posix.resolve(baseUrl, value)]
.map((segment) => (segment === '*' ? `$${++matchId}` : segment === '$' ? '$$' : segment))
Expand All @@ -64,14 +37,6 @@ const getConfigAlias = (settings: AstroSettings): Alias[] | null => {
}
}

// 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;
};

Expand All @@ -80,39 +45,42 @@ export default function configAliasVitePlugin({
settings,
}: {
settings: AstroSettings;
}): vite.PluginOption {
const { config } = settings;
/** Aliases from the tsconfig.json or jsconfig.json configuration. */
const configAlias = getConfigAlias(settings);
}): VitePlugin | null {
const { tsConfig, tsConfigPath } = settings;
if (!tsConfig || !tsConfigPath || !tsConfig.compilerOptions) return null;

const { baseUrl, paths } = tsConfig.compilerOptions;
if (!baseUrl || !paths) return null;

// resolve the base url from the configuration file directory
const resolvedBaseUrl = path.posix.resolve(
path.posix.dirname(slash(tsConfigPath).replace(/^\/?/, '/')),
slash(baseUrl)
);

// if no config alias was found, bypass this plugin
if (!configAlias) return {} as vite.PluginOption;
const configAlias = getConfigAlias(paths, resolvedBaseUrl);

return {
name: 'astro:tsconfig-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;
}
config() {
if (configAlias.length) {
return {
resolve: {
alias: configAlias,
},
};
}
},
resolveId(id) {
if (id.startsWith('.') || id.startsWith('/')) return;

// Handle baseUrl mapping for non-relative and non-root imports.
// Since TypeScript only applies `baseUrl` autocompletions for files that exist
// in the filesystem only, we can use this heuristic to skip resolve if needed.
const resolved = path.posix.join(resolvedBaseUrl, id);
if (fs.existsSync(resolved)) {
return resolved;
}
},
};
Expand Down
15 changes: 15 additions & 0 deletions packages/astro/test/alias-tsconfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,20 @@ describe('Aliases with tsconfig.json', () => {
const scripts = $('script').toArray();
expect(scripts.length).to.be.greaterThan(0);
});

it('can load via baseUrl', async () => {
const html = await fixture.fetch('/').then((res) => res.text());
const $ = cheerio.load(html);

expect($('#foo').text()).to.equal('foo');
});

it('works in css @import', async () => {
const html = await fixture.fetch('/').then((res) => res.text());
console.log(html)
// imported css should be bundled
expect(html).to.include('#style-red');
expect(html).to.include('#style-blue');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p id="foo">foo</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<p id="style-blue">i am blue</p>
<p id="style-red">i am red</p>
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
---
import Client from '@components/Client.svelte'
import Foo from 'src/components/Foo.astro';
import StyleComp from 'src/components/Style.astro';
import '@styles/main.css'
---
<html lang="en">
<head>
Expand All @@ -10,6 +13,8 @@ import Client from '@components/Client.svelte'
<body>
<main>
<Client client:load />
<Foo />
<StyleComp />
</main>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#style-red {
color: red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "@styles/extra.css";

#style-blue {
color: blue;
}
9 changes: 3 additions & 6 deletions packages/astro/test/fixtures/alias-tsconfig/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
"@components/*": [
"src/components/*"
],
"@layouts/*": [
"src/layouts/*"
],
"@assets/*": [
"src/assets/*"
],
"@styles/*": [
"src/styles/*"
]
}
}
}

0 comments on commit ea9b3dd

Please sign in to comment.