React SSR服务端渲染

React SSR

本文内容主要为webpack和ssr的部分简单实现

react + next

Server-Side Data Fetching特性

next实现了在服务端获取数据并且渲染html的功能,好处自然就是用户端体验更好并且利于seo,其中获取数据的api为getStaticProps

export async function getServerSideProps({params}) {
	const req = await fetch(`http://127.0.0.1/${params.id}.json`);
  const date = await req.json();
  
  return {
    props: {something: data},
  }
}
复制代码

next是一套用于react服务端渲染的框架,下面主要介绍一下使用

初始化

创建 npx create-next-app

脚本配置 "scripts": {"dev": "next dev"}

路由

next文件目录结构决定了路由,每一个page相当于是一个独立的路由,一般工程下有一个api目录作为server-only routes,其余的则一般[directory name] = route name, 路径下的index.js处理相应页面(route component),react的组件的本质是函数,所以每一个page需要有一个export default function来作为next的组件,如果路由后面根由url params参数,则可以在同路径下新建一个[id].js来处理(dynamic component),

//[id].js 利用next-router获取url中的参数
import {useRouter} from 'next/rotuer'

export default function ChildPage() {
  const router = useRouter()
  const {id} = router.query
  
  return JSX.element; 
}
复制代码

关于webpack手写Loader

Loader本质叫就是一个声明式函数,作用是做代码转换(origin data —> js code)供webpack编译

初始化

const path = require('path')

module.exports = {
  mode: 'development',
  entry: path.join(__dirname, 'src/index.js'),
  output: {
    filename: 'bundle.js',
    path: `${__dirname}/dist`
  },
  module: {
    rules:[
      test: /\.js$/,
      use: [{
      	path.resolve(__dirname, './xxx/xxx.js'), //这里须为绝对路径,xxx为希望作为解析用的文件
    		options: {
    			str: 'MyLoaderParam'  // loader的参数
  			}
      }]
  } 
}
复制代码

loader.js

参数传递

//对外暴露一个函数,接收源码为参数输入
module.exports = function(source) {
  //输出 {str:’MyLoaderParam‘},如果是复杂参数也可以用官方工具loaderUtils处理
  console.log(this.query) 
  //须有返回
  return source;
}
复制代码

异步事件处理,主要利用this.async

module.exports = function(source) {
  const result = source;
  //处理异步,
  const callback = this.async();
  //模拟一个异步
  setTimeOut(()=>{
    const result = source;
   	// return result; 的话会报错
    callback(null, result);
  }, 3000)  
}
复制代码

如果是多loader的顺序执行,在webpack.config < use中添加相应配置,注意顺序是自下而上(链式),从右到左(compose方式)

处理路径

resolveLoader: {
  modules: ['node_modules', './loaders']
}
复制代码

优先去node_modules里面寻找,之后是./loaders,这样use里面就可以直接用loader的name来配置而不需要path.resovle了

webpack还提供了一些其他方法来帮助编写loader,官方文档地址:webpack.docschina.org/concepts/lo…

关于webpack手写Plugins

引用:’A webpack plugin is a JavaScript object that has an apply method. This apply method is called by the webpack compiler, giving access to the entire compilation lifecycle.’

plugins是webpack灵魂,现在官方和三方也有各式各样的Plugins,常用的比如HtmlWebpackPlugin(可以在构建完成后向dist输出资源时候生成html),MiniCssExtractPlugin(抽离css),plugin包含:apply()、参数、compiler

官方示例

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    //hooks后的run表示一个构件的阶段,如果是同步的后面用tap,异步用tapAsync且箭头函数参数带callback
    compiler.hooks.run.tap(pluginName, (compilation) => {
      console.log('The webpack build process is starting!!!');
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin;
复制代码

hooks api文档:webpack.js.org/api/compile…

所以手写一个Plugin的基础结构就是

class xxxPlugin {
  //options为传入参数
  constructor(options){
  }
  apply(compiler) {
  }
}
module.exports = xxxPlugin;
复制代码

webpack.config中require引入后加配置plugins: [new xxxPlugin({name: 'name'})] (name为外部要传过去的参数)

以emit为例写一个异步钩子函数,emit发生在生成资源到output目录之前

//在dist输出目录下加入一个新文件
apply(compiler) {
  compiler.hooks.emit.tapAsync(
  	"xxxPlugin",
    (compilation, cb) => {
      //compilation.assets为拿到的资源
      compilation.assets['new.txt'] = {
        source: function(){
          return "new.txt's content here."
        },
        size: funciton(){
        	//30bytes
        	return 30;
      	}
      }
      cb();
    }
  );
}
复制代码

webpack + react-ssr

思路:

server:

  • 利用react-dom的renderToString方法将组件渲染为字符串
  • 服务端的路由返回对应正确的模板

client:

  • 打包针对适合服务端的组件出来

完整demo地址:github.com/PakoMY/reac…

yarn init -y

yarn add webpack webpack-cli webpack-dev-server -D

目录新建webpack.config.js

const path = require('path')
const HtmlPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: path.join(__dirname, 'src/index.js'),
  output: {
    filename: 'bundle.js',
    path: `${__dirname}/dist`
  },
  module: {
    rules: [{
      test: /\.(js|jsx)$/,
      loader: 'babel-loader',
      exclude: /node_modules/
    }]
  },
  plugins: [
    new HtmlPlugin({
      template: path.join(__dirname, 'src/client/index.html')
    })
  ],
  devServer: {
    hot: true,
    port: 3000,
    overlay: true
  },
}
复制代码

(地址中demo将client和server的webpack.config分别进行了配置,注意webpack.server.config在output中加入了libraryTarget: 'commonjs2'配置,做一个输出按照commonjs标准模式导入的转换,否则node代码编译会报错)

yarn add file-loader css-loader babel-loader @babel/core @babel/preset-env @babel/preset-react -D

/src下新建 client 、server 、shared三个文件夹

yarn add react react-dom

根目录新建.babelrc

{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
 	"plugins": ["react-hot-loader/babel"]
}
复制代码

webpack (注意webpack4.0开始webpack和webpack-cli拆分了,如果本地反复提示安装webpack-cli可以全局安装npm install -g webpack-cli并且注意版本号一致,不想全局安装也可以直接切到node_modules/bin下执行webpack命令,每次文件有修改都要执行然后会在dist目录下生成一个静态html页面 如果devServer配置了端口号可以执行 webpack-dev-server

yarn add html-webpack-plugin -D

webpack-dev-server –hot

yarn add react-hot-loader -D (需要在app.js末尾补充 export default hot(App))

package.json 补充scripts

"scripts": {
    	"build:client": "webpack",
      "dev:client": "webpack-dev-server"
}
复制代码

server.js

const express = require('express')
const app = express()
//react dom server 可以在服务端渲染组件 ReactDOMServer.renderToString(element)
const ReactDOMServer = require('react-dom/server')

ReactDOMServer.renderToString()

const port = process.env.PORT || 5000
app.listen(port, () => console.log(`server on port `${port}))


复制代码

后面会补充非next框架下如何配置ssr的路由

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享