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的路由