介紹下個人開源項目Zz.js(輕量易用的 React SSR 開發骨架)

寫在前面

在這裏主要是向你們介紹下個人開源項目-zz.js的開發背景、特性、功能、以及如何應用和將來的一些規劃。javascript

若是你對next.js nuxt.js有了解的話不妨也看看zz.jscss

Zz項目地址:github.com/Bigerfe/koa…html

Zz介紹

zz 是一個基於 koa2 react16 webpack4 babel7 react-router5 構建而成的 ssr 服務端渲染開發骨架。前端

能夠方便的幫咱們快速的構建起一個基礎開發服務,幫助咱們迅速進入 react ssr的開發。java

初衷

起初只是想研究下 react ssr的實現原理,可是學習的過程當中發現網上不少文章介紹的參差不齊,大都是十分簡略的實現方式,包括React官方也缺乏完整的SSR(Server-Side Rendering)文檔,只是簡單的介紹了一下須要用到的API,也沒法適用於具體項目的開發。node

因此很是想把這塊的技術吃透,學習的過程當中同時也萌生了打造一個開源項目的想法,一個是幫助本身提高技術,另外還能夠幫助一些小夥伴兒快速的搞定 react srr 基礎骨架的搭建,作到開箱即用。react

另一個目的也算是提供一個基於 koa2 的 react ssr 的完整實現,畢竟以前尚未基於koassr的實現。webpack

可讓對 ssr 服務端渲染感興趣的同窗方便學習和研究,若是還能一塊兒來完善這個項目那就更好了。git

技術棧

koa2 Koa 是一個新的 web 框架,由 Express 幕後的原班人馬打造, 致力於成爲 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。github

React目前最流行的前端框架之一

React Router5

ReactDOMServerReact官方提供的服務端渲染有關的庫

Webpack 基於 webpack4進行工程化處理

Babel7

運行環境

  • 服務器 Node.js >= 10

  • 瀏覽器版本大於等於IE9, React支持到IE9,但爲了更好的在IE下使用,你可能須要引入Polyfill

功能特性

如下是目前已具有功能點

  • 支持本地開發HMR
  • 支持tree shaking以及打包去重依賴
  • 支持csr/ssr自定義layout
  • 同時支持SSR以及CSR兩種開發模式,本地開發環境以及線上環境皆可無縫切換兩種渲染模式
  • 路由分治管理,再也不須要維護單獨的路由表,省去維護的煩惱
  • 支持某個具體的頁面是 SSR 模式仍是 CSR 模式
  • 僞 PWA 支持,開啓此特性後頁面的二次訪問再也不請求接口,同時解決頁面回退後原頁面定位不許的問題
  • 路由雙模式支持,可方便的配置當前路由是否按需加載
  • Webpack生產環境構建,也能夠修改現有配置

快速上手

從這裏開始你將瞭解到怎樣讓 zz 在本地快速的跑起來,而後進行實際項目開發。

環境準備

首先你須要安裝 node ,而且確保 node 版本是10或以上。mac 下推薦使用 nvm 來管理 node 版本)

$ node -v
10.0x
複製代碼

腳手架安裝

爲了方便咱們建立應用和頁面,這裏提供了一個配套的 zz-cli 腳手架。

先全局安裝腳手架。

$ npm i zz-cli -g

複製代碼

建立應用

//初始化項目
$ zzjs -i 
$ <Your Project Name>
$ cd <Your Project Name>
$ npm i
$ npm run dev //本地開發的watch 模式
$ open http://<Your local ip>:8808
複製代碼

啓動腳本

可經過不一樣的命令開啓不一樣的渲染模式。

$ npm run dev //開啓本地開發 可修改配置內的屬性 isSSR ,支持兩種渲染模式
$ npm run dev:csr //開啓本地開發 並已 wds 爲服務啓動 - csr 模式
$ //更多.....
複製代碼

目錄結構

├── dist // 生產環境打包後的資源目錄
│ ├── static //打包的靜態資源文件
│ ├── server //用於同構的運行於 node 端的文件
├── docs //  幫助文檔
├── server // 開發時 node 端代碼
├── src // 開發時 react 組件相關代碼
│ ├── app //應用入口
│ │ ├── layout //layout 組件
│ │ ├── index.js //webpack entry 打包入口
│ │ ├── provider.js  //提供數據的基礎組件
│ ├── common // 公共資源
│ │ ├── components // 公共組件 
│ │ ├── fetch // fetch模塊 
│ │ ├── module // 公共模塊
│ ├── config // 基礎配置文件
│ ├── zz-base // zz基礎組件
│ ├── pages // 業務頁面
│ │ ├── index //默認首頁
│ │ │ ├── config 路由配置
│ ├── routes // 路由配置 無需維護
├── test // 單頁測試
├── webpack //構建配置
├── app.js //生產環境 app 啓動入口  ---> 好比 pm2 start app.js
複製代碼

約定

頁面入口

關於/src/pages/下每一個頁面的入口的約定,目前只支持一級路由的設置,全部的頁面的入口都是 index.js, zz內部會自動進行識別。

路由約定

每一個頁面的路由配置的方式再也不是集中式配置,而是分治配置,每一個頁面對應一個路由配置,請按照下面的格式進行配置

舉個栗子

// /src/pages/index 頁面目錄

// /src/pages/index/config/route.js 路由配置

//路由代碼,能夠方便的設置路由是否按需加載

import React from 'react';
import BaseBundle from '../../../routes/route-base-bundle';
//import LazyPageCom from '../index'; //靜態組件模式

//動態組件配置 
const LazyPageCom = (props) => (
    <BaseBundle load={() => import(/*webpackChunkName:"chunk-index"*/'../index')}> {(CompIndex) => <CompIndex {...props} />} </BaseBundle> ); //一個頁面組件可配置多個路由入口 export default [ { path:'/', component: LazyPageCom, exact:true },{ path: '/index', component: LazyPageCom } ] 複製代碼

你只須要修改 webpackChunkName 的名稱和 export 導出的數據便可,固然也能夠對當前頁面配置多個路由.

建立頁面

可經過腳手架快速的建立頁面

$ cd <Your Project Name>
$ zzjs -p
$ <Your  pageName>
$ open http://<Your local ip>:8808/<Your pageName>
複製代碼

路由分治管理

爲了方便維護和擴展,zz 把路由進行了分治管理,每一個頁面的路由都是獨立的,只須要單獨的配置便可。

請參考路由約定

數據預取同構

數據預取的目的是在 node 端渲染組件前提早從接口或者某個數據源獲取到數據,也可讓某個頁面在 CSR 下能夠拿到數據,進行組件的 update。

爲了方便的實現同構咱們在頁面組件內約定了一個數據預取的靜態方法 getInitialProps,當前頁面首屏數據都是從這個方法內進行返回。

//基礎參數的帶入
    //opt={query:{},params:{}} 
    static async getInitialProps(zzOpt){//數據預取
        

        if(__SERVER__){
            //若是是服務端渲染的話 能夠作的處理
        }
        //接口 a
       const fetch1= fetch.postForm('/fe_api/a', {
            data: { a: 4000 }
        });

        //接口 b
       const fecth2= fetch.postForm('/fe_api/b', {
            data: { c: 2000 }
        });

        const resArr =await fetch.multipleFetch(fetch1, fecth2);
       
        //返回數據固定格式 page 表明頁面信息,支持 seo 的設置
        //fetchData是接口返回的數據 
        return {
            page:{
                tdk: {
                    title: 'ksr 框架',
                    keyword: 'ssr react',
                    description: '我是描述'
                }
            },
            fetchData: resArr
        } 
    }

複製代碼

頁面 SEO

在 數據預取同構 已經看到了 getInitialProps 方法返回的數據是一個固定的格式,結果內包含一個 page字段.

page 字段表示的就是當前頁面的 SEO 的信息.

//此處代碼已略
   return {
            page:{
                tdk: {
                    title: 'ksr 框架',
                    keyword: 'ssr react',
                    description: '我是描述'
                }
            },
            fetchData: resArr
        } 
複製代碼

頁面渲染

一個page 的渲染

  • 頁面組件須要繼承一個 zz 的基礎組件 ZzPageBase,爲咱們封裝了一些基礎數據獲取和存儲功能.

  • 須要設置 static contextType = RootContext 爲的是讓組件能夠得到全局的數據.

  • 實現 static async getInitialProps 數據預取方法.

  • componentDidMount 內是否須要作數據的更新,若是須要更新能夠調用getInitialProps方法.

參考完整代碼

import React,{useContext} from 'react';
import { Link } from 'react-router-dom';
import RootContext from '../../app/route-context';//自定義 context
import ZzPageBase from '../../zz-base/common/components/zz-page-base';//基礎組件 頁面組件都須要繼承
import fetch from '../../common/fetch';//內置的 fech 模塊


export default class Index extends ZzPageBase{

    constructor(props,context){
        super(props,context);
    }

    enableSpaDataCache=true;//開啓 僞 pwa 數據緩存 

    //獲得 context 對象
    static contextType = RootContext;

    //基礎參數的帶入
    //opt={query:{},params:{}} 
    static async getInitialProps(zzOpt){//數據預取
        

        if(__SERVER__){
            //若是是服務端渲染的話 能夠作的處理
        }

       const fetch1= fetch.postForm('/fe_api/a', {
            data: { a: 4000 }
        });

       const fecth2= fetch.postForm('/fe_api/b', {
            data: { c: 2000 }
        });

        const resArr =await fetch.multipleFetch(fetch1, fecth2);
       
        //返回數據固定格式 page 表明頁面信息,支持 seo 的設置
        //fetchData是接口返回的數據 
        return {
            page:{
                tdk: {
                    title: 'ksr 框架',
                    keyword: 'ssr react',
                    description: '我是描述'
                }
            },
            fetchData: resArr
        } 
    }

    componentDidMount(){
       
       //數據更新 參考
       //this.isSSR 標識當前頁面是不是 ssr 輸出
       //this.hasSpaCacheData標識是否有僞 pwa 的緩存數據

        if (!this.isSSR && !this.hasSpaCacheData){// 頁面若是是客戶端的須要從新獲取數據
            Index.getInitialProps(this.props.zzOpt).then(data=>{
                this.setState({
                    ...data
                },()=>{
                    document.title=this.state.page.tdk.title;
                });
            });
        }
    }

    render(){
     
        const {page,fetchData}=this.state;//得到數據
     
        //參考代碼,須要對數據作邊界容錯處理

        return <div className="detailBox"> <div> { page && <div><span>title:{page.tdk.title}</span> <span>ky:{page.tdk.keyword}</span> </div> } </div> { res && res.data.map(item=>{ return <div key={item.id}>{item.keyId}:{item.keyName}---{item.setContent}</div> }) } </div>
    }
}
複製代碼

僞 PWA 支持

在頁面組件內設置enableSpaDataCache值,便可開啓這個特性。此特性開啓後,可讓這個頁面的二次訪問再也不有數據請求,當前是否須要還要根據本身的實際業務觸發。

export default class Index extends zzPageBase{

    constructor(props,context){
        super(props,context);
    }

    enableSpaDataCache=true;//開啓 僞 pwa 數據緩存 

}
複製代碼

特殊字段

__SERVER__ 常量、表示當前是不是服務端渲染,常常會在組件內被使用

this.isSSR 常量、表示當前頁面的渲染是服務端渲染仍是客戶端渲染

this.hasSpaCacheData 常量 、 表示當前頁面內是否有僞 pwa 的數據

基礎配置

zz 默認有本身的一套配置,好比本地開發端口、靜態資源的 cdn 地址等

固然這些配置你也能夠進行修改

全局配置文件 /src/config/project-config.js

//fetch 接口 開發環境和生產環境
const DevApiHost ='http://dev.xxx.com';  //開發環境接口地址
const ProductionApiHost ='http://pro.xxx.com';//生產環境接口地址

export default {
    //判斷是不是開發環境,不然能夠理解爲生產環境,最好統一使用此方法。保證正確
    getIsDev(){
        return process.env.NODE_ENV ==='production'
    },
    openProductionStaticFolder: true,//線上環境是否開啓靜態目錄訪問能力
    isSSR: true,//是否開啓 ssr 
    nodeServerPort:8808,//服務器和本地 node 服務器啓動端口,可自行設置
    //業務開發中 fecth api 的地址 ,能夠根據環境進行區分
    reqApiUrlHost:process.env.IS_DEV?DevApiHost:ProductionApiHost,
    devWdsPort:8809,//wds 服務啓動的端口,用於開發環境的靜態資源的訪問和熱更新操做
    routeIndexFolderName: 'index',  //標識業務頁面的首頁目錄名稱,路由集中處理後會將此入口排在入口 list 的第一個位置
    //TODO:打包到生產環境的時候這個地址會隨機的進行分配 可能致使分配不均
    staticAssetsCdnHost: [
        '//c1.static.aa.com/',
        '//c2.static.aa.com/',
        '//c3.static.aa.com/'
    ],
    Production_JS_Host:'//c1.static.aa.com',//生產環境 js 資源 host
    Production_CSS_Host:'//x2.static.aa.com',//生產環境 css 資源 host
}
複製代碼

部署

項目部署就按照常規的方法進行部署 ,使用 pm2 來作進程守護,固然這裏只是一個簡單的栗子,僅供參考。

執行生產環境構建

$ npm run build
複製代碼

使用 pm2 啓動 app.js

$ pm2 start app.js -n zz-ssr
複製代碼

Demo

爲了方便有更好的體驗,特地準備了一個簡單的 DEMO (有點醜, 不要介意^_^ )

demo.zz.bigerfe.com

將來的規劃

  • 狀態管理不足,若是太複雜的項目可能利於後期維護,因此後面會支持redux
  • 加如單元測試
  • 還沒想好....

最後

這個項目我會持續的進行維護和更新,固然一我的的力量是有限的,但願更多的人能夠一塊兒來幫助 zz 成長和完善,歡迎提交 pull request ^_^。


推薦關注個人微信公衆號【前端張大胖】,天天推送高質量文章、自學經驗和心得,咱們一塊兒交流成長。

相關文章
相關標籤/搜索