部署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

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

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

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

使用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

你可能会问“怎么不祭出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 ·

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom