本次项目是公司进行的CRM改造项目,因此,项目主要用于内部员工,如销售,运营,售后等进行客户的相关数据的维护,项目前端技术选用React,UI框架选用Ant Design,由于是内部系统,因此对UI界面的要求并不算高,基本上基于Ant Design本身的控件进行开发。
先对项目结构进行一个大致的预览:
项目的框架是由前辈搭建的,我来主要剖析一下这个项目。项目主要分为两个大块:
由于项目使用nodejs的express框架进行了首屏渲染,所以独立出来的build文件夹,就很有剖析的必要了。
server文件夹
这里的data文件夹下是放置了一些静态数据,比如省市区的信息。routes.js 文件配置了express的路由,server.dev.js 和 server.js 是启动服务的主要文件。
server.js
let express = require('express');
let compression = require('compression');
let path = require('path');
let app = express();
let distFolder = path.join(__dirname, './dist');
let serveStatic = require('serve-static');
let packageConfig = require('./package.json');
let routes = require('./routes');
const config = packageConfig.config;
const port = config.port;
routes(app, config);
app.use(compression());
app.use(serveStatic(distFolder, {
maxAge: '7d',
index: ['index.html'],
etag: false,
setHeaders: function (res, path, stat) {
if (path.indexOf('index.html') >= 0) {
res.setHeader('Cache-Control', 'max-age=0,no-cache,no-store,must-revalidate');
res.setHeader('pragma', "no-cache");
}
}
}));
app.listen(port, () => {
console.log('App is listening at http://localhost:%s', port);
});
文件中的node监听端口在package.json中进行配置,并且配置了index.html的启动文件。
server.dev.js
let WebpackDevServer = require("webpack-dev-server");
let webpack = require("webpack");
let webpackConfig = require('../webpack/webpack.config.dev.js');
let packageConfig = require('../../package.json');
let routes = require('./routes');
const compiler = webpack(webpackConfig);
const host = '127.0.0.1';
const config = packageConfig.config;
const port = config.port;
const server = new WebpackDevServer(compiler, {
hot: true,
quiet: false,
noInfo: false,
publicPath: webpackConfig.output.publicPath,
stats: 'minimal',
before: (app) => {
routes(app, config);
}
});
server.listen(port, host, function (error, result) {
if (error) {
console.log(error);
}
console.log('webpack dev server http://%s:%s', host, port);
});
server.dev.js用于开发时,进行本地调试,这个地方用到了webpack-dev-server进行了开发调试时,使用webpack的一个本地server,至于选择哪一个文件启动,是在package.json中配置了启动命令。
launcher文件夹
这里的四个文件,callback.html 和 silent-refresh.html 是统一账户Oauth2登录所需要的文件,这里不展开叙述,主要看index.html 和 index.tsx。
index.html
<html>
<head>
<script>
(function (global) {
if (global.location && !global.location.origin) {
global.location.origin = global.location.protocol + "//" +
global.location.hostname +
(global.location.port ? ':' + global.location.port : '');
}
global.appsettingcb = function (appSettings) {
global.appSettings = appSettings;
};
})(typeof window !== "undefined" ? window : this);
script>
<script src="settings?callback=appsettingcb">script>
head>
<body>
<div id="root" class="root-container" style="height: 100%">
div>
body>
html>
我截取了我觉得比较重要的一部分,就是如何读取我配置在package.json中的配置。首先看第二个script标签,src赋值了"settings?callback=appsettingcb"。一开始我很迷惑,这里的setting请求的到底是哪个文件,但是结合server中的routes.js就一目了然了,这里看一下routes.js,截取其中一段。
routes.js
const routes = (app, packageConfig) => {
app.route('/settings').get((_, res) => {
res.jsonp(packageConfig);
});
}
这里指数,当get请求的路径是setting时,将packageConfig这个变量,最后以jsonp的形式返回,这里对于packageConfig这个变量,是在server.js 中调用时,传入的。
server.js
let packageConfig = require('./package.json');
let routes = require('./routes');
const config = packageConfig.config;
const port = config.port;
routes(app, config);
这里指出了config是获取的package.json文件中的config变量,这里我们再看一下package.json 中的config变量。
package.json
{
"name": "reactboilerplate",
"version": "1.0.0",
"description": "boilerplate",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"clean": "gulp clean",
"tslint": "gulp tslint",
"start": "gulp start",
"build": "cross-env NODE_ENV=development gulp build",
"build:production": "cross-env NODE_ENV=production gulp build",
"build:development": "cross-env NODE_ENV=development gulp build"
},
"keywords": [
"react-boilerplate"
],
"author": "n",
"license": "ISC",
"config": {
"port": 9636,
"api": {
"server": "http://localhost:21022/"
}
}
}
最后一段定义了config变量,在变量中写下平时需要定义的变量,比如端口号,或者服务器地址。
到了这里再回头看index.html,src中的settings?callback=appsettingcb,这里请求了setting,返回的是jsonp形式的config变量,“callback”,是jsonp中的一个默认的回调函数变量,这里将"appsettingcb"的值赋给他,再看回第一个script标签
global.appsettingcb = function (appSettings) {
global.appSettings = appSettings;
};
这个函数的定义是把传入的appsettings赋值给全局的appsettings。这里全局的appsettings,是通过另外一个文件,考虑到项目是typescript进行开发,因此通过了global.d.ts来进行声明。
global.d.ts
interface AppSettings {
api: {
server: string;
};
}
interface Window {
appSettings: AppSettings;
}
如果用JavaScript进行开发的话可以省略这一步。最后通过setting.ts将其进行实例化,再export,就可以再项目中使用了。
setting.ts
const appSettings = window.appSettings;
export default appSettings;
至此,index.html中的script标签,已经全部剖析完毕,这里理清了如何把package.json中的config给读取到项目中。接下俩看下面的body标签。定义了一个id为root的div,这是为后面的react寻找根地址做准备,接下来就需要看index.tsx了。
index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { HashRouter as Router, Route } from 'react-router-dom';
import { Root } from '../../app/modules/systems';
ReactDOM.render(
<Router>
<Route component={Root} />
</Router>,
document.getElementById('root'));
这就正式开始写react了,首先确定了,路由是用的react-router-dom,然后锁定了id为root的div,开始进行渲染。
这一篇对build文件夹,也就是node的server文件进行了剖析,着重点是读取package.json中的配置,很巧妙的运用了jsonp,以及node的路由,下一篇将对react项目写组件进行总结。