diff --git a/.changeset/tidy-mirrors-drum.md b/.changeset/tidy-mirrors-drum.md new file mode 100644 index 000000000000..de8d6f550299 --- /dev/null +++ b/.changeset/tidy-mirrors-drum.md @@ -0,0 +1,11 @@ +--- +'astro': patch +'@astrojs/renderer-lit': patch +'@astrojs/renderer-preact': patch +'@astrojs/renderer-react': patch +'@astrojs/renderer-solid': patch +'@astrojs/renderer-svelte': patch +'@astrojs/renderer-vue': patch +--- + +Improve renderer APIs for Vite diff --git a/docs/src/pages/reference/renderer-reference.md b/docs/src/pages/reference/renderer-reference.md index 127cbc35a9a6..0e8dc1a7961b 100644 --- a/docs/src/pages/reference/renderer-reference.md +++ b/docs/src/pages/reference/renderer-reference.md @@ -49,22 +49,32 @@ This means that Astro users don't need to install the UI framework packages them The main entrypoint of a renderer is a simple JS file which exports a manifest for the renderer. The required values are `name`, `server`, and `client`. -Additionally, this entrypoint can define a [Snowpack plugin](https://www.snowpack.dev/guides/plugins) that should be used to load non-JavaScript files. +Additionally, this entrypoint can define a [Vite config object](https://vitejs.dev/config/) that should be used to load non-JavaScript files. ```js +import myVitePlugin from 'vite-plugin-myplugin'; + export default { name: '@astrojs/renderer-xxx', // the renderer name client: './client.js', // relative path to the client entrypoint - server: './server.js', // optional, relative path to the server entrypoint - snowpackPlugin: '@snowpack/plugin-xxx', // optional, the name of a snowpack plugin to inject - snowpackPluginOptions: { example: true }, // optional, any options to be forwarded to the snowpack plugin - knownEntrypoints: ['framework'], // optional, entrypoint modules that will be used by compiled source - external: ['dep'], // optional, dependencies that should not be built by snowpack - polyfills: ['./shadow-dom-polyfill.js'], // optional, module scripts that should be loaded before client hydration. - hydrationPolyfills: ['./hydrate-framework.js'], // optional, polyfills that need to run before hydration ever occurs. - jsxImportSource: 'preact', // optional, the name of the library from which JSX is imported - jsxTransformOptions: async () => { - // optional, a function to transform JSX files + server: './server.js', // (optional) relative path to the server entrypoint + viteConfig(options = { mode: 'development', command: 'serve' }) { + // (optional) return config object for Vite (https://vitejs.dev/config/) + return { + plugins: [myVitePlugin()], // tip: usually this will depend on a Vite plugin + optimizeDeps: { + include: ['my-client-dep'], // tip: it’s always a good idea to specify any client-side hydration deps here + }, + ssr: { + external: ['my-client-dep/node/server.js'], // tip: use ssr.external in case you encounter code meant only for Node + }, + }; + }, + polyfills: ['./shadow-dom-polyfill.js'], // (optional) scripts that should be injected on the page for the component + hydrationPolyfills: ['./hydrate-framework.js'], // (optional) polyfills that need to run before hydration ever occurs + jsxImportSource: 'preact', // (optional) the name of the library from which JSX is imported ("react", "preact", "solid-js", etc.) + jsxTransformOptions: async (options = { mode: 'development', ssr: true }) => { + // (optional) a function to transform JSX files const { default: { default: jsx }, } = await import('@babel/plugin-transform-react-jsx'); @@ -72,7 +82,6 @@ export default { plugins: [jsx({}, { runtime: 'automatic', importSource: 'preact' })], }; }, - vitePlugins: [], // optional, inject Vite plugins here (https://vitejs.dev/plugins/#plugins) }; ``` @@ -92,19 +101,15 @@ This is an `async` function that returns information about how to transform matc > Keep in mind that this transform doesn't need to handle TSX separately from JSX, Astro handles that for you! -The arguments passed to `jsxTransformOptions` follow Snowpack's `load()` plugin hook. These allow you to pass separate Babel configurations for various conditions, like if your files should be compiled differently in SSR mode. +`jsxTransformOptions` receives context about whether it’s running in `development` or `production` mode, as well as whether or not it’s running in SSR or client hydration. These allow you to pass separate Babel configurations for various conditions, like if your files should be compiled differently in SSR mode. ```ts export interface JSXTransformOptions { (context: { - /** True if builder is in dev mode (`astro dev`) */ - isDev: boolean; - /** True if HMR is enabled (add any HMR code to the output here). */ - isHmrEnabled: boolean; + /** "development" or "production" */ + mode: string; /** True if builder is in SSR mode */ - isSSR: boolean; - /** True if file being transformed is inside of a package. */ - isPackage: boolean; + ssr: boolean; }) => { plugins?: any[]; presets?: any[]; diff --git a/packages/astro/src/@types/astro-core.ts b/packages/astro/src/@types/astro-core.ts index 65489b825f77..c475ee09b98a 100644 --- a/packages/astro/src/@types/astro-core.ts +++ b/packages/astro/src/@types/astro-core.ts @@ -162,7 +162,7 @@ export interface JSXTransformConfig { plugins?: babel.PluginItem[]; } -export type JSXTransformFn = (options: { isSSR: boolean }) => Promise; +export type JSXTransformFn = (options: { mode: string; ssr: boolean }) => Promise; export interface ManifestData { routes: RouteData[]; diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index dfba64f7e25f..e12ede30290f 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -1,5 +1,5 @@ import type { TransformResult } from 'rollup'; -import type { Plugin } from '../core/vite'; +import type { Plugin, ResolvedConfig } from '../core/vite'; import type { AstroConfig, Renderer } from '../@types/astro-core'; import type { LogOptions } from '../core/logger'; @@ -43,15 +43,22 @@ function isSSR(options: undefined | boolean | { ssr: boolean }): boolean { /** Use Astro config to allow for alternate or multiple JSX renderers (by default Vite will assume React) */ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin { + let viteConfig: ResolvedConfig; + return { name: '@astrojs/vite-plugin-jsx', enforce: 'pre', // run transforms before other plugins + configResolved(resolvedConfig) { + viteConfig = resolvedConfig; + }, async transform(code, id, ssrOrOptions) { const ssr = isSSR(ssrOrOptions); if (!JSX_EXTENSIONS.has(path.extname(id))) { return null; } + const { mode } = viteConfig; + // load renderers (on first run only) if (JSX_RENDERERS.size === 0) { const jsxRenderers = await loadJSXRenderers(config.renderers); @@ -76,7 +83,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin loader: getLoader(path.extname(id)), jsx: 'preserve', }); - return transformJSX({ code: jsxCode, id, renderer: [...JSX_RENDERERS.values()][0], ssr }); + return transformJSX({ code: jsxCode, id, renderer: [...JSX_RENDERERS.values()][0], mode, ssr }); } // Attempt: Multiple JSX renderers @@ -130,7 +137,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin loader: getLoader(path.extname(id)), jsx: 'preserve', }); - return transformJSX({ code: jsxCode, id, renderer: JSX_RENDERERS.get(importSource) as Renderer, ssr }); + return transformJSX({ code: jsxCode, id, renderer: JSX_RENDERERS.get(importSource) as Renderer, mode, ssr }); } // if we still can’t tell, throw error @@ -170,14 +177,15 @@ async function loadJSXRenderers(rendererNames: string[]): Promise { +async function transformJSX({ code, mode, id, ssr, renderer }: TransformJSXOptions): Promise { const { jsxTransformOptions } = renderer; - const options = await jsxTransformOptions!({ isSSR: ssr || false }); // must filter for this beforehand + const options = await jsxTransformOptions!({ mode, ssr }); // must filter for this beforehand const result = await new Promise((resolve, reject) => { const plugins = [...(options.plugins || [])]; babel.transform( diff --git a/packages/renderers/renderer-solid/index.js b/packages/renderers/renderer-solid/index.js index c6471a75d9e8..1b090b42403e 100644 --- a/packages/renderers/renderer-solid/index.js +++ b/packages/renderers/renderer-solid/index.js @@ -3,14 +3,14 @@ export default { client: './client.js', server: './server.js', jsxImportSource: 'solid-js', - jsxTransformOptions: async ({ isSSR }) => { + jsxTransformOptions: async ({ ssr }) => { const [{ default: solid }] = await Promise.all([import('babel-preset-solid')]); const options = { presets: [solid({}, { generate: isSSR ? 'ssr' : 'dom', hydratable: true })], plugins: [], }; - if (isSSR) { + if (ssr) { options.plugins.push([ 'babel-plugin-module-resolver', {