美文网首页
使用 egg 重构工程

使用 egg 重构工程

作者: McDu | 来源:发表于2021-03-28 21:18 被阅读0次

背景介绍

签证工程原来使用的也是 node 服务端渲染的模式,只不过引用的前端资源在另一个工程里,node 工程和前端工程维护两份代码,node 工程需要进行首屏渲染,给window.__INITIAL_STATE__ 属性赋值准备好的初始 state,加载前端资源时,react 会拿 INITIAL_STATE 的数据生成虚拟 DOM,通过 diff 算法判断 DOM 结构没有变化的话即使用服务端的首屏渲染的页面,并且将页面的生命周期函数、DOM 节点的事件加入到服务端渲染的静态页面上,也就是激活标记。

使用 egg 重构签证工程原理和上面的一致,只不过将原来的两个工程用更合理的方式写在一起(visa_node 工程)。主要参考线上运行的旅行星推官的代码。最后实现了:

  1. 前后端使用同一份代码,并且通过环境判断处理了node 环境可能引用的浏览器环境的 window、document 变量的文件(这些文件主要是页面 require 进的一些立即执行的方法中包含了这些变量);
  2. 接口请求前后端统一使用 axios 库;
  3. 申请了 beta 机器和线上机器进行部署。

下面会介绍一下服务端渲染、egg 框架、签证 aggregate 页面同构核心方法。

ssr 介绍

什么是服务端渲染(SSR)?

react、vue 这些构建客户端应用程序的框架,默认情况下可以通过 js 生成 DOM 并操作 DOM。也可以将同一个组件在服务器端渲染为静态的 HTML 字符串(比如
ReactDOMServer.renderToString ),直接发送到浏览器,最后将这些静态标记“激活”为客户端可交互的应用程序。这种服务端渲染的应用程序也被称为“同构”,因为应用程序的大部分代码都可以在服务器和客户端上运行。

为什么使用服务器端渲染 (SSR) ?

更好的 SEO、更快的首屏渲染、便捷开发(前端不需要配置 nginx、代理,只需和后端定义好接口)

egg 框架介绍及签证重构

egg 是什么?

官网有详细介绍。

egg 框架是阿里开源的一个服务于企业级的基础框架,基于 koa 进行二次开发,奉行「约定优于配置」,即在 koa 框架的基础上,基于一定的约定,根据功能差异将代码放到不同的目录下管理,从而降低整体团队的沟通成本和开发成本。

目录结构

visa_node 工程的主要的目录结构:

以签证工程的 aggregate 页面为例,讲一下 如何使一份代码同时在服务端和前端运行。

router.js

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
    const {router, controller, middleware} = app;

    router.get('/', controller.home.index);
    router.get('/visanode/aggregate', controller.aggregate.index);
};

当执行 GET /,controller 文件夹下的 home 文件里的 index 方法就会执行,url 匹配到 /visanode/aggregate 同理。支持目录级联访问:${directoryName}.${fileName}.${functionName}.

controller/aggregate.js

const Controller = require('egg').Controller;
// App 根节点,Store 为最初始定义的那个,一般 state 为空对象
const {App, Store} = require('../../src/page/aggregate/index.js');
const {queryInit, fetchFilter} = require('../../src/page/aggregate/actions.js');

class aggregateController extends Controller {
    async index() {
        const {ctx} = this;
        const {query} = ctx.request.query;

        // 业务逻辑,Store dispatch action,准备页面首次渲染需要的数据
        Store.dispatch(queryInit(query));
        await Store.dispatch(fetchFilter(query));

        // renderReactSSR 是在 helper 对象上扩展的一个属性,用于渲染页面
        await ctx.helper.renderReactSSR(
            'aggregate.nj',
            App,
            Store,
            `${query}签证产品推荐`
        );
    }
}

module.exports = aggregateController;

app/extend/helper.js

/**
     * React 服务端渲染
     * @param {String} viewPath 视图路径
     * @param {Object} component 组件
     * @param {Object} store 数据源
     * @param {String} title 标题
     * @param {Object} other 其他
     * @return {Object} 视图信息
     */
    renderReactSSR(viewPath, component, store, title = '去哪儿网', other = {}) {
        const reactDOM = ReactDOMServer.renderToString(
            React.createElement(
                Provider,
                {store},
                React.createElement(component)
            )
        );
        return this.ctx.render(viewPath, {
            title,
            reactDOM,
            initialState: JSON.stringify(store.getState()),
            skString: global.skString,
            ...other
        });
    }

this.ctx.render(viewPath, option) : 框架在 ctx 对象上提供了 render 方法,返回值为 Promise ,render 方法会直接赋值给 ctx.body。 所以我们在 controller 里渲染页面的时候要这样写:

  await ctx.helper.renderReactSSR(...);

view模板渲染

app/view/aggregate.nj

{% extends "./layout.html" %}

{% block header %}
    <link rel="stylesheet" href="{{ ctx.loadManifest('aggregate.css') }}" />
{% endblock %}

{% block body %}
    <div class="yo-root" id="app">{{ reactDOM | safe }}</div>
    <script> window.__INITIAL_STATE__ = {{ initialState | safe }}; </script>
    <script type="text/javascript" src="{{ ctx.loadManifest('vendor.js') }}"></script>
    <script type="text/javascript" src="{{ ctx.loadManifest('aggregate.js') }}"></script>
{% endblock %}

ctx.loadManifest 加载的是前端使用 webpack 打包后的资源。| safe 意思是将输入到页面的内容通过一个 safe 的过滤器转译一下。window.__INITIAL_STATE__ 存放的是 initialState,前端渲染 DOM 的时候会使用这个 state。

src/page/aggregate/index.js

import Store from './store';
import hydrateToPage from 'util/hydrateToPage';
import React from 'react';

import Header from './components/header.js';
import List from './components/list.js';

import 'style/page/aggregate.scss';
require('./ui/immersive'); // 适配

const App = () => {
    return (
        <div className="g-wrap">
            <Header />
            <List />
        </div>
    );
};

export {App, Store}; // 导出的这两个对象在 controller 里被引入,服务端渲染
hydrateToPage(App, Store); // 前端渲染方法

src/util/hydrateToPage.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import isNodeEnv from './isNodeEnv';

const thirdpartApp = isNodeEnv ? null : require('./thirdparty/thirdpartApp').default;

export default (Component, store) => {
    if (isNodeEnv) {
        return null; // 如果是 node 环境,运行这个环境返回 null
    }
    // 有时候需要在所有页面加额外的东西,可以在这里加
    if (thirdpartApp.isccb) {
        document.body.className += ' ccb-bg';
    }

    ReactDOM.render(
        <Provider store={store}>
            <Component />
        </Provider>,
        document.getElementById('app')
    );
};

webpack 里添加页面入口文件:

entry: {
   aggregate: './src/page/aggregate/index.js',
}

最后会在 prd 目录下的 manifest.json文件中生成下面的资源映射,view 模板渲染中引的便是这里的前端资源:

"aggregate.css": "http://q.dev.qunarzz.com:7013/prd/aggregate@dev.css",
"aggregate.js": "http://q.dev.qunarzz.com:7013/prd/aggregate@dev.js",
"vendor.js": "http://q.dev.qunarzz.com:7013/prd/vendor.bundle.js"

egg 内置对象

本地开发

package.json

"scripts": {
    "dev": "export VISA_PORT=7012 && egg-bin dev --port 7012",
    "dev-js": "webpack-dev-server --config webpack.config.dev.js --port 7013",
  }

本地开发只需运行这两个命令:

  1. npm run dev-js: 运行前端
  2. npm run dev: 运行后端

最后

Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本 ”

相关链接:

相关文章

  • 使用 egg 重构工程

    背景介绍 签证工程原来使用的也是 node 服务端渲染的模式,只不过引用的前端资源在另一个工程里,node 工程和...

  • python 制作egg库以及打包(1)

    Egg和Wheel本质上都是一个 zip 格式包,Egg 文件使用.egg扩展名,Wheel 使用.whl扩展名。...

  • swift类型化通知

    为什么要类型化通知? 在重构工程的时候,发现工程中使用Notification API的地方非常多,notific...

  • Egg.js学习与实战系列 · 文件上传配置

    在使用Egg.js搭建文件上传服务时,遇到了几个一般新手都会遇到的坑。经查阅官方文档,Egg框架中默认使用egg-...

  • Egg egg-bin

    本地开发调试Egg应用程序时需使用egg-bin模块 egg-bin是一个为便捷开发者在本地开发、调试、测试Egg...

  • Egg-js中使用sequelize事务

    Egg-js中使用sequelize事务 http://wenpeng.wang/2018/08/12/Egg-j...

  • egg.js sequelize deprecated Stri

    在使用egg.js的egg-sequelize报以下错误sequelize deprecated String b...

  • uni-app写一个阅读类app

    这个程序主要是学习uni-app和egg.js,介绍:前端开发使用uni-app,后端使用egg.js,数据库使用...

  • Note19 egg+ vue项目搭建入坑

    egg.js 与 vue 结合 , 使用脚手架 easywebpack-cli。 了解egg目录结构(koa) 全...

  • 跨域问题

    1.跨域forbidden 状态码405 使用egg-cors npm install egg-cors --sa...

网友评论

      本文标题:使用 egg 重构工程

      本文链接:https://www.haomeiwen.com/subject/mcmehltx.html