概要

本文以个人阅读实践经验归纳前端架构构建过程,以Step by Step方式说明创建一个前端项目的过程。并会对每个阶段所使用的技术进行可替代分析,如Express替换Hapi或者Koa的优缺点分析。本文仅供参考。

流程

1. Package.json

首先,我们需要创建package.json文件。对设计初期已知的引用包和依赖包进行管理,使用ES6的,需要设置babel。其次编写脚本命令。一般文件形式如下:

  1. {
  2. "name": "practice",
  3. "description": "Ryan Project",
  4. "version": "1.0.0",
  5. "main": "server.js",
  6. "scripts": {
  7. "start": "node server.js",
  8. "watch": "nodemon server.js"
  9. },
  10. "babel": {
  11. "presets": [
  12. "es2015",
  13. "react"
  14. ]
  15. },
  16. "dependencies": {
  17. "alt": "^0.17.8",
  18. "async": "^1.5.0",
  19. "body-parser": "^1.14.1",
  20. "colors": "^1.1.2",
  21. "compression": "^1.6.0",
  22. "express": "^4.13.3",
  23. "history": "^1.13.0",
  24. "mongoose": "^4.2.5",
  25. "morgan": "^1.6.1",
  26. "react": "latest",
  27. "react-dom": "latest",
  28. "react-highcharts": "^10.0.0",
  29. "react-router": "^1.0.0",
  30. "request": "^2.65.0",
  31. "serve-favicon": "^2.3.0",
  32. "socket.io": "^1.3.7",
  33. "swig": "^1.4.2",
  34. "underscore": "^1.8.3",
  35. "xml2js": "^0.4.15"
  36. },
  37. "devDependencies": {
  38. "babel-core": "^6.1.19",
  39. "babel-preset-es2015": "^6.1.18",
  40. "babel-preset-react": "^6.1.18",
  41. "babel-register": "^6.3.13",
  42. "babelify": "^7.2.0",
  43. "bower": "^1.6.5",
  44. "browserify": "^12.0.1",
  45. "gulp": "^3.9.0",
  46. "gulp-autoprefixer": "^3.1.0",
  47. "gulp-concat": "^2.6.0",
  48. "gulp-cssmin": "^0.1.7",
  49. "gulp-if": "^2.0.0",
  50. "gulp-less": "^3.0.3",
  51. "gulp-plumber": "^1.0.1",
  52. "gulp-sourcemaps": "^1.6.0",
  53. "gulp-uglify": "^1.4.2",
  54. "gulp-util": "^3.0.7",
  55. "optimize-js": "^1.0.0",
  56. "vinyl-buffer": "^1.0.0",
  57. "vinyl-source-stream": "^1.1.0",
  58. "watchify": "^3.6.0"
  59. },
  60. "license": "MIT"
  61. }

输入完成后,运行npm install,将package.json中的包安装到项目目录中,存放于对应node_modules文件夹

2. Server.js

即服务端,可以使用Express、Koa、Hapi等方式去创建服务端,设置服务端口。也可以设置socket相关的工作。

Express创建服务端

  1. var express = require('express');
    var app = express();
  2.  
  3. //创建路由
    app.get('/', function(req, res) {
      res.send('Hello world');
    });
    //创建REST API
    var router = express.Router();
    router.route('/items/:id')
    .get(function(req, res, next) {
      res.send('Get id: ' + req.params.id); })
    .put(function(req, res, next) {
      res.send('Put id: ' + req.params.id); })
    .delete(function(req, res, next) {   
      res.send('Delete id: ' + req.params.id); });
  1. app.use('/api', router);
    var server = app.listen(3000, function() {
      console.log('Express is listening to http://localhost:3000');
    });

Koa创建服务端

  1. var koa = require('koa');
  2. var app = koa();
  3. //创建路由
    app.use(function *() {
      this.body = 'Hello world';
    });
    //创建REST API
    app.use(route.get('/api/items', function*() { this.body = 'Get'; }));
    app.use(route.post('/api/items', function*() { this.body = 'Post'; }));
    app.use(route.put('/api/items/:id', function*(id) { this.body = 'Put id: ' + id; }));
    app.use(route.delete('/api/items/:id', function*(id) { this.body = 'Delete id: ' + id; }));
  4. var server = app.listen(3000, function() {
  5. console.log('Koa is listening to http://localhost:3000');
  6. });

Hapi创建服务端

  1. var Hapi = require('hapi');
  2. var server = new Hapi.Server(3000);
  3. server.route({
      method: 'GET',
      path: '/',
      handler: function(request, reply) {
        reply('Hello world'); } });
    server.route([
      { method: 'GET', path: '/api/items', handler: function(request, reply) { reply('Get item id'); } },
      { method: 'GET', path: '/api/items/{id}', handler: function(request, reply) { reply('Get item id: ' + request.params.id); } },
      { method: 'POST', path: '/api/items', handler: function(request, reply) { reply('Post item'); } },
      { method: 'PUT', path: '/api/items/{id}', handler: function(request, reply) { reply('Put item id: ' + request.params.id); } },
      { method: 'DELETE', path: '/api/items/{id}', handler: function(request, reply) { reply('Delete item id: ' + request.params.id); } },
      { method: 'GET', path: '/', handler: function(request, reply) { reply('Hello world'); } } ]);
  4. server.start(function() {
  5. console.log('Hapi is listening to http://localhost:3000');
  6. });

三者间优缺点比较

  优点 缺点
Express 庞大的社区,相对成熟。极易方便创建服务端,创建路由方面代码复用率高 基于callback机制,不可以组合使用,也不能捕获异常
Koa

相比Express,移除Route和View,中间件的使用移植和编写都比较方便,拥抱ES6,

借助Promise和generator而非callback,能够捕获异常和组合使用

以Express一样,需要routers中间件处理不同的选择
Hapi  基于配置而非代码的框架,对于大型项目的一致性和可重用性比较有用。 为大型项目定制,导致在小项目中,常见的过于形式化的代码。相关的开源资料也比较少

3. 工程化工具

首先,我们需要先设计好我们项目的目录结构,以便使用工程化工作进行压缩打包等操作。

简单举例如下项目的结构

  1. --/public
  2. --/css
  3. --/js
  4. --/fonts
  5. --/img
  6. --/app
  7. --/actions
  8. --/components
  9. --/stores
  10. --/stylesheets
  11. --main.less
  12. --alt.js
  13. --route.js
  14. --main.js

其次,需要webpack或者browserify工具,打包压缩一系列的脚本文件。使用babel转换ES6语法,因为绝大部分的浏览器还不支持ES6,所以需要转换为ES5。最后,创建gulpfile.js文件,使用gulp创建系列的工程指令,如绑定vendor文件、引用sourcemap、使用类似uglify、gulp-cssmin等辅助压缩文件。

如下是简易的gulpfile.js文件的配置

  1. var gulp = require('gulp');
  2. var gutil = require('gulp-util');
  3. var gulpif = require('gulp-if'); //conditionally run a task
  4. var autoprefixer = require('gulp-autoprefixer'); //Prefix CSS
  5. var cssmin = require('gulp-cssmin');
  6. var less = require('gulp-less'); //Less for Gulp
  7. var concat = require('gulp-concat');
  8. var plumber = require('gulp-plumber'); //Prevent pipe breaking caused by errors from gulp plugins
  9. var buffer = require('vinyl-buffer'); //convert streaming vinyl files to use buffers
  10. var source = require('vinyl-source-stream'); //Use conventional text streams at the start of your gulp or vinyl pipelines
  11. var babelify = require('babelify');
  12. var browserify = require('browserify');
  13. var watchify = require('watchify');
  14. var uglify = require('gulp-uglify'); //Minify files with UglifyJS.
  15. var sourcemaps = require('gulp-sourcemaps');
  16.  
  17. var production = process.env.NODE_ENV === 'production'
  18.  
  19. var dependencies = [
  20. 'alt',
  21. 'react',
  22. 'react-dom',
  23. 'react-router',
  24. 'underscore'
  25. ]
  26. /*
  27. |--------------------------------------------------------------------------
  28. | Combine all JS libraries into a single file for fewer HTTP requests.
  29. |--------------------------------------------------------------------------
  30. */
  31. gulp.task('vendor', function () {
  32. return gulp.src([
  33. 'bower_components/jquery/dist/jquery.js',
  34. 'bower_components/bootstrap/dist/js/bootstrap.js',
  35. 'bower_components/magnific-popup/dist/jquery.magnific-popup.js',
  36. 'bower_components/toastr/toastr.js'
  37. ]).pipe(concat('vendor.js'))
  38. .pipe(gulpif(production, uglify({mangle: false})))
  39. .pipe(gulp.dest('public/js'))
  40. })
  41. /*
  42. |--------------------------------------------------------------------------
  43. | Compile third-party dependencies separately for faster performance.
  44. |--------------------------------------------------------------------------
  45. */
  46. gulp.task('browserify-vendor', function(){
  47. return browserify()
  48. .require(dependencies)
  49. .bundle()
  50. .pipe(source('vendor.bundle.js'))
  51. .pipe(buffer())
  52. .pipe(gulpif(production, uglify({mangle: false})))
  53. .pipe(gulp.dest('public/js'))
  54. })
  55.  
  56. /*
  57. |--------------------------------------------------------------------------
  58. | Compile only project files, excluding all third-party dependencies.
  59. |--------------------------------------------------------------------------
  60. */
  61. gulp.task('browserify',['browserify-vendor'], function(){
  62. return browserify({entries:'app/main.js', debug: true})
  63. .external(dependencies)
  64. .transform(babelify, {presets: ['es2015','react']})
  65. .bundle()
  66. .pipe(source('bundle.js'))
  67. .pipe(buffer())
  68. .pipe(soucemaps.init({loadMaps: true}))
  69. .pipe(gulpif(production, uglify({mangle: false})))
  70. .pipe(sourcemaps.write('.'))
  71. .pipe(gulp.dest('public/js'))
  72. })
  73.  
  74. /*
  75. |--------------------------------------------------------------------------
  76. | Same as browserify task, but will also watch for changes and re-compile.
  77. |--------------------------------------------------------------------------
  78. */
  79. gulp.task('browserify-watch', ['browserify-vendor'], function(){
  80. var bundler = watchify(browserify({ entries:'app/main.js', debug: true}), watchify.args)
  81. bundler.external(dependencies)
  82. bundler.transform(babelify, {presets: ['es2015', 'react']})
  83. bundler.on('update', rebundle)
  84. return rebundle()
  85.  
  86. function rebundle() {
  87. var start = Date.now()
  88. return bundler.bundle()
  89. .on('error', function(err){
  90. gutil.log(gutil.colors.red(err.toString()))
  91. })
  92. .on('end', function() {
  93. gutil.log(gutil.colors.green(`Finished rebundling in ${(Date.now() - start)} ms`))
  94. })
  95. .pipe(source('bundle.js'))
  96. .pipe(buffer())
  97. .pipe(sourcemaps.init({loadMaps: true}))
  98. .pipe(sourcemaps.write('.'))
  99. .pipe(gulp.dest('public/js'))
  100. }
  101. })
  102.  
  103. gulp.task('styles', function(){
  104. return gulp.src('app/stylesheets/main.less')
  105. .pipe(plumber())
  106. .pipe(less())
  107. .pipe(autoprefixer())
  108. .pipe(gulpif(production, cssmin()))
  109. .pipe(gulp.dest('public/css'))
  110. })
  111.  
  112. gulp.task('watch', function(){
  113. gulp.watch('app/stylesheets/**/*.less', ['styles'])
  114. })
  115.  
  116. gulp.task('default', ['styles','vendor','browserify-watch','watch'])
  117. gulp.task('build', ['styles', 'vendor', 'browserify'])

Gulp Task所做的操作如下说明:

Gulp Task

说明

Vendor

将所有第三方的js类库合并到一个文件

Browserify-vendor

将package.json中dependencies的依赖模块buffer化,以提供性能

Browserify

编译和绑定只与app相关的文件(无依赖项),并引用sourcemap对应、uglify压缩、buffer优化、babel转化ES6

Browserify-watch

利用watchify监测bundle.js文件的变化,并重新编译

Styles

编译less样式文件,自动添加前缀

Watch

监测Less文件,发生变化重新编译

Default

运行以上所有任务,且进程挂起监控watch

Build

运行以上所有任务,退出

4. 其他包管理(可无)

bower包管理工具的引入。由于NPM主要运用于Node.js项目的内部依赖包管理,安装的模块位于项目根目录下的node_modules文件夹内。并且采用嵌套的依赖关系树,即子依赖包各自有自己的依赖关系树,并不会造成他们之间的冲突。但是这种情况在纯前端的包管理就不那么友好了,比如你使用多个jquery版本。在使用方面npm主要用于管理类似grunt,gulp, ESlint,CoffeScript等npm模块。而bower管理纯前端css/js的包,比如jquery, bootstrap

使用步骤

1. 创建bower.json文件,将依赖包添加进(作用跟package.json类似)

  1. {
  2. "name": "practice",
  3. "dependencies": {
  4. "jquery": "^2.1.4",
  5. "bootstrap": "^3.3.5",
  6. "magnific-popup": "^1.0.0",
  7. "toastr": "^2.1.1"
  8. }
  9. }

2. 运行

npm install bower -g

bower install

5. 渲染部件

在渲染部分,React提供了客户端、服务端的渲染方式。具体区别如下:

1. 客户端渲染:

  可以直接在浏览器运行ReactJS,这是通用的比较简单的方式,网上也有很多例子。http://reactjs.org。服务端只创建初始化的html,装载你的组件UI,提供接口和数据。前端做路由与渲染的工作。缺点就是用户等待时间长。

2. 服务端渲染:

  html从后端生成,包含所有你的组件脚本UI以及数据。可以理解为生成一个静态的结果集页面。响应快,体验好。主要运用于提高主屏性能和SEO。服务端渲染,需要消耗CPU,但可以借助缓存实现优化。React中,通过renderToStaticMarkup方法实现。并且,你还需要保留对应的State以及所需要的数据。

例子援引如下开源项目,有兴趣的朋友可以去了解下。

http://sahatyalkabov.com/create-a-character-voting-app-using-react-nodejs-mongodb-and-socketio/

以React-Router为例(客户端)

1. 创建app/component/App.js

首先创建组件的容器app,this.props.children用于渲染其他组件

  1. import React, {Component} from 'react'
  2.  
  3. class App extends Component {
  4. render() {
  5. return (
  6. <div>
  7. {this.props.children}
  8. </div>
  9. );
  10. }
  11. }
  12.  
  13. export default App

2. 创建app/routes.js

如下点,指定路由/和/add,对应Home和AddCharacter组件

  1. import React from 'react'
  2. import {Route} from 'react-router'
  3. import App from './components/App'
  4. import Home from './components/Home'
  5. import AddCharacter from './components/AddCharacter';
  6.  
  7. export default (
  8. <Route component ={App} >
  9. <Route path= '/' component={Home} />
  10. <Route path= '/add' component={AddCharacter} />
  11. </Route>
  12. )

3.创建main.js

将Router组合的组件渲染到id为app的div里。

  1. import React from 'react'
  2. import Router from 'react-router'
  3. import ReactDOM from 'react-dom'
  4. import { createHistory } from 'history'; // you need to install this package
  5. import routers from './routes'
  6.  
  7. let history = createHistory();
  8. ReactDOM.render(<Router history={history}>
  9. {routers}
  10. </Router>, document.getElementById('app'))

5. app/components/添加home组件

  1. Import React from react
  2. Class Home extends React.Component{
  3. Render(){
  4. Return (
  5. <div className =’home’>
  6. Hello </div>)
  7. } }
  8. Export default Home

6. 组件

app/component/添加AddCharacter组件

这里采用的是alt(基于Flux)第三方库,所以还需要添加Actions和Store,以及alt.js文件。这里不一一列举,可以查看上面的源码地址。

Tip: 也可以使用react-redux来构建我们自己的app组件,redux能更好的管理react的state。 

7. 数据库

创建数据库数据,如果你是单页应用,那么建议使用mongoDB。具体实现不再一一描述,可以上网搜索相关内容

8. API

如果是基于mongoose的话,则只需要利用上面的Express、Koa或者Hapi创建API,访问mongoose数据.

如果是大型项目,有自己独立的后端语言,如C#或者Java。则可以基于微服务框架创建服务API。使用axios或者superagent等库访问数据。

参考文献

http://sahatyalkabov.com/create-a-character-voting-app-using-react-nodejs-mongodb-and-socketio/

http://stackoverflow.com/questions/27290354/reactjs-server-side-rendering-vs-client-side-rendering

http://stackoverflow.com/questions/18641899/what-is-the-difference-between-bower-and-npm

https://ifelse.io/2015/08/27/server-side-rendering-with-react-and-react-router/

https://www.airpair.com/node.js/posts/nodejs-framework-comparison-express-koa-hapi

关于React前端构建的一般过程 - 理论篇的更多相关文章

  1. 下一代的前端构建工具:parcel打包react

    1. parcel很受欢迎,webpack太慢了,试试Parcel下一代的前端构建工具 2.Parcel很快,但缺少好多插件,没有base64,没有办法拆分打包文件.... 3.总结:适合小项目 4. ...

  2. 【前端构建】WebPack实例与前端性能优化

    计划把微信的文章也搬一份上来. 这篇主要介绍一下我在玩Webpack过程中的心得.通过实例介绍WebPack的安装,插件使用及加载策略.感受构建工具给前端优化工作带来的便利. 壹 | Fisrt 曾几 ...

  3. react 前端项目技术选型、开发工具、周边生态

    react 前端项目技术选型.开发工具.周边生态 声明:这不是一篇介绍 React 基础知识的文章,需要熟悉 React 相关知识 主架构:react, react-router, redux, re ...

  4. 前端构建大法 Gulp 系列 (四):gulp实战

    前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gulp专家 前 ...

  5. 前端构建:Less入了个门

    一.前言   说到前端构建怎能缺少CSS预处理器呢!其实CSS的预处理器有很多啦,比较出名的有Scss.Sass.Stylus和Less.(最近还听说出现了Autoprefixer等CSS后处理器,可 ...

  6. Gulp, 比Grunt更好用的前端构建工具

    Gulp, 比Grunt更好用的前端构建工具 本文主要从两个方面介绍Gulp:一,Gulp相对于Grunt的优势: 二,Gulp的安装和使用流程 Gulp相对于Grunt的优势 gulp.js 的作者 ...

  7. Lucene核心--构建Lucene搜索(上篇,理论篇)

    2.1构建Lucene搜索 2.1.1 Lucene内容模型 一个文档(document)就是Lucene建立索引和搜索的原子单元,它由一个或者多个字段(field)组成,字段才是Lucene的真实内 ...

  8. Gulp前端构建工具

    Gulp, 比Grunt更好用的前端构建工具 Gulp, 比Grunt更好用的前端构建工具 本文主要从两个方面介绍Gulp:一,Gulp相对于Grunt的优势: 二,Gulp的安装和使用流程 Gulp ...

  9. 如何用webpack实现自动化的前端构建工作流

    什么是自动化的前端构建流? 1. 自动补全css私有前缀,自动转化less\sass为css,自动转化es6\vue\jsx语法为js,自动打包小图片为base64以减少http请求,自动给js,cs ...

随机推荐

  1. Selenium+Python :WebDriver设计模式( Page Object )

    Page Object 设计原理 Page Object设计模式是Selenium自动化测试项目的最佳设计模式之一,强调测试.逻辑.数据和驱动相互分离. Page Object模式是Selenium中 ...

  2. 关于iphone自动播放音频和视频问题的解决办法

    大家都知道 做移动端 会遇到音频和视频无法自动播放问题(我也遇到了)于是想办法解决这个问题 我只是找到了在微信中解决的办法(如果谁有在别的浏览器有这个办法  请私聊我 )我是没有发现 document ...

  3. 查找 TextBox 对象中非法数据的示例

    private void GetErrors(StringBuilder sb, DependencyObject obj){ foreach (object child in LogicalTree ...

  4. PHP-Manual的学习----【语言参考】----【类型】-----【float浮点型】

    笔记:1.浮点型(也叫浮点数 float,双精度数 double 或实数 real)可以用以下任一语法定义: <?php$a = 1.234; $b = 1.2e3; $c = 7E-10;?& ...

  5. 【BZOJ4320】ShangHai2006 Homework 分段+并查集

    [BZOJ4320]ShangHai2006 Homework Description   1:在人物集合 S 中加入一个新的程序员,其代号为 X,保证 X 在当前集合中不存在.    2:在当前的人 ...

  6. iOS UICollection 和UITableview新特性

    很详细优秀的博客: http://www.jianshu.com/p/e97780a24224 iOS10新特性总结 http://blog.csdn.net/yyacheng/article/det ...

  7. MyBatis -- 一步步教你使用MyBatis

    1.建立开发环境 1.1  创建项目,java项目或者javaweb项目均可,如图: 1.2  加入所须要的jar包到项目lib文件夹下 一个MyBatis-3.2.4.jar包 一个驱动包mysql ...

  8. Bootstrap 轮播图(Carousel)插件

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. ubuntu 13.04 设定静态IP

    切换到root用户,然后进入/etc/network目录.备份interfaces文件(备份文件是一个好习惯) 下面编辑interfaces文件,添加如下语句: # Assgin static IP ...

  10. 如何禁止eclipse对js文件的校验(building validate)

    在项目(project)上点击右键,依次选择1.Select Properties -> JavaScript -> Include Path2.Select Source tab. ( ...