Skip to content

Commit

Permalink
feat: basic ssr
Browse files Browse the repository at this point in the history
  • Loading branch information
sorrycc committed Nov 18, 2024
1 parent 7eba3b4 commit 8b79132
Show file tree
Hide file tree
Showing 22 changed files with 279 additions and 29 deletions.
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@examples/hackernews",
"@examples/normal",
"@examples/shadcn-cli",
"@examples/ssr",
"@examples/tailwindcss",
"@examples/with-antd4"
]
Expand Down
5 changes: 5 additions & 0 deletions .changeset/yellow-taxis-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@umijs/tnf': patch
---

feat: basic ssr
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Config is loaded from `.tnfrc.ts` by default.
- `externals: Record<string, string>`: An object that maps package names to their corresponding paths.
- `less: { modifyVars?: Record<string, string>; globalVars?: Record<string, string>; math?: 'always' | 'strict' | 'parens-division' | 'parens' | 'strict-legacy' | number; sourceMap?: any; plugins?: (string | [string, Record<string, any>])[];}`: The configuration passed to lessLoader.
- `router: { defaultPreload?: 'intent' | 'render' | 'viewport'; defaultPreloadDelay?: number; devtool?: { options?: { initialIsOpen?: boolean; position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' }; } | false; convention?: [@tanstack/router-generator](https://github.com/TanStack/router/blob/main/packages/router-generator/src/config.ts#L22C14-L22C26).Config }`: The router configuration.
- `ssr: {}`: The ssr configuration.
- `tailwindcss: boolean`: Turn on/off tailwindcss. Need to be used in conjunction with `src/tailwind.css` and `tailwind.config.js`.

## FAQ
Expand Down
11 changes: 11 additions & 0 deletions examples/ssr/.tnfrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from '@umijs/tnf';

export default defineConfig({
router: {
defaultPreload: 'intent',
convention: {
routeFileIgnorePattern: 'components',
},
},
ssr: {},
});
17 changes: 17 additions & 0 deletions examples/ssr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@examples/ssr",
"private": true,
"scripts": {
"build": "tnf build",
"dev": "tnf dev",
"preview": "tnf preview"
},
"dependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@umijs/tnf": "workspace:*"
},
"devDependencies": {
"typescript": "^5.6.3"
}
}
15 changes: 15 additions & 0 deletions examples/ssr/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const express = require('express');
const app = express();

app.use((req, res, next) => {
if (req.url === '/' || req.url === '/foo') {
return require('./dist/server.js').render(req, res);
}
next();
});

app.use(express.static('dist'));

app.listen(7001, () => {
console.log('Server is running on http://localhost:7001');
});
18 changes: 18 additions & 0 deletions examples/ssr/src/pages/__root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Link, Outlet, createRootRoute } from '@umijs/tnf/router';

export const Route = createRootRoute({
component: () => (
<>
<div>Hello</div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/foo">Foo</Link>
</li>
</ul>
<Outlet />
</>
),
});
3 changes: 3 additions & 0 deletions examples/ssr/src/pages/components/index.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.foo {
color: green;
}
11 changes: 11 additions & 0 deletions examples/ssr/src/pages/components/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createFileRoute } from '@umijs/tnf/router';

function Home() {
return (
<div>
<h3>Welcome Home!</h3>
</div>
);
}

export default Home;
27 changes: 27 additions & 0 deletions examples/ssr/src/pages/foo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createFileRoute, useLoaderData } from '@umijs/tnf/router';

export const Route = createFileRoute('/foo')({
component: Foo,
loader: async () => {
const res = await fetch('https://dummyjson.com/users');
const data = await res.json();
return data;
},
});

function Foo() {
const { users } = useLoaderData({
from: '/foo',
});
return (
<div>
<h3>Welcome Foo!</h3>
<p>Users</p>
<ul>
{users.map((user: any) => (
<li key={user.id}>{user.firstName}</li>
))}
</ul>
</div>
);
}
6 changes: 6 additions & 0 deletions examples/ssr/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createFileRoute } from '@umijs/tnf/router';
import Home from './components';

export const Route = createFileRoute('/')({
component: Home,
});
7 changes: 7 additions & 0 deletions examples/ssr/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "./.tnf/tsconfig.json",
"compilerOptions": {}

// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions scripts/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { chromium } from '@playwright/test';
import assert from 'assert';
import { exec } from 'child_process';
import http from 'http';
import path from 'path';
Expand Down Expand Up @@ -27,9 +28,7 @@ async function runE2E() {
const page = await browser.newPage();
await page.goto(`http://localhost:${port}`);
const content = await page.textContent('body');
if (!content) {
throw new Error('Page content not found');
}
assert(content?.includes('Hello'), 'Page content should include "Hello"');

console.log('E2E tests passed!');
process.exit(0);
Expand Down
27 changes: 23 additions & 4 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'pathe';
import { BundlerType, createBundler } from './bundler/bundler';
import { PluginHookType } from './plugin/plugin_manager';
import { sync } from './sync/sync';
import { type Context } from './types';
import { type Context, Mode } from './types';

export async function build({
context,
Expand Down Expand Up @@ -35,19 +35,38 @@ export async function build({

// build
const bundler = createBundler({ bundler: BundlerType.MAKO });
const baseBundleConfig = {
mode,
alias: config.alias,
externals: config.externals,
};
// client
await bundler.build({
bundlerConfig: {
...baseBundleConfig,
entry: {
client: path.join(context.paths.tmpPath, 'client.tsx'),
},
mode,
alias: config.alias,
less: config.less,
externals: config.externals,
},
cwd,
watch,
});
// server
if (config.ssr) {
await bundler.build({
bundlerConfig: {
...baseBundleConfig,
entry: {
server: path.join(context.paths.tmpPath, 'server.tsx'),
},
platform: 'node',
clean: false,
},
cwd,
watch,
});
}

// build end
await context.pluginManager.apply({
Expand Down
2 changes: 2 additions & 0 deletions src/bundler/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ export interface Bundler {
}

export interface BundlerConfig {
clean?: boolean;
entry: Record<string, string>;
mode: Mode;
platform?: 'node' | 'browser';
alias?: Config['alias'];
externals?: Config['externals'];
less?: Config['less'];
Expand Down
9 changes: 7 additions & 2 deletions src/bundler/bundler_mako.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ export default {

// build config
const config = {
clean: bundlerConfig.clean,
entry: bundlerConfig.entry,
externals: bundlerConfig.externals,
less: bundlerConfig.less,
mode: bundlerConfig.mode,
platform: bundlerConfig.platform,
resolve: {
alias: bundlerConfig.alias,
},
externals: bundlerConfig.externals,
less: bundlerConfig.less,
} as BuildParams['config'];

const isDev = bundlerConfig.mode === Mode.Development;
Expand All @@ -36,6 +38,9 @@ export default {
host: _host,
};
}
if (config.platform === 'node') {
config.cjs = true;
}

const mako = await import('@umijs/mako');
await mako.build({
Expand Down
1 change: 1 addition & 0 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const ConfigSchema = z.object({
convention: RouterGeneratorConfig,
})
.optional(),
ssr: z.object({}).optional(),
tailwindcss: z.boolean().optional(),
clickToComponent: z
.union([
Expand Down
6 changes: 6 additions & 0 deletions src/sync/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { Context } from '../types';
import { writeClientEntry } from './write_client_entry';
import { writeGlobalStyle } from './write_global_style';
import { writeRouteTree } from './write_route_tree';
import { writeRouter } from './write_router';
import { writeServerEntry } from './write_server_entry';
import { writeTailwindcss } from './write_tailwindcss';
import { writeTypes } from './write_types';

Expand All @@ -24,6 +26,10 @@ export async function sync(opts: SyncOptions) {
await writeRouteTree({ context });
const globalStyleImportPath = writeGlobalStyle({ context });
const tailwindcssPath = await writeTailwindcss({ context });
writeRouter({ opts });
if (context.config?.ssr) {
writeServerEntry({ opts });
}
writeClientEntry({
opts,
globalStyleImportPath,
Expand Down
51 changes: 31 additions & 20 deletions src/sync/write_client_entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,47 +17,57 @@ export function writeClientEntry({
config,
} = opts.context;

writeFileSync(
path.join(tmpPath, 'client.tsx'),
`
if (config?.ssr) {
writeFileSync(
path.join(tmpPath, 'client.tsx'),
`
import ReactDOM from 'react-dom/client';
import {
RouterProvider,
} from '@umijs/tnf/router';
import { createRouter } from './router';
${globalStyleImportPath}
${tailwindcssPath ? `import '${tailwindcssPath}'` : ''}
const router = createRouter();
const hydrateRoot = ReactDOM.hydrateRoot(document, <html><body><RouterProvider router={router} /><script src="/client.js"></script></body></html>);
hydrateRoot.onRecoverableError = (error, errorInfo) => {
console.log('Hydration error:', error);
console.log('Error info:', errorInfo);
};
`,
);
} else {
writeFileSync(
path.join(tmpPath, 'client.tsx'),
`
import React from 'react';
import ReactDOM from 'react-dom/client';
import {
RouterProvider,
createRouter,
} from '@umijs/tnf/router';
import { routeTree } from './routeTree.gen';
import { createRouter } from './router';
${globalStyleImportPath}
${tailwindcssPath ? `import '${tailwindcssPath}'` : ''}
const router = createRouter({
routeTree,
defaultPreload: ${config?.router?.defaultPreload ? `'${config.router.defaultPreload}'` : 'false'},
defaultPreloadDelay: ${config?.router?.defaultPreloadDelay || 50},
});
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const router = createRouter();
const TanStackRouterDevtools =
process.env.NODE_ENV === 'production'
? () => null
: React.lazy(() =>
import('@tanstack/router-devtools').then((res) => ({
default: res.TanStackRouterDevtools,
})),
)
);
const ClickToComponent =
process.env.NODE_ENV === 'production'
? () => null
: React.lazy(() =>
import('click-to-react-component').then((res) => ({
default: res.ClickToComponent,
})),
)
);
const pathModifier = (path) => {
return path.startsWith('${cwd}') ? path : '${cwd}/' + path;
}
};
ReactDOM.createRoot(document.getElementById('root')!).render(
<>
<RouterProvider router={router} />
Expand All @@ -73,6 +83,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
}
</>
);
`,
);
`,
);
}
}
Loading

0 comments on commit 8b79132

Please sign in to comment.