前言
React 是 Facebook 开源出来的目前比较流行和热门的前端框架,因其基于组件的开发符合当下和未来的前端发展趋势。
React 还引入了 JSX
语法,对于初次接触 JSX
的人可能会感觉有点别扭,但是当你用过一遍之后就会发觉已经对其爱不释手了。不得不赞叹 Facebook 的开发者,为了能让开发更简便,而想出来的牛逼的解决方案。当你深入使用 React,你会发觉整个 React 的设计思想都是令人叹为观止的。
Hello world
对于初次接触 React 的开发者而言,想要使用它创建一个 Hello world 应用倒不难,先来看看官方文档给出的使用方法。
需要在页面中分别载入 React
框架和 JSXTransformer
这个 JSX 的编译转换库。
<script src="react.js"></script>
<script src="JSXTransformer.js"></script>
Hello world 的应用代码无论是内联还是外链,都需要在 script 标签上加上 type="text/jsx"
的自定义类型,用以标明该代码中包含了 JSX
的语法,以方便 JSXTransformer
对其进行编译转换。
<div id="example"></div>
<script type="text/jsx">
React.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
</script>
模块化的尝试
这种开发方式开发一个简单的应用没什么问题,如果你想要使用 React 开发一个大应用,那么势必会想使用模块化的开发方式把大应用拆分成很多小组件。
在开发阶段模块需要异步加载,那么 JSX
依赖 type="text/jsx"
来进行实时编译就有问题了。如果你使用 RequireJS
或 SeaJS
这类模块加载器的话,可能需要借助额外的插件来实现在加载完模块后再对其进行编译解析,加载和编译转换的操作都要在浏览器中实现。这种开发环境比较复杂,尤其还要在页面中引入 JSXTtansformer
和加载器的库文件,使用代价比较高。
实时编译和合并
除了在浏览器中进行编译,还可以将编译转换的工作移到浏览器之外,在 node 环境中进行编译。我们可以使用 browserify
或者 webpack
,本文将以 browserify
为例来讲解如何搭建一个实时编译的开发环境,当然除了 browserify
还有构建工具 gulp
。
ES6(2015) & ES7(2016)
除了基于组件化的开发,React 对新的 ES6
和 ES7
的支持度都很好,所以说 React 能适应未来前端发展趋势并不是泛泛而谈。
虽然 ES6
已经正式发布,但是大部分现代化的浏览器到目前为止其实都对其支持度不佳,但这并不妨碍我们使用,因为还有推动了 ES6
发展的 Babel
。借助 Babel
,我们可以将 ES6
的代码转换成能兼容主流浏览器的 ES5
的语法。
基于上面提到的几个方面,我们的 React 开发环境主要需要实现以下功能:
JSX
的编译转换ES6
和ES7
的编译转换- 实时合并模块
- 开发时保存文件则自动刷新浏览器的
livereload
的server
功能
目录结构
myapp
|
+ dist // 用于发布上线的构建目录
+ src // 开发的根目录
|
+ ...
+ style
|
+ css
+ less
+ js
|
+ lib
|
+ react.js
+ babel-external-helpers.js
+ ...
+ index.js // 开发时的app入口文件
+ bundle.js // browserify合并好的入口文件
+ index.html // 默认的启动页面
+ ...
+ gulpfile.js
index.html 访问入口页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React Demo</title>
<body>
<!-- 用于将应用插入到页面的根节点 -->
<div class="root" id="root"></div>
<!-- 第三方库文件 -->
<script src="/js/lib/react.js"></script>
<script src="/js/lib/babel-external-helpers.js"></script>
<!-- browserify转换后的应用文件 -->
<script src="/js/bundle.js"></script>
</body>
</html>
livereload
使用 gulp-connet
插件可以很轻松的搭建一个 livereload 的服务器。
var connect = require('gulp-connect');
gulp.task('connect', function () {
connect.server({
root: 'src', // server的root目录
port: 3001, // server绑定的端口号
livereload: true, // 开启livereload的功能
fallback: 'src/index.html'
});
});
其中的 fallback
配置项值得注意,使用上面的任务启动了 server 后,通过 http://localhost:3001
来访问,会默认查找 root
目录下的 index.html
文件,当访问其他的路径如:http://localhost:3001/users
,服务器会报 404 的错误,这是正常的,因为这个简单的服务器并没有 route
功能,如果配置了 fallback: 'src/index.html'
,那么服务器不会直接报404了,而是回退到 src/index.html
。如果你的 React 应用有 route
功能,这个配置项非常有用,假如你已经有了一个 http://localhost:3001/users
的 route
,当你在该路径下刷新页面的时候他就会正确的保持 route
状态,否则会直接报 404。
各种 xxxify 的应用
browsify
用于合并模块watchify
配合browserify
实现实时合并babelify
合并模块时对ES6
和ES7
进行编译转换,同时还可以对JSX
进行编译转换cacheify
提供缓存服务,提升编译转换的性能
var browserify = require('browserify');
var watchify = require('watchify');
var babelify = require('babelify');
var cacheify = require('cacheify');
var connect = require('gulp-connect');
// browserify的配置项
var browserifyOpts = {
entries: 'src/js/index.js', // 入口文件
debug: true, // 是否包含sourcemap
insertGlobals: true,
detectGlobals: false
};
var bundle = function () {
return watcher.bundle()
.on('error', function (err) {
console.log(err.message);
console.log(err.stack);
})
.pipe(source('bundle.js'))
.pipe(gulp.dest('src/js'))
.pipe(connect.reload());
};
var babelifyCache = cacheify(babelify.configure({
externalHelpers: true, // 不将babel的helper代码插入到模块中
stage: 0 // 该模式能支持诸如static关键字等ES7的特性
}), db);
var bundler = browserify(browserifyOpts)
.transform(babelifyCache);
var watcher = watchify(bundler)
.on('update', bundle)
.on('log', gutil.log);
gulp.task('watch-js', bundle);
browserify 的配置项中包含了 insertGlobals
和 detectGlobals
,其中 insertGlobals
用于跳过检测始终插入定义的 process
global
__filename
__dirname
,如果确定不会用到这些全局变量就设置为 true 吧。detectGlobals
用于检测当前代码中是否包含了上述的全局变量。如果代码中确定根本用不到那些全局变量设置这2个配置项能提升合并的性能。
babelify 的配置项的 externalHelpers
比较重要,在使用 babel 将 ES6 转换成 ES5 的代码时,会默认将一些诸如 class
的实现代码都插入到转换后的模块中,如果你有很多个模块,它都会插入一遍这些代码,此时就会有很多冗余的代码。将该配置设置为 true 后,则不回插入这些实现代码。此时要将这些代码已外链的形式引入到页面中,官方有详细的说明 文档。
<script src="/js/lib/babel-external-helpers.js"></script>
browserify 在合并的时候会提供一个使用中间件的 transform
入口,有一句软件开发的名言,大意是:任何复杂的系统,都可以通过引入一个中间层来得到简化。browserify 有非常多的 transform
中间件,资源相当丰富。
在上面的代码中请注意 babelify
和 reactify
使用时的先后顺序。
除了 watch js,还可以对 html 和 css 进行 watch。如果项目有使用到 sass
和 less
这类预处理框架,同样可以实现实时编译。
var connect = require('connect');
var less = require('gulp-less');
var htmlSrcPath = 'src/**/*.html';
var lessSrcPath = 'src/style/less/**/*.less';
var cssDestPath = 'src/style/css';
// watch html
gulp.task('watch-html', function () {
gulp.watch(htmlSrcPath, function () {
return gulp.src(htmlSrcPath)
.pipe(connect.reload());
});
});
// watch css 包含了 less 的实时编译
gulp.task('watch-css', function () {
var lessTransformer = function () {
return gulp.src(lessSrcPath)
.pipe(less())
.pipe(gulp.dest(cssDestPath))
.pipe(connect.reload());
};
lessTransformer();
gulp.watch(lessSrcPath, lessTransformer);
});
上述的功能使用 webpack
也同样会有替代的解决方案,具体选用哪个框架看自己的喜好了。个人认为如果是基于 gulp,后续的构建工作也会更轻松些。
这是我的 React 系列文章的第一篇,由于涉及的面确实比较广,为了能尽量说的详细些,篇幅有点长。
后续更新:React 官方已经推荐抛弃 JSXTransformer
转而使用 babel
,后者已经默认内置了对 JSX
的转换。
“React 高效开发环境的搭建”目前已有 3 条评论