从0搭建前端项目架构-第四篇-用服务端渲染开发

574 阅读2分钟

服务端渲染开发

  • 在server/server.js中,没有进行开发环境和线上环境区分,现在进行区分下。
    ...省略
    const isDev = process.env.NODE_ENV === 'development';
    
    if(!isDev){
        const serverEntry = require('../dist/server-entry').default
        const template = fs.readFileSync(path.join(__dirname,'../dist/index.html'),'utf8')
        app.use('/public',express.static(path.join(__dirname,'../dist')))
        app.get('*',(req,res)=>{
            const data = ReactSSR.renderToString(serverEntry)
            res.send(template.replace('<app></app>',data))
        })
    }else{
        const devStatic  = require('./util/dev-static')
        devStatic(app)
    }
  • 在server目录下创建util文件夹,在util文件夹新建dev-static.js
    cd server
    mkdir util
    cd util 
    touch dev-static.js
  • 安装下面几个包
    npm i axios -S
    npm i memory-fs -D
    npm i http-proxy-middleware -D
  • 在dev-static.js中配置服务端端渲染
    const axios = require('axios')
    const MemoryFs = require('memory-fs')
    const path = require('path')
    const webpack = require('webpack')
    const proxy = require('http-proxy-middleware')
    const serverConfig = require('../../build/webpack.config.server')
    const ReactDomServer = require('react-dom/server')
    const getTemplate = () => {
        return new Promise((resolve,reject)=>{
            axios.get('http://localhost:8888/public/index.html')
                .then(res=>{
                    resolve(res.data)
                })
                .catch(reject)
        })
    }
    
    const Module = module.constructor
    
    const mfs = new MemoryFs
    
    const serverCompiler = webpack(serverConfig)
    serverCompiler.outputFileSystem = mfs
    
    let serverBundle
    serverCompiler.watch({},(err,stats)=>{
        
        if(err) throw err
        stats = stats.toJson()
        console.log('stats',stats.error)
        stats.errors.forEach(err=>console.error(err))
        stats.warnings.forEach(warn=>console.warn(warn))
    
    
        const bundlePath = path.join(
            serverConfig.output.path,
            serverConfig.output.filename
        )
        const bundle =mfs.readFileSync(bundlePath,'utf-8')
    
        const m = new Module()
        m._compile(bundle,'server-entry.js')
        serverBundle = m.exports.default
    })
    
    module.exports = function(app) {
    
        app.use('/public',proxy({
            target:'http://localhost:8888'
        }))
    
        app.get('*',(req,res)=>{
            getTemplate().then(template=>{
                const content = ReactDOMServer.renderToString(serverBundle)
                res.send(template.replace('<!-- app -->',content))
            })
        })
    }
  • 在package.json中的start改成dev:server
    "dev:server": "cross-env NODE_ENV=development node server/server.js"

此时服务端渲染配置就好了。分别执行npm run dev:client和npm run dev:server 在浏览器中打开http://localhost:8888 就能看到页面的内容了。

优化工程架构

  • 抽取webpack.config.client.js和webpack.config.server.js的公共代码
    • 新建webpack.base.js并写入公共代码
      const path = require('path');
      module.exports = {
          output:{
              path: path.join(__dirname,'../dist'),
              publicPath: '/public/'
          },
          module: {
              rules: [
                  {
                      test: /.jsx$/,
                      loader: 'babel-loader'
                  },
                  {
                      test: /.js$/,
                      loader: 'babel-loader',
                      exclude: [
                          path.join(__dirname,'../node_modules')
                      ]
                  }
              ]
          },
      }
    
    • 在webpack.config.client.js和webpack.config.server.js中,将上述代码删除,安装并引入webpack-merge包
      npm i webpack-merge -D
    
      const webpackMerge = require('webpack-merge') 
      const baseConfig = require('./webpack.base')
      
      ...省略
      //webpack.config.client.js
      const config = webpackMerge(baseConfig,{
          entry: {
              app: path.join(__dirname,'../client/app.js')
          },
          output: {
              filename: '[name].[hash].js',
          },
          plugins:[
              new HTMlPlugin({
                  template:path.join(__dirname,'../client/template.html')
              })
          ]
      })
      
      //webpack.config.server.js
      module.exports = webpackMerge(webpackBase,{
          target:'node',
          entry: {
              app: path.join(__dirname,'../client/server-entry.js')
          },
          output: {
              filename: 'server-entry.js',
              libraryTarget: 'commonjs2'
          },
      })
    
  • 热加载服务端代码
    • 安装nodemon
      npm i nodemon -D
    
    • 在根目录下创建nodemon.json
      {
          "restartable":"rs",
          "ignore": [
              ".git",
              "node_modules/**/node_modules",
              ".eslintrc",
              "client",
              "buildd"
          ],
          "env":{
              "NODE_ENV":"development"
          },
          "verbose":true,
          "ext":"js"
      }
    
    • 修改package.json
      "dev:server": "nodemon server/server.js",
    

详细代码github地址