diff --git a/.changeset/healthy-meals-wink.md b/.changeset/healthy-meals-wink.md new file mode 100644 index 000000000..4cc5e10b1 --- /dev/null +++ b/.changeset/healthy-meals-wink.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Expose `getViteConfig` from `astro/config` to unblock usage with Vitest diff --git a/examples/with-vitest/.gitignore b/examples/with-vitest/.gitignore new file mode 100644 index 000000000..02f6e50b4 --- /dev/null +++ b/examples/with-vitest/.gitignore @@ -0,0 +1,19 @@ +# build output +dist/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git a/examples/with-vitest/.npmrc b/examples/with-vitest/.npmrc new file mode 100644 index 000000000..ef83021af --- /dev/null +++ b/examples/with-vitest/.npmrc @@ -0,0 +1,2 @@ +# Expose Astro dependencies for `pnpm` users +shamefully-hoist=true diff --git a/examples/with-vitest/.stackblitzrc b/examples/with-vitest/.stackblitzrc new file mode 100644 index 000000000..43798ecff --- /dev/null +++ b/examples/with-vitest/.stackblitzrc @@ -0,0 +1,6 @@ +{ + "startCommand": "npm start", + "env": { + "ENABLE_CJS_IMPORTS": true + } +} \ No newline at end of file diff --git a/examples/with-vitest/README.md b/examples/with-vitest/README.md new file mode 100644 index 000000000..8f1f2e6a0 --- /dev/null +++ b/examples/with-vitest/README.md @@ -0,0 +1,9 @@ +# Astro + [Vitest](https://vitest.dev/) Example + +``` +npm init astro -- --template with-vitest +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/with-vitest) + +This example showcases Astro working with [Vitest](https://vitest.dev/). diff --git a/examples/with-vitest/astro.config.ts b/examples/with-vitest/astro.config.ts new file mode 100644 index 000000000..ad7965b1a --- /dev/null +++ b/examples/with-vitest/astro.config.ts @@ -0,0 +1,4 @@ +import { defineConfig } from 'astro/config'; + +// https://astro.build/config +export default defineConfig(); diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json new file mode 100644 index 000000000..c445064c9 --- /dev/null +++ b/examples/with-vitest/package.json @@ -0,0 +1,17 @@ +{ + "name": "@example/with-vitest", + "version": "0.0.1", + "type": "module", + "private": true, + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "test": "vitest" + }, + "devDependencies": { + "astro": "^1.0.0-rc.4", + "vitest": "^0.20.3" + } +} diff --git a/examples/with-vitest/public/favicon.ico b/examples/with-vitest/public/favicon.ico new file mode 100644 index 000000000..578ad458b Binary files /dev/null and b/examples/with-vitest/public/favicon.ico differ diff --git a/examples/with-vitest/sandbox.config.json b/examples/with-vitest/sandbox.config.json new file mode 100644 index 000000000..9178af77d --- /dev/null +++ b/examples/with-vitest/sandbox.config.json @@ -0,0 +1,11 @@ +{ + "infiniteLoopProtection": true, + "hardReloadOnChange": false, + "view": "browser", + "template": "node", + "container": { + "port": 3000, + "startScript": "start", + "node": "14" + } +} diff --git a/examples/with-vitest/src/pages/index.astro b/examples/with-vitest/src/pages/index.astro new file mode 100644 index 000000000..4389d5d25 --- /dev/null +++ b/examples/with-vitest/src/pages/index.astro @@ -0,0 +1,13 @@ +--- +--- + + + + + + Astro + + +

Astro

+ + diff --git a/examples/with-vitest/test/basic.test.ts b/examples/with-vitest/test/basic.test.ts new file mode 100644 index 000000000..0f6d96168 --- /dev/null +++ b/examples/with-vitest/test/basic.test.ts @@ -0,0 +1,21 @@ +import { assert, expect, test } from 'vitest' + +// Edit an assertion and save to see HMR in action + +test('Math.sqrt()', () => { + expect(Math.sqrt(4)).toBe(2) + expect(Math.sqrt(144)).toBe(12) + expect(Math.sqrt(2)).toBe(Math.SQRT2) +}) + +test('JSON', () => { + const input = { + foo: 'hello', + bar: 'world', + } + + const output = JSON.stringify(input) + + expect(output).eq('{"foo":"hello","bar":"world"}') + assert.deepEqual(JSON.parse(output), input, 'matches original') +}) diff --git a/examples/with-vitest/tsconfig.json b/examples/with-vitest/tsconfig.json new file mode 100644 index 000000000..be8e3ea96 --- /dev/null +++ b/examples/with-vitest/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + // Preact specific settings + "jsx": "react-jsx", + "jsxImportSource": "preact", + // Enable top-level await, and other modern ESM features. + "target": "ESNext", + "module": "ESNext", + // Enable node-style module resolution, for things like npm package imports. + "moduleResolution": "node", + // Enable JSON imports. + "resolveJsonModule": true, + // Enable stricter transpilation for better output. + "isolatedModules": true, + // Add type definitions for our Astro runtime. + "types": ["astro/client"] + } +} diff --git a/examples/with-vitest/vitest.config.ts b/examples/with-vitest/vitest.config.ts new file mode 100644 index 000000000..a34f19bb1 --- /dev/null +++ b/examples/with-vitest/vitest.config.ts @@ -0,0 +1,9 @@ +/// +import { getViteConfig } from 'astro/config'; + +export default getViteConfig({ + test: { + /* for example, use global to avoid globals imports (describe, test, expect): */ + // globals: true, + }, +}); diff --git a/packages/astro/config.d.ts b/packages/astro/config.d.ts index b63f18336..b43ea268d 100644 --- a/packages/astro/config.d.ts +++ b/packages/astro/config.d.ts @@ -1,3 +1,4 @@ +type ViteUserConfig = import('vite').UserConfig; type AstroUserConfig = import('./dist/types/@types/astro').AstroUserConfig; /** @@ -5,3 +6,8 @@ type AstroUserConfig = import('./dist/types/@types/astro').AstroUserConfig; * https://astro.build/config */ export function defineConfig(config: AstroUserConfig): AstroUserConfig; + +/** + * Use Astro to generate a fully resolved Vite config + */ +export function getViteConfig(config: ViteUserConfig): ViteUserConfig; diff --git a/packages/astro/config.mjs b/packages/astro/config.mjs index cf19c5aa4..47193dbff 100644 --- a/packages/astro/config.mjs +++ b/packages/astro/config.mjs @@ -1,3 +1,43 @@ export function defineConfig(config) { return config; } + +export function getViteConfig(inlineConfig) { + // Return an async Vite config getter which exposes a resolved `mode` and `command` + return async ({ mode, command }) => { + // Vite `command` is `serve | build`, but Astro uses `dev | build` + const cmd = command === 'serve' ? 'dev' : command; + + // Use dynamic import to avoid pulling in deps unless used + const [ + { mergeConfig }, + { nodeLogDestination }, + { openConfig }, + { createVite }, + { runHookConfigSetup, runHookConfigDone }, + ] = await Promise.all([ + import('vite'), + import('./dist/core/logger/node.js'), + import('./dist/core/config.js'), + import('./dist/core/create-vite.js'), + import('./dist/integrations/index.js'), + ]); + const logging = { + dest: nodeLogDestination, + level: 'info', + }; + const { astroConfig: config } = await openConfig({ + cmd, + logging, + }); + await runHookConfigSetup({ config, command: cmd }); + const viteConfig = await createVite( + { + mode, + }, + { astroConfig: config, logging: logging, mode } + ); + await runHookConfigDone({ config }); + return mergeConfig(viteConfig, inlineConfig); + } +} diff --git a/packages/astro/package.json b/packages/astro/package.json index d5ee1cbcf..9a47eebee 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -32,7 +32,6 @@ "./jsx/*": "./dist/jsx/*", "./jsx-runtime": "./dist/jsx-runtime/index.js", "./config": "./config.mjs", - "./internal": "./internal.js", "./app": "./dist/core/app/index.js", "./app/node": "./dist/core/app/node.js", "./client/*": "./dist/runtime/client/*", diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 631c1dc2b..d197fb409 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -24,7 +24,7 @@ export type ViteConfigWithSSR = vite.InlineConfig & { ssr?: vite.SSROptions }; interface CreateViteOptions { astroConfig: AstroConfig; logging: LogOptions; - mode: 'dev' | 'build'; + mode: 'dev' | 'build' | string; } const ALWAYS_NOEXTERNAL = new Set([ @@ -74,7 +74,7 @@ export async function createVite( astroScriptsPlugin({ config: astroConfig }), // The server plugin is for dev only and having it run during the build causes // the build to run very slow as the filewatcher is triggered often. - mode === 'dev' && astroViteServerPlugin({ config: astroConfig, logging }), + mode !== 'build' && astroViteServerPlugin({ config: astroConfig, logging }), envVitePlugin({ config: astroConfig }), markdownVitePlugin({ config: astroConfig, logging }), htmlVitePlugin(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 121e6c5eb..ff53ebbca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -374,6 +374,14 @@ importers: vite-plugin-pwa: 0.11.11 workbox-window: 6.5.4 + examples/with-vitest: + specifiers: + astro: ^1.0.0-rc.4 + vitest: ^0.20.3 + devDependencies: + astro: link:../../packages/astro + vitest: 0.20.3 + packages/astro: specifiers: '@astrojs/compiler': ^0.22.1 @@ -8364,6 +8372,12 @@ packages: '@types/chai': 4.3.1 dev: true + /@types/chai-subset/1.3.3: + resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + dependencies: + '@types/chai': 4.3.1 + dev: true + /@types/chai/4.3.1: resolution: {integrity: sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==} dev: true @@ -15820,6 +15834,16 @@ packages: globalyzer: 0.1.0 globrex: 0.1.2 + /tinypool/0.2.4: + resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy/1.0.0: + resolution: {integrity: sha512-FI5B2QdODQYDRjfuLF+OrJ8bjWRMCXokQPcwKm0W3IzcbUmBNv536cQc7eXGoAuXphZwgx1DFbqImwzz08Fnhw==} + engines: {node: '>=14.0.0'} + dev: true + /tmp/0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -16491,7 +16515,6 @@ packages: rollup: 2.77.2 optionalDependencies: fsevents: 2.3.2 - dev: false /vite/3.0.4_sass@1.54.3: resolution: {integrity: sha512-NU304nqnBeOx2MkQnskBQxVsa0pRAH5FphokTGmyy8M3oxbvw7qAXts2GORxs+h/2vKsD+osMhZ7An6yK6F1dA==} @@ -16521,6 +16544,48 @@ packages: fsevents: 2.3.2 dev: false + /vitest/0.20.3: + resolution: {integrity: sha512-cXMjTbZxBBUUuIF3PUzEGPLJWtIMeURBDXVxckSHpk7xss4JxkiiWh5cnIlfGyfJne2Ii3QpbiRuFL5dMJtljw==} + engines: {node: '>=v14.16.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + c8: '*' + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + c8: + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/chai': 4.3.1 + '@types/chai-subset': 1.3.3 + '@types/node': 18.6.4 + chai: 4.3.6 + debug: 4.3.4 + local-pkg: 0.4.2 + tinypool: 0.2.4 + tinyspy: 1.0.0 + vite: 3.0.4 + transitivePeerDependencies: + - less + - sass + - stylus + - supports-color + - terser + dev: true + /vm2/3.9.10: resolution: {integrity: sha512-AuECTSvwu2OHLAZYhG716YzwodKCIJxB6u1zG7PgSQwIgAlEaoXH52bxdcvT8GkGjnYK7r7yWDW0m0sOsPuBjQ==} engines: {node: '>=6.0'}