) => {
17 | dispatch?.({
18 | type: 'updateContext',
19 | payload: {
20 | search: {
21 | text: e.target.value,
22 | },
23 | },
24 | });
25 | };
26 |
27 | const toSearch = () => {
28 | const searchVal = state?.search?.text ?? '';
29 |
30 | // eslint-disable-next-line no-restricted-globals
31 | // eslint-disable-next-line no-undef
32 | location.href = `https://search.youku.com/search_video?keyword=${searchVal}`;
33 | };
34 |
35 | return (
36 |
37 | {/* 这里需要给 value 一个兜底的状态 否则 context 改变 首次 render 的 text 值为 undefined 会导致 input 组件 unmount */}
38 | {/* ref: https://stackoverflow.com/questions/47012169/a-component-is-changing-an-uncontrolled-input-of-type-text-to-be-controlled-erro/47012342 */}
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | export default Search;
46 |
--------------------------------------------------------------------------------
/doc/react/initprops.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### 服务端获取initProps(推荐)
4 | > 当前前后端的交互不一定是http,而是rpc框架或者直接从数据库中获取并加工获得。这种方式就存在很大的局限,一是不方便node调试,而是必须使用http作为前后端交互通信方式,而且这种将node环境和客户端页面组件混写在一起的方式也会增加开发人员的理解,所以我们建议使用服务端直接获取方式。
5 |
6 |
7 | ```js
8 | // 服务端路由
9 | import Koa from 'koa';
10 | import srejs from '@srejs/react';
11 | const app = new Koa();
12 | const Sre = new srejs(app,process.env.NODE_ENV != 'production',false);
13 | app.use(async (ctx,next)=>{
14 | if(ctx.path==="/list"){
15 | // ListData将作为list页面组件初始props属性在react组件中被接收。
16 | const html = await Sre.render(ctx,'list',{ListData:['item1','item2','item3','item4',]});
17 |
18 | ctx.type = 'text/html';
19 | ctx.body = html;
20 | }else{
21 | await next();
22 | }
23 | })
24 |
25 | // 客户端页面组件
26 | export default function (props:typeProps){
27 | const {ListData} = props;
28 | return (
29 |
30 |
List
31 |
32 | {ListData.map((item,value)=>{
33 | return (
34 | -
35 |
{item}
36 |
37 | )
38 | })}
39 |
40 |
41 | )
42 | }
43 | ```
44 |
45 |
46 |
47 | ### 静态方法getInitialProps(不建议)
48 |
49 | > getInitialProps静态方法是由`nextjs`提出的概念,是在组件实例中挂载一个static静态方法,当服务渲染时预先调用此方法获取到数据,然后再SSR阶段通过props初始化到页面组件中,从而得到完整的html结果。这种方案也被业内其他框架追随,包括`egg-react-ssrs` `ykfe/ssr` `easy-team`等。
50 |
51 | ```js
52 | function Page(props) {
53 | return {props.name}
54 | }
55 |
56 | Page.getInitialProps = async (ctx) => {
57 | return Promise.resolve({
58 | name: 'Srejs + React + SSR',
59 | })
60 | }
61 |
62 | export default Page
63 | ```
--------------------------------------------------------------------------------
/packages/app/web/pages/index/index.scss:
--------------------------------------------------------------------------------
1 | //sass style
2 | //-------------------------------
3 | @use "sass:math";
4 |
5 | $baseFontSize: 32px !default;
6 |
7 |
8 | // pixels to rems
9 | @function pxToRem($px) {
10 | @return math.div($px, $baseFontSize) * 1rem;
11 | }
12 | body,
13 | div,
14 | dl,
15 | dt,
16 | dd,
17 | ul,
18 | ol,
19 | li,
20 | h1,
21 | h2,
22 | h3,
23 | h4,
24 | h5,
25 | h6,
26 | pre,
27 | form,
28 | fieldset,
29 | input,
30 | textarea,
31 | p,
32 | blockquote,
33 | th,
34 | td,
35 | header,
36 | img
37 | {
38 | margin: 0;
39 | padding: 0;
40 | font-family: '微软雅黑';
41 | }
42 | .clearfix:after {
43 | content: ".";
44 | display: block;
45 | height: 0;
46 | clear: both;
47 | visibility: hidden;
48 | }
49 |
50 | img{
51 | display: block;
52 | }
53 | input{
54 | border: none;
55 | outline: none;
56 | }
57 | li{
58 | list-style: none;
59 | }
60 |
61 | html,body,#app{
62 | height: 100%;
63 | width: 100%;
64 | font-size: 16px;
65 | overflow-y: auto;
66 | }
67 | *{
68 | -webkit-tap-highlight-color:transparent;
69 | -webkit-overflow-scrolling:touch;
70 | }
71 | .home{
72 | width: 100%;
73 | height: 100%;
74 | margin: 0 auto; /*水平居中*/
75 | padding-top: 70px;
76 | position: relative;
77 | top: 50%; /*偏移*/
78 | transform: translateY(-50%);
79 | background-image: url('@/images/srejs.png');
80 | background-size: auto;
81 | background-position: top;
82 | background-repeat: no-repeat;
83 | font-weight: bold;
84 | font-size: $baseFontSize;
85 | li a{
86 | color:#03a9f4;
87 | }
88 | .title,.footer{
89 | color:#000000;
90 | font-size: pxToRem(18px);
91 | }
92 | .footer{
93 | text-align: center;
94 | a{
95 | color: #000000 !important;
96 | }
97 | }
98 | }
--------------------------------------------------------------------------------
/doc/vue/suport-css.md:
--------------------------------------------------------------------------------
1 | # 内置css支持
2 |
3 | > 框架内置了`vue-style-loader`以及css预处理器loader,支持`*.vue`单个文件组件内的 `
20 | ```
21 |
22 | ## 预处理器less/scss
23 |
24 | ```vue
25 |
26 |
27 | style scoped
28 |
29 |
30 |
31 |
37 |
38 |
44 | ```
45 |
46 | **当我们采用*.less或者*.scss文件编写样式时,也可以从 JavaScript 中导入 CSS,例如,import './foo.css'**
47 |
48 | ## CSS Modules
49 |
50 | - 使用style module
51 |
52 | 在你的 `
69 | ```
70 |
71 | **详细原理查看官方文档[vue-loader](https://vue-loader.vuejs.org/zh/guide/css-modules.html#css-modules)**
72 |
73 | - 使用预处理器样
74 |
75 | 如果你的样式是从JavaScript中导入的,那么你只需要将把文件命名为`*.module.(less|scss|css)`。
76 |
77 | ```vue
78 |
79 |
80 | 对于外部样式文件,框架默认支持以文件命名:
81 | xxx.module.(less|scss|css)
82 | 开启使用css module
83 |
84 |
85 |
86 |
94 | ```
95 |
--------------------------------------------------------------------------------
/doc/vue3/suport-css.md:
--------------------------------------------------------------------------------
1 | # 内置css支持
2 |
3 | > 框架内置了`vue-style-loader`以及css预处理器loader,支持`*.vue`单个文件组件内的 `
20 | ```
21 |
22 | ## 预处理器less/scss
23 |
24 | ```vue
25 |
26 |
27 | style scoped
28 |
29 |
30 |
31 |
37 |
38 |
44 | ```
45 |
46 | **当我们采用*.less或者*.scss文件编写样式时,也可以从 JavaScript 中导入 CSS,例如,import './foo.css'**
47 |
48 | ## CSS Modules
49 |
50 | - 使用style module
51 |
52 | 在你的 `
69 | ```
70 |
71 | **详细原理查看官方文档[vue-loader](https://vue-loader.vuejs.org/zh/guide/css-modules.html#css-modules)**
72 |
73 | - 使用预处理器样
74 |
75 | 如果你的样式是从JavaScript中导入的,那么你只需要将把文件命名为`*.module.(less|scss|css)`。
76 |
77 | ```vue
78 |
79 |
80 | 对于外部样式文件,框架默认支持以文件命名:
81 | xxx.module.(less|scss|css)
82 | 开启使用css module
83 |
84 |
85 |
86 |
94 | ```
95 |
--------------------------------------------------------------------------------
/packages/vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@srejs/vue",
3 | "version": "1.4.2",
4 | "description": "@srejs/vue是一个轻量级服务端渲染骨架工具,为koa社区的nodejs开发框架提供具有服务端渲染能力的工具包,使得类似umajs类的web开发框架可以更方便实现前后端同构的服务端渲染能力。特点:轻量级,模板式调用页面进行服务端渲染,不限制后端路由。",
5 | "main": "lib/index.js",
6 | "directories": {
7 | "lib": "lib"
8 | },
9 | "files": [
10 | "bin",
11 | "lib",
12 | "index.d.ts"
13 | ],
14 | "keywords": [
15 | "ssr",
16 | "vue",
17 | "@umajs/plugin-vue-ssr",
18 | "umajs-vue-ssr",
19 | "@srejs",
20 | "nuxtjs",
21 | "vuessr"
22 | ],
23 | "bin": {
24 | "ssr": "./bin/index.js",
25 | "sre": "./bin/index.js",
26 | "srejs": "./bin/index.js"
27 | },
28 | "scripts": {
29 | "build": "babel src --out-dir lib --copy-files",
30 | "start": "babel src --watch --out-dir lib --source-maps inline --copy-files",
31 | "watch": "babel src --watch --out-dir lib --source-maps inline --copy-files",
32 | "prepublish": "npm run build"
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "git@github.com:dazjean/srejs.git"
37 | },
38 | "publishConfig": {
39 | "registry": "https://registry.npmjs.org",
40 | "access": "public"
41 | },
42 | "bugs": {
43 | "url": "https://github.com/dazjean/srejs/issues"
44 | },
45 | "homepage": "https://github.com/dazjean/srejs#readme",
46 | "license": "MIT",
47 | "dependencies": {
48 | "@srejs/common": "^1.4.0",
49 | "@srejs/vue-webpack": "^1.4.0",
50 | "etag": "^1.8.1",
51 | "fresh": "^0.5.2",
52 | "koa-send": "^5.0.0",
53 | "serialize-javascript": "^3.1.0",
54 | "vue-server-renderer": "^2.6.14"
55 | },
56 | "devDependencies": {
57 | "@babel/cli": "^7.22.10",
58 | "@babel/core": "^7.22.10",
59 | "@babel/plugin-transform-modules-commonjs": "^7.14.5",
60 | "@babel/plugin-transform-runtime": "^7.22.10",
61 | "@babel/preset-env": "^7.22.10"
62 | },
63 | "gitHead": "da596c6a49044e00ebdbb45cb25841e2fbeb3b83"
64 | }
65 |
--------------------------------------------------------------------------------
/packages/app/web/pages/router/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | //引入样式文件
3 | import './index.scss';
4 | //引入组件
5 | import React, { Component } from 'react';
6 | import { Route, Link, Switch, Redirect } from 'react-router-dom';
7 | class Home extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.goAboutPage = this.goAboutPage.bind(this);
11 | }
12 | goAboutPage() {
13 | this.props.history.push({
14 | pathname: '/about',
15 | state: {
16 | msg: '来自首页的问候!by state'
17 | }
18 | });
19 | }
20 | render() {
21 | return (
22 |
23 | 我是首页路由
24 |
25 |
子页面1
26 |
27 |
子页面2
28 |
子页面3
29 |
30 | );
31 | }
32 | }
33 |
34 | class About extends Component {
35 | constructor(props) {
36 | super(props);
37 | }
38 | render() {
39 | console.log(this.props.location);
40 | return (
41 |
42 | 我是一个路由跳转后的子页面
43 |
44 |
参数:{JSON.stringify(this.props.location)}
45 |
回首页
46 |
47 | );
48 | }
49 | }
50 |
51 | export default class APP extends Component {
52 | render() {
53 | return (
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/example/uma-css-module/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.0.2",
4 | "description": "umajs-react-ssr",
5 | "author": "dazjean",
6 | "license": "ISC",
7 | "directories": {
8 | "lib": "lib",
9 | "test": "__tests__"
10 | },
11 | "files": [
12 | "lib",
13 | "index.d.ts"
14 | ],
15 | "publishConfig": {
16 | "registry": "https://registry.npmjs.org"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/Umajs/Uma.git"
21 | },
22 | "scripts": {
23 | "fix:src": "npx eslint src --fix --ext .ts",
24 | "fix:web": "npx eslint web --fix --ext .tsx",
25 | "start": "ts-node-dev --respawn src/app.ts",
26 | "build": "tsc && srejs build",
27 | "fix": "npm run fix:src && npm run fix:web",
28 | "postinstall": "npm run fix && npm run build",
29 | "preprod": "npm run build",
30 | "prod": "node app/app.js --production"
31 | },
32 | "dependencies": {
33 | "@umajs/core": "^1.2.1",
34 | "@umajs/plugin-react-ssr": "^1.0.5",
35 | "@umajs/router": "^1.2.2",
36 | "koa": "^2.11.0",
37 | "react": "^17.0.2",
38 | "react-dom": "^17.0.2",
39 | "react-router-dom": "^5.2.0"
40 | },
41 | "devDependencies": {
42 | "@types/koa": "^2.0.48",
43 | "@types/react": "^17.0.5",
44 | "@types/react-dom": "^17.0.4",
45 | "@types/react-router-dom": "^5.1.7",
46 | "@typescript-eslint/eslint-plugin": "^4.22.1",
47 | "@typescript-eslint/parser": "^4.22.1",
48 | "eslint": "^7.26.0",
49 | "eslint-config-airbnb-base": "^14.2.0",
50 | "eslint-plugin-import": "^2.22.1",
51 | "eslint-plugin-prettier": "^3.4.0",
52 | "eslint-plugin-react": "^7.23.2",
53 | "eslint-plugin-react-hooks": "^4.2.0",
54 | "eslint-plugin-typescript": "^0.14.0",
55 | "npm-run-all": "^4.1.5",
56 | "prettier": "^2.3.0",
57 | "ts-node": "^8.2.0",
58 | "ts-node-dev": "^1.0.0-pre.32",
59 | "typescript": "^3.2.2"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/app/web/pages/router/index.scss:
--------------------------------------------------------------------------------
1 | //sass style
2 | //-------------------------------
3 | @use "sass:math";
4 |
5 | $baseFontSize: 32px !default;
6 |
7 |
8 | // pixels to rems
9 | @function pxToRem($px) {
10 | @return math.div($px, $baseFontSize) * 1rem;
11 | }
12 | body,
13 | div,
14 | dl,
15 | dt,
16 | dd,
17 | ul,
18 | ol,
19 | li,
20 | h1,
21 | h2,
22 | h3,
23 | h4,
24 | h5,
25 | h6,
26 | pre,
27 | form,
28 | fieldset,
29 | input,
30 | textarea,
31 | p,
32 | blockquote,
33 | th,
34 | td,
35 | header,
36 | img
37 | {
38 | margin: 0;
39 | padding: 0;
40 | font-family: '微软雅黑';
41 | }
42 | .clearfix:after {
43 | content: ".";
44 | display: block;
45 | height: 0;
46 | clear: both;
47 | visibility: hidden;
48 | }
49 | a{
50 | text-decoration: none;
51 | }
52 | img{
53 | display: block;
54 | }
55 | input{
56 | border: none;
57 | outline: none;
58 | }
59 | li{
60 | list-style: none;
61 | }
62 | html,body,#app{
63 | font-size: 16px;
64 | width: 100%;
65 | height: 100%;
66 | overflow-y: auto;
67 | }
68 | *{
69 | -webkit-tap-highlight-color:transparent;
70 | -webkit-overflow-scrolling:touch;
71 | }
72 |
73 | .demo{
74 | width: 100%;
75 | height: 100%;
76 | margin: 0 auto; /*水平居中*/
77 | position: relative;
78 | top: 50%; /*偏移*/
79 | transform: translateY(-50%);
80 | // background-image: url('images/srejs.png');
81 | background-size: auto;
82 | background-position-y: pxToRem(70px);
83 | font-weight: bold;
84 | font-size: $baseFontSize;
85 | text-align: center;
86 | // color: #ffffff;
87 | li a{
88 | color:#03a9f4;
89 | }
90 | .title,.footer{
91 | color: white;
92 | font-size: pxToRem(24px);
93 | }
94 | .footer{
95 | text-align: center;
96 | a{
97 | color: #ffffff !important;
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/packages/vue3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@srejs/vue3",
3 | "version": "1.4.1",
4 | "description": "@srejs/vue是一个轻量级服务端渲染骨架工具,为koa社区的nodejs开发框架提供具有服务端渲染能力的工具包,使得类似umajs类的web开发框架可以更方便实现前后端同构的服务端渲染能力。特点:轻量级,模板式调用页面进行服务端渲染,不限制后端路由。",
5 | "main": "lib/index.js",
6 | "directories": {
7 | "lib": "lib"
8 | },
9 | "files": [
10 | "bin",
11 | "lib",
12 | "index.d.ts"
13 | ],
14 | "keywords": [
15 | "ssr",
16 | "vue",
17 | "vue3.0",
18 | "@umajs/plugin-vue-ssr",
19 | "umajs-vue-ssr",
20 | "umajs-vue3-ssr",
21 | "@srejs",
22 | "nuxtjs",
23 | "vue ssr"
24 | ],
25 | "bin": {
26 | "ssr": "./bin/index.js",
27 | "sre": "./bin/index.js",
28 | "srejs": "./bin/index.js"
29 | },
30 | "scripts": {
31 | "link": "yarn link vue",
32 | "build": "babel src --out-dir lib --copy-files",
33 | "start": "babel src --watch --out-dir lib --source-maps inline --copy-files",
34 | "watch": "babel src --watch --out-dir lib --source-maps inline --copy-files",
35 | "prepublish": "npm run build"
36 | },
37 | "repository": {
38 | "type": "git",
39 | "url": "git@github.com:dazjean/srejs.git"
40 | },
41 | "publishConfig": {
42 | "registry": "https://registry.npmjs.org",
43 | "access": "public"
44 | },
45 | "bugs": {
46 | "url": "https://github.com/dazjean/srejs/issues"
47 | },
48 | "homepage": "https://github.com/dazjean/srejs#readme",
49 | "license": "MIT",
50 | "dependencies": {
51 | "@srejs/common": "^1.4.0",
52 | "@srejs/vue3-webpack": "^1.4.0",
53 | "@vue/server-renderer": "3.2.19",
54 | "etag": "^1.8.1",
55 | "fresh": "^0.5.2",
56 | "koa-send": "^5.0.0",
57 | "serialize-javascript": "^3.1.0"
58 | },
59 | "devDependencies": {
60 | "@babel/cli": "^7.22.10",
61 | "@babel/core": "^7.22.10",
62 | "@babel/plugin-transform-modules-commonjs": "^7.14.5",
63 | "@babel/plugin-transform-runtime": "^7.22.10",
64 | "@babel/preset-env": "^7.22.10"
65 | },
66 | "gitHead": "da596c6a49044e00ebdbb45cb25841e2fbeb3b83"
67 | }
68 |
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@srejs/react",
3 | "version": "1.4.0",
4 | "description": "@srejs/react是一个轻量级服务端渲染骨架工具,为koa社区的nodejs开发框架提供具有服务端渲染能力的工具包,使得类似umajs类的web开发框架可以更方便实现前后端同构的服务端渲染能力。特点:轻量级,模板式调用页面进行服务端渲染,不限制后端路由。",
5 | "main": "lib/index.js",
6 | "directories": {
7 | "lib": "lib"
8 | },
9 | "files": [
10 | "bin",
11 | "lib",
12 | "index.d.ts"
13 | ],
14 | "keywords": [
15 | "ssr",
16 | "react",
17 | "react-ssr",
18 | "@umajs/plugin-react-ssr",
19 | "umajs-react-ssr",
20 | "@srejs",
21 | "nextjs"
22 | ],
23 | "bin": {
24 | "ssr": "./bin/index.js",
25 | "sre": "./bin/index.js",
26 | "srejs": "./bin/index.js"
27 | },
28 | "scripts": {
29 | "build": "babel src --out-dir lib --copy-files",
30 | "start": "babel src --watch --out-dir lib --source-maps inline --copy-files",
31 | "watch": "babel src --watch --out-dir lib --source-maps inline --copy-files",
32 | "prepublish": "npm run build"
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "git@github.com:dazjean/srejs.git"
37 | },
38 | "publishConfig": {
39 | "registry": "https://registry.npmjs.org",
40 | "access": "public"
41 | },
42 | "bugs": {
43 | "url": "https://github.com/dazjean/srejs/issues"
44 | },
45 | "homepage": "https://github.com/dazjean/srejs#readme",
46 | "license": "MIT",
47 | "dependencies": {
48 | "@srejs/common": "^1.4.0",
49 | "@srejs/react-webpack": "^1.4.0",
50 | "etag": "^1.8.1",
51 | "fresh": "^0.5.2",
52 | "koa-send": "^5.0.0",
53 | "react": "^17.0.2",
54 | "react-dom": "^17.0.2",
55 | "react-router-dom": "^5.2.0",
56 | "serialize-javascript": "^6.0.0"
57 | },
58 | "devDependencies": {
59 | "@babel/cli": "^7.22.10",
60 | "@babel/core": "^7.22.10",
61 | "@babel/plugin-transform-modules-commonjs": "^7.14.5",
62 | "@babel/plugin-transform-runtime": "^7.22.10",
63 | "@babel/preset-env": "^7.22.10",
64 | "@babel/preset-react": "^7.7.4"
65 | },
66 | "gitHead": "da596c6a49044e00ebdbb45cb25841e2fbeb3b83"
67 | }
68 |
--------------------------------------------------------------------------------
/packages/app-vue/app.js:
--------------------------------------------------------------------------------
1 | import Koa from 'koa';
2 | import srejs from '@srejs/vue';
3 | const app = new Koa();
4 | const Sre = new srejs(app, process.env.NODE_ENV != 'production', false, {});
5 | app.use(async (ctx, next) => {
6 | if (ctx.path === '/vue' || ctx.path === '/') {
7 | const html = await Sre.render(
8 | ctx,
9 | 'vue',
10 | {
11 | title: 'vue ssr',
12 | keywords: 'srejs vue ssr',
13 | description: '简单好用的服务端渲染引擎工具!',
14 | state: {
15 | msg: 'vuex state'
16 | }
17 | },
18 | { ssr: true }
19 | );
20 | ctx.type = 'text/html';
21 | ctx.body = html;
22 | } else if (ctx.path.startsWith('/index')) {
23 | const html = await Sre.render(
24 | ctx,
25 | 'index',
26 | {
27 | title: 'Vue-router',
28 | keywords: 'srejs vue ssr',
29 | description: '简单好用的服务端渲染引擎工具!',
30 | home: '这是home页',
31 | about: '这是about页'
32 | },
33 | { ssr: true }
34 | );
35 | ctx.type = 'text/html';
36 | ctx.body = html;
37 | } else if (ctx.path.startsWith('/vuex')) {
38 | const html = await Sre.render(
39 | ctx,
40 | 'vuex',
41 | {
42 | title: '简单的计数器',
43 | keywords: 'vuessr vuex server',
44 | description: '简单的计数器 server',
45 | message: '这是来自服务端初始化数据',
46 | state: {
47 | count: 200
48 | }
49 | },
50 | { ssr: true }
51 | );
52 | ctx.type = 'text/html';
53 | ctx.body = html;
54 | } else {
55 | await next();
56 | ctx.type = 'text/html';
57 | ctx.body = '404 not found';
58 | }
59 | });
60 | app.listen(8001);
61 | console.log('8001端口启动成功!');
62 |
--------------------------------------------------------------------------------
/packages/app-vue3/app.js:
--------------------------------------------------------------------------------
1 | import Koa from 'koa';
2 | import srejs from '@srejs/vue3';
3 |
4 | const app = new Koa();
5 | const Sre = new srejs(app, process.env.NODE_ENV != 'production', false, { version: '3' });
6 | app.use(async (ctx, next) => {
7 | if (ctx.path === '/vue' || ctx.path === '/') {
8 | const html = await Sre.render(
9 | ctx,
10 | 'vue',
11 | {
12 | title: 'vue ssr',
13 | keywords: 'srejs vue ssr',
14 | description: '简单好用的服务端渲染引擎工具!',
15 | state: {
16 | msg: 'vuex state',
17 | say: 'vuex 你好 vue ssr!'
18 | }
19 | },
20 | { ssr: true }
21 | );
22 | ctx.type = 'text/html';
23 | ctx.body = html;
24 | } else if (ctx.path.startsWith('/index')) {
25 | const html = await Sre.render(
26 | ctx,
27 | 'index',
28 | {
29 | title: 'Vue-Router',
30 | keywords: 'srejs vue ssr',
31 | description: '简单好用的服务端渲染引擎工具!',
32 | home: '这是home页',
33 | about: '这是about页'
34 | },
35 | { ssr: true }
36 | );
37 | ctx.type = 'text/html';
38 | ctx.body = html;
39 | } else if (ctx.path.startsWith('/vuex')) {
40 | const html = await Sre.render(
41 | ctx,
42 | 'vuex',
43 | {
44 | title: '简单的计数器',
45 | keywords: 'vuessr vuex server',
46 | description: '简单的计数器 server',
47 | message: '这是来自服务端初始化数据',
48 | state: {
49 | count: 200
50 | }
51 | },
52 | { ssr: true }
53 | );
54 | ctx.type = 'text/html';
55 | ctx.body = html;
56 | } else {
57 | await next();
58 | ctx.type = 'text/html';
59 | ctx.body = '404 not found';
60 | }
61 | });
62 | app.listen(8001);
63 | console.log('8001端口启动成功!');
64 |
--------------------------------------------------------------------------------
/packages/vue-webpack/src/vue/hot.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import * as fs from 'fs';
3 | import { devMiddleware, hotMiddleware } from 'koa-webpack-middleware';
4 | import { getDevConfig } from './dev';
5 | export let DevMiddlewareFileSystem = fs;
6 |
7 | export class Hotwebpack {
8 | constructor(app, page = true) {
9 | this.app = app;
10 | this.webpackConfig = getDevConfig(page, false, true);
11 | this.complier = webpack(this.webpackConfig);
12 | this.webpackDevMiddleware();
13 | this.webpackHotMiddleware();
14 | }
15 | webpackHotMiddleware() {
16 | const hotPath = '/__webpack_hmr';
17 | let _hotMiddleware = hotMiddleware(this.complier, {
18 | path: hotPath,
19 | log: console.warn,
20 | heartbeat: 2000
21 | });
22 | // 中间件过滤非 /__webpack_hmr路由
23 | this.app.use(function (ctx, next) {
24 | if (ctx.url !== hotPath) {
25 | return next();
26 | } else {
27 | _hotMiddleware(ctx, next);
28 | }
29 | });
30 | }
31 | webpackDevMiddleware() {
32 | let _devMiddleware = devMiddleware(this.complier, {
33 | serverSideRender: false,
34 | publicPath: '/',
35 | noInfo: true,
36 | quiet: true,
37 | index: false //不响应根路径请求 避免和页面组件路由冲突
38 | });
39 | DevMiddlewareFileSystem = _devMiddleware.fileSystem;
40 | // 中间件过滤 非静态资源目录访问 .html,.js,.css,.png,jpeg,.svg,.jpg,.ttf
41 | this.app.use(function (ctx, next) {
42 | const filename = _devMiddleware.getFilenameFromUrl(ctx.url);
43 | if (filename === false) return next();
44 | try {
45 | var stat = DevMiddlewareFileSystem.statSync(filename);
46 | if (!stat.isFile()) {
47 | return next();
48 | }
49 | _devMiddleware(ctx, next);
50 | } catch (_e) {
51 | return next();
52 | }
53 | });
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/vue3-webpack/src/vue/hot.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import * as fs from 'fs';
3 | import { devMiddleware, hotMiddleware } from 'koa-webpack-middleware';
4 | import { getDevConfig } from './dev';
5 | export let DevMiddlewareFileSystem = fs;
6 |
7 | export class Hotwebpack {
8 | constructor(app, page = true) {
9 | this.app = app;
10 | this.webpackConfig = getDevConfig(page, false, true);
11 | this.complier = webpack(this.webpackConfig);
12 | this.webpackDevMiddleware();
13 | this.webpackHotMiddleware();
14 | }
15 | webpackHotMiddleware() {
16 | const hotPath = '/__webpack_hmr';
17 | let _hotMiddleware = hotMiddleware(this.complier, {
18 | path: hotPath,
19 | log: console.warn,
20 | heartbeat: 2000
21 | });
22 | // 中间件过滤非 /__webpack_hmr路由
23 | this.app.use(function (ctx, next) {
24 | if (ctx.url !== hotPath) {
25 | return next();
26 | } else {
27 | _hotMiddleware(ctx, next);
28 | }
29 | });
30 | }
31 | webpackDevMiddleware() {
32 | let _devMiddleware = devMiddleware(this.complier, {
33 | serverSideRender: false,
34 | publicPath: '/',
35 | noInfo: true,
36 | quiet: true,
37 | index: false //不响应根路径请求 避免和页面组件路由冲突
38 | });
39 | DevMiddlewareFileSystem = _devMiddleware.fileSystem;
40 | // 中间件过滤 非静态资源目录访问 .html,.js,.css,.png,jpeg,.svg,.jpg,.ttf
41 | this.app.use(function (ctx, next) {
42 | const filename = _devMiddleware.getFilenameFromUrl(ctx.url);
43 | if (filename === false) return next();
44 | try {
45 | var stat = DevMiddlewareFileSystem.statSync(filename);
46 | if (!stat.isFile()) {
47 | return next();
48 | }
49 | _devMiddleware(ctx, next);
50 | } catch (_e) {
51 | return next();
52 | }
53 | });
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/react-webpack/src/react/hot.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import * as fs from 'fs';
3 | import { devMiddleware, hotMiddleware } from 'koa-webpack-middleware';
4 | import { getDevConfig } from './dev';
5 | export let DevMiddlewareFileSystem = fs;
6 |
7 | export class Hotwebpack {
8 | constructor(app, page = true) {
9 | this.app = app;
10 | this.webpackConfig = getDevConfig(page, false, true);
11 | this.complier = webpack(this.webpackConfig);
12 | this.webpackDevMiddleware();
13 | this.webpackHotMiddleware();
14 | }
15 | webpackHotMiddleware() {
16 | const hotPath = '/__webpack_hmr';
17 | let _hotMiddleware = hotMiddleware(this.complier, {
18 | path: hotPath,
19 | log: console.warn,
20 | heartbeat: 2000
21 | });
22 | // 中间件过滤非 /__webpack_hmr路由
23 | this.app.use(function (ctx, next) {
24 | if (ctx.url !== hotPath) {
25 | return next();
26 | } else {
27 | _hotMiddleware(ctx, next);
28 | }
29 | });
30 | }
31 | webpackDevMiddleware() {
32 | let _devMiddleware = devMiddleware(this.complier, {
33 | serverSideRender: false,
34 | publicPath: '/',
35 | noInfo: true,
36 | quiet: true,
37 | index: false //不响应根路径请求 避免和页面组件路由冲突
38 | });
39 | DevMiddlewareFileSystem = _devMiddleware.fileSystem;
40 | // 中间件过滤 非静态资源目录访问 .html,.js,.css,.png,jpeg,.svg,.jpg,.ttf
41 | this.app.use(function (ctx, next) {
42 | const filename = _devMiddleware.getFilenameFromUrl(ctx.url);
43 | if (filename === false) return next();
44 | try {
45 | var stat = DevMiddlewareFileSystem.statSync(filename);
46 | if (!stat.isFile()) {
47 | return next();
48 | }
49 | _devMiddleware(ctx, next);
50 | } catch (_e) {
51 | return next();
52 | }
53 | });
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/example/uma-useContext-useReducer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.0.2",
4 | "description": "umajs-react-ssr",
5 | "author": "dazjean",
6 | "license": "ISC",
7 | "directories": {
8 | "lib": "lib",
9 | "test": "__tests__"
10 | },
11 | "files": [
12 | "lib",
13 | "index.d.ts"
14 | ],
15 | "publishConfig": {
16 | "registry": "https://registry.npmjs.org"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/Umajs/Uma.git"
21 | },
22 | "scripts": {
23 | "fix:src": "npx eslint src --fix --ext .ts",
24 | "fix:web": "npx eslint web --fix --ext .tsx",
25 | "start": "ts-node-dev --respawn src/app.ts",
26 | "build": "tsc && srejs build",
27 | "fix": "npm run fix:src && npm run fix:web",
28 | "postinstall": "npm run fix && npm run build",
29 | "preprod": "npm run build",
30 | "prod": "node app/app.js --production"
31 | },
32 | "dependencies": {
33 | "@umajs/core": "^1.2.1",
34 | "@umajs/plugin-react-ssr": "^1.0.5",
35 | "@umajs/plugin-views": "^2.0.1-alpha.0",
36 | "@umajs/router": "^1.2.2",
37 | "koa": "^2.11.0",
38 | "nunjucks": "^3.2.3",
39 | "react": "^17.0.2",
40 | "react-dom": "^17.0.2",
41 | "react-id-swiper": "^4.0.0",
42 | "react-router-dom": "^5.2.0",
43 | "swiper": "^6.6.1"
44 | },
45 | "devDependencies": {
46 | "@types/koa": "^2.0.48",
47 | "@types/react": "^17.0.5",
48 | "@types/react-dom": "^17.0.4",
49 | "@types/react-router-dom": "^5.1.7",
50 | "@typescript-eslint/eslint-plugin": "^4.22.1",
51 | "@typescript-eslint/parser": "^4.22.1",
52 | "eslint": "^7.26.0",
53 | "eslint-config-airbnb-base": "^14.2.0",
54 | "eslint-plugin-import": "^2.22.1",
55 | "eslint-plugin-prettier": "^3.4.0",
56 | "eslint-plugin-react": "^7.23.2",
57 | "eslint-plugin-react-hooks": "^4.2.0",
58 | "eslint-plugin-typescript": "^0.14.0",
59 | "npm-run-all": "^4.1.5",
60 | "prettier": "^2.3.0",
61 | "ts-node": "^8.2.0",
62 | "ts-node-dev": "^1.0.0-pre.32",
63 | "typescript": "^3.2.2"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/example/uma-react-redux/web/pages/index/features/counter/Counter.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import {
4 | decrement,
5 | increment,
6 | incrementByAmount,
7 | incrementAsync,
8 | incrementIfOdd,
9 | selectCount,
10 | setIncrementAmount
11 | } from './counterSlice';
12 | import styles from './Counter.module.css';
13 |
14 | export function Counter() {
15 | const count = useSelector(selectCount);
16 | const incrementAmount = useSelector(setIncrementAmount);
17 |
18 | const dispatch = useDispatch();
19 |
20 | const incrementValue = Number(incrementAmount) || 0;
21 |
22 | return (
23 |
24 |
25 |
32 | {count}
33 |
40 |
41 |
42 | setIncrementAmount(e.target.value)}
47 | />
48 |
54 |
60 |
66 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/packages/app/web/pages/index/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 | import React from 'react';
3 | import srejsLogo from '@/images/srejs.png';
4 | const commonjsLogo = require('@/images/srejs.png').default;
5 | import { testTreeSharkingA } from './fool';
6 | type typeProps = {
7 | title: string;
8 | userTitle: string;
9 | };
10 | const Index = (props: typeProps) => {
11 | const { title, userTitle } = props;
12 | testTreeSharkingA();
13 | return (
14 |
51 | );
52 | };
53 |
54 | function timeout() {
55 | return new Promise((reslove) => {
56 | setTimeout(() => {
57 | reslove({
58 | userTitle: '列表'
59 | });
60 | }, 500);
61 | });
62 | }
63 |
64 | Index.getInitialProps = async () => {
65 | return await timeout();
66 | };
67 | export default Index;
68 |
--------------------------------------------------------------------------------
/doc/react/page-router.md:
--------------------------------------------------------------------------------
1 | # 页面组件
2 | > `rootDir`默认为根目录下`src`文件夹下创建pages目录。eg:src/pages/index/index.js;在服务端使用时`index`作为页面组件标识。
3 |
4 |
5 | - 普通页面组件
6 | ```js
7 | export default class App extends Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 | render() {
12 | return (
13 |
14 | hello srejs
15 |
16 | );
17 | }
18 | }
19 |
20 | ```
21 |
22 | 服务端路由
23 |
24 | ```js
25 | import Koa from 'koa';
26 | import srejs from '@srejs/react';
27 | const app = new Koa();
28 | const Sre = new srejs(app,process.env.NODE_ENV != 'production',false);
29 | app.use(async (ctx,next)=>{
30 | if(ctx.path==="/"){
31 | const html = await Sre.render(ctx,'index',{title:'介绍'});
32 | ctx.type = 'text/html';
33 | ctx.body = html;
34 | }else{
35 | await next();
36 | }
37 | })
38 | ```
39 |
40 | - 使用react-router-dom
41 | > 项目如果使用路由,export导出组件为` `包裹的``
42 | ```js
43 | export default class APP extends Component {
44 | render() {
45 | return (
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 | }
55 | ```
56 |
57 | 使用react-router-dom时服务端路由需要和客户端路由保持一致;默认baseName为页面组件名称 eg:`router`嵌套路由组件,默认`baseName`为`/router`。可通过运行期支持动态传递修改`baseName`值。
58 |
59 | ```js
60 | import Koa from 'koa';
61 | import srejs from '@srejs/react';
62 | const app = new Koa();
63 | const Sre = new srejs(app,process.env.NODE_ENV != 'production',false);
64 | app.use(async (ctx,next)=>{
65 | if(ctx.path.startsWith('/router')){ // 客户端默认basename为页面组件名称 eg:web/pages/router/index.tsx basename默认为:router
66 | const html = await Sre.render(ctx,'router');
67 | ctx.type = 'text/html';
68 | ctx.body = html;
69 | }else{
70 | await next();
71 | }
72 | })
73 | ```
74 |
75 |
76 |
77 | **说明** 框架默认不采用页面组件工程目录作为默认路由,建议采用服务端路由自由搭配使用。如果习惯采用文件路由则初始化srejs时第三个参数`defaultRouter`设置为true。
--------------------------------------------------------------------------------
/example/uma-react-redux/.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/no-unresolved': 0,
24 | 'import/prefer-default-export': 0,
25 | 'import/no-dynamic-require': 0,
26 | 'object-curly-newline': 0,
27 | 'consistent-return': 0,
28 | 'no-unused-vars': 0,
29 | 'no-useless-constructor': 0,
30 | 'no-empty-function': 0,
31 | 'class-methods-use-this': 0,
32 | 'import/no-extraneous-dependencies': 0,
33 | '@typescript-eslint/no-unused-vars': 2,
34 | '@typescript-eslint/no-useless-constructor': 2,
35 | 'no-restricted-syntax': 0,
36 | 'no-param-reassign': 0,
37 | 'no-return-await': 0,
38 | 'no-use-before-define': 0,
39 | 'no-await-in-loop': 0,
40 | 'no-continue': 0,
41 | 'no-plusplus': 0,
42 | 'no-debugger': 0,
43 | 'no-console': 0,
44 | 'no-bitwise': 0,
45 | "padding-line-between-statements": [
46 | "warn",
47 | { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
48 | { "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"] },
49 | { "blankLine": "always", "prev": "*", "next": "return" },
50 | { "blankLine": "always", "prev": "block-like", "next": "*" },
51 | { "blankLine": "always", "prev": "block", "next": "*" },
52 | { "blankLine": "always", "prev": "function", "next": "*" },
53 | ],
54 | }
55 | }
56 |
--------------------------------------------------------------------------------