前段时间使用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.4M!我的心中有一万头草泥马在奔腾!
使用webpack打包
别急,接下来我们来调教下打包工具。目前React主流打包工具有2种:webpack、Browserify。Browserify没用过,这里主要谈谈webpack的配置经验。
同上,建议为不同的环境准备不同的webpack配置文件,比如:webpack.config.dev.js
、webpack.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_ENV
为production
(见: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
是不是很简单?
其他
还有一些东西可以加进来:
- CI工具:Jenkins CI、Travis CI
- 错误收集:Sentry、New Relic
具体就不展开了。
就写到这里,欢迎建议。
· EOF ·