diff --git a/packages/rax-plugin-ssr/package.json b/packages/rax-plugin-ssr/package.json index 214a6bb826..4b8412063f 100644 --- a/packages/rax-plugin-ssr/package.json +++ b/packages/rax-plugin-ssr/package.json @@ -20,6 +20,7 @@ "lodash.clone": "^4.5.0", "lodash.clonedeep": "^4.5.0", "rax-pwa-webpack-plugin": "^1.0.0-0", + "rax-server-renderer": "^1.1.0", "rax-ssr-dev-server": "^1.0.0", "webpack": "^4.38.0" } diff --git a/packages/rax-plugin-ssr/src/getEntries.js b/packages/rax-plugin-ssr/src/getEntries.js index 8fd615c175..b49703648b 100644 --- a/packages/rax-plugin-ssr/src/getEntries.js +++ b/packages/rax-plugin-ssr/src/getEntries.js @@ -1,32 +1,39 @@ const path = require('path'); -const fs = require('fs-extra'); +const qs = require('querystring'); + +const SSRLoader = require.resolve('./loader'); module.exports = (rootDir) => { const appDirectory = rootDir; const appSrc = path.resolve(appDirectory, 'src'); - const entries = {}; + const absoluteAppPath = path.join(appDirectory, 'src/app.js'); + const absoluteAppJSONPath = path.join(appDirectory, 'src/app.json'); + const absoluteDocumentPath = path.join(appDirectory, 'src/document/index.jsx'); + const absoluteShellPath = path.join(appDirectory, 'src/shell/index.jsx'); - const files = fs.readdirSync(path.resolve(appSrc, 'pages')); - files.map((file) => { - const absolutePath = path.resolve(appSrc, 'pages', file); - const pathStat = fs.statSync(absolutePath); + const appJSON = require(absoluteAppJSONPath); + const routes = appJSON.routes; - if (pathStat.isDirectory()) { - const relativePath = path.relative(appDirectory, absolutePath); - entries[file] = './' + path.join(relativePath, '/'); - } - }); + const entries = {}; + + routes.forEach((route) => { + const entry = route.name || route.component.replace(/\//g, '_'); + const absolutePagePath = path.resolve(appSrc, route.component); - const documentPath = path.resolve(appSrc, 'document/index.jsx'); - if (fs.existsSync(documentPath)) { - entries._document = documentPath; - } + const query = { + path: route.path, + absoluteDocumentPath, + absoluteShellPath, + absoluteAppPath, + absolutePagePath, + absoluteAppJSONPath, + // errorPath: path.join(appDirectory, 'src/pages/error/index.jsx'), // 从 route 中读取 + // assetsManifestPath: pathConfig.assetsManifest + }; - const shellPath = path.resolve(appSrc, 'shell/index.jsx'); - if (fs.existsSync(shellPath)) { - entries._shell = shellPath; - } + entries[entry] = `${SSRLoader}?${qs.stringify(query)}!${absolutePagePath}`; + }); return entries; }; diff --git a/packages/rax-plugin-ssr/src/index.js b/packages/rax-plugin-ssr/src/index.js index 9da59b3352..844982fdc0 100644 --- a/packages/rax-plugin-ssr/src/index.js +++ b/packages/rax-plugin-ssr/src/index.js @@ -24,7 +24,7 @@ module.exports = ({ chainWebpack, registerConfig, rootDir, onHook, log }) => { } onHook('after.dev', () => { - runSSRDev(ssrConfig, log); + runSSRDev(ssrConfig, rootDir, log); }); }); }; diff --git a/packages/rax-plugin-ssr/src/loader.js b/packages/rax-plugin-ssr/src/loader.js new file mode 100644 index 0000000000..d748d57e50 --- /dev/null +++ b/packages/rax-plugin-ssr/src/loader.js @@ -0,0 +1,97 @@ +const { parse } = require('querystring'); +const fs = require('fs'); + +module.exports = function(content) { + const query = typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query; + + const { + absoluteDocumentPath, + absoluteShellPath, + absoluteAppPath, + absolutePagePath, + absoluteAppJSONPath + } = query; + + const hasShell = fs.existsSync(absoluteShellPath); + const shellStr = hasShell ? `import Shell from '${absoluteShellPath}'` : 'const Shell = function (props) { return props.children };'; + + return ` + import { createElement } from 'rax'; + import renderer from 'rax-server-renderer'; + + import App from '${absoluteAppPath}'; + import Page from '${absolutePagePath}'; + import Document from '${absoluteDocumentPath}'; + import appJSON from '${absoluteAppJSONPath}'; + + ${shellStr} + + async function renderComponentToHTML(req, res, Component) { + const ctx = { + req, + res + }; + + const shellData = await getInitialProps(Shell, ctx); + const appData = await getInitialProps(App, ctx); + const pageData = await getInitialProps(Component, ctx); + + const initialData = { + shellData, + appData, + pageData + }; + + const contentElement = createElement(Shell, null, createElement(App, { + routerConfig: { + defaultComponet: Component, + routes: appJSON.routes + } + })); + + const contentHtml = renderer.renderToString(contentElement); + + const documentProps = { + pageHtml: contentHtml, + pageData: JSON.stringify(initialData) + }; + + await getInitialProps(Document, ctx); + const documentElement = createElement(Document, documentProps);; + const html = '' + renderer.renderToString(documentElement); + + return html; + } + + export async function render(req, res) { + const html = await renderToHTML(req, res); + + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(html); + } + + export async function renderToHTML(req, res) { + const html = await renderComponentToHTML(req, res, Page); + return html; + } + + async function getInitialProps(Component, ctx) { + if (!Component.getInitialProps) return null; + + const props = await Component.getInitialProps(ctx); + + if (!props || typeof props !== 'object') { + const message = '"getInitialProps()" should resolve to an object. But found "' + props + '" instead.'; + throw new Error(message); + } + + if (Component.defaultProps) { + Component.defaultProps = Object.assign({}, props, Component.defaultProps); + } else { + Component.defaultProps = props; + } + + return props; + } + `; +}; diff --git a/packages/rax-plugin-ssr/src/runSSRDev.js b/packages/rax-plugin-ssr/src/runSSRDev.js index ef7afcc1fd..cff1474555 100644 --- a/packages/rax-plugin-ssr/src/runSSRDev.js +++ b/packages/rax-plugin-ssr/src/runSSRDev.js @@ -1,14 +1,29 @@ +const path = require('path'); const chalk = require('chalk'); const address = require('address'); const deepmerge = require('deepmerge'); const webpack = require('webpack'); const SSRDevServer = require('rax-ssr-dev-server'); -module.exports = (config, log) => { +module.exports = (config, rootDir, log) => { const webpackConfig = config.toConfig(); + + const absoluteAppJSONPath = path.join(rootDir, 'src/app.json'); + const appJSON = require(absoluteAppJSONPath); + + const distDir = config.output.get('path'); + const filename = config.output.get('filename'); + + const routes = {}; + appJSON.routes.forEach((route) => { + const pathName = route.name || route.component.replace(/\//g, '_'); + routes[route.path] = path.join(distDir, filename.replace('[name]', pathName)); + }); + let devServerConfig = { port: 9999, host: address.ip(), + routes }; if (webpackConfig.devServer) { diff --git a/packages/rax-plugin-ssr/src/setSSRBase.js b/packages/rax-plugin-ssr/src/setSSRBase.js index 1639e1e282..f1e433fb59 100644 --- a/packages/rax-plugin-ssr/src/setSSRBase.js +++ b/packages/rax-plugin-ssr/src/setSSRBase.js @@ -16,7 +16,8 @@ module.exports = (config, rootDir) => { .clear() .set('@core/app', 'universal-app-runtime') .set('@core/page', 'universal-app-runtime') - .set('@core/router', 'universal-app-runtime'); + .set('@core/router', 'universal-app-runtime') + .set('rax-server-renderer', require.resolve('rax-server-renderer')); config.target('node'); diff --git a/packages/rax-ssr-dev-server/index.js b/packages/rax-ssr-dev-server/index.js index 9e38bda7a8..c008e6792b 100644 --- a/packages/rax-ssr-dev-server/index.js +++ b/packages/rax-ssr-dev-server/index.js @@ -1,6 +1,5 @@ const fs = require('fs'); const express = require('express'); -const RaxServer = require('rax-server'); const devMiddleware = require('webpack-dev-middleware'); const hotMiddleware = require('webpack-hot-middleware'); const httpProxyMiddleware = require('http-proxy-middleware'); @@ -18,28 +17,21 @@ class DevServer { // eslint-disable-next-line new-cap const router = express.Router(); - const server = new RaxServer(); - const { - pagesManifest, + routes, } = this.options; - Object.keys(pagesManifest).forEach(page => { - // _document, _shell - if (page.indexOf('_') > -1) { - return; - } - - router.get(`/${page}`, (req, res) => { - const pageConfig = this.getPageConfig(res, page); - server.render(req, res, pageConfig); + Object.keys(routes).forEach(routePath => { + router.get(routePath, (req, res) => { + const page = this.loadComponent(routes[routePath], res); + page.render(req, res); }); }); - if (pagesManifest.index) { + if (!routes['/'] && routes['/index']) { router.get('/', (req, res) => { - const pageConfig = this.getPageConfig(res, 'index'); - server.render(req, res, pageConfig); + const page = this.loadComponent(routes['/index'], res); + page.render(req, res); }); } @@ -68,42 +60,7 @@ class DevServer { this.app.listen(port, callback); } - getPageConfig(res, page) { - const { - appConfig = {}, - pagesManifest, - assetsManifest, - assetsManifestPath - } = this.options; - - let assets = assetsManifest || {}; - if (assetsManifestPath) { - const assetsContent = fs.readFileSync(assetsManifestPath, res); - assets = JSON.parse(assetsContent); - }; - - const pageConfig = { - page, - ...assets[page], - component: this.loadComponent(page, res), - document: { - title: appConfig.title, - component: pagesManifest._document ? this.loadComponent('_document', res) : null - }, - shell: { - component: pagesManifest._shell ? this.loadComponent('_shell', res) : null - } - }; - - return pageConfig; - } - - loadComponent(page, res) { - const { - pagesManifest - } = this.options; - - const bundlePath = pagesManifest[page]; + loadComponent(bundlePath, res) { const bundleContent = this.readFileSyncFromWebpack(bundlePath, res); const mod = eval(bundleContent); diff --git a/packages/rax-ssr-dev-server/package.json b/packages/rax-ssr-dev-server/package.json index c66d143f2a..f32d3e3208 100644 --- a/packages/rax-ssr-dev-server/package.json +++ b/packages/rax-ssr-dev-server/package.json @@ -18,7 +18,6 @@ "dependencies": { "express": "^4.17.1", "http-proxy-middleware": "^0.19.1", - "rax-server": "^1.0.0", "webpack-dev-middleware": "^3.7.0", "webpack-hot-middleware": "^2.25.0" } diff --git a/packages/universal-app-runtime/src/router.js b/packages/universal-app-runtime/src/router.js index 37d1e9ed20..275da46f03 100644 --- a/packages/universal-app-runtime/src/router.js +++ b/packages/universal-app-runtime/src/router.js @@ -2,14 +2,21 @@ import { createElement } from 'rax'; import * as RaxUseRouter from 'rax-use-router'; import { createHashHistory } from 'history'; import encodeQS from 'querystring/encode'; +import { isWeb } from 'universal-env'; let _history = null; export function useRouter(routerConfig) { - const { history = createHashHistory(), routes } = routerConfig; - _history = history; + if (isWeb) { + const { history = createHashHistory(), routes } = routerConfig; + _history = history; + } function Router(props) { + if (routerConfig.defaultComponet) { + return createElement(routerConfig.defaultComponet, props); + } + const { component } = RaxUseRouter.useRouter(() => routerConfig); if (!component || Array.isArray(component) && component.length === 0) {