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

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

没法使用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的问题。html

$npm i babel-cli --save前端

而后改写nodemon.json文件:node

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

而后再次启动node服务器就能够发现node正常识别import了。react

没法访问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的方式获取数据,从而解决这个问题。webpack

没法访问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同样。git

没法引入静态资源

  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进行首屏渲染的时候,是没有样式的,这就致使当客户端开始加载样式以后,会形成页面样式抖动的问题。es6

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

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引入。npm

(待续)

相关文章
相关标签/搜索