原文:http://www.cnblogs.com/accordion/p/4508154.html

grunt与seajs

grunt是前端流行的自定义任务的脚手架工具,我们可以使用grunt来为我们做一些重复度很高的事情,如压缩,合并,js语法检查等。通过定义grunt的配置文件Gruntfile.js,配置并注册grunt的任务,最终我们可以通过命令行来执行任务。

seajs主要用于模块化,通过define定义一个模块,可以通过require加载模块,exports导出模块。具体的seajs实现可通过本博客的系列博文--Seajs源码解析系列来进一步了解。

在实际生产中,如果紧紧定义一系列seajs模块而并不进行合并压缩的话,加载性能很低,原因大家都懂的,seajs在浏览器端处理依赖模块,并进行异步加载,这个过程中会有多个http请求,大大降低页面的加载速度。所以结合grunt构建工具,我们可以将模块的依赖处理放到服务端进行,并将所有模块合并压缩,完成生产所需的最终文件。

在seajs社区中,已经提供了一款npm模块,即grunt-cmd-transport。我们通过该模块给seajs模块命名,并处理各模块之间的依赖。这项工作听起来很简单,但是在笔者的实践过程中出现的问题却不少,因此本文着重讲解transport任务的相关配置。

grunt的相关文件

grunt相关文件包括了2个,首先是Gruntfile.js,另一个是package.json文件。Gruntfile进行grunt任务的配置及注册,package.json用于向Gruntfile提供参数,并设置依赖的npm模块。

在下面package.json中,定义spm键,设置模块的别名,在Gruntfile中,通过pkg = grunt.file.readJSON()来读取package配置文件,并通过<%= pkg.spm.alias %>获取模块别名。

package.json
{
"name" : "HelloSeaJS",
"version" : "1.0.0",
"author" : "yang li",
"spm": {
"alias": {
"jquery": "jquery"
}
},
"devDependencies" : {
"grunt" : "0.4.1",
"grunt-cmd-transport": "~0.2.0",
"grunt-cmd-concat": "~0.2.0",
"grunt-contrib-uglify" : "0.2.0",
"grunt-contrib-clean" : "0.4.0"
}
}

接下来我们进行设置grunt。Gruntfile.js其实就是一个node模块,依然使用闭包将所有的逻辑进行包裹,并提供了grunt参数,通过grunt.initConfig进行任务的配置。

对于seajs模块而言,首先需要处理各模块之间的依赖,我们通过设置transport任务来完成。seajs遵循的是CMD规范,在定义模块时不需要制定模块名和模块的依赖组,只需设置工厂函数即可。其实在未使用grunt进行合并seajs时(即在浏览器端处理模块依赖),seajs设置模块id和uri相同,为绝对路径。在这个过程中有些小技巧,在Seajs源码解析系列中并未提到,现在在这里着重分析下:

<script src="../sea-debug.js"></script>
<script>
seajs.config({
base: "../gallery/",
alias:{
jquery: 'jquery/jquery-1.11.1'
}
})
seajs.use("../application.js")
</script>

对于上述代码,application.js并没有合并seajs模块,我们通过seajs.use创建了一个匿名use模块,通过

var mod = Module.get(uri, isArray(ids) ? ids : [ids])

来实现,并设置依赖。在此处,依赖为[‘../application.js’];然后设置use模块的callback,并调用load函数加载依赖模块。在load函数中,use模块调用resolve函数解析出依赖的绝对路径,即[‘http://localhost:63342/mywork/js/application.js’],并创建一个新的Module表示该模块,这里用appMod表示,并以uri为key保存到modCache中。调用appMod.fetch加载对应的文件 并设置回调函数onRequest ,在application.js中定义了一个匿名模块define(function(){return {};}),此时模块的配置信息

meta = { 
id: id, 
uri: Module.resolve(id), // 绝对url 
deps: deps, 
factory: factory 
}

中id=‘undefined’,url=’’,

meta.uri ? Module.save(meta.uri, meta) : 
// Save information for "saving" work in the script onload event 
anonymousMeta = meta

由于此时meta.uri为空,因此meta信息保存在全局变量anonymousMeta中,用于后续处理。

红色字体强调了在调用fetch时设置的回调onRequest函数,当文件加载完毕,执行onRequest,

function onRequest() { 
delete fetchingList[requestUri] 
fetchedList[requestUri] = true

// Save meta data of anonymous module 
if (anonymousMeta) { 
Module.save(uri, anonymousMeta) 
anonymousMeta = null 
}

// Call callbacks 
var m, mods = callbackList[requestUri] 
delete callbackList[requestUri] 
while ((m = mods.shift())) m.load() 
}

此时anonymousMeta已在模块define时设置,因此将该meta配置文件配置到uri对应的Module对象上,

Module.save = function(uri, meta) { 
var mod = Module.get(uri) 
// Do NOT override already saved modules 
if (mod.status < STATUS.SAVED) { 
mod.id = meta.id || uri 
mod.dependencies = meta.deps || [] 
mod.factory = meta.factory 
mod.status = STATUS.SAVED 

}

由于meta.id=undefined,因此最终mod.id=uri。

对于通过define(id,deps,function(){})设置了id的具名模块,是根据id生成uri。在meta中,通过Module.resolve(id)完成,

Module.resolve = function(id, refUri) {
if (!id) return "" id = parseAlias(id)
id = parsePaths(id)
id = parseVars(id)
id = normalize(id) var uri = addBase(id, refUri)
uri = parseMap(uri) return uri
}

通过对id的一系列设置(别名解析,路径修正,变量解析以及添加扩展名,最终添加协议等)生成uri。

transport任务

transport任务是打包seajs模块的难点,上节提到了seajs模块的id和uri之间的关系,它们是由seajs来维护的。但是如果通过grunt对seajs进行打包,则模块之间的关系由transport来维护。通过transport生产的seajs模块,有一个显著的变化,即匿名模块变为了具名模块,并且设置了依赖模块。

下面通过配置项来讲解transport任务:

grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
transport : {
options : {
path: ['.'],
alias: '<%= pkg.spm.alias %>' // 注意在package.json中jquery的alias设置
},
utils: { // 存放引用的模块,设定模块名和依赖,模块的idleading要与在application中引用的路径一致
options: {
idleading: '../dist/src/'
},
files: [{
expand: true,
cwd: 'lib/src',
src: '*',
filter: 'isFile',
dest: '.build/lib/src'
}]
},
application : {
options: {
idleading: '../dist/'
},
files: [
{
expand: true,
cwd : 'lib',
src : 'application.js',
filter : 'isFile',
dest : '.build/lib'
}
]
}
}
}

在options中,设定了路径为‘.’,即相对于Gruntfile文件的当前路径,alias为package.json中定义的alias;在utils任务中,设置了idleading选项,最终 模块的id = idleading + 文件名 。值得注意的是idleading路径的设置,这里需要小心设置, 它是根据引用最终打包后文件的html的位置决定的 。最后,将lib/src下的所有文件设置完id和依赖后放到.build/lib/src下。application任务和utils任务类似,只是单独设置application.js文件的id和依赖。

着重讲解idleading的设置。我们计划将生成的文件(处理完依赖且合并压缩后的文件)放到dist文件夹下面,最终通过view/hello.html引用,

设置transport:util任务的idleading = ‘../dist/src/’,文件经过transport之后,lib/src/name.js文件会被设置并且保存到.build/lib/src中,此时name.js的模块名为’../dist/src/name’,依赖为[]。同理,lib/application.js保存到.build/lib中,并且模块名为’../dist/application’,依赖为[‘./src/util’,’jquery’,’./src/test’,’./src/name’]。然后经过合并压缩之后,生产最终的application.js文件,在view/hello.html中引用(开篇提到)。

在hello.html引用的application文件包含了5个模块,并且每个模块都有id和依赖,因此根据上节具名模块的id与uri的关系,可知道模块id影响到文件的加载。之所以在设置idleading = “../dist/”是根据hello.html的位置决定的。在Module.resolve(id)中,有一步骤为addBase,即有当前相对路径转换为绝对路径,而当前路径是相对于html的位置定义的,具体原因是html引入了seajs,seajs判断当前html的位置,设置为当前路径。这也正是idleading的设置为”../dist/”的原因。

下图可以印证了上文所述:

当然如果html的路径有变化,相应的idleading也要改变:

如果在view/layout/hello.html中引用文件,那么需要改变transport:util的idleading = ‘../../dist/lib/src’,transport:application的idleading = ‘../../dist/lib’,只有保证每个模块的uri正确,才能fetch文件并且执行相应的回调函数。

因此,对于transport任务而言,idleading的设置需要十分注意。

concat、uglify、clean任务

这两个任务很容易定义,而且 grunt官网 上就是以uglify为例讲解Gruntfile的配置,因此,这两个任务的配置我们有很多资料可以参考。我们使用通配符来匹配文件,使用expand来批量处理,也可以自定义过滤函数。废话不多说,呈上这两个任务的配置:

    concat : {
options : {
separator: '/*-------每个文件的分割-------*/',
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %> */',
footer: '/*-------合并文件的footer-------*/'
},
utils: {
src: ['.build/lib/src/*.js','!.build/lib/src/*-debug.js'],
dest: '.build/util.js'
},
application : {
src: ['.build/lib/application.js','.build/util.js'],
dest: 'dist/application.js'
}
},
uglify : {
main : {
files : {
'dist/application.min.js' : ['dist/application.js'] //对dist/application.js进行压缩,之后存入dist/application.js文件
}
}
},
clean : {
build : ['.build'] //清除.build文件
}

最终的Gruntfile文件定义如下:

module.exports = function(grunt){
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
transport : {
options : {
alias: '<%= pkg.spm.alias %>' // 注意在package.json中jquery的alias设置
},
utils: { // 存放引用的模块,设定模块名和依赖,模块的idleading要与在application中引用的路径一致
options: {
idleading: '../../dist/src/'
},
files: [{
expand: true,
cwd: 'lib/src',
src: '*',
filter: 'isFile',
dest: '.build/lib/src'
}]
},
application : {
options: {
idleading: '../../dist/'
},
files: [
{
expand: true,
cwd : 'lib',
src : 'application.js',
filter : 'isFile',
dest : '.build/lib'
}
]
}
},
concat : {
options : {
separator: '/*-------每个文件的分割-------*/',
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %> */',
footer: '/*-------合并文件的footer-------*/'
},
utils: {
src: ['.build/lib/src/*.js','!.build/lib/src/*-debug.js'],
dest: '.build/util.js'
},
application : {
src: ['.build/lib/application.js','.build/util.js'],
dest: 'dist/application.js'
}
},
uglify : {
main : {
files : {
'dist/application.min.js' : ['dist/application.js'] //对dist/application.js进行压缩,之后存入dist/application.js文件
}
}
},
clean : {
build : ['.build'] //清除.build文件
}
});
grunt.loadNpmTasks('grunt-cmd-transport');
grunt.loadNpmTasks('grunt-cmd-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-clean');
// 默认被执行的任务列表。
grunt.registerTask('build',['transport','concat','uglify'])
};

总结

通过对seajs的源码分析可以了解模块id与uri的关系,进而方便对grunt的transport调试。其实之所以用grunt对seajs模块进行打包会出现各种各样的问题,归根结底是路径错误。路径错误包括很多,比如模块依赖的路径错误,模块名错误,以及package.json的alias设置错误,最后,需要十分注意html文件的位置,因为seajs定义的cmd依赖于html文件当前位置。

Grunt打包之seajs项目【转】的更多相关文章

  1. Grunt打包seajs项目

    在使用seajs时,常常将若干脚本分为多次require进来,这样开发中比较方便,但是,会增加http请求次数,在生产环境中需要进行打包合并.压缩等操作. 以Grunt构建工具为例,对一个seajs项 ...

  2. 初学seaJs模块化开发,利用grunt打包,减少http请求

    原文地址:初学seaJs模块化开发,利用grunt打包,减少http请求 未压缩合并的演示地址:demo2 学习seaJs的模块化开发,适合对seajs基础有所了解的同学看,目录结构 js — —di ...

  3. grunt任务之seajs模块打包

    grunt与seajs grunt是前端流行的自定义任务的脚手架工具,我们可以使用grunt来为我们做一些重复度很高的事情,如压缩,合并,js语法检查等.通过定义grunt的配置文件Gruntfile ...

  4. 用spm2构建seajs项目的过程

    前言 Javascript模块化规范有CommonJs规范,和主要适用于浏览器环境的AMD规范,以及国内的CMD规范,它是SeaJs遵循的模块化规范.因为以前项目中用SeaJs做过前端的模块管理工具, ...

  5. 【grunt整合版】30分钟学会使用grunt打包前端代码

    grunt 是一套前端自动化工具,一个基于nodeJs的命令行工具,一般用于:① 压缩文件② 合并文件③ 简单语法检查 对于其他用法,我还不太清楚,我们这里简单介绍下grunt的压缩.合并文件,初学, ...

  6. 【grunt第三弹】grunt在前端实际项目中的应用

    前言 [grunt第二弹]30分钟学会使用grunt打包前端代码(02) [grunt第一弹]30分钟学会使用grunt打包前端代码 经过前两次的学习,我们了解了grunt打包的一些基础知识,对于压缩 ...

  7. 使用grunt打包ueditor源代码

    支持版本支持 UEditor 1.3.0+ 的版本 使用方法1.线上下载ueditor下载地址:ueditor,要下载"完整版 + 源码" 2.安装nodejs下载nodejs并安 ...

  8. 使用grunt打包前端代码

    grunt 是一套前端自动化工具,一个基于nodeJs的命令行工具,一般用于:① 压缩文件② 合并文件③ 简单语法检查 对于其他用法,我还不太清楚,我们这里简单介绍下grunt的压缩.合并文件,初学, ...

  9. 【grunt整合版】 30分钟学会使用grunt打包前端代码

    grunt 是一套前端自动化工具,一个基于nodeJs的命令行工具,一般用于:① 压缩文件② 合并文件③ 简单语法检查 对于其他用法,我还不太清楚,我们这里简单介绍下grunt的压缩.合并文件,初学, ...

随机推荐

  1. Spring配置中<context:annotation-config> VS <context:component-scan>

    Spring 中在使用注解(Annotation)会涉及到< context:annotation-config> 和 < context:component-scan>配置, ...

  2. spark与hive的集成

    一:介绍 1.在spark编译时支持hive 2.默认的db 当Spark在编译的时候给定了hive的支持参数,但是没有配置和hive的集成,此时默认使用hive自带的元数据管理:Derby数据库. ...

  3. 寻找bug并消灭系列——记录在Android开发所遇到的bug(一)

    之前使用了Android Studio的插件直接为button绑定了监听器,并实现onClick方法(我的onClick方法无论点击哪一个都是要实现setcontentview这个方法设置layout ...

  4. TCP/IP(八)之总结ICP/IP四层模型

    前言 在这里有一个问题,有的书上说TCP/IP是四层有的却说是五层.其实这个问题我也上网查了一下资料. tcp/ip是事实标准,分4层.osi模型是国际标准,分7层.讲课的时候,一般把他们综合起来讲, ...

  5. Bear and Floodlight 状态压缩DP啊

    Bear and Floodlight Time Limit: 4000MS   Memory Limit: 262144KB   64bit IO Format: %I64d & %I64u ...

  6. 点聚合功能---基于ARCGIS RUNTIME SDK FOR ANDROID

    一直不更新博客的原因,如果一定要找一个,那就是忙,或者说懒癌犯了. 基于ArcGIS RunTime SDK for Android的点聚合功能,本来是我之前做过的一个系统里面的一个小部分,今天抽出一 ...

  7. git fsck -- 一致性检查

    格式:           git fsck  [选项] <path> 选项 git commit -a 提交所有改动的文件(a -- all) git commit -m 提交说明(m ...

  8. PDO浅谈之php连接mysql

    一.首先我们先说一下什么是pdo?  百科上说 PDO扩展为PHP访问数据库定义了一个轻量级的.一致性的接口,它提供了一个数据访问抽象层,这样,无论使用什么数据库,都可以通过一致的函数执行查询和获取数 ...

  9. asp.net数据四舍五入

    #region 数据四舍五入 /// <summary> /// 四舍五入 /// </summary> /// <param name="dblnum&quo ...

  10. marked插件在线实时解析markdown的web小工具

    访问地址: https://mdrush.herokuapp.com/ github项目: https://github.com/qcer/MDRush 实现简介: 1.动态数据绑定 借助Vuejs, ...