Minor change to jsxTransformOptions, update Renderer API docs (#1630)

This commit is contained in:
Drew Powers 2021-10-22 11:55:02 -06:00 committed by Drew Powers
parent b0ef391e57
commit 2c15795607
5 changed files with 53 additions and 29 deletions

View file

@ -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

View file

@ -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`. 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 ```js
import myVitePlugin from 'vite-plugin-myplugin';
export default { export default {
name: '@astrojs/renderer-xxx', // the renderer name name: '@astrojs/renderer-xxx', // the renderer name
client: './client.js', // relative path to the client entrypoint client: './client.js', // relative path to the client entrypoint
server: './server.js', // optional, relative path to the server entrypoint server: './server.js', // (optional) relative path to the server entrypoint
snowpackPlugin: '@snowpack/plugin-xxx', // optional, the name of a snowpack plugin to inject viteConfig(options = { mode: 'development', command: 'serve' }) {
snowpackPluginOptions: { example: true }, // optional, any options to be forwarded to the snowpack plugin // (optional) return config object for Vite (https://vitejs.dev/config/)
knownEntrypoints: ['framework'], // optional, entrypoint modules that will be used by compiled source return {
external: ['dep'], // optional, dependencies that should not be built by snowpack plugins: [myVitePlugin()], // tip: usually this will depend on a Vite plugin
polyfills: ['./shadow-dom-polyfill.js'], // optional, module scripts that should be loaded before client hydration. optimizeDeps: {
hydrationPolyfills: ['./hydrate-framework.js'], // optional, polyfills that need to run before hydration ever occurs. include: ['my-client-dep'], // tip: its always a good idea to specify any client-side hydration deps here
jsxImportSource: 'preact', // optional, the name of the library from which JSX is imported },
jsxTransformOptions: async () => { ssr: {
// optional, a function to transform JSX files 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 { const {
default: { default: jsx }, default: { default: jsx },
} = await import('@babel/plugin-transform-react-jsx'); } = await import('@babel/plugin-transform-react-jsx');
@ -72,7 +82,6 @@ export default {
plugins: [jsx({}, { runtime: 'automatic', importSource: 'preact' })], 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! > 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 its running in `development` or `production` mode, as well as whether or not its 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 ```ts
export interface JSXTransformOptions { export interface JSXTransformOptions {
(context: { (context: {
/** True if builder is in dev mode (`astro dev`) */ /** "development" or "production" */
isDev: boolean; mode: string;
/** True if HMR is enabled (add any HMR code to the output here). */
isHmrEnabled: boolean;
/** True if builder is in SSR mode */ /** True if builder is in SSR mode */
isSSR: boolean; ssr: boolean;
/** True if file being transformed is inside of a package. */
isPackage: boolean;
}) => { }) => {
plugins?: any[]; plugins?: any[];
presets?: any[]; presets?: any[];

View file

@ -162,7 +162,7 @@ export interface JSXTransformConfig {
plugins?: babel.PluginItem[]; plugins?: babel.PluginItem[];
} }
export type JSXTransformFn = (options: { isSSR: boolean }) => Promise<JSXTransformConfig>; export type JSXTransformFn = (options: { mode: string; ssr: boolean }) => Promise<JSXTransformConfig>;
export interface ManifestData { export interface ManifestData {
routes: RouteData[]; routes: RouteData[];

View file

@ -1,5 +1,5 @@
import type { TransformResult } from 'rollup'; 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 { AstroConfig, Renderer } from '../@types/astro-core';
import type { LogOptions } from '../core/logger'; 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) */ /** 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 { export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin {
let viteConfig: ResolvedConfig;
return { return {
name: '@astrojs/vite-plugin-jsx', name: '@astrojs/vite-plugin-jsx',
enforce: 'pre', // run transforms before other plugins enforce: 'pre', // run transforms before other plugins
configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
},
async transform(code, id, ssrOrOptions) { async transform(code, id, ssrOrOptions) {
const ssr = isSSR(ssrOrOptions); const ssr = isSSR(ssrOrOptions);
if (!JSX_EXTENSIONS.has(path.extname(id))) { if (!JSX_EXTENSIONS.has(path.extname(id))) {
return null; return null;
} }
const { mode } = viteConfig;
// load renderers (on first run only) // load renderers (on first run only)
if (JSX_RENDERERS.size === 0) { if (JSX_RENDERERS.size === 0) {
const jsxRenderers = await loadJSXRenderers(config.renderers); const jsxRenderers = await loadJSXRenderers(config.renderers);
@ -76,7 +83,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
loader: getLoader(path.extname(id)), loader: getLoader(path.extname(id)),
jsx: 'preserve', 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 // Attempt: Multiple JSX renderers
@ -130,7 +137,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
loader: getLoader(path.extname(id)), loader: getLoader(path.extname(id)),
jsx: 'preserve', 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 cant tell, throw error // if we still cant tell, throw error
@ -170,14 +177,15 @@ async function loadJSXRenderers(rendererNames: string[]): Promise<Map<string, Re
interface TransformJSXOptions { interface TransformJSXOptions {
code: string; code: string;
id: string; id: string;
ssr: boolean; mode: string;
renderer: Renderer; // note MUST check for JSX beforehand! renderer: Renderer; // note MUST check for JSX beforehand!
ssr: boolean;
} }
/** Transform JSX with Babel */ /** Transform JSX with Babel */
async function transformJSX({ code, id, ssr, renderer }: TransformJSXOptions): Promise<TransformResult> { async function transformJSX({ code, mode, id, ssr, renderer }: TransformJSXOptions): Promise<TransformResult> {
const { jsxTransformOptions } = renderer; 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<babel.BabelFileResult | null>((resolve, reject) => { const result = await new Promise<babel.BabelFileResult | null>((resolve, reject) => {
const plugins = [...(options.plugins || [])]; const plugins = [...(options.plugins || [])];
babel.transform( babel.transform(

View file

@ -3,14 +3,14 @@ export default {
client: './client.js', client: './client.js',
server: './server.js', server: './server.js',
jsxImportSource: 'solid-js', jsxImportSource: 'solid-js',
jsxTransformOptions: async ({ isSSR }) => { jsxTransformOptions: async ({ ssr }) => {
const [{ default: solid }] = await Promise.all([import('babel-preset-solid')]); const [{ default: solid }] = await Promise.all([import('babel-preset-solid')]);
const options = { const options = {
presets: [solid({}, { generate: isSSR ? 'ssr' : 'dom', hydratable: true })], presets: [solid({}, { generate: isSSR ? 'ssr' : 'dom', hydratable: true })],
plugins: [], plugins: [],
}; };
if (isSSR) { if (ssr) {
options.plugins.push([ options.plugins.push([
'babel-plugin-module-resolver', 'babel-plugin-module-resolver',
{ {