diff options
author | 2021-03-23 13:47:54 -0400 | |
---|---|---|
committer | 2021-03-23 13:47:54 -0400 | |
commit | 854d0feb34f605c0fe3f5627a261e327164c449e (patch) | |
tree | c7d88676affbd271fd6304a73d98c149e265f042 /src | |
parent | 3f16550765cccee0de8f2f6e5451bf41aec13601 (diff) | |
download | astro-854d0feb34f605c0fe3f5627a261e327164c449e.tar.gz astro-854d0feb34f605c0fe3f5627a261e327164c449e.tar.zst astro-854d0feb34f605c0fe3f5627a261e327164c449e.zip |
Add support for React components. (#18)
* Add support for React components.
This adds support for react components via a new `extensions` config in astro.config.mjs. In the future we can extend this to do things like look at the import statements, as Snowpack does.
* Fix the tests
Diffstat (limited to 'src')
-rw-r--r-- | src/@types/astro.ts | 4 | ||||
-rw-r--r-- | src/@types/compiler.ts | 2 | ||||
-rw-r--r-- | src/codegen/index.ts | 50 | ||||
-rw-r--r-- | src/frontend/render/react.ts | 28 | ||||
-rw-r--r-- | src/runtime.ts | 12 |
5 files changed, 87 insertions, 9 deletions
diff --git a/src/@types/astro.ts b/src/@types/astro.ts index 69994375c..8d5979eec 100644 --- a/src/@types/astro.ts +++ b/src/@types/astro.ts @@ -2,12 +2,16 @@ export interface AstroConfigRaw { dist: string; projectRoot: string; hmxRoot: string; + jsx?: string; } +export type ValidExtensionPlugins = 'hmx' | 'react' | 'preact' | 'svelte' | 'vue'; + export interface AstroConfig { dist: string; projectRoot: URL; hmxRoot: URL; + extensions?: Record<string, ValidExtensionPlugins> } export interface JsxItem { diff --git a/src/@types/compiler.ts b/src/@types/compiler.ts index 456924267..4e0ee6250 100644 --- a/src/@types/compiler.ts +++ b/src/@types/compiler.ts @@ -1,6 +1,8 @@ import type { LogOptions } from '../logger'; +import type { ValidExtensionPlugins } from './astro'; export interface CompileOptions { logging: LogOptions; resolve: (p: string) => string; + extensions?: Record<string, ValidExtensionPlugins>; } diff --git a/src/codegen/index.ts b/src/codegen/index.ts index 725546f51..a9fc433f4 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -1,4 +1,5 @@ import type { CompileOptions } from '../@types/compiler'; +import type { ValidExtensionPlugins } from '../@types/astro'; import type { Ast, TemplateNode } from '../compiler/interfaces'; import type { JsxItem, TransformResult } from '../@types/astro'; @@ -89,10 +90,34 @@ function generateAttributes(attrs: Record<string, string>): string { return result + '}'; } -function getComponentWrapper(_name: string, { type, url }: { type: string; url: string }, { resolve }: CompileOptions) { +interface ComponentInfo { + type: string; + url: string; +} + +const defaultExtensions: Readonly<Record<string, ValidExtensionPlugins>> = { + '.hmx': 'hmx', + '.jsx': 'react', + '.vue': 'vue', + '.svelte': 'svelte' +}; + +function getComponentWrapper(_name: string, { type, url }: ComponentInfo, compileOptions: CompileOptions) { + const { + resolve, + extensions = defaultExtensions + } = compileOptions; + const [name, kind] = _name.split(':'); - switch (type) { - case '.hmx': { + + const plugin = extensions[type] || defaultExtensions[type]; + + if(!plugin) { + throw new Error(`No supported plugin found for extension ${type}`); + } + + switch (plugin) { + case 'hmx': { if (kind) { throw new Error(`HMX does not support :${kind}`); } @@ -101,7 +126,7 @@ function getComponentWrapper(_name: string, { type, url }: { type: string; url: wrapperImport: ``, }; } - case '.jsx': { + case 'preact': { if (kind === 'dynamic') { return { wrapper: `__preact_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${resolve('preact')}')`, @@ -114,7 +139,20 @@ function getComponentWrapper(_name: string, { type, url }: { type: string; url: }; } } - case '.svelte': { + case 'react': { + if (kind === 'dynamic') { + return { + wrapper: `__react_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${resolve('react')}', '${resolve('react-dom')}')`, + wrapperImport: `import {__react_dynamic} from '${internalImport('render/react.js')}';`, + }; + } else { + return { + wrapper: `__react_static(${name})`, + wrapperImport: `import {__react_static} from '${internalImport('render/react.js')}';`, + }; + } + } + case 'svelte': { if (kind === 'dynamic') { return { wrapper: `__svelte_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.svelte.js'))}, \`http://TEST\${import.meta.url}\`).pathname)`, @@ -127,7 +165,7 @@ function getComponentWrapper(_name: string, { type, url }: { type: string; url: }; } } - case '.vue': { + case 'vue': { if (kind === 'dynamic') { return { wrapper: `__vue_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.vue.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${resolve('vue')}')`, diff --git a/src/frontend/render/react.ts b/src/frontend/render/react.ts new file mode 100644 index 000000000..d55d30c00 --- /dev/null +++ b/src/frontend/render/react.ts @@ -0,0 +1,28 @@ +import React from 'react'; +import ReactDOMServer from 'react-dom/server'; + +export function __react_static(ReactComponent: any) { + return (attrs: Record<string, any>, ...children: any): string => { + let html = ReactDOMServer.renderToString( + React.createElement( + ReactComponent, + attrs, + children + ) + ); + return html; + }; +} + +export function __react_dynamic(ReactComponent: any, importUrl: string, reactUrl: string, reactDomUrl: string) { + const placeholderId = `placeholder_${String(Math.random())}`; + return (attrs: Record<string, string>, ...children: any) => { + return `<div id="${placeholderId}"></div><script type="module"> + import React from '${reactUrl}'; + import ReactDOM from '${reactDomUrl}'; + import Component from '${importUrl}'; + + ReactDOM.render(React.createElement(Component, ${JSON.stringify(attrs)}), document.getElementById('${placeholderId}')); + </script>`; + }; +} diff --git a/src/runtime.ts b/src/runtime.ts index 18bacb6eb..ed498c331 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -96,12 +96,15 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro } export async function createRuntime(astroConfig: AstroConfig, logging: LogOptions) { - const { projectRoot, hmxRoot } = astroConfig; + const { projectRoot, hmxRoot, extensions } = astroConfig; const internalPath = new URL('./frontend/', import.meta.url); // Workaround for SKY-251 - const hmxPlugOptions: { resolve?: (s: string) => string } = {}; + const hmxPlugOptions: { + resolve?: (s: string) => string; + extensions?: Record<string, string> + } = { extensions }; if (existsSync(new URL('./package-lock.json', projectRoot))) { const pkgLockStr = await readFile(new URL('./package-lock.json', projectRoot), 'utf-8'); const pkgLock = JSON.parse(pkgLockStr); @@ -125,7 +128,10 @@ export async function createRuntime(astroConfig: AstroConfig, logging: LogOption }, packageOptions: { knownEntrypoints: ['preact-render-to-string'], - external: ['@vue/server-renderer'], + external: [ + '@vue/server-renderer', + 'node-fetch' + ], }, }); const snowpack = await startSnowpackServer({ |