summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@matthewphillips.info> 2021-03-23 13:47:54 -0400
committerGravatar GitHub <noreply@github.com> 2021-03-23 13:47:54 -0400
commit854d0feb34f605c0fe3f5627a261e327164c449e (patch)
treec7d88676affbd271fd6304a73d98c149e265f042 /src
parent3f16550765cccee0de8f2f6e5451bf41aec13601 (diff)
downloadastro-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.ts4
-rw-r--r--src/@types/compiler.ts2
-rw-r--r--src/codegen/index.ts50
-rw-r--r--src/frontend/render/react.ts28
-rw-r--r--src/runtime.ts12
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({