mirror of
https://github.com/withastro/astro.git
synced 2025-01-20 22:12:38 -05:00
Add React component SSR (#28)
* Add React component SSR * Add React component SSR
This commit is contained in:
parent
3db5959377
commit
04a443a888
8 changed files with 123 additions and 96 deletions
67
examples/snowpack/astro/components/Card.css
Normal file
67
examples/snowpack/astro/components/Card.css
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
grid-column: span 1;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: Open Sans, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI,
|
||||||
|
Roboto, sans-serif;
|
||||||
|
color: #1a202c;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
border-color: #2a85ca;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(46, 94, 130, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover .card-image {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:nth-child(4n + 0) .card-image {
|
||||||
|
background: #f2709c;
|
||||||
|
background: linear-gradient(30deg, #ff9472, #f2709c);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:nth-child(4n + 1) .card-image {
|
||||||
|
background: #fbd3e9;
|
||||||
|
background: linear-gradient(30deg, #bb377d, #fbd3e9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:nth-child(4n + 2) .card-image {
|
||||||
|
background: #b993d6;
|
||||||
|
background: linear-gradient(30deg, #8ca6db, #b993d6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:nth-child(4n + 3) .card-image {
|
||||||
|
background: #00d2ff;
|
||||||
|
background: linear-gradient(30deg, #3a7bd5, #00d2ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image__sm {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image__lg {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
margin: 0 0 0.25rem 0;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 20px;
|
||||||
|
font-family: 'Overpass';
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { format as formatDate, parseISO } from 'date-fns';
|
import { format as formatDate, parseISO } from 'date-fns';
|
||||||
|
import './Card.css';
|
||||||
|
|
||||||
export default function Card({ item }) {
|
export default function Card({ item }) {
|
||||||
return (
|
return (
|
||||||
|
@ -10,18 +11,24 @@ export default function Card({ item }) {
|
||||||
>
|
>
|
||||||
{item.img ? (
|
{item.img ? (
|
||||||
<img
|
<img
|
||||||
class="card-image card-image-small"
|
class="card-image card-image__sm"
|
||||||
src={item.img}
|
src={item.img}
|
||||||
alt=""
|
alt=""
|
||||||
style={{ background: item.imgBackground || undefined }}
|
style={{ background: item.imgBackground || undefined }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div class="card-image card-image-small"></div>
|
<div class="card-image card-image__sm"></div>
|
||||||
)}
|
)}
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<h3 class="card-title">{item.title}</h3>
|
<h3 class="card-title">{item.title}</h3>
|
||||||
{item.date && <time class="snow-toc-link">{ formatDate(parseISO(item.date), 'MMMM d, yyyy') }</time>}
|
{item.date && (
|
||||||
{item.description && <p style="margin: 0.5rem 0 0.25rem;">{ item.description }</p>}
|
<time class="snow-toc-link">
|
||||||
|
{formatDate(parseISO(item.date), 'MMMM d, yyyy')}
|
||||||
|
</time>
|
||||||
|
)}
|
||||||
|
{item.description && (
|
||||||
|
<p style="margin: 0.5rem 0 0.25rem;">{item.description}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
@ -76,6 +76,13 @@ let communityGuides;
|
||||||
return
|
return
|
||||||
<Card item={post} />;
|
<Card item={post} />;
|
||||||
})}
|
})}
|
||||||
|
<Card item={{
|
||||||
|
url: 'https://www.snowpack.dev/posts/2021-01-13-snowpack-3-0',
|
||||||
|
img: 'https://www.snowpack.dev/img/social-snowpackv3.jpg',
|
||||||
|
date: '2021-01-12 00:00:00Z',
|
||||||
|
title: 'Snowpack v3.0',
|
||||||
|
description: 'Snowpack v3.0 is here!',
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -8,9 +8,11 @@
|
||||||
.card-grid-3 {
|
.card-grid-3 {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-grid-4 {
|
.card-grid-4 {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-banner {
|
.discord-banner {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
border: 1px solid #2e2077;
|
border: 1px solid #2e2077;
|
||||||
|
@ -38,68 +40,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
|
||||||
display: flex;
|
|
||||||
grid-column: span 1;
|
|
||||||
overflow: hidden;
|
|
||||||
font-family: Open Sans, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI,
|
|
||||||
Roboto, sans-serif;
|
|
||||||
color: #1a202c;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
.card:hover {
|
|
||||||
border-color: #2a85ca;
|
|
||||||
box-shadow: 0 2px 2px 0 rgba(46, 94, 130, 0.4);
|
|
||||||
}
|
|
||||||
.card:hover .card-image {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-image {
|
|
||||||
width: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.card-image-small {
|
|
||||||
flex-grow: 1;
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-image-large {
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.card-text {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
.card-title {
|
|
||||||
margin: 0 0 0.25rem 0;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 20px;
|
|
||||||
font-family: 'Overpass';
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
.content-title {
|
.content-title {
|
||||||
font-family: 'Overpass';
|
font-family: 'Overpass';
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:nth-child(4n + 0) .card-image {
|
|
||||||
background: #f2709c;
|
|
||||||
background: linear-gradient(30deg, #ff9472, #f2709c);
|
|
||||||
}
|
|
||||||
.card:nth-child(4n + 1) .card-image {
|
|
||||||
background: #fbd3e9;
|
|
||||||
background: linear-gradient(30deg, #bb377d, #fbd3e9);
|
|
||||||
}
|
|
||||||
.card:nth-child(4n + 2) .card-image {
|
|
||||||
background: #b993d6;
|
|
||||||
background: linear-gradient(30deg, #8ca6db, #b993d6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:nth-child(4n + 3) .card-image {
|
|
||||||
background: #00d2ff;
|
|
||||||
background: linear-gradient(30deg, #3a7bd5, #00d2ff);
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ module.exports = function (snowpackConfig, { resolve, extensions } = {}) {
|
||||||
const contents = await readFile(filePath, 'utf-8');
|
const contents = await readFile(filePath, 'utf-8');
|
||||||
const compileOptions = {
|
const compileOptions = {
|
||||||
resolve,
|
resolve,
|
||||||
extensions
|
extensions,
|
||||||
};
|
};
|
||||||
const result = await compileComponent(contents, { compileOptions, filename: filePath, projectRoot });
|
const result = await compileComponent(contents, { compileOptions, filename: filePath, projectRoot });
|
||||||
return result.contents;
|
return result.contents;
|
||||||
|
|
|
@ -111,10 +111,7 @@ const defaultExtensions: Readonly<Record<string, ValidExtensionPlugins>> = {
|
||||||
'.svelte': 'svelte',
|
'.svelte': 'svelte',
|
||||||
};
|
};
|
||||||
|
|
||||||
type DynamicImportMap = Map<
|
type DynamicImportMap = Map<'vue' | 'react' | 'react-dom' | 'preact', string>;
|
||||||
'vue' | 'react' | 'react-dom' | 'preact',
|
|
||||||
string
|
|
||||||
>;
|
|
||||||
|
|
||||||
function getComponentWrapper(_name: string, { type, plugin, url }: ComponentInfo, dynamicImports: DynamicImportMap) {
|
function getComponentWrapper(_name: string, { type, plugin, url }: ComponentInfo, dynamicImports: DynamicImportMap) {
|
||||||
const [name, kind] = _name.split(':');
|
const [name, kind] = _name.split(':');
|
||||||
|
@ -136,7 +133,9 @@ function getComponentWrapper(_name: string, { type, plugin, url }: ComponentInfo
|
||||||
case 'preact': {
|
case 'preact': {
|
||||||
if (kind === 'dynamic') {
|
if (kind === 'dynamic') {
|
||||||
return {
|
return {
|
||||||
wrapper: `__preact_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${dynamicImports.get('preact')!}')`,
|
wrapper: `__preact_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${dynamicImports.get(
|
||||||
|
'preact'
|
||||||
|
)!}')`,
|
||||||
wrapperImport: `import {__preact_dynamic} from '${internalImport('render/preact.js')}';`,
|
wrapperImport: `import {__preact_dynamic} from '${internalImport('render/preact.js')}';`,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,7 +176,9 @@ function getComponentWrapper(_name: string, { type, plugin, url }: ComponentInfo
|
||||||
case 'vue': {
|
case 'vue': {
|
||||||
if (kind === 'dynamic') {
|
if (kind === 'dynamic') {
|
||||||
return {
|
return {
|
||||||
wrapper: `__vue_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.vue.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${dynamicImports.get('vue')!}')`,
|
wrapper: `__vue_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.vue.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${dynamicImports.get(
|
||||||
|
'vue'
|
||||||
|
)!}')`,
|
||||||
wrapperImport: `import {__vue_dynamic} from '${internalImport('render/vue.js')}';`,
|
wrapperImport: `import {__vue_dynamic} from '${internalImport('render/vue.js')}';`,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -236,9 +237,9 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
const componentExports: ExportNamedDeclaration[] = [];
|
const componentExports: ExportNamedDeclaration[] = [];
|
||||||
|
|
||||||
let script = '';
|
let script = '';
|
||||||
let propsStatement: string = '';
|
let propsStatement = '';
|
||||||
const importExportStatements: Set<string> = new Set();
|
const importExportStatements: Set<string> = new Set();
|
||||||
const components: Record<string, { type: string; url: string, plugin: string | undefined }> = {};
|
const components: Record<string, { type: string; url: string; plugin: string | undefined }> = {};
|
||||||
const componentPlugins = new Set<ValidExtensionPlugins>();
|
const componentPlugins = new Set<ValidExtensionPlugins>();
|
||||||
|
|
||||||
if (ast.module) {
|
if (ast.module) {
|
||||||
|
@ -277,7 +278,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
components[componentName] = {
|
components[componentName] = {
|
||||||
type: componentType,
|
type: componentType,
|
||||||
plugin,
|
plugin,
|
||||||
url: importUrl
|
url: importUrl,
|
||||||
};
|
};
|
||||||
if (plugin) {
|
if (plugin) {
|
||||||
componentPlugins.add(plugin);
|
componentPlugins.add(plugin);
|
||||||
|
|
|
@ -105,7 +105,6 @@ async function transformFromSource(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function compileComponent(
|
export async function compileComponent(
|
||||||
source: string,
|
source: string,
|
||||||
{ compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
|
{ compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
|
||||||
|
@ -136,7 +135,6 @@ export default __render;
|
||||||
// \`__renderPage()\`: Render the contents of the Astro module as a page. This is a special flow,
|
// \`__renderPage()\`: Render the contents of the Astro module as a page. This is a special flow,
|
||||||
// triggered by loading a component directly by URL.
|
// triggered by loading a component directly by URL.
|
||||||
export async function __renderPage({request, children, props}) {
|
export async function __renderPage({request, children, props}) {
|
||||||
|
|
||||||
const currentChild = {
|
const currentChild = {
|
||||||
setup: typeof setup === 'undefined' ? (passthrough) => passthrough : setup,
|
setup: typeof setup === 'undefined' ? (passthrough) => passthrough : setup,
|
||||||
layout: typeof __layout === 'undefined' ? undefined : __layout,
|
layout: typeof __layout === 'undefined' ? undefined : __layout,
|
||||||
|
|
|
@ -63,7 +63,7 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mod = await snowpackRuntime.importModule(selectedPageUrl);
|
const mod = await snowpackRuntime.importModule(selectedPageUrl);
|
||||||
const html = (await mod.exports.__renderPage({
|
let html = (await mod.exports.__renderPage({
|
||||||
request: {
|
request: {
|
||||||
host: fullurl.hostname,
|
host: fullurl.hostname,
|
||||||
path: fullurl.pathname,
|
path: fullurl.pathname,
|
||||||
|
@ -73,6 +73,15 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
|
||||||
props: {},
|
props: {},
|
||||||
})) as string;
|
})) as string;
|
||||||
|
|
||||||
|
// inject styles
|
||||||
|
// TODO: handle this in compiler
|
||||||
|
const styleTags = Array.isArray(mod.css) && mod.css.length ? mod.css.reduce((markup, url) => `${markup}\n<link rel="stylesheet" type="text/css" href="${url}" />`, '') : ``;
|
||||||
|
if (html.indexOf('</head>') !== -1) {
|
||||||
|
html = html.replace('</head>', `${styleTags}</head>`);
|
||||||
|
} else {
|
||||||
|
html = styleTags + html;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
contents: html,
|
contents: html,
|
||||||
|
@ -99,7 +108,7 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
|
||||||
|
|
||||||
interface RuntimeOptions {
|
interface RuntimeOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
env: 'dev' | 'build'
|
env: 'dev' | 'build';
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRuntime(astroConfig: AstroConfig, { env, logging }: RuntimeOptions) {
|
export async function createRuntime(astroConfig: AstroConfig, { env, logging }: RuntimeOptions) {
|
||||||
|
@ -113,9 +122,7 @@ export async function createRuntime(astroConfig: AstroConfig, { env, logging }:
|
||||||
extensions?: Record<string, string>;
|
extensions?: Record<string, string>;
|
||||||
} = {
|
} = {
|
||||||
extensions,
|
extensions,
|
||||||
resolve: env === 'dev' ?
|
resolve: env === 'dev' ? async (pkgName: string) => snowpack.getUrlForPackage(pkgName) : async (pkgName: string) => `/_snowpack/pkg/${pkgName}.js`,
|
||||||
async (pkgName: string) => snowpack.getUrlForPackage(pkgName) :
|
|
||||||
async (pkgName: string) => `/_snowpack/pkg/${pkgName}.js`
|
|
||||||
};
|
};
|
||||||
/*if (existsSync(new URL('./package-lock.json', projectRoot))) {
|
/*if (existsSync(new URL('./package-lock.json', projectRoot))) {
|
||||||
const pkgLockStr = await readFile(new URL('./package-lock.json', projectRoot), 'utf-8');
|
const pkgLockStr = await readFile(new URL('./package-lock.json', projectRoot), 'utf-8');
|
||||||
|
|
Loading…
Add table
Reference in a new issue