部署React+Redux Web App

http://www.hustlzp.com/post/2016/03/react-redux-deployment

前段时间使用React+Redux做了个后台管理的项目,在React初体验中分享了下入门经验。这篇文章谈谈我的部署实践。

目标

怎样才是好的部署呢?我觉至少有以下2点:

  • 优化性能包括代码执行速度、页面载入时间
  • 自动化:重复的事情尽量让机器完成,最好能运行一条命令就完成部署

代码层面

首先从代码层面来分析。

使用React+Redux,往往会用到其强大的调试工具Redux DevTools。在手动配置DevTools时需要围绕Store、Component进行一些配置。然而这些都是用来方便调试的,生产环境下我们不希望加入这些东西,所以建议就是从代码上隔离development和production环境:

containers/
    Root.js    
    Root.dev.js
    Root.prod.js
    ...
store/
    index.js
    store.dev.js
    store.prod.js

同时采用单独的入口文件(比如上面的containers/Root.js)按需加载不同环境的代码

if (process.env.NODE_ENV === 'production') {
    module.exports = require('./Root.prod');
} else {
    module.exports = require('./Root.dev');
}

一个细节需要注意:ES6语法不支持在if中编写import,所以这里采用了Commonjs的模块引入方法require

具体可以看看ReduxReal World示例项目。

代码层面还需要注意的一点就是按需import,否则可能会在打包时生成不必要的代码

OK,我们现在用webpack打个包,webpack --config webpack.config.prod.js --progress,结果可能会让你下一跳:8.4M!我的心中有一万头草泥马在奔腾!

使用webpack打包

别急,接下来我们来调教下打包工具。目前React主流打包工具有2种:webpackBrowserifybrowserify没用过,这里主要谈谈webpack的配置经验。

同上,建议为不同的环境准备不同的webpack配置文件,比如:webpack.config.dev.jswebpack.config.prod.js。下面我们来看看几个比较关键的配置选项:

devtools

文档在这里,我对source map技术不太了解,所以几个选项真不知道是干什么的。不过好在下面的表格中有写哪些是production supported,随便选择一个就好,感觉结果区别不大。这里我选择了source-map,webpack一下后生成了2个包:

  • bundle.js:3.32 MB
  • bundle.js.map:3.78 MB

唔,这样好多了,把用于定位源码的source map分离出去了,一下子减少了一半以上的体积。(注:source map只会在浏览器devtools激活时加载,并不会影响正常的页面加载速度,具体可参考When is jQuery source map loaded?JavaScript Source Map 详解。)

plugins

你可能会问“怎么不祭出UglifyJS啊?”,现在就祭...webpack文档中有一节Optimization,讲到了一些优化技巧。Chunks略高级没用过,看前面两个吧。提到了3个插件:UglifyJsPlugin、OccurenceOrderPlugin、DedupePlugin,第一个插件应该都懂是干啥,后面两个描述得挺高深的,不过不懂没关系,全用上试试,反正没副作用:

plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            }
        }),new webpack.optimize.DedupePlugin(),new webpack.optimize.OccurenceOrderPlugin()
    ]

打包结果:1.04 MB。

不要忽视NODE_ENV

NODE_ENV其实就是一个环境变量,在Node中可以通过process.env.NODE_ENV获取。目前大家往往用这个环境变量来标识当前到底是development还是production环境。

React提供了2个版本的代码(见:Development vs. Production Builds):

We provide two versions of React: an uncompressed version for development and a minified version for production. The development version includes extra warnings about common mistakes,whereas the production version includes extra performance optimizations and strips all error messages.

同时在React文档中明确建议在生产环境下设置NODE_ENVproduction(见:npm):

Note: by default,React will be in development mode. To use React in production mode,set the environment variable NODE_ENV to production (using envify or webpack's DefinePlugin). A minifier that performs dead-code elimination such as UglifyJS is recommended to completely remove the extra code present in development mode.

可以通过webpack的DefinePlugin设置环境变量,如下:

plugins: [
    ...
    new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production')
    }),]

打包结果:844 KB。

OK,webpack到此为止,给出完整的webpack.config.prod.js

var path = require('path');
var webpack = require('webpack');

module.exports = {
    devtool: 'source-map',entry: [
        './index.js'
    ],output: {
        path: path.join(__dirname,'webpack-output'),filename: 'bundle.js',publicPath: '/webpack-output/'
    },plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            }
        }),new webpack.optimize.OccurenceOrderPlugin(),new webpack.DefinePlugin({
            'production')
        }),],module: {
        loaders: [
            {
                test: /.js$/,loader: 'babel',exclude: /node_modules/,include: __dirname
            },{
                test: /\.css$/,loaders: ["style",68);">"css"]
            },38);">/\.scss$/,68);">"css",68);">"sass"]
            }
        ]
    },};

打包结果存放在webpack-output文件夹下。

使用FIS3添加hash

前端公认的Best Practice就是给资源打上hash标签,这对缓存前端资源很有用。webpack文档中有一节Long-term Caching就是专门讲整的,然而配置起来好麻烦的样子,最后我还是选择了百度FIS3做hash。

使用方法见文档,写的很详细。贴一下我的fis-conf.js

// 需要打包的文件
fis.set('project.files',['index.html',68);">'static/**',68);">'webpack-output/**']);

// 压缩CSS
fis.match('*.css',{
    optimizer: fis.plugin('clean-css')
});

// 压缩PNG图片
fis.match('*.png',68);">'png-compressor')
});

fis.match('*.{js,css,png}',{
    useHash: true,//启用has
    domain: 'http://7xrdyx.com1.z0.glb.clouddn.com',136);font-style:italic;">// 添加CDN前缀
});

其中,通过useHash: true启用了hash功能,同时压缩了CSS、PNG图片,然后通过domain添加了CDN前缀。

运行fis3 release -d ./output后,就把所有的文件打包到output文件夹下了,截个图:

使用CDN

844 KB虽然比最开始的8.4 M缩小到了1/10,但其实也很吓人。包大小基本上已经压缩到极限了,但我们还可以通过CDN来加快页面加载时间。

我选择的是七牛效果不错,而且免费额度够用。

上一步中我们已经用FIS3添加七牛CDN的前缀,接下来就是上传打包文件了。手动上传太麻烦,七牛提供了一个用来批上传的命令行工具qrsync,具体用法见文档。

使用Fabric进行远程部署

部署的时候难免会涉及到登陆server执行部署命令,你可以手动操作,但我推荐还是用一些工具来做。这方面工具不少,选择顺手的就行,我因为之前有过Python开发经验,所以一直用Fabric,很好用。安装下python,然后安装包管理工具pip,然后sudo pip install fabric就行了。

在项目文件下创建fabfile.py,通过写Python代码描述远程部署过程:

# coding: utf-8
from fabric.api import run,env,cd

def deploy():
    env.host_string = "username@ip"
    with cd('/path/to/your/project'):
        run('git pull')
        run('npm install')
        run('webpack --progress --config webpack.config.prod.js')
        run('fis3 release -d ./output')
        run('qrsync qrsync.conf.json')

其中,env.host_string描述server信息,然后cd到项目文件夹,git pull从GitHub拉取源码,npm install安装第三方库,接下来就是各种打包,最后批量上传到CDN。

执行fab deploy就部署到生产服务器了。

Nginx

收尾工作交给Nginx

  • 域名与本地文件夹路径关联起来
  • gzip支持:这个一定要做效果很赞,具体启用方法就是将/etc/Nginx/Nginx.conf与gzip相关的东西uncomment一下就行
  • 不存在的path一律导向/index.html:否则在非根路径下刷新浏览器,就会出现404,开发React的童鞋应该都懂这个坑...

我的Nginx.conf如下所示:

server {
    listen 80;
    server_name yourdomain.com;
    root /path/to/your/project;

    location / {
        try_files $uri /index.html;
    }
}

注:有童鞋可能奇怪为什么没有添加cache的配置,因为所有东西都上传到CDN了...

浏览器实际加载效果

在Chrome调试工具下看。

禁止缓存:

可以看到bundle的最终大小为206KB,加载时间是118ms。

启用缓存:

效果还不错。

开发->部署流程

从开发到部署的流程如下:

  • 编写代码:01010000101001010
  • 提交到代码仓库:git add -A && git commit -m 'Some notes.' && git push
  • 部署到server:fab deploy

是不是很简单?

其他

还有一些东西可以加进来:

具体就不展开了。

就写到这里,欢迎建议。

· EOF ·

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...