/
how-to-build-frontend-dev-env.html
436 lines (362 loc) · 19.6 KB
/
how-to-build-frontend-dev-env.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<title>刘旸 Zation</title>
<meta name="author" content="刘旸">
<meta name="description"
content="经历过各种技术和职位,最终发现前端开发才是我的归宿,现为Kber前端程序员,专注于实践优秀的前端代码架构。同时也关注用户体验以及新兴设计元素。">
<link href="/stylesheets/main.css?1423851139" media="screen" rel="stylesheet" type="text/css" />
<link href="/images/favicon.png?1401972413" rel="icon" type="image/png" />
<!--[if lt IE 9]>
<script src="/javascripts/html5shiv-printshiv.js?1401972413" type="text/javascript"></script>
<![endif]-->
</head>
<body class="detail">
<div class="main-container">
<header class="nav-container">
<div class="search-container">
<div class="search-cancel-icon">✖</div>
<button class="search-cancel">Cancel</button>
<input placeholder="Search for article title or tags" class="search" type="text">
<div class="search-qualifier"></div>
<ul class="search-result">
</ul>
</div>
<ul class="social-links">
<li class="google-plus">
<a target="_blank" href="https://plus.google.com/100624981016590778324?rel=author">
Google
</a>
</li>
<li class="weibo">
<a target="_blank" href="http://weibo.com/zation1">
Weibo
</a>
</li>
<li class="rss">
<a target="_blank" href="/feed.xml">
RSS
</a>
</li>
<li class="facebook">
<a target="_blank" href="http://www.facebook.com/yang.liu.37669">
Facebook
</a>
</li>
<li class="github">
<a target="_blank" href="https://github.com/zation">
Github
</a>
</li>
</ul>
<a href="/" class="logo">刘旸 Zation</a>
<ul class="nav">
<li><a class="blog-link" href="/">Blog</a></li>
<li><a class="about-link" href="/about.html">About</a></li>
<li><a class="resume-link" href="/resume.html">Resume</a></li>
</ul>
</header>
<section class="content">
<article class="article">
<header class="article-title">
<h1>
如何构建自动化的前端开发流程
</h1>
</header>
<ul class="extro-info">
<li class="date">
2013. 03. 15
</li>
<li class="tag">
<a href="/tags/grunt.html">grunt</a>
</li>
<li class="tag">
<a href="/tags/bower.html">bower</a>
</li>
<li class="comment">
<a class="ds-thread-count"
data-thread-key="how-to-build-frontend-dev-env"
href="#comment"></a>
</li>
</ul>
<div class="article-content">
<p>如今的前端开发中,已经不再只是一些简单的静态文件了,对于很多Web App来说,前端代码甚至比后端代码要更加复杂,更加难于管理,例如:</p>
<ul>
<li>我们有许多的第三方库的依赖需要管理;</li>
<li>我们有独立的前端测试需要自动运行;</li>
<li>我们还有很多代码需要在发布时进行打包压缩;</li>
<li>⋯⋯</li>
</ul>
<p>所以构建一个自动化的前端开发流程是非常必要的,但现在前端开发流程的构建是百花齐放,没有一个统一的标准,还有很多依赖于后端的架构来做前端开发管理。例如在Rails开发中,就有各种前端库的gem包。但是这种依赖于后端框架的管理方式有许多问题:</p>
<ul>
<li>许多gem包的维护者并不是前端库的维护者,所以更新不一定即时;</li>
<li>不利于前端代码与后端代码做分离;</li>
<li>增加了前端开发者的学习和使用成本;</li>
<li>⋯⋯</li>
</ul>
<p>于是现在出现了一些不依赖于后端代码(虽然还是要依赖Node.js⋯⋯)的管理工具,对于前端开发者非常友好,例如:<a href="http://yeoman.io/">YEMAN</a>、<a href="http://jamjs.org/">Jam</a>、<a href="http://volojs.org/">volo</a>、<a href="https://github.com/component/component">component</a>、<a href="http://brunch.io/">Brunch</a>⋯⋯但是这些工具都或多或少有自己的一些问题,所以我决定用一些更轻量的工具(<a href="http://twitter.github.com/bower/">bower</a>、<a href="http://gruntjs.com/">grunt</a>)来搭建自己的前端开发流程。本文的例子来自本人正在开发的一个项目,可以在github上查看<a href="https://github.com/zation/flyingtortoise">所有的代码</a>。 </p>
<h3>什么是开发流程?</h3>
<p>在我看来一个完整的开发流程应该包括:</p>
<ul>
<li>本地开发环境的初始化</li>
<li>第三方依赖的管理</li>
<li>源文件编译</li>
<li>自动化测试</li>
<li>发布到pipeline和各个环境</li>
</ul>
<p>而现代的开发流程,就是要使上面的各个部分都可以自动化,一个命令就可以使这些流程都自动走完,并且快速的得到错误或通过的反馈,让我们可以方便快速的修复错误和release。</p>
<h3>本地开发环境的初始化</h3>
<p>这里我使用的工具是<a href="http://nodejs.org/">Node.js</a>和<a href="https://npmjs.org/">NPM</a>,它们都基于JavaScript,使用Json来配置,对于前端开发人员非常友好。</p>
<p>安装完成Node.js和NPM后,在项目根目录下创建NPM的配置文件<code>package.json</code>:</p>
<pre><code>{
"name": "Project Name",
"version": "0.0.1",
"description": "Project Description",
"repository": {
"type": "git",
"url": "git://github.com/path/to/your_project"
},
"author": "Author Name",
"license": "BSD",
"readmeFilename": "README.md",
"gitHead": "git head",
"devDependencies": {
"grunt": "latest",
"grunt-contrib-connect": "latest",
"grunt-contrib-concat": "latest",
"grunt-contrib-jasmine": "latest",
"grunt-contrib-watch": "latest",
"grunt-contrib-compass": "latest"
}
}
</code></pre>
<p>其中最重要的一个配置项是<strong>devDependencies</strong>,这是用于开发的依赖,例如:自动化测试、源文件编译等等,其中各个依赖的作用和用法将会在后面讲到。而前端生产代码的依赖会使用另一个工具来管理,也在后面讲到。创建完成以后运行<code>npm install</code>,NPM就会将这些依赖都安装到项目根目录的<code>node_modules</code>文件夹中。</p>
<h3>第三方依赖的管理</h3>
<p>这里我使用的工具是<a href="http://twitter.github.com/bower/">bower</a>。其实NPM也可以管理,但是NPM并不是读取第三方依赖原始的repository,而是读取自己管理的一个repository,所以更新可能会慢点,并且它使用CommonJS的接口方便Node.js项目的开发,并不是针对纯前端开发的项目;而bower是读取原始的github repository,没有更新延迟的问题,所有包都是针对纯前端开发项目的。</p>
<p>要使用bower只需要简单的三步:</p>
<ol>
<li>安装:<code>npm install bower -g</code></li>
<li>在项目根目录中创建配置文件<code>.bowerrc</code></li>
<li>在项目根目录中创建依赖配置文件<code>components.json</code></li>
</ol>
<p>我们首先来看看<code>.bowerrc</code>的内容:</p>
<pre><code>{
"directory" : "components",
"json" : "component.json",
"endpoint" : "https://bower.herokuapp.com"
}
</code></pre>
<p>其中<strong>directory</strong>指定了所有的依赖会被安装到哪里;<strong>json</strong>指定了依赖配置文件的路径;<strong>endpoint</strong>制定了依赖的repository的寻址服务器,你可以替换为自己的寻址服务器。</p>
<p>然后我们来看看<code>components.json</code>的内容:</p>
<pre><code>{
"name": "Project Name",
"version": "0.0.1",
"dependencies": {
"jquery": "latest",
"underscore": "latest",
"backbone": "latest",
"jasmine-jquery": "latest",
"jasmine-ajax": "git@github.com:pivotal/jasmine-ajax.git"
}
}
</code></pre>
<p>其中最重要的就是<strong>dependencies</strong>,它指定了所有前端开发依赖的包。所有bower包含的依赖都可以在<a href="http://sindresorhus.com/bower-components/">这里</a>查到,对于bower没有包含的依赖也可以直接指定github的repository,例如:<code>"jasmine-ajax": "git@github.com:pivotal/jasmine-ajax.git"</code>。</p>
<p>最后运行<code>bower install</code>就可以在components文件夹中看到所有第三方依赖的文件了。但是bower有一个问题,就是它将所有github repository中的文件都下载下来了,其中有许多是我们不需要的文件。下面我们会将我们需要的文件提取出来打包放到我们指定的目录中。</p>
<h3>源文件编译</h3>
<p>这里我使用的工具是<a href="http://gruntjs.com/">grunt</a>,他本身主要是基于Node.js的文件操作包,其中有许多插件可以让我们完成js文件的compile和compress、sass到css的转换等等操作。要使用它需要先安装命令行工具:<code>npm install grunt-cli -g</code>,然后在项目根目录中创建文件<code>Gruntfile.js</code>,这个文件用于定义各种task,我们首先定义一个task将从bower下载的第三方依赖都打包到文件<code>app/js/lib.js</code>中:</p>
<pre><code>module.exports = function(grunt) {
var dependencies = [
'components/jquery/jquery.js',
'components/underscore/underscore.js',
'components/backbone/backbone.js'];
grunt.initConfig({
concat: {
js: {
src: dependencies,
dest: 'app/js/lib.js'
}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
};
</code></pre>
<p>这里的grunt-contrib-concat就是grunt的一个插件,用于文件的合并操作,我们已经在前面的<code>package.json</code>中引入了。<code>js</code>是task name;<code>src</code>指定了合并的源文件地址;<code>dest</code>指定了合并的目标文件。这样当我们运行<code>grunt concat:js</code>后,所有的依赖文件都会被合并为<code>app/js/lib.js</code>。这样做的好处是我们可以控制每个依赖的引入顺序,但是麻烦的是每次引入新的依赖都需要手动加入到<code>dependencies</code>数组中。这个暂时没有更好的解决方案,因为不是所有的包都在自己的<code>components.js</code>中声明了main文件,很多时候必须自己手动指定。</p>
<p>JavaScript文件编译完成以后就是CSS文件,在现代的前端开发中,我们已经很少直接写CSS文件了,一般都使用SASS或者LESS。grunt也提供了这种支持,这里我使用的是<a href="https://github.com/gruntjs/grunt-contrib-compass">grunt-contrib-compass</a>:</p>
<pre><code>module.exports = function(grunt) {
var sasses = 'sass';
grunt.initConfig({
compass: {
development: {
options: {
sassDir: sasses,
cssDir: 'app/css'
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-compass');
};
</code></pre>
<p>然后运行<code>grunt compass:development</code>就可以完成CSS文件的编译了。</p>
<h3>自动化测试</h3>
<p>这里我使用的自动化测试工具是<a href="http://pivotal.github.com/jasmine/">Jasmine</a>,它grunt中同样有一个插件:<a href="https://github.com/gruntjs/grunt-contrib-jasmine">grunt-contrib-jasmine</a>。下面我们来看看如何在<code>Gruntfile.js</code>中定义测试的task:</p>
<pre><code>module.exports = function(grunt) {
var sources = 'app/js/**/*.js',
specs = 'spec/**/*Spec.js';
grunt.initConfig({
jasmine: {
test: {
src: [sources],
options: {
specs: specs,
helpers: ['spec/helper/**/*.js'],
vendor: 'app/js/lib.js'
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-jasmine');
};
</code></pre>
<p>配置完成以后就可以运行<code>grunt jasmine:test</code>来跑测试,但问题是每次写完代码都要手动执行一次非常麻烦,最好可以每次代码有更改都自动跑一次,让我们可以更快的得到反馈。grunt的<a href="https://github.com/gruntjs/grunt-contrib-watch">watch插件</a>就提供了这种支持:</p>
<pre><code>module.exports = function(grunt) {
var sources = 'app/js/**/*.js',
specs = 'spec/**/*Spec.js';
grunt.initConfig({
jasmine: {
test: {
src: [sources],
options: {
specs: specs,
helpers: ['spec/helper/**/*.js'],
vendor: 'app/js/lib.js'
}
}
},
watch: {
test: {
files: [sources, specs],
tasks: ['jasmine:test']
}
}
});
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-watch');
};
</code></pre>
<p><code>files</code>指定了需要监听变动的文件;<code>tasks</code>指定了修改后自动触发的task。现在只要我们运行<code>grunt watch:test</code>,那么有任何源文件、测试文件的改动,Jasmine测试都会自动运行了。有时候我们也希望测试的结果显示在网页上,便于我们做js的调试。那么可以将<code>tasks: ['jasmine:test']</code>改为<code>tasks: ['jasmine:test:build']</code>,然后打开根目录下的<code>_SpecRunner.html</code>文件,就可以在网页中看到测试结果了,再加上一些Chrome的<a href="https://chrome.google.com/webstore/detail/live-reload/pccddenngcbofbojodpghgpbheckgddn">Livereload插件</a>,就可以不用刷新实时的看到测试结果,效率非常之高。虽然grunt插件中也有<a href="https://github.com/gruntjs/grunt-contrib-livereload">livereload</a>,但是与grunt-contrib-watch无法很好的集成,所以我没有使用这种方式。</p>
<h3>CI Pipeline</h3>
<p>由于我的项目是host在github上,所以我选择<a href="https://travis-ci.org/">travis-ci</a>作为我的CI服务器。要启用travis-ci需要以下几步:</p>
<ol>
<li>在<a href="https://travis-ci.org/">travis-ci</a>中注册一个账号,获取一个token;</li>
<li>在你的github项目的Settings–>Service Hooks中找到Travis,填入token并且启用;</li>
<li>回到<a href="https://travis-ci.org/">travis-ci</a>,在Accounts–>Repositories中打开你的项目的service hook</li>
<li>Push一个<code>.travis.yml</code>到github,触发第一次build。</li>
<li>修改<code>package.json</code>的<code>scripts</code>项,指定运行测试的命令</li>
</ol>
<p>下面我们来看看如何配置<code>.travis.yml</code>:</p>
<pre><code>language: node_js
node_js:
- "0.8"
before_script:
- npm install -g grunt-cli
</code></pre>
<p>由于我们的环境是基于Node.js搭建的,所以在<strong>language</strong>设置了node<em>js;而**node</em>js<strong>指定了Node.js的版本;</strong>before_script**指定了在测试运行前需要执行的命令,由于我们的脚本都是基于grunt的,所以需要先安装grunt的命令行包。</p>
<p>然后再修改<code>package.json</code>:</p>
<pre><code>{
⋯⋯
"scripts": {
"test": "grunt jasmine:test"
}
⋯⋯
}
</code></pre>
<p>将修改以后的<code>package.json</code>push到github上,再次触发一个新的build,你可以看到你之前错误的build已经绿了。</p>
<p>这里还有一个小提示:如何让build状态显示在项目的readme中?很简单,只需要在README.md中加入以下代码就可以了:</p>
<pre><code>[![Build Status](https://travis-ci.org/path/to/your_repository.png?branch=master)](http://travis-ci.org/path/to/your_repository)
</code></pre>
<p>到这里基本的环境搭建就完成了,当然我们还可以使用grund的<code>registerTask</code>来定义一个任务序列,还可以加入template的编译⋯⋯这些都可以通过grunt来灵活设置。最重要的是现在别人拿到一个项目的代码以后,可以通过一些命令来快速的搭建本地环境,方便的进行测试和开发,而且没有依赖与后端的开发环境,只要定义好接口,前端开发可以完全独立开了。虽然这其中还有很多问题没有解决,例如:</p>
<ul>
<li>如何让第三方依赖自申明main文件</li>
<li><code>package.json</code>与<code>components.json</code>其实有些重复</li>
<li>Live Reload还需要Chrome插件才能完成</li>
<li>⋯⋯</li>
</ul>
<p>这正是由于现在前端开发环境还没有后端开发的那种标准化,也正是挑战和机遇之所在!</p>
</div>
</article>
<div id="comment">
<div class="ds-thread" data-thread-key="how-to-build-frontend-dev-env"></div>
</div>
</section>
</div>
<footer class="page-footer">
<div class="nav-container">
<div class="search-container">
<div class="search-cancel-icon">✖</div>
<button class="search-cancel">Cancel</button>
<input placeholder="Search for article title or tags" class="search" type="text">
<div class="search-qualifier"></div>
<ul class="search-result">
</ul>
</div>
<ul class="social-links">
<li class="google-plus">
<a target="_blank" href="https://plus.google.com/100624981016590778324?rel=author">
Google
</a>
</li>
<li class="weibo">
<a target="_blank" href="http://weibo.com/zation1">
Weibo
</a>
</li>
<li class="rss">
<a target="_blank" href="/feed.xml">
RSS
</a>
</li>
<li class="facebook">
<a target="_blank" href="http://www.facebook.com/yang.liu.37669">
Facebook
</a>
</li>
<li class="github">
<a target="_blank" href="https://github.com/zation">
Github
</a>
</li>
</ul>
<a href="/" class="logo">刘旸 Zation</a>
<ul class="nav">
<li><a class="blog-link" href="/">Blog</a></li>
<li><a class="about-link" href="/about.html">About</a></li>
<li><a class="resume-link" href="/resume.html">Resume</a></li>
</ul>
</div>
</footer>
<script src="/javascripts/main.js?1447233592" type="text/javascript"></script>
<script type="text/javascript">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-8993667-6', 'zation.me');
ga('require', 'displayfeatures');
ga('send', 'pageview');
</script>
<script type="text/javascript">
var duoshuoQuery = {short_name:"zation"};
(function() {
var ds = document.createElement('script');
ds.type = 'text/javascript';ds.async = true;
ds.src = 'http://static.duoshuo.com/embed.js';
ds.charset = 'UTF-8';
(document.getElementsByTagName('head')[0]
|| document.getElementsByTagName('body')[0]).appendChild(ds);
})();
</script>
</body>
</html>