Quantcast
Channel: 懒得折腾
Viewing all articles
Browse latest Browse all 764

部署React+Redux Web App

$
0
0

部署React+Redux Web App

March 09, 2016

前段时间使用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

具体可以看看Redux的Real World示例项目。

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

OK,我们现在用webpack打个包,webpack --config webpack.config.prod.js --progress,结果可能会让你下一跳:8.4 M!求心理阴影面积…

使用webpack打包

接下来我们来调教下打包工具。目前React主流打包工具有2种:webpackBrowserify。Browserify没用过,这里主要谈谈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

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),production版性能更好:

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。

虽然比之前的1 M减少得不多,不过可以提升React的运行性能,还是很值的。

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.DedupePlugin(),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('production')
        }),
    ],
    module: {
        loaders: [
            {
                test: /.js$/,
                loader: 'babel',
                exclude: /node_modules/,
                include: __dirname
            },
            {
                test: /\.css$/,
                loaders: ["style", "css"]
            },
            {
                test: /\.scss$/,
                loaders: ["style", "css", "sass"]
            }
        ]
    },
};

打包结果输出到webpack-output文件夹下。

使用FIS3添加hash

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

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

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

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

// 压缩PNG图片
fis.match('*.png', {
    optimizer: fis.plugin('png-compressor')
});

fis.match('*.{js,css,png}', {
    useHash: true,  // 启用hash
    domain: 'http://7xrdyx.com1.z0.glb.clouddn.com',    // 添加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从Git仓库拉取源码,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的最终大小为206 KB,加载时间是118 ms。

启用缓存:

效果还不错。

开发->部署流程

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

  • 写代码、本地调试
  • 代码提交到远程Git仓库
  • 部署:fab deploy

附:使用npm scripts

最近npm scripts有点火,很多人都用它来取代Grunt、Gulp做自动化构建。

我们将部署命令放到package.jsonscripts中,然后通过npm run <script-name>的方式调用不同的script,这样会更加的cleaner:

{
    "name": "your-project-name",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "start": "node server.js",
        "build": "webpack --progress --config webpack.config.prod.js && fis3 release -d ./output",
        "upload": "qrsync qrsync.conf.json",
        "deploy": "fab deploy"
    },
    ...
}

然后fabfile.py可以改写为:

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

def deploy():
    env.host_string = "user@ip"
    with cd('/path/to/your/project'):
        run('git pull')
        run('npm install')
        run('npm run build')
        run('npm run upload')

部署命令变成:npm run deploy,更加赏心悦目。



Viewing all articles
Browse latest Browse all 764

Trending Articles