若要得到更好的阅读体验请移驾http://willkan.github.io/blog/html/Workflow/javascript
接触前端一年了,瞎摸过来这么久一直很但愿看到一个完整的前端构建流程,遗憾没找到相关文章,因此本身在作这个网页的时候顺便纪录下来本身从架构到部署的整个过程。php
我一直但愿有个网站可让我用markdown写文章,而且能够在markdown中插入本身的demo(不是源码),因而就作了这么个项目让我能够轻松发布本身的文章。(文中并不会提到如何解析markdown,解析markdown能够了解下grunt-markdown
)css
网上前端资源很是丰富,作一个网页彻底不必重复造轮子,下面是我作这个网页用到的工具及其分析。html
响应式css框架:Foundation 4 不少人听到响应式会第一时间想到Bootstrap,但在网上查询后我发现不少人再说Bootstrap在移动端的性能并很差,甚至会拖慢应用,很大一部分缘由是由于Bootstrap只采用jQuery这么个在移动端上并不太适合的库。而Foundation4在移动端采用Zepto,而且采用mobile first概念(就是说首先为小屏幕设计,随着屏幕逐渐增大,层次越复杂,能够和"渐进加强"类比)。 Bootstrap和Foundation的详细对比你们能够参见twitter-bootstrap-vs-foundation-4-which-one-is-right-for-you(注意,这里比较的Bootstrap版本还比较老) 感受Foundation 4 在国内使用群还比较少,因此教程仍是参见官网吧。 可是有一点比较奇怪的是Foundation 4的官方Docs在个人移动设备上仍会卡顿。是html文件太大?仍是Foundation 4的css太臃肿? 在以后真正进入开发时我会做出对比。前端
DOM操做库:Zepto 为何选择Zepto?由于Zepto性能比其余DOM操做库在webkit上表现好太多了。而webkit又是IOS和Android上主力的浏览器核心。部分dom操做性能对比请查看http://jsperf.com/jqm3/103java
模板解释引擎:HandleBars 其余模版引擎也能够,只是这里我比较喜欢Handlebars而已。模版解释的好处在于再也不须要再写大量而不清晰的js插入dom结构语句。此外,我将回用Handlebars往markdown解析后的文件中插入demo。node
前端模块化:seajs 要么requirejs,要么seajs,为啥选seajs,请查看seajs处的解释 架构工具:yeomanlinux
提升开发效率和规范 移动端动画:采用css3动画实现页面切换而不是传统的setTimeOut调整dom的样式,css3
css预编译:Sass 由于用到了foundation 4,而foundaton 4 官方版本又拥有Sass版本,因此用上了Sassgit
测试:CasperJS 感受上mocha,jasmine更偏向于本地纯js调试,而CasperJS这基于PhantomJS的工具则更适合浏览器调试
工具选好了以后,咱们开始构建项目了。首先介绍一下yeoman,借用一下infoq上的介绍:
Yeoman是由Paul Irish、Addy Osmani、Sindre Sorhus、Mickael Daniel、Eric Bidelman和Yeoman社区共同开发的一个项目。它旨在为开发者提供一系列健壮的工具、程序库和工做流,帮助他们快速构建出漂亮、引人注目的Web应用。Yeoman拥有以下特性:
- 快速建立骨架应用程序——使用可自定义的模板(例如:HTML五、Boilerplate、Twitter Bootstrap等)、AMD(经过RequireJS)以及其余工具轻松地建立新项目的骨架。
- 自动编译CoffeeScrip和Compass——在作出变动的时候,Yeoman的LiveReload监视进程会自动编译源文件,并刷新浏览器,而不须要你手动执行。
- 自动完善你的脚本——全部脚本都会自动针对jshint(软件开发中的静态代码分析工具,用于检查JavaScript源代码是否符合编码规范)运行,从而确保它们遵循语言的最佳实践。
- 内建的预览服务器——你不须要启动本身的HTTP服务器。内建的服务器用一条命令就能够启动。
- 很是棒的图像优化——Yeoman使用OptPNG和JPEGTran对全部图像作了优化,从而你的用户能够花费更少时间下载资源,有更多时间来使用你的应用程序。
- 生成AppCache清单——Yeoman会为你生成应用程序缓存的清单,你只须要构建项目就好。
- “杀手级”的构建过程——你所作的工做不只被精简到最少,让你更加专一,并且Yeoman还会优化全部图像文件和HTML文件、编译你的CoffeeScript和Compass文件、生成应用程序的缓存清单,若是你使用AMD,那么它还会经过r.js来传递这些模块。这会为你节省大量工做。
- 集成的包管理——Yeoman让你能够经过命令行(例如,yeoman搜索查询)轻松地查找新的包,安装并保持更新,而不须要你打开浏览器。
- 对ES6模块语法的支持——你可使用最新的ECMAScript 6模块语法来编写模块。这仍是一种实验性的特性,它会被转换成eS5,从而你能够在全部流行的浏览器中使用编写的代码。
- PhantomJS单元测试——你能够经过PhantomJS轻松地运行单元测试。当你建立新的应用程序的时候,它还会为你自动建立测试内容的骨架。
yeoman是基于node.js,首先得安装node.js,而后在命令行中安装yeoman
npm install -g yo grunt-cli bower
mac或linux下npm安装到全局须要root权限
yo用于构建项目的手足架。
刚安装完的yeoman(1.0版本)中是不含模版的,我先去找了个模版generator-backbone
npm install -g generator-backbone
而后使用该模版开始构建
mkdir myapps cd myapps yo bakcbone myapps
命令行提示是否使用twitter和coffeejs,该项目中都用不到,因此都不选
此时myapps的目录结构以下
但该架构并不彻底知足个人需求,因而我进行了手动修改,修改后结构以下
yo的使用基本结束。
以后本文提到的路径都是myapps
的相对路径
bower用于载入网页须要的组件
因为目录变动,咱们须要修改bower的路径,bower的路径在.bowerrrc
文件中,将其内容改成
{ "directory": "app/public/bower_components" }
bower.json
文件是用来描述须要载入的组件
grunt用于自动化,例如编译scss文件,编译coffe文件,部署等。
由于myapps的目录修改过,因此Gruntfile.js
中的相关路径也得修改。
后面的开发中会大量用到grunt,此处先提一下grunt的一些我的经验
yeoman预置的Gruntfile.js很聪明,已设置为从package.json
读取grunt插件并注册,只需修改此文件并执行 npm install
,便可注册grunt的插件。
在grunt.initConfig()
中咱们能够配置各类子任务,例如
grunt.initConfig({ copy: { dist: { files: [{ expand: true, dot: true, cwd: '<%= yeoman.app %>', dest: '<%= yeoman.dist %>', src: [ '*.{ico,txt}', '.htaccess', 'images/{,*/}*.{webp,gif}' ] }] } } })
而后经过grunt.registerTask()
来注册任务,例如
grunt.registerTask('default', [ 'copy:dist', //执行copy下的dist子任务, 'copy' //执行copy下全部任务,执行顺序为定义时的顺序 ])
Foundation 4是一个CSS框架,有CSS版本和SCSSS版本,均可以在官网得到。
直接上个人html代码先
<div class="row"> <div class="small-12 large-9 columns"> <h1> 前端工做流程 </h1> <hr/> <article></article> </div> <button class="hide-for-medium hide-for-large nav-btn"></button> <aside class="hide-for-small small-6 large-3 columns" data-magellan-expedition="fixed"></aside> </div>
这里解释一下Foundation的栅栏表格。
columns
表示行,row
表示列.columns
元素必须得是.row
的后继元素
默认版本的Foundation中,把行分红12份,使用以下:
small-1
表示该列宽度占最近.row
祖先元素的1/12small-3
表示该列宽度占最近.row
祖先元素的3/12small-3 large-4
表示该列宽度在小屏幕中占3/12,在大屏幕中占4/12small
,large
表示屏幕大小,可在_global.scss
中找到定义,能够在该文件中搜索关键字$small-screen
Foundation的css使用就解释到这,更多请查看API
而后来看看Foundation的js插件部分,foundation依赖于zepto或jQuery,引入其余插件前须要先引入foundation.js
若使用zepto请注意zepto是可定制的,foundation使用的是他本身定制的zepto,因此请使用foundation/js/vendor/zepto.js
鉴于SCSS的方便,我采用了SCSS版本。这就带来了个问题,每次都要编译才能被浏览器解释。grunt这时候就起做用了。
首先说一下我SCSS的配置
app/public/bower_components/foundation
app/resource/scss
中app/public/style/scss
中app/public/style/main.css
经过@import
引入,html中只导入该css而后就是grunt的配置了
gem install compass
package.json
中已含有grunt-contrib-compass
插件,该插件能够对SCSS进行编译而后能够开始配置Gruntfile.js
:
... grunt.initConfig({ ... //实时监控 watch: { ... compass: { files: ['<%= yeoman.app %>/resource/scss/{,*/}*.{scss,sass}'],//监控的文件路径 tasks: ['compass']//若监控文件改动,执行的任务 }, ... }, //compass配置 comapss: { //全局配置 options: { sassDir: '<%= yeoman.app %>/resource/scss', cssDir: 'app/public/styles/scss', imagesDir: '<%= yeoman.app %>/public/images', javascriptsDir: '<%= yeoman.app %>/public//scripts', fontsDir: '<%= yeoman.app %>/public/styles/fonts', //此处配置SCSS的引入源路径,我添加了foundation的SCSS路径和animate的SCSS路径 importPath: [ '<%= yeoman.app %>/public/bower_components', '<%= yeoman.app %>/public/bower_components/foundation/scss', '<%= yeoman.app %>/public/bower_components/animate.scss/source' ], relativeAssets: true }, //局部配置,覆盖同名项 dist: {}, server: { options: { debugInfo: true } } }, ... }); //往server任务中插入compass子任务,执行该任务命令为grunt server grunt.registerTask('server', [ ...//其余任务, 'compass:server', ...//其余任务 ]) ...
在命令行中执行grunt server,便可实现SCSS的实施编译
在HTML和CSS框架部分咱们已经完成了HTML框架的构建。
下面咱们开始使用HTML模版来编写网页须要模版。
HTML模版引擎的好处这里就不说了,在很是多的模版引擎中我选择了Handlebars,仅仅是由于我喜欢这个模版。
模版的编写就不说了,这部分主要仍是描述如何模版文件的放置结构和实现模版的预编译。
首先来看看目录结构
app/resource/templates
中, 后缀名是.hbs
app/resource/templates/helpers
中.hbs
这些文件编译生成的文件我放置在.tmp/templates
这临时文件夹中.tmp/templates
中的文件和helpers链接在一块儿,生成.tmp/template.js
下面来看看预编译
首先,为何要预编译?
模版文件并不能直接被js执行,须要经Handlebars解释成一个函数后才能被执行,也就是说浏览器得先执行模版解释,这对性能并很差的移动设备来讲无疑带来了性能损耗。因此更好的作法是在服务器段就把这些模版文件预编译成可执行的函数。
那该怎么实现预编译呢?
grunt这时候又来了。
预编译Handlebars须要grunt-contrib-handlebars
这个插件,载入方式仍是在package.json
中添加该项,而后npm install
那接下来看看这插件的配置(grunt.initConfig()中的配置
)
handlebars: { compile: { options: { //函数所在命名空间 namespace: "Handlebars.templates", //函数名称格式 processName: function (filename) { return filename .replace(/app\/resource\/templates\//, '') .replace(/\.hbs$/, ''); } }, files: { //'生成文件':[须要编译的模版文件] '.tmp/templates/template.js': ['<%= yeoman.app %>/resource/templates/*.hbs'] } } }
上述代码实现下列转换(文件所在路径都是app/resource/templates
)
把helpers也合并进来
这里用到grunt-contrib-concat
,注意文件链接顺序,应该是先链接helpers类文件,应为模版中可能会用到自定义的helper,只有先定义了helper,后面的模版函数才能正常执行
HTML引人预编译的文件
预编译带来的好处还有一个:咱们再也不须要引入完整的handlebars.js和.hbs
这类模版文件,只须要引入handlebars.runtime.js(和handlebars.js相比,没有模版解释函数)和预编译生成的文件.tmp/template.js
实时编译监控
咱们能够添加监控的文件和任务以实现.hbs
这类文件的实时编译,这样咱们只要直接修改模版文件,并不会由于预编译而给调试修改带来麻烦。
接下来让咱们看看该如何组织js文件。
之前咱们引入js文件的方式是:
<script src=""></script>
或者是用jQuery$.ajax()
但这些引入方式有一个致命缺陷就是文件依赖问题,咱们更但愿看到的是像java同样能够import
,因而一部分前端的前辈们就致力于解决模块化问题。
seajs(CMD规范)和requirejs(AMD规范)就是解决模块化问题比较好的两个库。
两者的详细对比我就不说了,你们能够看看SeaJS与RequireJS最大的区别。
虽然模块化的确颇有用,但无能否认目前大多数的库仍是传统的写法(外国也有一部分库已支持AMD),也就是说须要这些s库进行人工CMD化或AMD化。国际上的一些比较著名的库我还没找到原生支持CMD,都须要人工CMD化。
而我此次项目选择的是seajs,缘由是两者在构建上都没有成熟的方案,seajs的生态圈更本地化(做者是玉伯大大),相比一堆英文的交流我更愿意用中文交流,也算是为国内前端生态圈尽一份力吧。
我在这讲的主要是本身在使用seajs过程当中遇到的问题和解决方案,想查看API者请移驾https://github.com/seajs/seajs/issues/266,若是玉伯大大看到这篇文章,容小弟稍微抱怨下用github做参考文档真心不太容易用。。。资料找起来仍是挺费劲的。
先规范一下scripts目录结构
如下描述的相对路径是app/public/scripts
sea-modules
放置的是经过spm(下面会提到)加载的库example
example/logic
放置的是本身编写的逻辑模块example/utils
放置的是CMD化后的开源js库example/templates
放置的是CMD化后的模版文件(例如前文提到的app目录下的.tmp/templates.js
,.tmp下的文件还未CMD化)example/static
放置的是已经CMD化的静态资源文件注意seajs的路径解释规则,请查看模块标志
这里注意paths
中定义的路径就是其直接路径,并不会互相解释,例如
seajs.config({ //base默认为'app/public/scripts/sea-modules' paths:{ scripts: '/public/scripts', logic: 'scripts/logic' //此处并不会被解释为'/public/scripts/logic',而是'app/scripts/sea-modules/logic' } });
此外paths的配置还应注意不该以'/'结尾(仅限2.1.1版本),详情请见https://github.com/seajs/seajs/issues/926
这个问题是由于使用seajs还应注意一个初学者不太能发现的ID 和路径匹配原则
seajs2.0开始推荐将全部js封装成CMD模块.
对于非CMD的js咱们须要对其进行封装,我说一下封装foundation.js这个文件(依赖dom选择器)
define(function(require, exports, module){ var $ = require('$');//写入依赖,须要再seajs配置中配置alias的$ ...//源代码 return Foundation; })
而对于相似于模板预编译成的文件,我建议使用grunt对其进行封装,grunt-contrib-concat
支持对文件进行包装,这里不做描述
通过上面的步骤,网站的组织已经比较清晰,再编辑完网页全部功能后,进入下一步----部署.
yeoman配置的Gruntfile.js
并不能适应全部自动化,咱们须要对其进行适当修改.
先来看看个人grunt build
执行了哪些任务
grunt.registerTask('build', [ 'clean:dist',//清空临时目录 'compass:dist',//编译SCSS 'markdown',//将markdown转换为handlebars模板格式 'handlebars:markdown',//预编译handlebars 'handlebars:compile',//预编译handlebars 'concat:template',//链接handlebars预编译后的文件以及助手 'wrap:template',//对链接后的文件进行CMD封装 'transport:seajs',//获取CMD模块ID 'concat:seajs',//链接CMD模块 'uglify:seajs',//压缩CMD模块链接而成的文件 'useminPrepare', 'imagemin',//压缩图像 'htmlmin',//压缩html文件 'cssmin',//压缩css 'copy',//将没处理过而网站又须要的文件拷贝过来 'rev',//为css和js添加版本号 'usemin' ]);
usemin
的做用请看官方说明
Replaces references to non-optimized scripts or stylesheets into a set of HTML files (or any templates/views).
屌丝翻译:替换HTML文件(或任意模板文件)的引用
举个例子,main.css
经处理后变成main-rev.css
,usemin就是把html中引用到main.css
的地方都改成main-rev.css
这里我只讲述文档比较少的transport和concat,这两个任务分别用到了grunt-cmd-transport
和grunt-cmd-concat
对于为分配ID的CMD模块,咱们首先使用grunt-cmd-transport
抽取其ID,先上代码
transport: { seajs: { options: { alias: { underscore: 'underscore', backbone: 'backbone', $: '$', modernizr: 'modernizr' }, paths: [ 'app/public/scripts/example/static', 'app/public/scripts/sea-modules', '.build' ] }, files: [ { cwd: 'app/public/scripts/example', src: [ 'static/{,*/,*/*/}*.js', 'templates/*.js', 'utils/*.js', 'logic/*.js' ], dest: '.build' } ] } }
解释一下options
alias
能够指向一个文件也能够指向自定义的别名,alias中的键值对(假设为key:value
)的表示须要处理的文件中若含有require('key')
则用require('value')
来代替
paths
是一个数组,表示须要用到的模块路径,例如处理的文件中须要require('logic')
,而logic模块并不在默认路径中,则须要在paths
中添加logic模块所在的路径(注意,此处应把file.dest
指向的路径也添加进去)
idleading
咱们能够用这属性给全部抽取的ID添加一个前缀
而后是抽取
file.src
这数组的顺序相同file.dest
指向的路径下模块ID的命名方式
ID名字 = options.idleading + 文件相对CWD路径
例如
假设无idleading,cwd为'app/build/scripts',dest为'.build',文件相对路径为'logic/a'则ID名字是'logic/a',抽取出来的目录结构是
.build |--logic |--a.js
模块抽取完后就要开始进行链接.仍是先上代码
concat: { seajs: { options: { relative: true, include: 'all', paths: [ 'app/public/scripts/sea-modules', 'app/public/scripts/example/static', '.build' ] }, files: { 'dist/public/scripts/main.js': ['.build/{,*/,*/*/}*.js'] } } }
options的paths
解释和transport的options.paths
同样
这是我本身应用的一个策略,也就是尽可能保证一步部署,再也不对部署后的文件上进行手工修改
usemin并不会对seajs的引用进行修改,为了实现部署优先,咱们在index.html应采用下面方式
<!-- 引入seajs --> <script src="public/scripts/sea-modules/seajs/seajs/2.1.1/sea.js"></script> <!-- 引入seajs配置文件 --> <script src="public/scripts/config.js"></script> <!-- 引入seajs合并后的文件, 路径为合并后的文件相对路径 --> <script src="public/scripts/main.js"></script> <!-- 调用启动模块 --> <script> seajs.use(['modernizr','logic/load-markdown']); </script>
解释一下引入合并后的文件的缘由:
解释一下部署后的调用启动模块
当引入合并后的文件时,会对一系列CMD模块进行define,define过程应该是这样的:
若define()的参数中含有ID,则先对ID进行路径解释,把解释后的路径添加到seajs.cache
中
seajs.cache
中是否含有解释后的路径,如有,则直接调用该路径对应的模块,若无则想服务器获取该模块seajs.use
和require
时得到相同的解释路径我选择的测试工具是CasperJS.
基于PhantomJS, 也就是说CasperJS能够执行一系列浏览器上的动做, 应该就是所谓的UI测试吧,而这正是web最须要保证质量的事.
建议使用CasperJS 1.1版本(目前最新版本)
使用请参看API
对于刚迈入大四的我来讲,也就是一两个月前接触过测试这一律念,而ui测试通俗点说就是: 点击了按钮a, 页面发生了响应. 而测试就是判断这响应是否符合测试设定的标准, 不管符不符合, 都返回测试结果.
原本想描述本身的建网页流程,感受写着写着有点变成流水帐了…还丢了一坨代码上来…