seajs+spm之再研究
好久没有用seajs了,之前对spm也只是一知半解,这些天再次拿起来研究.谈谈我的认识与理解.
声明:本文不适合对seajs完全不了解的同学阅读.对于想知道seajs来龙去脉以及spm相关的同学"可能"有帮助.对于我自己也是个梳理的机会.
一.seajs部分
1.seajs由来:
传统web前端的js开发,主要基于script标签的引入,一个文件一个script标签,或者对他们进行简单的压缩与合并,以减少http请求.
没错,我们以前都是这么干的,甚至现在还有很多人这么干.
随着这些年的发展,前端越来越被重视,逻辑越来越复杂,前端代码的维护变得越来越难.
2009年,nodejs诞生(nodejs是什么东西,可以自行google),其模块化的编程思想,给攻城师牛人们(包括但不限于前端攻城师)带来了很大的启发.特别是它的模块化依赖管理系统.工程师们想把这个依赖管理机制移植到浏览器端.但是浏览器端目前还没有高度统一的规范,更别说依赖管理机制了.于是工程师们绞尽脑汁,日思夜想.
不久后,requireJS诞生了,也的确火了,但它所坚持的原则规范并不被前面那些的大部分工程师们接受.
这时,我国的一位前端大牛也逐渐参与到模块化开发的研究中来.经过不断的虚心学习,借鉴,摸索,seaJS诞生了.他的作者是玉伯(--仰视,尊敬).
以上历史介绍,只是我简单的整理与理解.详细的可以参考玉伯本人的描述https://github.com/seajs/seajs/issues/588
2.seajs是什么,能做什么
它是模块加载器(也即js文件加载器),所有模块都可以通过它加载,而且很重要的是,它可以帮你很好的管理模块依赖.当你的页面上有十几二十个js文件的时候,用了seajs,你就会知道它有多么美妙.
3.怎么用
这里只讲个大概,教程随便google就一打.
所有的模块几乎(下面会讲为什么不是全部)都被define(function(require,exports,module){})这个全局函数包围,require参数用于表明被依赖模块,exports和module用于输出接口,让别的模块可以调用.对于开发者,按照它的规范写代码,就能很好的管理依赖了.
现在来讲,上面那个"几乎".其实也是后来看aralejs(aralejs是基于seajs的组件库,其开放程度可以用包罗万象来形容,具体可自行google)上的模块才发现的(只是个别模块这样做).
对于我们后来写成的模块,seajs官网是推荐上面那种方式的,而且也没有什么问题.之前随着requireJS的推出,一些开源的类库或者框架(比如jquery,backbone等)都纷纷加入了requireJS的行列,后续的新版本默认支持AMD模块规范.这些开源代码为了兼容页面上有无requireJS的情况,并没有将所有代码都包在define函数中,而是判断页面中是否定义了define函数进行模块定义,类似于下面这样
1 if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
2
3 define( "jquery", [], function () { return jQuery; } );
4
5 }
而arale或者说seajs官方的做法是,可能为了兼容AMD,将其改成了
1 if ( typeof define === "function" ) {
2
3 define("jquery/jquery/1.7.2/jquery-debug", [], function () { return jQuery; } );
4
5 }
可能有其他更特殊的原因吧,还有json的模块更简单,直接在最后追加
1 define("gallery/json/1.0.3/json-debug", [], function() {
2
3 return window.JSON;
4
5 });
而underscore和bockbone就是按照"标准",在最外层包上define.不是特别清楚为什么有这么些个做法.可以再看看其他被包装过的模块的都是什么样子的.
4.seajs的原理
这个可以参考我之前的文章(差不多一年前了囧,现在才来写这篇) : http://www.cnblogs.com/webstone/articles/3043119.html
5.引出下一节
其实说到这里,seajs以及它的模块都可以在浏览器里正常运行.那么为什么还要有spm呢? 且继续往下看
二.spm部分
1.spm由来:
各个模块开发测试完毕后,为了进一步提升页面性能,我们还要对代码进行压缩,合并操作.
于是问题产生了.
为什么压缩后,被依赖的模块不加载了?
为什么合并后的模块一个都不执行了?(新版本不执行任何操作,1.3.1及以前会只执行第一个模块)
对于第一个问题,这涉及到seajs模块依赖处理的原理,对于第一节中谈到的模块组织方式define(factory),seajs寻找依赖是依靠正则判断factory.toString()中的require关键字,如下代码片段
1 var REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g;
2
3 ...
4
5 function parseDependencies(code) {
6
7 var ret = []
8
9
10
11 code.replace(SLASH_RE, "")
12
13 .replace(REQUIRE_RE, function(m, m1, m2) {
14
15 if (m2) {
16
17 ret.push(m2)
18
19 }
20
21 })
22
23
24
25 return ret
26
27 }
28
29 …
30
31 // Parse dependencies according to the module factory code
32
33 if (!isArray(deps) && isFunction(factory)) {
34
35 deps = parseDependencies(factory.toString())
36
37 }
而代码经过压缩后,require就不见了,自然就找不到依赖了.还有就是,这种依赖分析很消耗性能.
第二个问题,涉及到模块标识问题.其实上述的模块组织方式称为"匿名模块".多个没有名字的模块合并到一起后,各自都返回了输出接口,那么对于这个"大模块",应该返回哪一个接口呢?所以理想情况下每个模块都需要一个模块标识,这样就可以任何合并了.
其实seajs支持两种规范:
第一种就是第一节中的那种,叫做CMD规范,即define(factory)只传入一个callback.
第二种是给define传入三个参数define(id, deps, factory),id为模块标识,reps为依赖模块数组,第三个factory同上面那个factory.
这种模块属于 Modules/Transport 规范 ,参考https://github.com/seajs/seajs/issues/242
第二种方式下,seajs的工作原理略有不同.它不再对factory进行正则依赖分析,直接加载deps中的依赖模块(数组中的字符串即模块标识,要与被依赖模块的id相匹配,或者与seajs.config别名中的映射后相匹配),这样一来,依赖处理的速度更快了,同时所有模块均可以任意压缩合并.
另外,路径与模块标识的问题可以参考这篇文章: https://github.com/seajs/seajs/issues/930
所以呢,为了使CMD模块(也即匿名模块),更加便于管理,包括模块的压缩,合并,打包,部署,甚至公用下载安装,spm就应运而生了.
2.spm是什么,能做什么
spm是CMD模块管理器,就像npm,其实spm本身就是nodejs的一个模块,负责CMD模块的打包发布删除等操作.当然对外输出的模块已经不是CMD模块了,是 Modules/Transport 模块.
接着要说的是spm的源,就像npm有个代码集中管理的地方,甚至svn的代码仓库也有些类似.
当然,这个源可以是官方默认的https://spmjs.org/ ,也可以是私有源,搭建方法参考:https://github.com/spmjs/spm-yuan,然后需要通过修改配置文件中的soucre值来将spm的源地址改为私有源(默认为官方)
然后我们就可以开发我们的模块了.个人认为,最终生成的模块不一定要发布到源上.具有公共特性的组件类的模块才需要放到源上,业务模块放到项目中就好.只是build出来的id会看起来比较怪异,因为必须要与加载的路径一致.
3.spm的主要功能
nodejs和spm及其插件的安装这里就不说了,也是google一大把
我们从使用的角度,按开发顺序走下来
A.如果是开发一个公用组件,将来是要上传到spm源的模块,可以这么干(这种情况,除了源配置,我们暂时使用默认的配置信息):
1)spm init
打开终端,进入你将要开发组件的那个目录,执行
spm init
然后,spm为你生成一个基本目录结构,像下面这样
{{newModuleName}}/ //这一层是你执行spm init前创建的 src/ {{newModuleName}}.js {{newModuleName}}.css examples/ index.md tests/ {{newModuleName}}-spec.js dist/ //这一层现在不会有,后面build完会出现,输出的文件在这一层下 README.md HISTORY.md package.json
然后你就可以在src下的js和css里开发组件了,这两个文件初始化了一小部分代码,在src下你可以自由创建文件夹与其他js,css甚至模板文件等,用于被依赖
2)spm build
组件开发完成后,在终端下,同样是在上一步的目录下,执行
spm build
然后,就生成了上一步看到的dist目录,下面有打包好的具名模块,其中如果有依赖,默认是会被打包进来的
3)接着开始测试模块
官网说,spm test以及spm totoro都是支付宝内部才能使用的插件,不推荐外部使用,我还没有深入研究过.
所以呢,我们只能暂时自己创建测试环境,不过也不太麻烦啦
4)spm publish
测试通过后,我们就可以把它发布到源上了,就像nodejs模块可以通过npm发布一样.
在终端,同样的目录下执行
spm login
按照提示,登陆你的源账号,如果没有账号则按照提示创建账号登陆
接着执行
spm publish
如果没有意外,就可以发布成功了
打开浏览器,访问你的源地址,就能看到你刚刚发布的模块了
其他同学就可以通过spm install {{family}}/{{moduleName}} 安装(即下载)你发布的模块了
B.如果是开发业务模块,按照我个人的观点,业务模块不需要被发布到源上,或者说不需要和公用组件放在一块
这里我想起一个误区,而且我之前就一直是这么认为的:
"源上的模块是可以被http访问到的"
走出误区:简单讲,源只是一个存储模块文件的地方,不提供模块的web服务.于是就有了"部署"这件事情.但是spm deploy也是支付宝内部插件,我也没深入研究.
通常大型项目,静态资源文件与应用服务是分离的,也即模块文件是存放在另一台服务器上的.
但是小型项目,放在一起也基本能够满足需求.
但好像没有看到有人将公用模块与业务模块分离的.经过个人简单试验,这样是行的通的,并打算应用在项目中.
之前一直在探索如何使用spm构建业务模块,但总找不到合适的资料,尤其是它要求的目录结构以及生成的id让我束手无策.后来看到有大牛使用grunt-cmd-transport构建模块,自己跟着试了一下,的确可行.但似乎有些问题,比如:依赖的css没有被打包进来.在咨询了官方的人员后,并没有给出我这些问题的答案,而是建议我用spm,并给出了一些方案.我又重新拿起spm开始尝试.终于研究出了可行的方案,相对公用组件的开发方式,只需要修改package.json中的一些配置即可,而grunt的配置实在太多,看的人眼花.再次感谢支付宝贯高(github @popomore)给我的建议
由于需要比较多的笔墨,我将在下一篇讲述如何使用spm构建业务模块.
seajs+spm之再研究的更多相关文章
- slice方法可以将“类似数组的对象”变成真正的数组 (遇到时候再研究一次)
典型的“类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串. // arguments对象 function args() { return arguments } ...
- CSS3系列之3D制作 再研究
水平翻转效果: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <t ...
- switch留个爪,之后还需要再研究下
public class SwitchDemo { public static void main (String [] args) { for(int i = 0; i < 10; i++) ...
- dns服务 很多问题,后续再研究
慕课网:http://www.imooc.com/video/5220 参考:http://jingyan.baidu.com/article/870c6fc32c028eb03fe4be30.htm ...
- 使用SeaJS实现模块化JavaScript开发
前言 SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制.与jQuery等JavaScript框架不同,SeaJS不会扩展封 ...
- 转: javascript模块加载框架seajs详解
javascript模块加载框架seajs详解 SeaJS是一个遵循commonJS规范的javascript模块加载框架,可以实现javascript的模块化开发和模块化加载(模块可按需加载或全部加 ...
- javascript模块加载框架seajs详解
SeaJS是一个遵循commonJS规范的javascript模块加载框架,可以实现javascript的模块化开发和模块化加载(模块可按需加载或全部加载).SeaJS可以和jQuery完美集成,使用 ...
- SeaJS入门教程系列之使用SeaJS(二)
SeaJS入门教程系列之使用SeaJS(二) 作者: 字体:[增加 减小] 类型:转载 时间:2014-03-03我要评论 这篇文章主要介绍了SeaJS入门教程系列之使用SeaJS,着重介绍了SeaJ ...
- 改修jquery支持cmd规范的seajs
手动包装jquery1.10.2,firebug说$没有定义 define(function (require, exports, module) {//jquery源码module.exports= ...
随机推荐
- 上位机用USB做虚拟串口,总算抓到一个纯代码的总结了,没有坑的完美解决。
用libUSB来实现自己的驱动+下位机理论速度.=1M字节每秒. 达到极限速度 WINDOWS已经自带虚拟串口驱动,只不过还需要一个Inf文件 方法1:直接下载一个串口inf,来修改文件. 方 ...
- Android应用程序模拟手机按键
记得以前在做一个C++项目时,需要在某一步操作之后人为用代码模拟敲键盘上的回车键(Enter)效果. 出于好奇,这几天研究了一下Android中手机(或平板)上各种按键的键值.模拟方法及最终效果. 1 ...
- mac 80端口映射 配置
mac 80端口映射 配置 macbook 下,要绑定 80 端口的话. 一种方式是用 root 权限启动,即 sudo 启动服务进程.但 sudo 指令存在一定的安全问题,能不使用的情况下我们都尽量 ...
- 10.C#匿名函数的变量捕获(五章5.5)
首先感谢园友的指定,后续的文章一定会多码多想,出来的文章才有说服力.那今天接上篇我们来聊一聊匿名函数,对于匿名函数,我们知道使用delegate关键字,那我们来需要知道匿名函数在变量是的处理方式,先说 ...
- Javascript基础系列之(三)数据类型 (数值 Number)
javascript中想限定一个数的数值,无需限定它是整数还是浮点数型 var num1 = 80 ; var num2 = 55.51; var num3 = -34; var num4 = 9e5 ...
- Entity Framework with nolock. 允许脏读
public static List<T> ToListReadUncommitted<T>(this IQueryable<T> query) { using ( ...
- java.lang.NoClassDefFoundError: com/sun/mail/util/LineInputStreamsJavamail问题
异常描述如下: Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/mail/util/LineI ...
- javaEE规范和SSH三大框架到底有什么关系
转自博客:http://blog.csdn.net/bingjing12345/article/details/20641891 1994-2000 年是互联网的大航海时代. 请注意,下面的时间点及其 ...
- meta之renderer
今天不小心看了下慕课网首页的源码,看到有一行 1 <meta name="renderer" content="webkit|ie-comp|ie-stand&qu ...
- 【Gym 100971A】Treasure Island
题意 题目链接给你一个地图,'#'代表水,'.'代表陆地,'?'代表擦去的地图,可能是'#'也可能是'.'.地图中本该只有一块相连的陆地,若只有一种方案则输出确定的地图.若有多种方案,则输出‘Ambi ...