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

(待續)

相關文章
相關標籤/搜索