1 React前端工程架构 - 基本配置

 

0x0 概述

上一篇文章我们聊过,工程架构的目标,主要是保证「开发效率」的。包括:

本篇文章,将会就React的实际配置做下做下初步说明

内容包括:

0x1 编译打包基础配置

本节commit

webpack

webpack是一个模块打包器,核心是他的loader

I 基础配置阶段

1.在项目根目录下,把项目配置成npm的项目 - 通过生成的package.json文件描述依赖的npm包

$ npm init

相关选项 - 一般情况下直接enter用默认值就好

{
  "name": "fe-execise",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git@gitee.com:azen/FE-execise.git"
  },
  "author": "Azen",
  "license": "ISC"
}

2.安装webpack、react

$ npm i webpack
$ npm i react

3.新建文件夹 & 初始文件

webpack.config.js

const path = require('path');

module.exports = {
    //  入口
    entry: {
        app: path.join(__dirname, '../client/app.js')
    },
    output: {
        filename: '[name].[hash].js',
        path: path.join(__dirname, '../dist'),
        publicPath: "public/"
    }
};

path包:把相对路径转成绝对路径,防止跨操作系统时路径出错 - entry - 告诉webpack打包入口文件是app

output:


package.json

{
  "name": "fe-execise",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config build/webpack.config.js"
  },
  "repository": {
    "type": "git",
    "url": "git@gitee.com:azen/FE-execise.git"
  },
  "author": "Azen",
  "license": "ISC",
  "dependencies": {
    "react": "^16.5.1",
    "webpack": "^4.19.0"
  },
  "devDependencies": {
    "webpack-cli": "^3.1.0"
  }
}

scrpits:

简单测试

$ npm run build

结果:

II 简单业务文件 & 入口文件实现

App.jsx

import React from 'react'

export default class App extends React.Component {
    render() {
        return (
            <div>初次见面,请多关照~(๑^ں^๑)</div>
        )
    }
}

app.js

$ npm i react-dom -S


import ReactDOM from 'react-dom'
import App from './App.jsx'
import React from "react";

ReactDOM.render(<App />, document.body);

配置:


webpack.config.js

需要给webpack配置,让它能识别react相关语法 - jsx文件和一些我们自定义的js文件中会用到react语法

module.exports = {
    ...
    module: {
        rules: [
            {
                test: /.(jsx|js)$/,
                loader: 'babel-loader',
                exclude: [
                    path.join(__dirname, '../node_modules')
                ]
            }
        ]
    },
};

配置:


$ npm i babel-loader@7 babel-core -D

babel

是一个能编译各种js语法的工具,编译出来的是浏览器里默认能执行的es5语法

bable-loader:是一个webpack插件,不包含babel的核心代码,需要再安装下「babel-core」

babel默认只能编译es6的代码,不能编译jsx,需要在根目录写下配置文件`.babelrc`

{
  "presets": [
    ["es2015", {"loose": true}],
    "react"
  ]
}

配置:

安装babel语法引擎

npm i babel-preset-es2015 babel-preset-es2015-loose babel-preset-react -D

简单测试

npm run build

结果:

浏览器展示

希望在浏览器中展示js效果,需要安装html-webpack-plugin

作用:

  1. 生成一个html入口页面
  2. 在webpack编译的时候,所有entry都注入到这个html入口页面中(在html中做引用)
  3. 路径会根据我们webpack配置的publicPath拼接而成的

安装:

$ npm i html-webpack-plugin -D

修改配置文件,把插件配置给webpack

const HTMLPlugin = require('html-webpack-plugin');

module.exports = {
    ...
    plugins: [
        new HTMLPlugin()
    ]
};

效果:

最终结果

我们看到,源代码里只有一句js引用,木有我们场景的<a><p>等标签,这样对SEO不利 - 蜘蛛认为我们的网站中木有内容

下一节解决这个问题

0x2 服务端渲染基础配置

本节commit

0 概述

webapp开发模式下,都是在浏览器端使用js实时渲染出来内容的

问题:

  1. SEO不友好
    • 根据url请求到的html是空白页面
    • 蜘蛛会认为网站没东西
  2. 首次加载时间长,体验不好

解决思路

服务端渲染:在服务端的nodejs环境下,提前做好渲染,得到要访问页面的html内容,返回给浏览器

可用工具

I 代码调整阶段

之前app.js文件中,我们直接把App直接mount到document.body

问题:服务端运行环境中木有document,这个东东只有浏览器端有

解法:定义server-entry.js文件作为服务端渲染入口文件

import App from './App.jsx'
import React from "react"

export default <App />


配置:新建一个webpack配置文件,用来做服务端编译配置

build/webpack.config.server.js

const path = require('path');

module.exports = {
    target: 'node',
    entry: {
        app: path.join(__dirname, '../client/server-entry.js')
    },
    output: {
        filename: 'server-entry.js',
        path: path.join(__dirname, '../dist'),
        publicPath: '',
        libraryTarget: 'commonjs2'
    },
    module: {
        rules: [
            {
                test: /.(jsx|js)$/,
                loader: 'babel-loader',
                exclude: [
                    path.join(__dirname, '../node_modules')
                ]
            }
        ]
    }
};

说明:

构建

先安装一个删除文件夹的小工具

$ npm i rimraf -D

定义构建方法:

package.json

{
  ...
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "clear": "rimraf dist",
    "build:client": "webpack --config build/webpack.config.client.js",
    "build:server": "webpack --config build/webpack.config.server.js",
    "build": "npm run clear && npm run build:client && npm run build:server"
  },
  ...
}

在build之前,先把dist包清空下,删掉历史打的js包,避免编译结果污染

构建结果

生成了三个文件

服务端打包出来的server-entry.js文件,可以用来放在node.js环境中做渲染,生成对应的html文件

简略看下打包出来的server-entry.js文件

module.exports=function(e){...xxxxxx...}

II 配置渲染服务器,构建nodejs渲染环境

需要起一个nodejs网络服务,使用express作为网络服务框架

express文档

$ npm i express -S

实现渲染服务器

创建server文件夹,放web服务相关代码

实现server.js文件,用来实现web服务相关逻辑

const express = require('express');
const ReactSSR = require('react-dom/server');
const serverEntry = require('../dist/server-entry.js').default;

const app = express();

app.get('*', function (req, res) {
    const appString = ReactSSR.renderToString(serverEntry);
    res.send(appString)
});

app.listen(2333, function () {
   console.log('🚀 服务起来了,正在监听2333端口(✌゚∀゚)☞')
});

npm中配置起这个服务的script

{
    ...
  "scripts": {
    ...
    "web:start": "node server/server.js"
  }
    ...
}

渲染内容不完整

问题

虽然渲染出了基础的内容 ,但是这个不是我们想要的完整内容:

  1. 没有这个页面的js文件引用
  2. 不是完整的html文件,只有一行语句

思路

把服务端渲染出来的html标签内容,拼接到编译打包好的客户端index.html文件的指定位置,然后把拼接后的内容返回给浏览器

方案

template.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
</head>
<body>
<div id="root">
    <app></app>
</div>
</body>
</html>

app.js

...
ReactDOM.render(<App />, document.getElementById("root"));

webpack.config.client.js

...
plugins: [
    new HTMLPlugin({
         template: path.join(__dirname, '../client/template.html')
    })
]

server.js

...
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join(__dirname, '../dist/index.html'), 'utf-8');
...
app.use('/public', express.static(path.join(__dirname, '../dist')));
app.get('*', function (req, res) {
    const appString = ReactSSR.renderToString(serverEntry);
    res.send(template.replace('<app></app>', appString))
});
...

说明:

webpack.config.xxx.js

...
output: {
    ...
    publicPath: '/public'
},
...

配置结果

III 问题

  1. 每次修改了client目录下的代码,都需要重新build一遍才能生效
  2. 每次修改了web server的代码,都需要重新web:run一遍才能成功

下一章,我们来实现「开发时代码的实时更新