{namespace}
+{foo}
+{index}
+style-red
+style-blue
+{globResult}
+diff --git a/.changeset/polite-boxes-tease.md b/.changeset/polite-boxes-tease.md new file mode 100644 index 000000000000..1971ebd81672 --- /dev/null +++ b/.changeset/polite-boxes-tease.md @@ -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). diff --git a/packages/astro/src/vite-plugin-config-alias/README.md b/packages/astro/src/vite-plugin-config-alias/README.md index 3b470adeda33..4106b54ccaed 100644 --- a/packages/astro/src/vite-plugin-config-alias/README.md +++ b/packages/astro/src/vite-plugin-config-alias/README.md @@ -7,9 +7,8 @@ Consider the following example configuration: ``` { "compilerOptions": { - "baseUrl": "src", "paths": { - "components:*": ["components/*.astro"] + "components:*": ["./src/components/*.astro"] } } } diff --git a/packages/astro/src/vite-plugin-config-alias/index.ts b/packages/astro/src/vite-plugin-config-alias/index.ts index 9b102c5648cb..acf1c445b770 100644 --- a/packages/astro/src/vite-plugin-config-alias/index.ts +++ b/packages/astro/src/vite-plugin-config-alias/index.ts @@ -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); diff --git a/packages/astro/test/alias-tsconfig-no-baseurl.test.js b/packages/astro/test/alias-tsconfig-no-baseurl.test.js new file mode 100644 index 000000000000..e97b4937bf4e --- /dev/null +++ b/packages/astro/test/alias-tsconfig-no-baseurl.test.js @@ -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'); + }); + }); +}); diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/astro.config.mjs b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/astro.config.mjs new file mode 100644 index 000000000000..faf568df7356 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/astro.config.mjs @@ -0,0 +1,7 @@ +import svelte from '@astrojs/svelte'; +import { defineConfig } from 'astro/config'; + +// https://astro.build/config +export default defineConfig({ + integrations: [svelte()], +}); diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/deps/namespace-package/index.js b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/deps/namespace-package/index.js new file mode 100644 index 000000000000..fd728a832594 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/deps/namespace-package/index.js @@ -0,0 +1 @@ +export const namespace = 'namespace'; \ No newline at end of file diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/deps/namespace-package/package.json b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/deps/namespace-package/package.json new file mode 100644 index 000000000000..957fd91644b5 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/deps/namespace-package/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/namespace-package-no-baseurl", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": "./index.js" +} diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/package.json b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/package.json new file mode 100644 index 000000000000..d00f54ccbe52 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/package.json @@ -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" + } +} diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Alias.svelte b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Alias.svelte new file mode 100644 index 000000000000..066885bd9690 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Alias.svelte @@ -0,0 +1,4 @@ + +
foo
\ No newline at end of file diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Style.astro b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Style.astro new file mode 100644 index 000000000000..9468ef80e338 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Style.astro @@ -0,0 +1,2 @@ +i am blue
+i am red
\ No newline at end of file diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/glob/a.js b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/glob/a.js new file mode 100644 index 000000000000..0ed5c30080aa --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/glob/a.js @@ -0,0 +1 @@ +export default 'a'; diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/pages/index.astro b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/pages/index.astro new file mode 100644 index 000000000000..e880d48440de --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/pages/index.astro @@ -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' +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(', ') +--- + + + + +{namespace}
+{foo}
+{index}
+style-red
+style-blue
+{globResult}
+