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}
diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Client.svelte b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Client.svelte new file mode 100644 index 000000000000..2450d326aac4 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Client.svelte @@ -0,0 +1,2 @@ + +
test
diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Foo.astro b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Foo.astro new file mode 100644 index 000000000000..42bd5c2a5349 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/components/Foo.astro @@ -0,0 +1 @@ +

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(', ') +--- + + + + + Aliases using tsconfig + + +
+ + + + +

{namespace}

+

{foo}

+

{index}

+

style-red

+

style-blue

+

{globResult}

+
+ + diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/styles/extra.css b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/styles/extra.css new file mode 100644 index 000000000000..0b41276e8545 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/styles/extra.css @@ -0,0 +1,3 @@ +#style-red { + color: red; +} \ No newline at end of file diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/styles/main.css b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/styles/main.css new file mode 100644 index 000000000000..9fd83beee390 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/styles/main.css @@ -0,0 +1,5 @@ +@import "@styles/extra.css"; + +#style-blue { + color: blue; +} \ No newline at end of file diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/utils/constants.js b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/utils/constants.js new file mode 100644 index 000000000000..28e8a5c17c6d --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/utils/constants.js @@ -0,0 +1,3 @@ +export * from '.' + +export const foo = 'foo' diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/utils/index.js b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/utils/index.js new file mode 100644 index 000000000000..96896d7118eb --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/src/utils/index.js @@ -0,0 +1 @@ +export const index = 'index' diff --git a/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/tsconfig.json b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/tsconfig.json new file mode 100644 index 000000000000..b29bad6dfe63 --- /dev/null +++ b/packages/astro/test/fixtures/alias-tsconfig-no-baseurl/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "paths": { + "@components/*": [ + "./src/components/*" + ], + "@styles/*": [ + "./src/styles/*" + ], + // this can really trip up namespaced packages + "@*": [ + "./src/*" + ] + } + }, + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e47377c548b9..18ced319d1a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1801,6 +1801,23 @@ importers: specifier: ^5.2.9 version: 5.2.9 + packages/astro/test/fixtures/alias-tsconfig-no-baseurl: + dependencies: + '@astrojs/svelte': + specifier: workspace:* + version: link:../../../../integrations/svelte + '@test/namespace-package-no-baseurl': + specifier: workspace:* + version: link:deps/namespace-package + astro: + specifier: workspace:* + version: link:../../.. + svelte: + specifier: ^5.2.9 + version: 5.2.9 + + packages/astro/test/fixtures/alias-tsconfig-no-baseurl/deps/namespace-package: {} + packages/astro/test/fixtures/alias-tsconfig/deps/namespace-package: {} packages/astro/test/fixtures/api-routes: