From 279aa95f1f66ab613733e468ee5de5d416a709af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8B=92=E7=8B=92=E7=A5=9E?= Date: Thu, 24 Dec 2020 14:01:53 +0800 Subject: [PATCH] feat: support without document file (#546) * feat: support without document file * fix: generate html * feat: ssr * chore: remove useless version --- examples/basic-without-document/README.md | 1 + examples/basic-without-document/build.json | 6 + examples/basic-without-document/package.json | 22 +++ .../basic-without-document/public/rax.png | Bin 0 -> 44951 bytes examples/basic-without-document/src/app.json | 19 +++ examples/basic-without-document/src/app.ts | 4 + .../src/components/Logo/index.css | 5 + .../src/components/Logo/index.tsx | 21 +++ .../src/pages/About/index.css | 16 +++ .../src/pages/About/index.tsx | 31 +++++ .../src/pages/Home/index.css | 16 +++ .../src/pages/Home/index.tsx | 42 ++++++ examples/basic-without-document/tsconfig.json | 33 +++++ .../src/pages/Home/models/counter.ts | 4 +- examples/with-rax-store/tsconfig.json | 31 +++-- examples/with-rax/src/pages/About/index.css | 4 +- packages/error-stack-tracey/index.d.ts | 2 +- packages/plugin-rax-app/src/base.js | 2 +- packages/plugin-rax-web/package.json | 3 +- .../src/DocumentPlugin/builtInLoader.ts | 40 ++++++ .../{loader.ts => customLoader.ts} | 35 ++--- .../src/DocumentPlugin/index.ts | 86 ++++++++---- packages/plugin-rax-web/src/index.ts | 5 +- packages/plugin-rax-web/src/types.ts | 16 +++ .../plugin-rax-web/src/utils/htmlStructure.ts | 90 ++++++++++++ .../src/utils/setRegisterMethod.ts | 8 ++ packages/plugin-ssr/package.json | 3 +- packages/plugin-ssr/src/constants.ts | 3 + packages/plugin-ssr/src/index.ts | 20 +-- packages/plugin-ssr/src/ssr/entryLoader.js | 131 ------------------ packages/plugin-ssr/src/ssr/entryPlugin.js | 67 --------- packages/plugin-ssr/src/ssr/entryPlugin.ts | 81 +++++++++++ packages/plugin-ssr/src/ssr/getBase.js | 75 ---------- packages/plugin-ssr/src/ssr/getBase.ts | 56 ++++++++ packages/plugin-ssr/src/ssr/getBuiltInHTML.ts | 16 +++ .../ssr/{getEntryName.js => getEntryName.ts} | 2 +- packages/plugin-ssr/src/ssr/getMpaRoutes.js | 41 ------ .../plugin-ssr/src/ssr/loaders/EntryLoader.ts | 97 +++++++++++++ .../src/ssr/loaders/builtInHTMLLoader.ts | 107 ++++++++++++++ .../src/ssr/loaders/customDocumentLoader.ts | 91 ++++++++++++ .../src/ssr/{setBuild.js => setBuild.ts} | 2 +- .../src/ssr/{setDev.js => setDev.ts} | 50 ++++--- packages/plugin-ssr/src/types.ts | 17 +++ .../src/web/{setDev.js => setDev.ts} | 2 +- packages/rax-app-renderer/src/renderer.tsx | 4 + 45 files changed, 990 insertions(+), 417 deletions(-) create mode 100644 examples/basic-without-document/README.md create mode 100644 examples/basic-without-document/build.json create mode 100644 examples/basic-without-document/package.json create mode 100644 examples/basic-without-document/public/rax.png create mode 100644 examples/basic-without-document/src/app.json create mode 100644 examples/basic-without-document/src/app.ts create mode 100644 examples/basic-without-document/src/components/Logo/index.css create mode 100644 examples/basic-without-document/src/components/Logo/index.tsx create mode 100644 examples/basic-without-document/src/pages/About/index.css create mode 100644 examples/basic-without-document/src/pages/About/index.tsx create mode 100644 examples/basic-without-document/src/pages/Home/index.css create mode 100644 examples/basic-without-document/src/pages/Home/index.tsx create mode 100644 examples/basic-without-document/tsconfig.json create mode 100644 packages/plugin-rax-web/src/DocumentPlugin/builtInLoader.ts rename packages/plugin-rax-web/src/DocumentPlugin/{loader.ts => customLoader.ts} (63%) create mode 100644 packages/plugin-rax-web/src/types.ts create mode 100644 packages/plugin-rax-web/src/utils/htmlStructure.ts create mode 100644 packages/plugin-rax-web/src/utils/setRegisterMethod.ts create mode 100644 packages/plugin-ssr/src/constants.ts delete mode 100644 packages/plugin-ssr/src/ssr/entryLoader.js delete mode 100644 packages/plugin-ssr/src/ssr/entryPlugin.js create mode 100644 packages/plugin-ssr/src/ssr/entryPlugin.ts delete mode 100644 packages/plugin-ssr/src/ssr/getBase.js create mode 100644 packages/plugin-ssr/src/ssr/getBase.ts create mode 100644 packages/plugin-ssr/src/ssr/getBuiltInHTML.ts rename packages/plugin-ssr/src/ssr/{getEntryName.js => getEntryName.ts} (88%) delete mode 100644 packages/plugin-ssr/src/ssr/getMpaRoutes.js create mode 100644 packages/plugin-ssr/src/ssr/loaders/EntryLoader.ts create mode 100644 packages/plugin-ssr/src/ssr/loaders/builtInHTMLLoader.ts create mode 100644 packages/plugin-ssr/src/ssr/loaders/customDocumentLoader.ts rename packages/plugin-ssr/src/ssr/{setBuild.js => setBuild.ts} (57%) rename packages/plugin-ssr/src/ssr/{setDev.js => setDev.ts} (54%) create mode 100644 packages/plugin-ssr/src/types.ts rename packages/plugin-ssr/src/web/{setDev.js => setDev.ts} (92%) diff --git a/examples/basic-without-document/README.md b/examples/basic-without-document/README.md new file mode 100644 index 000000000..6fef4aa54 --- /dev/null +++ b/examples/basic-without-document/README.md @@ -0,0 +1 @@ +# basic-without-document diff --git a/examples/basic-without-document/build.json b/examples/basic-without-document/build.json new file mode 100644 index 000000000..5c8a1cb53 --- /dev/null +++ b/examples/basic-without-document/build.json @@ -0,0 +1,6 @@ +{ + "targets": ["web"], + "web": { + "ssr": true + } +} diff --git a/examples/basic-without-document/package.json b/examples/basic-without-document/package.json new file mode 100644 index 000000000..f8957f78b --- /dev/null +++ b/examples/basic-without-document/package.json @@ -0,0 +1,22 @@ +{ + "name": "with-rax", + "description": "rax example", + "dependencies": { + "rax": "^1.1.0", + "rax-document": "^0.1.0", + "rax-image": "^2.0.0", + "rax-link": "^1.0.1", + "rax-text": "^1.0.0", + "rax-view": "^1.0.0" + }, + "devDependencies": { + "@types/rax": "^1.0.0" + }, + "scripts": { + "start": "rax-app start", + "build": "rax-app build" + }, + "engines": { + "node": ">=8.0.0" + } +} diff --git a/examples/basic-without-document/public/rax.png b/examples/basic-without-document/public/rax.png new file mode 100644 index 0000000000000000000000000000000000000000..ffb83fb5520bb98f87c240b0ae6460734b82e525 GIT binary patch literal 44951 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"] +} diff --git a/examples/basic-without-document/src/app.ts b/examples/basic-without-document/src/app.ts new file mode 100644 index 000000000..1023ae2d5 --- /dev/null +++ b/examples/basic-without-document/src/app.ts @@ -0,0 +1,4 @@ +import { createElement } from 'rax'; +import { runApp } from 'rax-app'; + +runApp(); diff --git a/examples/basic-without-document/src/components/Logo/index.css b/examples/basic-without-document/src/components/Logo/index.css new file mode 100644 index 000000000..2f8d8d114 --- /dev/null +++ b/examples/basic-without-document/src/components/Logo/index.css @@ -0,0 +1,5 @@ +.logo { + width: 200rpx; + height: 180rpx; + margin-bottom: 20rpx; +} \ No newline at end of file diff --git a/examples/basic-without-document/src/components/Logo/index.tsx b/examples/basic-without-document/src/components/Logo/index.tsx new file mode 100644 index 000000000..488be5cb2 --- /dev/null +++ b/examples/basic-without-document/src/components/Logo/index.tsx @@ -0,0 +1,21 @@ +import { createElement, PureComponent } from 'rax'; +import Image from 'rax-image'; + +import './index.css'; + +class Logo extends PureComponent { + render() { + const source = { + uri: 'https://img.alicdn.com/imgextra/i4/O1CN0145ZaIM1QEObAAbKa1_!!6000000001944-2-tps-1701-1535.png', + }; + console.log('with router =>', this.props); + return ( + + ); + } +} + +export default Logo; diff --git a/examples/basic-without-document/src/pages/About/index.css b/examples/basic-without-document/src/pages/About/index.css new file mode 100644 index 000000000..a8814796d --- /dev/null +++ b/examples/basic-without-document/src/pages/About/index.css @@ -0,0 +1,16 @@ +.home { + align-items: center; + margin-top: 200rpx; +} + +.title { + font-size: 35rpx; + font-weight: bold; + margin: 20rpx 0; +} + +.info { + font-size: 36rpx; + margin: 8rpx 0; + color: #555; +} diff --git a/examples/basic-without-document/src/pages/About/index.tsx b/examples/basic-without-document/src/pages/About/index.tsx new file mode 100644 index 000000000..4d21b577a --- /dev/null +++ b/examples/basic-without-document/src/pages/About/index.tsx @@ -0,0 +1,31 @@ +import { createElement, Component } from 'rax'; +import View from 'rax-view'; +import Text from 'rax-text'; +import { getSearchParams } from 'rax-app'; + +import './index.css'; + +class About extends Component { + componentDidMount() { + console.log('about search params', getSearchParams()); + } + + onShow() { + console.log('about show...'); + } + + onHide() { + console.log('about hide...'); + } + + render() { + return ( + + About Page + (this.props as any).history.push('/')}>Go Home + + ); + } +} + +export default About; diff --git a/examples/basic-without-document/src/pages/Home/index.css b/examples/basic-without-document/src/pages/Home/index.css new file mode 100644 index 000000000..a8814796d --- /dev/null +++ b/examples/basic-without-document/src/pages/Home/index.css @@ -0,0 +1,16 @@ +.home { + align-items: center; + margin-top: 200rpx; +} + +.title { + font-size: 35rpx; + font-weight: bold; + margin: 20rpx 0; +} + +.info { + font-size: 36rpx; + margin: 8rpx 0; + color: #555; +} diff --git a/examples/basic-without-document/src/pages/Home/index.tsx b/examples/basic-without-document/src/pages/Home/index.tsx new file mode 100644 index 000000000..0e2564774 --- /dev/null +++ b/examples/basic-without-document/src/pages/Home/index.tsx @@ -0,0 +1,42 @@ +import { createElement } from 'rax'; +import { usePageShow, usePageHide, getSearchParams } from 'rax-app'; +import View from 'rax-view'; +import Text from 'rax-text'; +import Logo from '@/components/Logo'; + +import './index.css'; + +export default function Home(props) { + const { history } = props; + + const searchParams = getSearchParams(); + + console.log('home search params =>', searchParams); + console.log('home page props =>', props); + + usePageShow(() => { + console.log('home show...'); + }); + + usePageHide(() => { + console.log('home hide...'); + }); + + return ( + + + {props?.data?.title || 'Welcome to Your Rax App'} + {props?.data?.info || 'More information about Rax'} + history.push('/about', { id: 1 })}>Go About + + ); +} + +Home.getInitialProps = async () => { + return { + data: { + title: 'Welcome to Your Rax App with SSR', + info: 'More information about Rax', + }, + }; +}; diff --git a/examples/basic-without-document/tsconfig.json b/examples/basic-without-document/tsconfig.json new file mode 100644 index 000000000..2fad2a09f --- /dev/null +++ b/examples/basic-without-document/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compileOnSave": false, + "buildOnSave": false, + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "module": "esnext", + "target": "es6", + "jsx": "preserve", + "jsxFactory": "createElement", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "rootDir": "./", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"], + "rax-app": [".rax/index.ts"] + } + }, + "include": ["src", ".rax"], + "exclude": ["node_modules", "build", "public"] +} diff --git a/examples/with-rax-store/src/pages/Home/models/counter.ts b/examples/with-rax-store/src/pages/Home/models/counter.ts index 1ddffb7ce..e1cbc1fb4 100644 --- a/examples/with-rax-store/src/pages/Home/models/counter.ts +++ b/examples/with-rax-store/src/pages/Home/models/counter.ts @@ -1,4 +1,4 @@ -import { IRootDispatch } from 'rax-app'; +import { IStoreDispatch } from 'rax-app'; export const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time)); @@ -16,7 +16,7 @@ export default { }, }, - effects: (dispatch: IRootDispatch) => ({ + effects: (dispatch: IStoreDispatch) => ({ async decrementAsync() { await delay(10); dispatch.counter.decrement(); diff --git a/examples/with-rax-store/tsconfig.json b/examples/with-rax-store/tsconfig.json index 335d39904..2fad2a09f 100644 --- a/examples/with-rax-store/tsconfig.json +++ b/examples/with-rax-store/tsconfig.json @@ -1,20 +1,33 @@ { + "compileOnSave": false, + "buildOnSave": false, "compilerOptions": { - "module": "esNext", - "target": "es2015", + "baseUrl": ".", "outDir": "build", - "jsx": "react", + "module": "esnext", + "target": "es6", + "jsx": "preserve", "jsxFactory": "createElement", "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": ["es6", "dom"], "sourceMap": true, - "alwaysStrict": true, - "baseUrl": ".", + "allowJs": true, + "rootDir": "./", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "skipLibCheck": true, "paths": { "@/*": ["./src/*"], - "rax-app": [".rax/index.ts"], - "rax": ["node_modules/@types/rax"] + "rax-app": [".rax/index.ts"] } }, - "include": ["src/*", ".rax"], - "exclude": ["build"] + "include": ["src", ".rax"], + "exclude": ["node_modules", "build", "public"] } diff --git a/examples/with-rax/src/pages/About/index.css b/examples/with-rax/src/pages/About/index.css index 85ae2d072..a8814796d 100644 --- a/examples/with-rax/src/pages/About/index.css +++ b/examples/with-rax/src/pages/About/index.css @@ -1,10 +1,10 @@ -.about { +.home { align-items: center; margin-top: 200rpx; } .title { - font-size: 45rpx; + font-size: 35rpx; font-weight: bold; margin: 20rpx 0; } diff --git a/packages/error-stack-tracey/index.d.ts b/packages/error-stack-tracey/index.d.ts index a28678c9d..f8dbf7b34 100644 --- a/packages/error-stack-tracey/index.d.ts +++ b/packages/error-stack-tracey/index.d.ts @@ -1,4 +1,4 @@ declare module 'error-stack-tracey' { - export function parse(error: object, bundleContent: string): object; + export function parse(error: object, bundleContent: string): any[]; export function print(message: string, stackFrame: any[]): void; } diff --git a/packages/plugin-rax-app/src/base.js b/packages/plugin-rax-app/src/base.js index 8a2e3c494..f1244dff7 100644 --- a/packages/plugin-rax-app/src/base.js +++ b/packages/plugin-rax-app/src/base.js @@ -56,7 +56,7 @@ module.exports = (api, { target, babelConfigOptions, progressOptions = {} }) => } config.plugin('DefinePlugin').tap((args) => [ - Object.assign(...args, { + Object.assign({}, ...args, { 'process.env.PUBLIC_URL': JSON.stringify(publicUrl), }), ]); diff --git a/packages/plugin-rax-web/package.json b/packages/plugin-rax-web/package.json index c5289d066..579b3a69b 100644 --- a/packages/plugin-rax-web/package.json +++ b/packages/plugin-rax-web/package.json @@ -28,6 +28,7 @@ "@builder/app-helpers": "^2.0.0", "react-dev-utils": "^10.0.0", "chalk": "^4.1.0", - "html-minifier": "^4.0.0" + "html-minifier": "^4.0.0", + "cheerio": "1.0.0-rc.3" } } diff --git a/packages/plugin-rax-web/src/DocumentPlugin/builtInLoader.ts b/packages/plugin-rax-web/src/DocumentPlugin/builtInLoader.ts new file mode 100644 index 000000000..aefa34514 --- /dev/null +++ b/packages/plugin-rax-web/src/DocumentPlugin/builtInLoader.ts @@ -0,0 +1,40 @@ +import * as qs from 'qs'; +import * as fs from 'fs'; +import { formatPath } from '@builder/app-helpers'; +import { IBuiltInDocumentQuery } from '../types'; + +/** + * loader for wrap document and pages to be server render function, which can render page to html + */ +export default function () { + const query: IBuiltInDocumentQuery = typeof this.query === 'string' ? qs.parse(this.query.substr(1)) : this.query; + const { staticExportPagePath, builtInDocumentTpl } = query; + + const formatedPagePath = staticExportPagePath ? formatPath(staticExportPagePath) : null; + const needStaicExport = formatedPagePath && fs.existsSync(formatedPagePath); + let source; + + if (needStaicExport) { + source = ` + import { createElement } from 'rax'; + import renderer from 'rax-server-renderer'; + import Page from '${formatedPagePath}'; + + export function renderInitialHTML(assets) { + const contentElement = createElement(Page, {}); + + const initialHtml = contentElement ? renderer.renderToString(contentElement, { + defaultUnit: 'rpx' + }) : ''; + + return initialHtml; + } +`; + } else { + source = ` + export const renderInitialHTML = () => ''; + `; + } + source += `\n export const html = \`${builtInDocumentTpl}\`;`; + return source; +} diff --git a/packages/plugin-rax-web/src/DocumentPlugin/loader.ts b/packages/plugin-rax-web/src/DocumentPlugin/customLoader.ts similarity index 63% rename from packages/plugin-rax-web/src/DocumentPlugin/loader.ts rename to packages/plugin-rax-web/src/DocumentPlugin/customLoader.ts index fff9faceb..a3475f214 100644 --- a/packages/plugin-rax-web/src/DocumentPlugin/loader.ts +++ b/packages/plugin-rax-web/src/DocumentPlugin/customLoader.ts @@ -1,41 +1,25 @@ import * as qs from 'qs'; -import * as path from 'path'; import * as fs from 'fs'; - -const isWin = process.platform === 'win32'; - -/** - * Transform Windows-style paths, such as 'C:\Windows\system32' to 'C:/Windows/system32'. - * Because 'C:\Windows\system32' will be escaped to 'C:Windowssystem32' - * @param {*} p - */ -const formatPath = (p) => { - return isWin ? p.split(path.sep).join('/') : p; -}; +import { ICustomDocumentQuery } from '../types'; /** * loader for wrap document and pages to be server render function, which can render page to html */ export default function () { - const query = typeof this.query === 'string' ? qs.parse(this.query.substr(1)) : this.query; - const { - absoluteDocumentPath, - absolutePagePath, - pagePath, - htmlInfo = {}, - manifests, - } = query; + const query: ICustomDocumentQuery = typeof this.query === 'string' ? qs.parse(this.query.substr(1)) : this.query; + const { documentPath, staticExportPagePath, pagePath, htmlInfo = {} } = query; const { doctype, title } = htmlInfo; - const formatedPagePath = absolutePagePath ? formatPath(absolutePagePath) : null; - - const pageStr = formatedPagePath && fs.existsSync(formatedPagePath) ? `import Page from '${formatedPagePath}';` : 'const Page = null;'; + const pageStr = + staticExportPagePath && fs.existsSync(staticExportPagePath) + ? `import Page from '${staticExportPagePath}';` + : 'const Page = null;'; const doctypeStr = doctype === null || doctype === '' ? '' : `${doctype || ''}`; const source = ` import { createElement } from 'rax'; import renderer from 'rax-server-renderer'; - import Document from '${formatPath(absoluteDocumentPath)}'; + import Document from '${documentPath}'; ${pageStr} function renderToHTML(assets) { @@ -55,8 +39,7 @@ export default function () { __initialHtml: initialHtml, __pagePath: '${pagePath}', __styles: assets.styles, - __scripts: assets.scripts, - __manifests: ${manifests} + __scripts: assets.scripts }; }; diff --git a/packages/plugin-rax-web/src/DocumentPlugin/index.ts b/packages/plugin-rax-web/src/DocumentPlugin/index.ts index 5e549a396..fb89d214e 100644 --- a/packages/plugin-rax-web/src/DocumentPlugin/index.ts +++ b/packages/plugin-rax-web/src/DocumentPlugin/index.ts @@ -1,9 +1,18 @@ import * as qs from 'qs'; import * as path from 'path'; -import * as fs from 'fs'; +import * as fs from 'fs-extra'; import * as webpack from 'webpack'; import * as webpackSources from 'webpack-sources'; import * as errorStackTracey from 'error-stack-tracey'; +import { formatPath } from '@builder/app-helpers'; +import { + getBuiltInHtmlTpl, + generateHtmlStructure, + insertCommonElements, + insertLinks, + insertScripts, +} from '../utils/htmlStructure'; +import { IHtmlInfo, IBuiltInDocumentQuery, ICustomDocumentQuery } from '../types'; const { parse, print } = errorStackTracey; const { RawSource } = webpackSources; @@ -11,6 +20,7 @@ const PLUGIN_NAME = 'DocumentPlugin'; export default class DocumentPlugin { options: any; + documentPath: string | undefined; constructor(options) { /** * An plugin which generate HTML files @@ -26,6 +36,10 @@ export default class DocumentPlugin { * @param {function} [options.configWebpack] custom webpack config for document */ this.options = options; + const { + context: { rootDir }, + } = options; + this.documentPath = getAbsoluteFilePath(rootDir, 'src/document/index'); } apply(compiler) { @@ -63,12 +77,8 @@ export default class DocumentPlugin { } // Support custom loader - const loaderForDocument = options.loader || require.resolve('./loader'); - - // Document path is specified - const absoluteDocumentPath = getAbsoluteFilePath(rootDir, 'src/document/index'); - - const manifestString = options.manifests ? JSON.stringify(options.manifests) : null; + const loaderForDocument = + options.loader || (this.documentPath ? require.resolve('./customLoader') : require.resolve('./builtInLoader')); delete webpackConfig.entry.index; // Add ssr loader for each entry @@ -76,29 +86,41 @@ export default class DocumentPlugin { const pageInfo = pages[entryName]; const { tempFile, source, pagePath } = pageInfo; - const absolutePagePath = + if (!webpackConfig.entry[tempFile]) { + webpackConfig.entry[tempFile] = []; + } + + const staticExportPagePath: string = options.staticExport && source ? getAbsoluteFilePath(rootDir, path.join('src', source)) : ''; const targetPage = source && options.staticConfig.routes.find((route) => route.source === source); - const htmlInfo = { + const htmlInfo: IHtmlInfo = { ...options.htmlInfo, title: (targetPage && targetPage.window && targetPage.window.title) || options.htmlInfo.title, }; - const query: any = { - absoluteDocumentPath, - absolutePagePath, - pagePath, - htmlInfo, - }; - if (manifestString) { - query.manifests = manifestString; + if (this.documentPath) { + const query: ICustomDocumentQuery = { + documentPath: this.documentPath, + staticExportPagePath, + pagePath, + htmlInfo, + }; + + webpackConfig.entry[tempFile].push(`${loaderForDocument}?${qs.stringify(query)}!${this.documentPath}`); + } else { + const builtInDocumentTpl = getBuiltInHtmlTpl(htmlInfo); + const query: IBuiltInDocumentQuery = { + staticExportPagePath, + builtInDocumentTpl, + }; + // Generate temp entry file + const tempEntryPath = path.join(__dirname, 'tempEntry.js'); + fs.ensureFileSync(tempEntryPath); + // Insert elements which define in app.json + insertCommonElements(options.staticConfig); + webpackConfig.entry[tempFile].push(`${loaderForDocument}?${qs.stringify(query)}!${tempEntryPath}`); } - - if (!webpackConfig.entry[tempFile]) { - webpackConfig.entry[tempFile] = []; - } - webpackConfig.entry[tempFile].push(`${loaderForDocument}?${qs.stringify(query)}!${absoluteDocumentPath}`); }); let cachedHTML = {}; @@ -153,6 +175,7 @@ export default class DocumentPlugin { cachedHTML = await generateHtml(compilation, { pages, publicPath, + existDocument: !!this.documentPath, }); } @@ -180,12 +203,23 @@ async function generateHtml(compilation, options) { const files = compilation.entrypoints.get(entryName).getFiles(); const assets = getAssetsForPage(files, publicPath); const documentContent = compilation.assets[`${tempFile}.js`].source(); - let pageSource; try { const Document: any = loadDocument(documentContent); - pageSource = Document.renderToHTML(assets); + if (options.existDocument) { + const $ = generateHtmlStructure(Document.renderToHTML(assets)); + pageSource = $.html(); + } else { + const initialHTML = Document.renderInitialHTML(); + const builtInDocumentTpl = Document.html; + insertLinks(assets.styles.map((style) => ``)); + insertScripts(assets.scripts.map((script) => `') + + const contentElement = createElement(Component, pageData); + + const initialHtml = renderer.renderToString(contentElement, { + defaultUnit: 'rpx' + }); + // use let statement, because styles and scripts may be changed by assetsProcessor + let styles = ${JSON.stringify(this.styles)}; + let scripts = ${JSON.stringify(this.scripts)}; + + // process public path for different runtime env + ${this.assetsProcessor || ''} + + builtInLinks = [...builtInLinks, ...styles]; + builtInScripts = [...builtInScripts, ...scripts]; + + return generateHtml(\`${this.builtInHTML}\`, initialHtml); + } + `; + return this; + } +} + +export default function () { + const query = typeof this.query === 'string' ? qs.parse(this.query.substr(1)) : this.query; + + if (!query.entryPath) { + query.entryPath = formatPath(this.resourcePath); + } + + const builtInHTMLLoader = new BuiltInHTMLLoader(query); + + return builtInHTMLLoader + .addInitImport() + .addVariableDeclaration() + .addGenerateHtml() + .addGetInitialProps() + .addRenderComponentToHTML() + .addRenderToHTML() + .addRender() + .addRenderWithContext() + .addExport() + .getSource(); +} diff --git a/packages/plugin-ssr/src/ssr/loaders/customDocumentLoader.ts b/packages/plugin-ssr/src/ssr/loaders/customDocumentLoader.ts new file mode 100644 index 000000000..bcfdcc4a5 --- /dev/null +++ b/packages/plugin-ssr/src/ssr/loaders/customDocumentLoader.ts @@ -0,0 +1,91 @@ +import * as qs from 'qs'; +import { formatPath } from '@builder/app-helpers'; +import EntryLoader from './EntryLoader'; + +class CustomDocumentLoader extends EntryLoader { + documentPath: string; + styles: string[]; + scripts: string[]; + assetsProcessor: string; + constructor(options) { + super(options); + this.documentPath = options.documentPath; + this.styles = options.styles || []; + this.scripts = options.scripts; + this.assetsProcessor = options.assetsProcessor; + } + addInitImport() { + super.addInitImport(); + this.source += ` + import Document from '${this.documentPath}'; + `; + return this; + } + addRenderComponentToHTML() { + this.source += ` + async function renderComponentToHTML(Component, ctx) { + const pageData = await getInitialProps(Component, ctx); + const initialData = appConfig.app && appConfig.app.getInitialData ? await appConfig.app.getInitialData() : {}; + + const data = { + __SSR_ENABLED__: true, + initialData, + pageData, + }; + + const contentElement = createElement(Component, pageData); + + const initialHtml = renderer.renderToString(contentElement, { + defaultUnit: 'rpx' + }); + // use let statement, because styles and scripts may be changed by assetsProcessor + let styles = ${JSON.stringify(this.styles)}; + let scripts = ${JSON.stringify(this.scripts)}; + + // process public path for different runtime env + ${this.assetsProcessor || ''} + + // This loader is executed after babel, so need to be tansformed to ES5. + const DocumentContextProvider = function() {}; + DocumentContextProvider.prototype.getChildContext = function() { + return { + __initialHtml: initialHtml, + __initialData: JSON.stringify(data), + __styles: styles, + __scripts: scripts, + }; + }; + DocumentContextProvider.prototype.render = function() { + return createElement(Document, initialData); + }; + + const DocumentContextProviderElement = createElement(DocumentContextProvider); + + const html = '' + renderer.renderToString(DocumentContextProviderElement); + + return html; + } + `; + return this; + } +} + +export default function () { + const query = typeof this.query === 'string' ? qs.parse(this.query.substr(1)) : this.query; + if (!query.entryPath) { + query.entryPath = formatPath(this.resourcePath); + } + + const customDocumentLoader = new CustomDocumentLoader(query); + + return customDocumentLoader + .addInitImport() + .addVariableDeclaration() + .addGetInitialProps() + .addRenderComponentToHTML() + .addRenderToHTML() + .addRender() + .addRenderWithContext() + .addExport() + .getSource(); +} diff --git a/packages/plugin-ssr/src/ssr/setBuild.js b/packages/plugin-ssr/src/ssr/setBuild.ts similarity index 57% rename from packages/plugin-ssr/src/ssr/setBuild.js rename to packages/plugin-ssr/src/ssr/setBuild.ts index 7caea3ec0..9e9f00899 100644 --- a/packages/plugin-ssr/src/ssr/setBuild.js +++ b/packages/plugin-ssr/src/ssr/setBuild.ts @@ -1,3 +1,3 @@ -module.exports = (config) => { +export default (config) => { config.optimization.minimize(false); }; diff --git a/packages/plugin-ssr/src/ssr/setDev.js b/packages/plugin-ssr/src/ssr/setDev.ts similarity index 54% rename from packages/plugin-ssr/src/ssr/setDev.js rename to packages/plugin-ssr/src/ssr/setDev.ts index 118e7d701..4ee39d9d1 100644 --- a/packages/plugin-ssr/src/ssr/setDev.js +++ b/packages/plugin-ssr/src/ssr/setDev.ts @@ -1,39 +1,43 @@ -const path = require('path'); -const Module = require('module'); -const fs = require('fs-extra'); -const { parse, print } = require('error-stack-tracey'); -const getEntryName = require('./getEntryName'); -const getMpaRoutes = require('./getMpaRoutes'); +import * as path from 'path'; +import * as Module from 'module'; +import * as fs from 'fs'; +import * as errorStackTracey from 'error-stack-tracey'; +import { getMpaEntries } from '@builder/app-helpers'; +import getEntryName from './getEntryName'; + +const { parse, print } = errorStackTracey; function exec(code, filename, filePath) { - const module = new Module(filename, this); - module.paths = Module._nodeModulePaths(filePath); + const module: any = new Module(filename, this); + module.paths = (Module as any)._nodeModulePaths(filePath); module.filename = filename; module._compile(code, filename); return module.exports; } -module.exports = (config, context) => { +export default (config, api) => { + const { context, getValue } = api; const { rootDir, userConfig } = context; - const { web: webConfig = {} } = userConfig; + const { web: webConfig = {}, outputDir } = userConfig; + const distDir = path.join(rootDir, outputDir, 'node'); config.mode('development'); - - let routes = []; + const staticConfig = getValue('staticConfig'); + const { routes } = staticConfig; if (webConfig.mpa) { - routes = getMpaRoutes(config); + const entries = getMpaEntries(api, { target: 'web', appJsonContent: staticConfig }); + routes.forEach((route) => { + const { entryName } = entries.find(({ source }) => source === route.source); + route.path = `/${entryName}.html`; + route.entryName = entryName; + route.componentPath = path.join(distDir, `${entryName}.js`); + }); } else { - const absoluteAppJSONPath = path.join(rootDir, 'src/app.json'); - const distDir = config.output.get('path'); - const filename = config.output.get('filename'); - // eslint-disable-next-line - routes = require(absoluteAppJSONPath).routes; - routes.forEach((route) => { const entryName = getEntryName(route.path); route.entryName = entryName; - route.componentPath = path.join(distDir, filename.replace('[name]', entryName)); + route.componentPath = path.join(distDir, `${entryName}.js`); }); } @@ -41,16 +45,20 @@ module.exports = (config, context) => { config.devtool('eval-cheap-source-map'); config.devServer.hot(false); + const originalBeforeDevFunc = config.devServer.get('before'); // There can only be one `before` config, this config will overwrite `before` config in web plugin. config.devServer.set('before', (app, devServer) => { + if (originalBeforeDevFunc) { + originalBeforeDevFunc(app, devServer); + } // outputFileSystem in devServer is MemoryFileSystem by defalut, but it can also be custom with other file systems. const outputFs = devServer.compiler.compilers[0].outputFileSystem; routes.forEach((route) => { app.get(route.path, async (req, res) => { const bundleContent = outputFs.readFileSync(route.componentPath, 'utf8'); - process.once('unhandledRejection', async (error) => { + process.once('unhandledRejection', async (error: Error) => { const errorStack = await parse(error, bundleContent); print(error.message, errorStack); }); diff --git a/packages/plugin-ssr/src/types.ts b/packages/plugin-ssr/src/types.ts new file mode 100644 index 000000000..8b492b2e4 --- /dev/null +++ b/packages/plugin-ssr/src/types.ts @@ -0,0 +1,17 @@ +export interface IInjectedHTML { + scripts: string[]; + links: string[]; + metas: string[]; +} + +export interface IEntryLoaderQuery { + styles: string[]; + scripts: string[]; + absoluteAppConfigPath: string; + entryPath: string; + assetsProcessor?: string; + documentPath?: string; + builtInHTML?: string; + injectedHTML?: IInjectedHTML; +} + diff --git a/packages/plugin-ssr/src/web/setDev.js b/packages/plugin-ssr/src/web/setDev.ts similarity index 92% rename from packages/plugin-ssr/src/web/setDev.js rename to packages/plugin-ssr/src/web/setDev.ts index 1cab0ef29..911c69c0b 100644 --- a/packages/plugin-ssr/src/web/setDev.js +++ b/packages/plugin-ssr/src/web/setDev.ts @@ -1,4 +1,4 @@ -module.exports = (config) => { +export default (config) => { const allEntries = config.entryPoints.entries(); // eslint-disable-next-line for (const entryName in allEntries) { diff --git a/packages/rax-app-renderer/src/renderer.tsx b/packages/rax-app-renderer/src/renderer.tsx index 629c96b3f..ddbe52e2b 100644 --- a/packages/rax-app-renderer/src/renderer.tsx +++ b/packages/rax-app-renderer/src/renderer.tsx @@ -62,6 +62,10 @@ function App(props) { } async function raxAppRenderer(options) { + if (!options.appConfig) { + options.appConfig = {}; + } + const { appConfig, setAppConfig } = options || {}; setAppConfig(appConfig);