Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove baseUrl requirement for tsconfig path aliases #12731

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/polite-boxes-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Remove `baseUrl` requirement for tsconfig path aliases. If a `baseUrl` is not set, `paths` entries in your tsconfig file will resolve relative to the tsconfig file. This aligns with [TypeScript’s module resolution strategy](https://www.typescriptlang.org/docs/handbook/modules/reference.html#relationship-to-baseurl).
3 changes: 1 addition & 2 deletions packages/astro/src/vite-plugin-config-alias/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ Consider the following example configuration:
```
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"components:*": ["components/*.astro"]
"components:*": ["./src/components/*.astro"]
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions packages/astro/src/vite-plugin-config-alias/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ const getConfigAlias = (settings: AstroSettings): Alias[] | null => {
const { tsConfig, tsConfigPath } = settings;
if (!tsConfig || !tsConfigPath || !tsConfig.compilerOptions) return null;

const { baseUrl, paths } = tsConfig.compilerOptions as CompilerOptions;
if (!baseUrl) return null;
// TypeScript resolves `paths` relative to the tsconfig file if `baseUrl` is not set
// https://www.typescriptlang.org/docs/handbook/modules/reference.html#relationship-to-baseurl
const { baseUrl = '.', paths } = tsConfig.compilerOptions as CompilerOptions;

// resolve the base url from the configuration file directory
const resolvedBaseUrl = path.resolve(path.dirname(tsConfigPath), baseUrl);
Expand Down
154 changes: 154 additions & 0 deletions packages/astro/test/alias-tsconfig-no-baseurl.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import assert from 'node:assert/strict';
import { after, before, describe, it } from 'node:test';
import * as cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';

describe('Aliases with tsconfig.json', () => {
let fixture;

/**
* @param {string} html
* @returns {string[]}
*/
function getLinks(html) {
let $ = cheerio.load(html);
let out = [];
$('link[rel=stylesheet]').each((_i, el) => {
out.push($(el).attr('href'));
});
return out;
}

/**
* @param {string} href
* @returns {Promise<{ href: string; css: string; }>}
*/
async function getLinkContent(href, f = fixture) {
const css = await f.readFile(href);
return { href, css };
}

before(async () => {
fixture = await loadFixture({
// test suite was authored when inlineStylesheets defaulted to never
build: { inlineStylesheets: 'never' },
root: './fixtures/alias-tsconfig-no-baseurl/',
});
});

describe('dev', () => {
let devServer;

before(async () => {
devServer = await fixture.startDevServer();
});

after(async () => {
await devServer.stop();
});

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

// Should render aliased element
assert.equal($('#client').text(), 'test');

const scripts = $('script').toArray();
assert.ok(scripts.length > 0);
});

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

assert.equal($('#foo').text(), 'foo');
assert.equal($('#constants-foo').text(), 'foo');
assert.equal($('#constants-index').text(), 'index');
});

it('can load namespace packages with @* paths', async () => {
const html = await fixture.fetch('/').then((res) => res.text());
const $ = cheerio.load(html);

assert.equal($('#namespace').text(), 'namespace');
});

it('works in css @import', async () => {
const html = await fixture.fetch('/').then((res) => res.text());
// imported css should be bundled
assert.ok(html.includes('#style-red'));
assert.ok(html.includes('#style-blue'));
});

it('works in components', async () => {
const html = await fixture.fetch('/').then((res) => res.text());
const $ = cheerio.load(html);

assert.equal($('#alias').text(), 'foo');
});

it('works for import.meta.glob', async () => {
const html = await fixture.fetch('/').then((res) => res.text());
const $ = cheerio.load(html);

assert.equal($('#glob').text(), '/src/components/glob/a.js');
});
});

describe('build', () => {
before(async () => {
await fixture.build();
});

it('can load client components', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);

// Should render aliased element
assert.equal($('#client').text(), 'test');

const scripts = $('script').toArray();
assert.ok(scripts.length > 0);
});

it('can load via baseUrl', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);

assert.equal($('#foo').text(), 'foo');
assert.equal($('#constants-foo').text(), 'foo');
assert.equal($('#constants-index').text(), 'index');
});

it('can load namespace packages with @* paths', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);

assert.equal($('#namespace').text(), 'namespace');
});

it('works in css @import', async () => {
const html = await fixture.readFile('/index.html');
const content = await Promise.all(getLinks(html).map((href) => getLinkContent(href)));
const [{ css }] = content;
// imported css should be bundled
assert.ok(css.includes('#style-red'));
assert.ok(css.includes('#style-blue'));
});

it('works in components', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);

assert.equal($('#alias').text(), 'foo');
});

it('works for import.meta.glob', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);

assert.equal($('#glob').text(), '/src/components/glob/a.js');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import svelte from '@astrojs/svelte';
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
integrations: [svelte()],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const namespace = 'namespace';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@test/namespace-package-no-baseurl",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": "./index.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@test/aliases-tsconfig-no-baseurl",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/svelte": "workspace:*",
"@test/namespace-package-no-baseurl": "workspace:*",
"astro": "workspace:*",
"svelte": "^5.2.9"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<script>
import { foo } from 'src/utils/constants';
</script>
<div id="alias">{foo}</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<script></script>
<div id="client">test</div>
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
@@ -0,0 +1 @@
export default 'a';
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
import Alias from '@components/Alias.svelte';
import Client from '@components/Client.svelte'
import '@styles/main.css';
import { namespace } from '@test/namespace-package-no-baseurl';
import Foo from 'src/components/Foo.astro';
import StyleComp from 'src/components/Style.astro';
import { foo, index } from 'src/utils/constants';

const globResult = Object.keys(import.meta.glob('@components/glob/*.js')).join(', ')
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Aliases using tsconfig</title>
</head>
<body>
<main>
<Client client:load />
<Foo />
<StyleComp />
<Alias client:load />
<p id="namespace">{namespace}</p>
<p id="constants-foo">{foo}</p>
<p id="constants-index">{index}</p>
<p id="style-red">style-red</p>
<p id="style-blue">style-blue</p>
<p id="glob">{globResult}</p>
</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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from '.'

export const foo = 'foo'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const index = 'index'
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"paths": {
"@components/*": [
"./src/components/*"
],
"@styles/*": [
"./src/styles/*"
],
// this can really trip up namespaced packages
"@*": [
"./src/*"
],
// this approximates the functionality you get with a baseUrl set
"src/*": [
"./src/*"
]
}
},
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading