• wordpress CMS主题:ssmay主题 wordpress CMS主题:ssmay主题
  • 首页 > 前端开发 > React SSR重构踩坑记录(持续更新)

    React SSR重构踩坑记录(持续更新)

    作者: 分类:前端开发 点击: 776 次
    wordpress CMS主题:ssmay主题

      扫描下面的二维码,“关注”我的百家号。

      最近将以前的一个毕业设计的网站的文章详情页做了服务端渲染的重构,看SSR的实现文档看似很简单,但是实现起来确实坑不少。

      无法使用import引入

      1. 错误信息: unexpected token import
      2. 场景:第一次在node中直接使用import Story from '../js/containers/story';就会报这个错误。
      3. 错误说明:node本身使用的是commonjs的语法,支持的模块引入和导出方式为require以及module.export,然而es6定义的js模块方式为importexport[default],因此node虽然支持了大部分的es6语法,但是由于es6的模块与node本身的cjs的模块产生了冲突,因此node不会支持esm的模块,因此造成了无法识别import的情况。
      4. 解决办法:以前整个项目中node和react使用同一个.babelrc文件,为了解决这个问题,同时也由于reactnode的差异越来越大,最后决定拆分.babelrc文件,在/node(后端)目录以及/public(前端)目录下分别创建.babelrc文件,作为前端和后端各自的babel配置。 其中node的.babelrc文件配置中的:
      // .babelrc
      "presets": [
          [
              "env",
      	{
      	    "targets": {
      		"node": "current"
      	    }
      	}
          ],
      "react",
      "es2015",
      "stage-0"
      ]
      

      可以让node识别es6的语法。 然后在根目录重新创建nodemon.json文件用来处理import问题。 上网查资料,babel-node插件可以解决不识别import的问题。

      $npm i babel-cli --save

      然后改写nodemon.json文件:

      // nodemon.json
      {
        "verbose": false,
        "env": {
          "NODE_ENV": "development",
          "BABEL_ENV": "node"
        },
        "watch": ["node", "config"],
        "ignore": ["public"],
        "execMap":{
          "js": "babel-node"
        }
      }
      

      然后再次启动node服务器就可以发现node正常识别import了。

      无法访问window对象

      1. 错误信息:window is not defined
      2. 场景:js文件中一开始使用了很多window.xxx的属性,import到node环境中之后就会报这个错误。
      3. 说明:服务端缺乏BOM和DOM环境,服务端下无法访问window,navigator等对象。
      4. 解决办法:针对此种错误,有三种解决办法:
        1. 通过fake window等对象(如window等库)的使用,给node环境创建全局window对象。
        2. 前端组件中延迟这些对象的调用,在didMount中才进行调用。
        3. 将组件中的所有用window的属性,都通过props的方式获取,然后将所有应该传入组件的props属性在node中传进组件。
          // storyController.js
      const props = {
        userInfo: ctx.session,
        articleInfo: {
          author: author[0].nickname,
          avatar: author[0].avatar,
          author_fans_count: fans_count[0].count,
          ...info[0]
        },
        isSelf: info[0].uid === ctx.session.uid
      };
      const html = renderToString(<Story {...props} />);
      ctx.render('story', {
        __PROPS__: JSON.stringify(props),
        title: info[0].title,
        html
      });
      
      // pages/story.js
      render(
        <Story {...window.__PROPS__} />,
        document.getElementById('root')
      );
      

      这样在组件中就可以通过props的方式获取数据,从而解决这个问题。

      无法访问alias路径

      1. 错误信息: cannot find module 'components/xxx'
      2. 场景:在组件中使用了webpack配置的alias路径,做ssr时node就会报这个错误。
      3. 错误说明:当在webpack中配置alias时,我们可以在组件中简写路径,但是在node中无法识别webpack的alias,所以这种路径node会从node_modules中寻找这个组件,找不到就会报错。
      4. 解决办法:解决办法自然是node也找一个alias的库:module-resolver库可以完美解决这个问题。 $ npm install --save-dev babel-plugin-module-resolver 安装完成之后,改造一下node下面的.babelrc文件即可:
      // .babelrc
      "plugins": [
      	["module-resolver", {
      		"cwd": "babelrc",
      		"root": ["../public/js"],
      		"alias": {
      			"scss": "../public/scss",
      			"components": "../public/js/components",
      			"containers": "../public/js/containers",
      			"constants": "../public/js/constants",
      			"lib": "../public/js/lib",
      			"router": "../public/js/router",
      			"stirngs": "../public/js/string.js",
      			"store": "../public/js/store"
      		}
      	}]
      ]
      

      其中的alias和webpack中的alias一样。

      无法引入静态资源

      1. 错误信息: /Users/xxx/xxx/node_modules/antd/lib/style/index.css:6
      2. 场景:组件中使用了antd组件,或者引入了我们自己的scss文件时,会报这个错误。
      3. 错误说明:客户端通常使用webpack进行编译,资源的加载通过各种loader进行处理,但这写loader只是针对于客户端环境的,编译生成的代码,无法应用于服务端,因此node无法解析scssless等文件。
      4. 解决办法:我们只要让node不解析这些样式文件即可。 在node入口文件app.js中最上方加入以下代码:
      // app.js
      require.extensions['.scss'] = function() {
        return null;
      };
      require.extensions['.css'] = function() {
        return null;
      };
      require.extensions['.less'] = function() {
        return null;
      };
      require.extensions['.png'] = function(module, file) {
        return module._compile('module.exports = ""', file);
      };
      require.extensions['.svg'] = function() {
        return null;
      };
      

      这样node就可以正常运行了,但是同时又暴露出了一个问题,当node进行首屏渲染的时候,是没有样式的,这就导致当客户端开始加载样式之后,会造成页面样式抖动的问题。

      为此我们通过编写webpack插件,将ExtractTextPlugin生成的css文件,内联插入页面的pug模板中,这样服务端首屏渲染就可以支持样式了。

      require方式引入组件报错

      1. 错误信息: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object
      2. 场景:node中使用require引入组件,然后传入renderToString会报错。
      3. 错误说明:这个错误涉及到esm和cjs交互的问题,我们通过require引入的东西和我们通过import引入的并不一样,具体原因可以参考另一篇文章深入解析ES Module
      4. 解决办法:
        1. 我们在组件中通过export default class xxx extends Component的方式导出组件,在node中必须要通过const Component = require('....').default的方式才能够正确获取到组件,大家可以自己console.log一下,直接require进来的是一个object,里面的default属性才是我们的组件。
        2. 安装babel-plugin-add-module-exports插件。 $ npm install babel-plugin-add-module-exports@next --save-dev 然后改写react中的.babelrc文件:
      // .babelrc
      "plugins": [
          ...
          "babel-plugin-add-module-exports"
      ]
      

      这是个比较hack的方法,强行将esm和cjs的表现置为相同,但是可能会出现问题,所以尽量不要将esm和cjs混用,在node中直接使用import引入组件最好,不要用require引入。

      (待续)

      本文转载于:猿2048https://www.mk2048.com/blog/blog_h0ic2bc0ab.html



      欢迎“关注”我的百家号。

      头条二维码
      加入我的QQ群
      头条二维码
      关注我的百家号

    文章作者:sunny
    本文地址:http://wanlimm.com/77202006208306.html
    版权所有 © 转载时必须以链接形式注明作者和原始出处!

    上一篇:
    下一篇:
    wordpress CMS主题:ssmay主题

    或许你会感兴趣的文章:

    发表评论

    电子邮件地址不会被公开。 必填项已用*标注

    This site uses Akismet to reduce spam. Learn how your comment data is processed.