Skip to content

Commit

Permalink
Minor change to jsxTransformOptions, update Renderer API docs (#1630)
Browse files Browse the repository at this point in the history
  • Loading branch information
drwpow committed Oct 22, 2021
1 parent b0ef391 commit 2c15795
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 29 deletions.
11 changes: 11 additions & 0 deletions .changeset/tidy-mirrors-drum.md
Original file line number Diff line number Diff line change
@@ -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
45 changes: 25 additions & 20 deletions docs/src/pages/reference/renderer-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,30 +49,39 @@ 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');
return {
plugins: [jsx({}, { runtime: 'automatic', importSource: 'preact' })],
};
},
vitePlugins: [], // optional, inject Vite plugins here (https://vitejs.dev/plugins/#plugins)
};
```

Expand All @@ -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[];
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/@types/astro-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export interface JSXTransformConfig {
plugins?: babel.PluginItem[];
}

export type JSXTransformFn = (options: { isSSR: boolean }) => Promise<JSXTransformConfig>;
export type JSXTransformFn = (options: { mode: string; ssr: boolean }) => Promise<JSXTransformConfig>;

export interface ManifestData {
routes: RouteData[];
Expand Down
20 changes: 14 additions & 6 deletions packages/astro/src/vite-plugin-jsx/index.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -170,14 +177,15 @@ async function loadJSXRenderers(rendererNames: string[]): Promise<Map<string, Re
interface TransformJSXOptions {
code: string;
id: string;
ssr: boolean;
mode: string;
renderer: Renderer; // note MUST check for JSX beforehand!
ssr: boolean;
}

/** 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 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 plugins = [...(options.plugins || [])];
babel.transform(
Expand Down
4 changes: 2 additions & 2 deletions packages/renderers/renderer-solid/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
{
Expand Down

0 comments on commit 2c15795

Please sign in to comment.