Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';

import { greet } from './funcs2';

export const TextBox = () => {
const [text, setText] = useState('hello');
useEffect(() => {
greet('TextBox').then((res) => {
console.log('Response from greet:', res);
});
}, []);
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
Expand Down
9 changes: 9 additions & 0 deletions examples/23_actions/src/components2/funcs2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use server';

import { getContext } from 'waku/server';

export const greet = async (name: string) => {
await Promise.resolve();
console.log('RSC Context:', getContext()); // ---> {}
return `Hello ${name} from server!`;
};
2 changes: 1 addition & 1 deletion examples/23_actions/src/entries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { lazy } from 'react';
import { defineEntries } from 'waku/server';
import { Slot } from 'waku/client';

const App = lazy(() => import('./components/App'));
const App = lazy(() => import('./components2/App'));

export default defineEntries(
// renderEntries
Expand Down
34 changes: 32 additions & 2 deletions packages/waku/src/lib/builder/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ const onwarn = (warning: RollupLog, defaultHandler: LoggingFunction) => {
return;
} else if (
warning.code === 'SOURCEMAP_ERROR' &&
warning.loc?.file?.endsWith('.tsx') &&
warning.loc?.column === 0 &&
warning.loc?.line === 1
) {
Expand Down Expand Up @@ -108,7 +107,12 @@ const analyzeEntries = async (rootDir: string, config: ResolvedConfig) => {
}
await buildVite({
plugins: [
rscAnalyzePlugin(clientFileSet, serverFileSet, fileHashMap),
rscAnalyzePlugin({
isClient: false,
clientFileSet,
serverFileSet,
fileHashMap,
}),
rscManagedPlugin({ ...config, addEntriesToInput: true }),
],
ssr: {
Expand All @@ -135,6 +139,25 @@ const analyzeEntries = async (rootDir: string, config: ResolvedConfig) => {
fname,
]),
);
await buildVite({
plugins: [
rscAnalyzePlugin({ isClient: true, serverFileSet }),
rscManagedPlugin(config),
],
ssr: {
target: 'webworker',
noExternal: /^(?!node:)/,
},
build: {
write: false,
ssr: true,
Copy link
Member Author

Choose a reason for hiding this comment

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

on second look, i'm not sure if this should be "ssr". we will need to revisit this double build anyway.

target: 'node18',
rollupOptions: {
onwarn,
input: clientEntryFiles,
},
},
});
const serverEntryFiles = Object.fromEntries(
Array.from(serverFileSet).map((fname, i) => [
`${DIST_ASSETS}/rsf${i}`,
Expand Down Expand Up @@ -171,6 +194,7 @@ const buildServerBundle = async (
plugins: [
nonjsResolvePlugin(),
rscTransformPlugin({
isClient: false,
isBuild: true,
clientEntryFiles,
serverEntryFiles,
Expand Down Expand Up @@ -275,6 +299,7 @@ const buildSsrBundle = async (
rootDir: string,
config: ResolvedConfig,
clientEntryFiles: Record<string, string>,
serverEntryFiles: Record<string, string>,
serverBuildOutput: Awaited<ReturnType<typeof buildServerBundle>>,
isNodeCompatible: boolean,
partial: boolean,
Expand All @@ -292,6 +317,7 @@ const buildSsrBundle = async (
rscEnvPlugin({ config }),
rscPrivatePlugin(config),
rscManagedPlugin({ ...config, addMainToInput: true }),
rscTransformPlugin({ isClient: true, isBuild: true, serverEntryFiles }),
],
ssr: isNodeCompatible
? {
Expand Down Expand Up @@ -346,6 +372,7 @@ const buildClientBundle = async (
rootDir: string,
config: ResolvedConfig,
clientEntryFiles: Record<string, string>,
serverEntryFiles: Record<string, string>,
serverBuildOutput: Awaited<ReturnType<typeof buildServerBundle>>,
partial: boolean,
) => {
Expand All @@ -364,6 +391,7 @@ const buildClientBundle = async (
rscEnvPlugin({ config }),
rscPrivatePlugin(config),
rscManagedPlugin({ ...config, addMainToInput: true }),
rscTransformPlugin({ isClient: true, isBuild: true, serverEntryFiles }),
],
build: {
emptyOutDir: !partial,
Expand Down Expand Up @@ -667,6 +695,7 @@ export async function build(options: {
rootDir,
config,
clientEntryFiles,
serverEntryFiles,
serverBuildOutput,
isNodeCompatible,
!!options.partial,
Expand All @@ -675,6 +704,7 @@ export async function build(options: {
rootDir,
config,
clientEntryFiles,
serverEntryFiles,
serverBuildOutput,
!!options.partial,
);
Expand Down
2 changes: 2 additions & 0 deletions packages/waku/src/lib/middleware/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getSsrConfigWithWorker,
} from '../renderers/dev-worker-api.js';
import { nonjsResolvePlugin } from '../plugins/vite-plugin-nonjs-resolve.js';
import { rscTransformPlugin } from '../plugins/vite-plugin-rsc-transform.js';
import { rscIndexPlugin } from '../plugins/vite-plugin-rsc-index.js';
import { rscHmrPlugin, hotUpdate } from '../plugins/vite-plugin-rsc-hmr.js';
import { rscEnvPlugin } from '../plugins/vite-plugin-rsc-env.js';
Expand Down Expand Up @@ -84,6 +85,7 @@ export const devServer: Middleware = (options) => {
rscPrivatePlugin(config),
rscManagedPlugin(config),
rscIndexPlugin(config),
rscTransformPlugin({ isClient: true, isBuild: false }),
rscHmrPlugin(),
],
optimizeDeps: {
Expand Down
31 changes: 20 additions & 11 deletions packages/waku/src/lib/plugins/vite-plugin-rsc-analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ const hash = async (code: string): Promise<string> => {
};

export function rscAnalyzePlugin(
clientFileSet: Set<string>,
serverFileSet: Set<string>,
fileHashMap: Map<string, string>,
opts:
| {
isClient: true;
serverFileSet: Set<string>;
}
| {
isClient: false;
clientFileSet: Set<string>;
serverFileSet: Set<string>;
fileHashMap: Map<string, string>;
},
): Plugin {
const rscTransform = rscTransformPlugin({ isBuild: false }).transform;
const clientEntryCallback = (id: string) => clientFileSet.add(id);
const serverEntryCallback = (id: string) => serverFileSet.add(id);
const rscTransform = rscTransformPlugin({
isClient: false,
isBuild: false,
}).transform;
return {
name: 'rsc-analyze-plugin',
async transform(code, id, options) {
Expand All @@ -37,17 +46,17 @@ export function rscAnalyzePlugin(
item.type === 'ExpressionStatement' &&
item.expression.type === 'StringLiteral'
) {
if (item.expression.value === 'use client') {
clientEntryCallback(id);
fileHashMap.set(id, await hash(code));
if (!opts.isClient && item.expression.value === 'use client') {
opts.clientFileSet.add(id);
opts.fileHashMap.set(id, await hash(code));
} else if (item.expression.value === 'use server') {
serverEntryCallback(id);
opts.serverFileSet.add(id);
}
}
}
}
// Avoid walking after the client boundary
if (clientFileSet.has(id)) {
if (!opts.isClient && opts.clientFileSet.has(id)) {
// TODO this isn't efficient. let's refactor it in the future.
return (
rscTransform as typeof rscTransform & { handler: undefined }
Expand Down
92 changes: 90 additions & 2 deletions packages/waku/src/lib/plugins/vite-plugin-rsc-transform.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,94 @@
import type { Plugin } from 'vite';
import * as swc from '@swc/core';
import * as RSDWNodeLoader from 'react-server-dom-webpack/node-loader';

import { EXTENSIONS } from '../config.js';
import { extname } from '../utils/path.js';
import { parseOpts } from '../utils/swc.js';

const transformClient = (
code: string,
id: string,
getServerId: (id: string) => string,
) => {
const ext = extname(id);
const mod = swc.parseSync(code, parseOpts(ext));
let hasUseServer = false;
for (const item of mod.body) {
if (item.type === 'ExpressionStatement') {
if (
item.expression.type === 'StringLiteral' &&
item.expression.value === 'use server'
) {
hasUseServer = true;
}
} else {
break;
}
}
if (hasUseServer) {
const exportNames = new Set<string>();
for (const item of mod.body) {
if (item.type === 'ExportDeclaration') {
if (item.declaration.type === 'FunctionDeclaration') {
exportNames.add(item.declaration.identifier.value);
} else if (item.declaration.type === 'VariableDeclaration') {
for (const d of item.declaration.declarations) {
if (d.id.type === 'Identifier') {
exportNames.add(d.id.value);
}
}
}
} else if (item.type === 'ExportNamedDeclaration') {
for (const s of item.specifiers) {
if (s.type === 'ExportSpecifier') {
exportNames.add(s.orig.value);
}
}
} else if (item.type === 'ExportDefaultExpression') {
exportNames.add('default');
} else if (item.type === 'ExportDefaultDeclaration') {
exportNames.add('default');
}
}
let code = `
import { createServerReference } from 'react-server-dom-webpack/client';
import { callServerRSC } from 'waku/client';
`;
for (const name of exportNames) {
code += `
export ${name === 'default' ? name : `const ${name} =`} createServerReference('${getServerId(id)}#${name}', callServerRSC);
`;
}
return code;
}
};

export function rscTransformPlugin(
opts:
| {
isClient: true;
isBuild: false;
}
| {
isClient: true;
isBuild: true;
serverEntryFiles: Record<string, string>;
}
| {
isClient: false;
isBuild: false;
}
| {
isClient: false;
isBuild: true;
clientEntryFiles: Record<string, string>;
serverEntryFiles: Record<string, string>;
},
): Plugin {
const getClientId = (id: string) => {
if (!opts.isBuild) {
throw new Error('not buiding');
if (opts.isClient || !opts.isBuild) {
throw new Error('not buiding for server');
}
for (const [k, v] of Object.entries(opts.clientEntryFiles)) {
if (v === id) {
Expand All @@ -37,6 +111,20 @@ export function rscTransformPlugin(
return {
name: 'rsc-transform-plugin',
async transform(code, id, options) {
if (opts.isClient) {
if (options?.ssr) {
return;
}
if (!EXTENSIONS.includes(extname(id))) {
return;
}
return transformClient(
code,
id,
opts.isBuild ? getServerId : (id) => id,
);
}
// isClient === false
if (!options?.ssr) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/waku/src/lib/renderers/dev-worker-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ const mergedViteConfig = await mergeUserViteConfig({
rscEnvPlugin({}),
rscPrivatePlugin({ privateDir: configPrivateDir, hotUpdateCallback }),
rscManagedPlugin({ basePath: configBasePath, srcDir: configSrcDir }),
rscTransformPlugin({ isBuild: false }),
rscTransformPlugin({ isClient: false, isBuild: false }),
rscDelegatePlugin(hotUpdateCallback),
],
optimizeDeps: {
Expand Down