├── .eslintrc.js
├── .github
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── README.md
├── package.json
├── src
└── index.ts
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | plugins: [
4 | 'typescript',
5 | '@typescript-eslint',
6 | ],
7 | extends: ['airbnb-base'],
8 | rules: {
9 | // allow debugger during development
10 | 'linebreak-style': 0,
11 | indent: [2, 4, {
12 | SwitchCase: 1,
13 | }],
14 | 'max-len': [2, { code: 160, ignoreUrls: true }],
15 | radix: ['error', 'as-needed'],
16 | 'object-shorthand': ['error', 'methods'],
17 | 'no-unused-expressions': ['error', {
18 | allowShortCircuit: true,
19 | }],
20 | 'no-bitwise': ['error', {
21 | allow: ['~'],
22 | }],
23 | 'import/extensions': 0,
24 | 'import/no-unresolved': 0,
25 | 'import/prefer-default-export': 0,
26 | 'import/no-dynamic-require': 0,
27 | 'object-curly-newline': 0,
28 | 'consistent-return': 0,
29 | 'no-shadow': 0,
30 | 'no-redeclare': 0,
31 | 'no-unused-vars': 0,
32 | 'no-useless-constructor': 0,
33 | 'no-empty-function': 0,
34 | 'class-methods-use-this': 0,
35 | 'import/no-extraneous-dependencies': 0,
36 | '@typescript-eslint/no-shadow': 2,
37 | '@typescript-eslint/no-redeclare': 2,
38 | '@typescript-eslint/no-unused-vars': 2,
39 | '@typescript-eslint/no-useless-constructor': 2,
40 | 'no-restricted-syntax': 0,
41 | 'no-param-reassign': 0,
42 | 'no-return-await': 0,
43 | 'no-use-before-define': 0,
44 | 'no-await-in-loop': 0,
45 | 'no-continue': 0,
46 | 'no-plusplus': 0,
47 | 'no-debugger': 0,
48 | 'no-console': 0,
49 | 'no-bitwise': 0,
50 | 'padding-line-between-statements': [
51 | 'warn',
52 | { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
53 | { blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] },
54 | { blankLine: 'always', prev: '*', next: 'return' },
55 | { blankLine: 'always', prev: 'block-like', next: '*' },
56 | { blankLine: 'always', prev: 'block', next: '*' },
57 | { blankLine: 'always', prev: 'function', next: '*' },
58 | ],
59 | },
60 | };
61 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v2
16 | with:
17 | node-version: 14
18 | - run: npm install
19 |
20 | publish-npm:
21 | needs: build
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v2
25 | - uses: actions/setup-node@v2
26 | with:
27 | node-version: 14
28 | registry-url: https://registry.npmjs.org/
29 | - run: npm publish
30 | env:
31 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
32 |
33 | publish-gpr:
34 | needs: build
35 | runs-on: ubuntu-latest
36 | permissions:
37 | contents: read
38 | packages: write
39 | steps:
40 | - uses: actions/checkout@v2
41 | - uses: actions/setup-node@v2
42 | with:
43 | node-version: 14
44 | registry-url: https://npm.pkg.github.com/
45 | - run: npm publish
46 | env:
47 | NODE_AUTH_TOKEN: ${{secrets.GIT_TOKEN}}
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | yarn.lock
4 | package-lock.json
5 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @umajs/plugin-react-ssr
2 |
3 | > 针对Umajs提供React服务端渲染模式的开发插件,插件基于服务端渲染骨架工具[Srejs](https://github.com/dazjean/srejs)开发。
4 |
5 | ## 插件介绍
6 |
7 | `plugin-react-ssr`插件扩展了`Umajs`中提供的统一返回处理`Result`对象,新增了`reactView`页面组件渲染方法,可在`controller`自由调用,使用类似传统模板引擎;也同时将方法挂载到了koa中间件中的`ctx`对象上;当一些公关的页面组件,比如404、异常提示页面、登录或者需要在中间件中拦截跳转时可以在`middleware`中调用。
8 |
9 | ## 插件安装
10 |
11 | ```
12 | yarn add @umajs/plugin-react-ssr --save
13 | ```
14 |
15 | ## 插件配置
16 |
17 | ```ts
18 | // plugin.config.ts
19 | export default <{ [key: string]: TPluginConfig }>{
20 | 'react-ssr': {
21 | enable:true,
22 | options:{
23 | rootDir:'web', // 客户端页面组件根文件夹
24 | rootNode:'app', // 客户端页面挂载根元素ID
25 | ssr: true, // 全局开启服务端渲染
26 | cache: false, // 全局使用服务端渲染缓存 开发环境设置true无效
27 | prefixCDN: '/' // 客户端代码部署CDN前缀
28 | }
29 | }
30 | };
31 | ```
32 |
33 | ## web 目录结构
34 |
35 | ```shell
36 | - web # rootDir配置可修改
37 | - pages # 固定目录
38 | - home #页面名称
39 | - index.tsx
40 | - index.scss
41 | ```
42 |
43 | ## 创建 react 页面组件
44 |
45 | 页面组件开发模式支持 js ,tsx。
46 |
47 | ```ts
48 | import './home.scss'
49 | import React from 'react'
50 | type typeProps = {
51 | say: string
52 | }
53 | export default function (props: typeProps) {
54 | const { say } = props
55 | return
{say}
56 | }
57 | ```
58 |
59 | ## 路由中使用插件
60 |
61 | ```ts
62 | import { BaseController, Path } from '@umajs/core'
63 | import { Result } from '@umajs/plugin-react-ssr'
64 |
65 | export default class Index extends BaseController {
66 | @Path('/')
67 | index() {
68 | return Result.reactView(
69 | 'home',
70 | { say: 'hi,I am a ReactView' },
71 | { cache: true }
72 | )
73 | }
74 | }
75 |
76 | ```
77 |
78 | ## **[使用文档](https://umajs.gitee.io/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93/React-ssr.html)**
79 |
80 | ## 案例
81 | - [uma-css-module](https://github.com/dazjean/Srejs/tree/mian/example/uma-css-module)
82 | - [uma-react-redux](https://github.com/dazjean/Srejs/tree/mian/example/uma-react-redux)
83 | - [uma-useContext-useReducer](https://github.com/dazjean/Srejs/tree/mian/example/uma-useContext-useReducer)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@umajs/plugin-react-ssr",
3 | "version": "2.0.9",
4 | "keywords": [
5 | "umajs",
6 | "umajs-plugin",
7 | "umajs-plugin-react",
8 | "umajs-plugin-react-ssr"],
9 | "description": "In umajs, React is used to develop the plug-in of SPA and MPA, which supports server-side rendering and client-side rendering",
10 | "author": "zunyi_zjj@163.com",
11 | "license": "MIT",
12 | "main": "lib/index.js",
13 | "directories": {
14 | "lib": "lib",
15 | "test": "__tests__"
16 | },
17 | "files": [
18 | "lib",
19 | "index.d.ts"
20 | ],
21 | "publishConfig": {
22 | "registry": "https://registry.npmjs.org",
23 | "access": "public"
24 | },
25 | "scripts": {
26 | "fix": "esw src --fix --ext .ts",
27 | "lint": "npx eslint src --ext .ts",
28 | "lint-w": "esw src --clear --color -w --ext .ts",
29 | "build-w": "tsc -w --inlineSourceMap",
30 | "start": "run-p lint-w build-w",
31 | "prebuild": "npm run lint",
32 | "build": "tsc",
33 | "prepublish": "npm run build"
34 | },
35 | "dependencies": {
36 | "@srejs/react": "latest",
37 | "consolidate": "^0.16.0",
38 | "get-stream": "^6.0.1"
39 | },
40 | "devDependencies": {
41 | "@types/jest": "^26.0.19",
42 | "@types/node": "^12.12.9",
43 | "@typescript-eslint/eslint-plugin": "^4.20.0",
44 | "@typescript-eslint/parser": "^4.20.0",
45 | "@umajs/core": "^2.0.1",
46 | "eslint": "^7.15.0",
47 | "eslint-config-airbnb-base": "^14.2.0",
48 | "eslint-plugin-import": "^2.22.1",
49 | "eslint-plugin-node": "^11.1.0",
50 | "eslint-plugin-promise": "^4.2.1",
51 | "eslint-plugin-typescript": "^0.14.0",
52 | "eslint-watch": "^7.0.0",
53 | "npm-run-all": "^4.1.5",
54 | "typescript": "^4.2.3"
55 | },
56 | "repository": {
57 | "type": "git",
58 | "url": "git@github.com:Umajs/plugin-react-ssr.git"
59 | },
60 | "bugs": {
61 | "url": "https://github.com/Umajs/plugin-react-ssr"
62 | },
63 | "homepage": "https://github.com/Umajs/plugin-react-ssr#readme"
64 | }
65 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IContext, TPlugin, Result as R, Uma, TPluginConfig } from '@umajs/core';
2 | import * as engineSource from 'consolidate';
3 | import * as getStream from 'get-stream';
4 | import Srejs from '@srejs/react';
5 |
6 | interface TviewOptions{
7 | ssr?: boolean, // 全局开启服务端渲染
8 | cache?: boolean, // 全局使用服务端渲染缓存
9 | useEngine?: boolean, // 渲染自定义html的页面组件时,选择性开启使用模板引擎
10 | baseName?: string, // 动态修改嵌套路由的basename 默认为页面组件名称。eg:/router
11 | layout?: boolean // 是否启用页面整体布局(默认为true), 开启后可在web/layout目录下编写布局代码.
12 | }
13 |
14 | export interface TssrPluginOptions extends TviewOptions {
15 | rootDir?:string, // 客户端页面组件根文件夹
16 | rootNode?:string, // 客户端页面挂载根元素ID
17 | defaultRouter?:boolean, // 开启默认文件路由
18 | prefixCDN?:string, // 构建后静态资源CDN地址前缀
19 | prefixRouter?:string // 默认页面路由前缀(在defaultRouter设置为true时有效)
20 | }
21 |
22 | interface IReactViewParms{
23 | viewName :string;
24 | initProps : any,
25 | options:TviewOptions
26 | }
27 | export class Result extends R {
28 | /**
29 | * @deprecated 请使用Result.react()函数渲染页面
30 | * @param viewName
31 | * @param initProps
32 | * @param options
33 | * @returns
34 | */
35 | static reactView(viewName: string, initProps?: any, options?:TviewOptions) {
36 | return new Result({
37 | type: 'reactView',
38 | data: {
39 | viewName,
40 | initProps,
41 | options,
42 | },
43 | });
44 | }
45 |
46 | /**
47 | *
48 | * @param viewName 页面组件名称
49 | * @param initProps react页面组件初始化props
50 | * @param options 页面运行配置参数
51 | * @returns
52 | */
53 | static react(viewName: string, initProps?: any, options?:TviewOptions) {
54 | return new Result({
55 | type: 'reactView',
56 | data: {
57 | viewName,
58 | initProps,
59 | options,
60 | },
61 | });
62 | }
63 | }
64 | const NODE_ENV = (process.env && process.env.NODE_ENV) || 'development';
65 | let SrejsInstance;
66 |
67 | /** 插件配置读取放到了@srejs/react框架中进行兼容,在生产环境部署前构建阶段不会执行插件 */
68 | let opt:TssrPluginOptions = Uma.config?.ssr || {}; // ssr.config.ts
69 | const reactSsrPlugin = Uma.config?.plugin?.react || Uma.config?.plugin['react-ssr'];
70 |
71 | if (reactSsrPlugin?.options) {
72 | opt = reactSsrPlugin.options;
73 | }
74 |
75 | let defaultRouter = false;
76 |
77 | // eslint-disable-next-line no-prototype-builtins
78 | if (opt.hasOwnProperty('defaultRouter')) {
79 | defaultRouter = opt.defaultRouter;
80 | }
81 |
82 | try {
83 | SrejsInstance = new Srejs(Uma.app, NODE_ENV === 'development', defaultRouter, opt);
84 | } catch (error) {
85 | console.error(error);
86 | }
87 |
88 | const renderDom = async (ctx:IContext, viewName:string, initProps?:any, options?:TviewOptions) => {
89 | const mergeProps = Object.assign(ctx.state || {}, initProps);
90 | let html = await SrejsInstance.render(ctx, viewName, mergeProps, options);
91 |
92 | const viewPlugin = Uma.config?.plugin.views; // use @umajs/plugin-views
93 | const ssrConfig = Uma.config?.plugin['react-ssr'];
94 |
95 | let useEngine = false;
96 |
97 | if (typeof (options?.useEngine) === 'boolean') {
98 | useEngine = options?.useEngine;
99 | } else {
100 | useEngine = ssrConfig?.options?.useEngine;
101 | }
102 |
103 | if (viewPlugin?.enable && useEngine) {
104 | const { opts } = viewPlugin.options;
105 | const { map } = opts;
106 | const engineName = map?.html;
107 |
108 | console.assert(engineName, '@umajs/plugin-views must be setting; eg====> map:{html:"nunjucks"}');
109 | const engine = engineSource[engineName];
110 | const state = { ...options, ...mergeProps };
111 |
112 | if (typeof html === 'object' && html.readable && options.cache) {
113 | // when cache model ,html return a file stream
114 | html = await getStream(html);
115 | }
116 |
117 | // 在SSR模式中, 将__SSR_DATA_匹配出来, 避免其中内容被模板引擎执行, 避免注入类攻击
118 | const ssrReg = new RegExp(/