Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat rsc #6516

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions examples/with-rsc/src/components/Container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use server';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use server 应该是用来标志 server action 的,组件默认情况下就是 server component


import EditButton from '@/components/EditButton';
import Counter from '@/components/Counter';
import InnerServer from '@/components/Content';

export default function Container() {
return (
<>
<Counter>
<InnerServer />
</Counter>
<EditButton noteId="editButton">
hello world
</EditButton>
<div> {serverPrint('serverPrint call')} </div>
</>
);
}

export function serverPrint(sentence) {
return sentence;
}
9 changes: 9 additions & 0 deletions examples/with-rsc/src/components/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use server';

export default function InnerServer() {
return (
<div>
inner server
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use client';
import { useState } from 'react';
import styles from './index.module.css';
import { clientPrint } from './EditButton';

export default function Counter() {
export default function Counter({ children }) {
const [count, setCount] = useState(0);

function updateCount() {
Expand All @@ -12,6 +13,8 @@ export default function Counter() {
return (
<button className={styles.button} type="button" onClick={updateCount}>
👍🏻 {count}
<div> {clientPrint('clientPrint call')} </div>
{children}
</button>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ export default function EditButton({ noteId, children }) {
</button>
);
}

export function clientPrint(sentence) {
return sentence;
}
8 changes: 2 additions & 6 deletions examples/with-rsc/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { useState } from 'react';
import styles from './index.module.css';
import Container from '@/components/Container';
// import Comments from '@/components/Comments';
// import Footer from '@/components/Footer';
import EditButton from '@/components/EditButton.client';
import Counter from '@/components/Counter.client';

export default function Home() {
console.log('Render: Index');

return (
<div>
<h2>Home Page</h2>
<Counter />
<EditButton noteId="editButton">
hello world
</EditButton>
<Container />
</div>
);
}
3 changes: 3 additions & 0 deletions packages/ice/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
"@swc/helpers": "0.5.1",
"@types/express": "^4.17.14",
"address": "^1.1.2",
"acorn": "^8.10.0",
"acorn-jsx": "^5.3.2",
"recast": "^0.23.4",
"build-scripts": "^2.1.2-0",
"chalk": "^4.0.0",
"commander": "^9.0.0",
Expand Down
103 changes: 98 additions & 5 deletions packages/ice/src/esbuild/rscServerRegister.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,109 @@
import fs from 'fs';
import url from 'url';
import type { Plugin, PluginBuild } from 'esbuild';
import { Parser } from 'acorn';
import jsx from 'acorn-jsx';
import recast from 'recast';
import ts from 'typescript';

const rscServerRegister = (): Plugin => {
return {
name: 'rsc-server-register',
setup: async (build: PluginBuild) => {
build.onLoad({ filter: /\/src\/.*\.client\.(js|ts|jsx|tsx)$/ }, async (args) => { // /src\/.*\
build.onLoad({ filter: /\/src\/.*\.(js|ts|jsx|tsx)$/ }, async (args) => {
const { path } = args;
const loader = path.endsWith('.tsx') || path.endsWith('.ts') ? 'tsx' : 'jsx';
let content: string = await fs.promises.readFile(path, 'utf-8');

if (content.indexOf('use client') === -1 && content.indexOf('use server') === -1) {
return { contents: content, loader };
}

// transform tsx/ts code to es6 jsx code
if (path.endsWith('tsx') || path.endsWith('ts')) {
const result = ts.transpileModule(content, {
compilerOptions: {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ES2022,
jsx: ts.JsxEmit.Preserve,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
},
});
content = result.outputText;
}

let body;
try {
// get AST of source code.
body = (Parser.extend(jsx()).parse(content, {
ecmaVersion: 2024,
sourceType: 'module',
}) as any).body;
} catch (x) {
console.error('Error parsing %s %s %s', url, x.message, path);
return { contents: content, loader };
}

let useClient = false;
let useServer = false;
for (let i = 0; i < body.length; i++) {
const node = body[i];
if (node.type !== 'ExpressionStatement' || !node.directive) {
break;
}
if (node.directive === 'use client') {
useClient = true;
}
if (node.directive === 'use server') {
useServer = true;
}
}

if (!useClient && !useServer) {
return { contents: content, loader };
}

if (useClient && useServer) {
throw new Error(
'Cannot have both "use client" and "use server" directives in the same file.',
);
}

let source: string = content;
const moduleId: string = url.pathToFileURL(path).href;
let source = 'const Server: any = require(\'react-server-dom-webpack/server.node\');const createClientModuleProxy = Server.createClientModuleProxy;';
source += transformContent(moduleId);

if (useClient) {
source = `\
const Server: any = require('react-server-dom-webpack/server.node');\n
const createClientModuleProxy = Server.createClientModuleProxy;\n`;
source += transformContent(moduleId);
} else if (useServer) {
source = `\
const Server = require('react-server-dom-webpack/server.node');\n
const registerServerReference = Server.registerServerReference;\n`;
for (let i = 0; i < body.length; i++) {
const node = body[i];
if (node.type === 'ImportDeclaration') {
// concat the 'import' statements.
const { start, end } = node;
source += content.substring(start, end);
} else if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') {
const { declaration } = node;
// Handling the case where the export is a function.
if (declaration.type === 'FunctionDeclaration') {
const functionName = declaration.id.name;
source += `${recast.print(declaration).code};\n`;
if (node.type === 'ExportNamedDeclaration') {
source += `registerServerReference(${functionName}, '${moduleId}', '${functionName}');\n`;
source += `module.exports.${functionName} = ${functionName};\n`;
} else {
source += `registerServerReference(${functionName}, '${moduleId}', null);\n`;
source += `module.exports = ${functionName};\n`;
}
}
}
}
}
return { contents: source, loader };
});
},
Expand All @@ -19,8 +112,8 @@ const rscServerRegister = (): Plugin => {

function transformContent(moduleId: string) {
const content = `\
const comp = createClientModuleProxy('${moduleId}');
module.exports = comp`;
const comp = createClientModuleProxy('${moduleId}');\n\
module.exports = comp;`;
return content;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/runtime/src/runServerApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio
}
}

if (renderOptions.clientManifest && req.url.indexOf('?') === -1) {
return renderDocument({ matches: [], routes, renderOptions, documentData });
}

// HashRouter loads route modules by the CSR.
if (appConfig?.router?.type === 'hash') {
return renderDocument({ matches: [], routes, renderOptions, documentData });
Expand Down
Loading
Loading