github项目代码地址
express.static('public')
指定静态文件的访问路径,比如在html中访问script的src属性时 会以这个文件夹为根路径// 创建node服务实例 方便在其它地方应用
import express from 'express'
const app = express()
app.use(express.static('public'))
// 服务运行在3000端口
app.listen(3000, ()=> console.log('app is running on 3000 port'))
export default app
'/'
:服务器端应用程序的入口文件,'*'
:接收所有的路由地址app.get('*',(req,res)=>{
const store = createStore()
const promise = matchRoutes(routes,req.path).map(({
route})=>{
if(route.loadData) return route.loadData(store)
})
Promise.all(promise).then(()=>{
// 这里知道数据获取完成了 返回一个html内容
res.send(render(req,store))
})
})
import List from "./pages/List";
export default [
...
{
path:'/List',
component:List.component,
loadData:List.loadData
}
]
import React,{
useEffect} from "react";
import {
connect } from "react-redux";
import {
fetchUser } from "../store/actions/user.action";
// 客户端和服务端公用组件 属于同构代码
function List ({
user,dispatch}) {
useEffect(() => {
dispatch(fetchUser())
}, []);
return <div>
<ul>
{
user.map(item=><li key={
item.id}>{
item.name}</li>)}
</ul>
</div>
}
function loadData (store) {
return store.dispatch(fetchUser())
}
const mapStateToProps = (state) => ({
user: state.user});
export default {
component: connect(mapStateToProps)(List),
loadData
}
export default (req,store) => {
const content = renderToString(
<Provider store={
store}>
<StaticRouter location={
req.path}>
{
renderRoutes(routes)}
</StaticRouter>
</Provider>
)
const initalState = serialize(store.getState());
return `
React SSR
${
content}
// 客户端接替ssr页面
`
}
window.INITIAL_STATE
const store = createStore(reducers, window.INITIAL_STATE, applyMiddleware(thunk))
webpack.base.js
// webpack公共配置 可以在需要的地方用merge跟其它配置合并
const path = require('path')
module.exports = {
mode:'development', // 开发环境
module: {
rules: [{
test:/\.js$/, // 匹配.js结尾的文件
exclude:/node_modules/, // 排除node_modules文件
use:{
loader:'babel-loader', //使用babel-loader加载器去处理.js结尾的文件
options:{
// options:babel-loader的相关配置项
presets:[
["@babel/preset-env",{
useBuiltIns:"usage" // 该配置让浏览器支持异步函数
}], // @babel/preset-env预置:转换的是高阶的js语法
"@babel/preset-react", // @babel/preset-react预置:转换jsx语法
]
}
}
}]
}
}
webpack.client.js
const path = require('path')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base')
const config = {
entry:'./src/client/index.js', //入口文件
output: {
// __dirname:项目的根路径,path:出口文件路径:项目根路径下的build文件夹下,
path: path.join(__dirname,'public'),
// filename:打包文件名称
filename:'bundle.js',
},
}
// 合并公共配置并导出
module.exports = merge(baseConfig,config)
webpack.server.js
nodeExternals()
:在服务器端打包文件中,包含了Node系统模块.导致打包文件本身体积庞大,可以通过webpack配置剔除打包文件中的Node模块const path = require('path')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base')
const nodeExternals = require('webpack-node-externals')
const config = {
target:'node', //应用在node
entry:'./src/server/index.js', //入口文件
output: {
// __dirname:项目的根路径,path:出口文件路径:项目根路径下的build文件夹下,
path: path.join(__dirname,'build'),
// filename:打包文件名称
filename:'bundle.js',
},
externals:[nodeExternals()]
}
// 合并公共配置并导出
module.exports = merge(baseConfig,config)
npm run dev
合并项目启动命令 执行所有'dev:'
开头的命令
dev:server-build
监听服务webpack的入口配置的文件变化,并打包代码dev:client-build
监听客户端webpack的入口配置的文件变化,并打包代码dev:server-run
监听服务端打包的代码并执行打包之后的文件,端口号为3000"scripts": {
"dev": "npm-run-all --parallel dev:*",
"dev:server-build": "webpack --config webpack.server.js --watch",
"dev:client-build": "webpack --config webpack.client.js --watch",
"dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\"",
},
actions
和reducers
可以同构.因为处理数据的逻辑可以复用.但是createStore
初始化的时候不方便复用因为在被客户端接管之后要保证客户端和服务端数据状态一致比较麻烦.