From 04a443a8887257e86d824bb22686a527f84c0875 Mon Sep 17 00:00:00 2001 From: Drew Powers <1369770+drwpow@users.noreply.github.com> Date: Thu, 25 Mar 2021 16:59:38 -0600 Subject: [PATCH] Add React component SSR (#28) * Add React component SSR * Add React component SSR --- examples/snowpack/astro/components/Card.css | 67 +++++++++++++++++++ examples/snowpack/astro/components/Card.jsx | 19 ++++-- examples/snowpack/astro/pages/guides.astro | 9 ++- .../public/css/components/_card-grid.scss | 64 +----------------- snowpack-plugin.cjs | 2 +- src/compiler/codegen.ts | 29 ++++---- src/compiler/index.ts | 12 ++-- src/runtime.ts | 17 +++-- 8 files changed, 123 insertions(+), 96 deletions(-) create mode 100644 examples/snowpack/astro/components/Card.css diff --git a/examples/snowpack/astro/components/Card.css b/examples/snowpack/astro/components/Card.css new file mode 100644 index 0000000000..2fa7efe201 --- /dev/null +++ b/examples/snowpack/astro/components/Card.css @@ -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; +} diff --git a/examples/snowpack/astro/components/Card.jsx b/examples/snowpack/astro/components/Card.jsx index a7e2dc4eb7..ee9460dcf9 100644 --- a/examples/snowpack/astro/components/Card.jsx +++ b/examples/snowpack/astro/components/Card.jsx @@ -1,5 +1,6 @@ -import {h} from 'preact'; -import {format as formatDate, parseISO} from 'date-fns'; +import { h } from 'preact'; +import { format as formatDate, parseISO } from 'date-fns'; +import './Card.css'; export default function Card({ item }) { return ( @@ -10,18 +11,24 @@ export default function Card({ item }) { > {item.img ? ( ) : ( -
+
)}

{item.title}

- {item.date && } - {item.description &&

{ item.description }

} + {item.date && ( + + )} + {item.description && ( +

{item.description}

+ )}
diff --git a/examples/snowpack/astro/pages/guides.astro b/examples/snowpack/astro/pages/guides.astro index 3febcb2f7c..f3d8d71794 100644 --- a/examples/snowpack/astro/pages/guides.astro +++ b/examples/snowpack/astro/pages/guides.astro @@ -76,8 +76,15 @@ let communityGuides; return ; })} + - \ No newline at end of file + diff --git a/examples/snowpack/public/css/components/_card-grid.scss b/examples/snowpack/public/css/components/_card-grid.scss index 5bfb8eee4f..5b0d25da86 100644 --- a/examples/snowpack/public/css/components/_card-grid.scss +++ b/examples/snowpack/public/css/components/_card-grid.scss @@ -8,9 +8,11 @@ .card-grid-3 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } + .card-grid-4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } + .discord-banner { grid-column: 1 / -1; 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 { 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); -} diff --git a/snowpack-plugin.cjs b/snowpack-plugin.cjs index 6b5e76a5b8..f4f2edb812 100644 --- a/snowpack-plugin.cjs +++ b/snowpack-plugin.cjs @@ -17,7 +17,7 @@ module.exports = function (snowpackConfig, { resolve, extensions } = {}) { const contents = await readFile(filePath, 'utf-8'); const compileOptions = { resolve, - extensions + extensions, }; const result = await compileComponent(contents, { compileOptions, filename: filePath, projectRoot }); return result.contents; diff --git a/src/compiler/codegen.ts b/src/compiler/codegen.ts index 58c7a6c9da..c0d9637033 100644 --- a/src/compiler/codegen.ts +++ b/src/compiler/codegen.ts @@ -10,7 +10,7 @@ import { walk } from 'estree-walker'; import babelParser from '@babel/parser'; import _babelGenerator from '@babel/generator'; import traverse from '@babel/traverse'; -import { ImportDeclaration,ExportNamedDeclaration, VariableDeclarator, Identifier, VariableDeclaration } from '@babel/types'; +import { ImportDeclaration, ExportNamedDeclaration, VariableDeclarator, Identifier, VariableDeclaration } from '@babel/types'; import { type } from 'node:os'; const babelGenerator: typeof _babelGenerator = @@ -111,10 +111,7 @@ const defaultExtensions: Readonly> = { '.svelte': 'svelte', }; -type DynamicImportMap = Map< - 'vue' | 'react' | 'react-dom' | 'preact', - string ->; +type DynamicImportMap = Map<'vue' | 'react' | 'react-dom' | 'preact', string>; function getComponentWrapper(_name: string, { type, plugin, url }: ComponentInfo, dynamicImports: DynamicImportMap) { const [name, kind] = _name.split(':'); @@ -136,7 +133,9 @@ function getComponentWrapper(_name: string, { type, plugin, url }: ComponentInfo case 'preact': { if (kind === 'dynamic') { 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')}';`, }; } else { @@ -177,7 +176,9 @@ function getComponentWrapper(_name: string, { type, plugin, url }: ComponentInfo case 'vue': { if (kind === 'dynamic') { 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')}';`, }; } else { @@ -207,8 +208,8 @@ function compileExpressionSafe(raw: string): string { async function acquireDynamicComponentImports(plugins: Set, resolve: (s: string) => Promise): Promise { const importMap: DynamicImportMap = new Map(); - for(let plugin of plugins) { - switch(plugin) { + for (let plugin of plugins) { + switch (plugin) { case 'vue': { importMap.set('vue', await resolve('vue')); break; @@ -236,9 +237,9 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro const componentExports: ExportNamedDeclaration[] = []; let script = ''; - let propsStatement: string = ''; + let propsStatement = ''; const importExportStatements: Set = new Set(); - const components: Record = {}; + const components: Record = {}; const componentPlugins = new Set(); if (ast.module) { @@ -277,9 +278,9 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro components[componentName] = { type: componentType, plugin, - url: importUrl + url: importUrl, }; - if(plugin) { + if (plugin) { componentPlugins.add(plugin); } importExportStatements.add(ast.module.content.slice(componentImport.start!, componentImport.end!)); @@ -293,7 +294,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro for (const componentExport of componentProps) { propsStatement += `${(componentExport.id as Identifier).name}`; if (componentExport.init) { - propsStatement += `= ${babelGenerator(componentExport.init!).code }`; + propsStatement += `= ${babelGenerator(componentExport.init!).code}`; } propsStatement += `,`; } diff --git a/src/compiler/index.ts b/src/compiler/index.ts index fea6b8a293..945d9bfc5c 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -105,12 +105,11 @@ async function transformFromSource( } } - export async function compileComponent( source: string, { compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string } ): Promise { - const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot }); + const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot }); const isPage = path.extname(filename) === '.md' || sourceJsx.items.some((item) => item.name === 'html'); // sort