用spm2构建seajs项目的过程
前言
Javascript模块化规范有CommonJs规范,和主要适用于浏览器环境的AMD规范,以及国内的CMD规范,它是SeaJs遵循的模块化规范。因为以前项目中用SeaJs做过前端的模块管理工具,所以这里总结一下自己的使用心得。
在试用SeaJs和官方推荐的CMD包管理工具——Spm2.x的过程中,遇到了很多高低版本不兼容和配置参数没弄明白的问题,后来在网上各处找资料才大概弄懂。这里我强调一下版本,是因为可能有的同学项目开始较早,用了以前版本的Seajs,再去看Seajs官网的API有些地方会不适用!下面各节对框架的描述中也都会带上版本。
文中可能会有一些理解有误或者没讲清楚的地方,有大神路过恳请指导...
一个简单的DEMO
Seajs可以实现前端Js文件的按需加载和前端模块管理的功能,不熟悉Seajs API (v2.3.0)的同学可以看看这里,非常简单。
首先上一个使用Seajs的demo。目录结构如下:
html:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="../js/lib/seajs1.2.0.js"></script>
</head>
<body>
<h1>spm前端构建测试</h1>
<script>
seajs.config({
base: '../js'
// paths:{'pathtest':'src'}, //生效版本:seajs2.3.0
});
seajs.use("src/eat.js",function(eat){
if(eat) eat.start();
});
</script>
</body>
</html>
eat.js
"use strict"
define(function(require,exports,module){
var rice=require("./rice"),
water=require("./water"),
i=require("./i");
exports.start=function(){
i.eat(rice.rice);
i.drink(water.water);
}
})
rice.js
"use strict"
define({rice:"i'm rice"})
i.js
"use strict"
define(function () {
return {
name:"tzyy",
eat:function(rice){
alert(rice+",Delicious!");
},
drink: function (water) {
alert(water+",爽!");
}
}
})
water.js
"use strict"
define({water:"i'm water"});
项目结构:

这样就实现的前端代码的模块化,和模块的按需加载、执行。
但是,这样还不够,我们打开Chrome控制台,network标签,刷新网页发现:

代码确实是模块化了,同时代码也分散到了各个文件中。当项目中的文件非常多,由于http请求是无状态的,每次都要建立、断开连接,另外浏览器下载静态文件的并发数量也是有上限的,会导致页面加载的速度比所有代码在同一个文件中要慢。
所以我们要对js文件进行压缩、合并。那么问题来了:
1.define函数是seajs提供的,require函数也是seajs给注入的,代码压缩,函数名字发生变化后seajs还认得吗?
2.现在的代码中require接收的参数都是模块文件的路径,文件要是都合并了,这些代码还上哪找去?
幸好有spm,它提供了构建CMD模块代码的功能,可以对模块文件进行压缩、合并的操作。
用spm 2.2.5进行模块代码的压缩&合并
了解关于CMD模块的压缩和合并之前最好先了解一下模块标识和构建过程相关的知识,以免掉入万人坑。
这两个点官方文档讲得很好,我就不再缀述了。
seajs模块标识
https://github.com/seajs/seajs/issues/258
spm2.x构建过程详解
http://docs.spmjs.org/doc/build-task
package.json配置
使用spm2构建cmd模块需要在模块根目录创建一个package.json文件。 继续以上面的项目为例:
{
"family":"tzyy",
"name":"test",
"version": "1.0.0",
"spm": {
"source": "src",
"idleading":"pack/","output": ["eat.js"]
}
}
spm视图像java项目里的maven那样建立一个依赖管理的机制,family、name、version,这三个属性用来定位一个项目,是必须填写的。但是我们这里只是用spm来做构建工具,所以这几个属性随便填写就可以了。
spm属性的值是一个json,其属性
src——指定源码目录,spm将使用src配置地址中的文件来构建项目
idleading——指定模块id前缀,spm在生成transport文件的过程中将这个属性作为模块id的前缀
output——一个数组,指定应该输出哪些文件的构建结果
压缩&合并
首先安装好nodejs、npm、spm2.2.5,安装的教程网上有很多。
进入/js目录,shift+右键——在此处打开命令窗口,然后执行命令:

这个命令在/js下创建了一个mypack目录来存放压缩后的文件

两个文件的内容为:
eat.js:
"use strict";define("pack/eat",["./rice","./water","./i"],function(a,b){var c=a("./rice"),d=a("./water"),e=a("./i");b.start=function(){e.eat(c.rice),e.drink(d.water)}}),define("pack/rice",[],{rice:"i'm rice"}),define("pack/water",[],{water:"i'm water"}),define("pack/i",[],function(){return{name:"tzyy",eat:function(a){alert(a+",Delicious!")},drink:function(a){alert(a+",爽!")}}});
eat-debug.js
"use strict";
define("pack/eat-debug", [ "./rice-debug", "./water-debug", "./i-debug" ], function(require, exports, module) {
var rice = require("./rice-debug"), water = require("./water-debug"), i = require("./i-debug");
exports.start = function() {
i.eat(rice.rice);
i.drink(water.water);
};
});
/**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict";
define("pack/rice-debug", [], {
rice: "i'm rice"
});
/**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict";
define("pack/water-debug", [], {
water: "i'm water"
});
/**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict";
define("pack/i-debug", [], function() {
return {
name: "tzyy",
eat: function(rice) {
alert(rice + ",Delicious!");
},
drink: function(water) {
alert(water + ",爽!");
}
};
});
可以看到eat.js已经被压缩,并且其依赖模块也被压缩后合并在了同一个文件中。
将html中启动模块的代码修改一下:
seajs.use("mypack/eat.js",function(eat){
if(eat) eat.start();
});
运行正常,看看网络请求:

刚刚那些文件只产生了一个网络请求,3ms远远小于 3ms+4ms+12ms+14ms吧。
seajs config中使用了别名的压缩&合并
但是,当我们的seajs项目中使用了别名的配置时,情况又不同了!修改项目结构如下:

为water.js配置别名如下:
seajs.config({
base: '../js',
alias:{
"water":"src/alias/water.js"
}
});
从src目录启动,可以正常运行。然后构建一下,发现:

得到的eat-debug.js:
/**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict"; define("pack/eat-debug", [ "./rice-debug", "water-debug", "./i-debug" ], function(require, exports, module) {
var rice = require("./rice-debug"), water = require("water-debug"), i = require("./i-debug");
exports.start = function() {
i.eat(rice.rice);
i.drink(water.water);
};
}); /**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict"; define("pack/rice-debug", [], {
rice: "i'm rice"
}); /**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict"; define("pack/i-debug", [], function() {
return {
name: "tzyy",
eat: function(rice) {
alert(rice + ",Delicious!");
},
drink: function(water) {
alert(water + ",爽!");
}
};
});
报错,找不到water模块,最终得到的输出文件中也没有包含water模块。
原因是如果seajs配置了别名,在package.json的spm属性里边应该也配置一下,这样spm会首先将别名替换成全名,然后再构建。
修改package.json,把alias配置贴过去,如下:
{
"family":"tzyy",
"name":"test",
"version": "1.0.0",
"spm": {
"source": "src",
"idleading":"pack/",
"alias":{
"water":"src/alias/water.js"
},
"output": ["eat.js"]
}
}
再次运行,结果依然不对!这种现象官网上并没有解释,经过试验和网上查资料,
我的理解:
这里要注意一下,由于seajs的config中指定了base路径 ../js,别名携程 src/alias/water.js是可以的,但是spm构建的时候,不会这么想,它似乎将src/alias/water.js作为一个顶级标识来处理,而顶级标识一般被认为是其他的公用模块,不会被transport,也不会被合并到输出文件中。 这里不对的话请指正:)
在package.json中将alias里边water的别名配置为 ./alias/water.js 这种相对路径,输出文件表现就正常了:
/**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict"; define("pack/eat-debug", [ "./rice-debug", "./alias/water-debug.js", "./i-debug" ], function(require, exports, module) {
var rice = require("./rice-debug"), water = require("./alias/water-debug.js"), i = require("./i-debug");
exports.start = function() {
i.eat(rice.rice);
i.drink(water.water);
};
}); /**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict"; define("pack/rice-debug", [], {
rice: "i'm rice"
}); /**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict"; define("pack/alias/water-debug", [], {
water: "i'm water"
}); /**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict"; define("pack/i-debug", [], function() {
return {
name: "tzyy",
eat: function(rice) {
alert(rice + ",Delicious!");
},
drink: function(water) {
alert(water + ",爽!");
}
};
});
但是此时cmd中依然会报错

不知道这是spm2.2.5的bug,还是我上面的理解有问题,官方文档和网上也没找到资料,求指导~
启动模块合并
从上一小节看来,压缩之后不是会自动合并模块吗? 为什么还要合并呢?
下面我们来变一下项目内容。增加一个启动模块drink.js:

drink.js内容:
define(function(require,exports,module){
var water=require("water"),
i=require("./i.js");
return function(){
i.drink(water.water);
}
});
在package中增加一个输出模块 drink.js
{
"family":"tzyy",
"name":"test",
"version": "1.0.0",
"spm": {
"source": "src",
"idleading":"pack/",
"alias":{
"water":"./alias/water.js"
},
"output": ["eat.js","drink.js"]
}
}
运行

可以看到生成了两个模块

html中增加一个启动模块
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="../js/lib/seajs1.2.0.js"></script>
</head>
<body>
<h1>spm前端构建测试</h1>
<script>
seajs.config({
base: '../js',
alias:{
"water":"src/alias/water.js"
}
});
seajs.use("mypack/eat.js",function(eat){
if(eat) eat.start();
});
seajs.use("mypack/drink.js",function(drink){
if(drink) drink();
});
</script>
</body>
</html>
在浏览器中运行,正常,打开控制台查看网络请求

可以看到请求了两个模块启动文件,eat.js 和 drink.js ,并没有被合并为一个文件。
而且,打开drink-debug.js,可以看到
define("pack/drink-debug", [ "./alias/water-debug.js", "./i-debug" ], function(require, exports, module) {
var water = require("./alias/water-debug.js"), i = require("./i-debug");
return function() {
i.drink(water.water);
};
});
/**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict";
define("pack/alias/water-debug", [], {
water: "i'm water"
});
/**
* Created by zouchengzhuo on 2015/10/21.
*/
"use strict";
define("pack/i-debug", [], function() {
return {
name: "tzyy",
eat: function(rice) {
alert(rice + ",Delicious!");
},
drink: function(water) {
alert(water + ",爽!");
}
};
});
而drink.js中,water和i 两个模块在eat.js中也是存在的,这也是对资源的浪费。
此时我们可以再新建一个模块,将上面那两个模块放入该模块中启动:
main.js
define(function (require,exports,module) {
var eat=require("./eat"),
drink=require("./drink");
eat.start();
drink();
})
package.json输出模块改为main.js
{
"family":"tzyy",
"name":"test",
"version": "1.0.0",
"spm": {
"source": "src",
"idleading":"pack/",
"alias":{
"water":"./alias/water.js"
},
"output": ["./main.js"]
}
}
html中启动代码修改为:
seajs.use("mypack/main.js");
再次运行可以看到所有文件都被压缩、合并了

当启动模块越来越多时,我们就可以这样干。
css的压缩、合并
spm2.x对css的压缩合并是通过seajs的seajs.importStyle函数来完成的。
我们在src目录下建一个css文件夹(之前把src目录放到js目录里边了,不想改了,大家记得调换一下顺序)

然后在main.js中require :
define(function (require,exports,module) {
var eat=require("./eat"),
drink=require("./drink");
require("./css/style.css"),
require("./css/style2.css");
eat.start();
drink();
})
运行spm build,最后得到的main-debug.js中可以看到如下代码片段:
这样css文件也被压缩到了main.js文件中。
define("pack/main-debug", [ "./eat-debug", "./rice-debug", "./alias/water-debug.js", "./i-debug", "./drink-debug", "./css/style-debug.css", "./css/style2-debug.css" ], function(require, exports, module) {
var eat = require("./eat-debug"), drink = require("./drink-debug");
require("./css/style-debug.css"), require("./css/style2-debug.css");
eat.start();
drink();
});
以及最下方的
define("pack/css/style-debug.css", [], function() {
seajs.importStyle("body{color:red}");
});
define("pack/css/style2-debug.css", [], function() {
seajs.importStyle("h1{font-size:100px}");
});
这里有一些关于seajs版本的坑
1.seajs2.2.1中,可以直接require css文件,但是seajs2.3.0不行,需要引入一个seajs-css插件
2.seajs2.3.0中,通过seajs.use启动一个transport文件中的模块时(也就是从合并过的代码中启动模块时),use中写的模块id必须和define函数中写的模块id相同,但是在seajs2.2.1中却是必须和文件路径相同。 seajs2.3.0如此做的原因是,可以在合并过的文件中可以通过模块id准确获取模块exports的内容,好传回use的回调函数中。
这些坑在seajs官网文档里面都不太容易找到。
开发、生产环境自动切换
定义一个变量,来设置模块启动路径,可以很方便的切换开发、生产环境。
将html中seajs配置、启动模块代码做如下修改:
var environment=location.hostname=="localhost"?"src":"mypack";
seajs.config({
base: '../js',
alias:{
"water":"src/alias/water.js"
}
});
seajs.use(environment+"/main.js");
这样通过localhost访问时,自动启用src目录下的代码,生产环境时,自动启用打包目录下的代码。
最后
前端领域的技术正在飞速发展,日新月异,各种技术层出不穷! 这篇文章主要在讲我使用spm2.2.5作为seajs的构建工具的使用经历,spm3.x和spm2.x的差别都非常大。和CommonJs、AMD规范相关的还有很多优秀的模块管理工具,希望能在学习使用这些工具的过程中能不断深入理解模块化的精髓!
这篇文章是学习过程中的产物,可能有很多没讲清楚或者理解有误的地方,如果您路过发现,还望在评论里指出,我会在文中更正!
用spm2构建seajs项目的过程的更多相关文章
- 使用grunt构建seajs项目
1.安装nodejs 2.安装grunt-cli npm install -g grunt-cli 3.进入到项目目录,同时准备好package.json和Gruntfile.js文件 //packa ...
- 转】用Maven构建Hadoop项目
原博文出自于: http://blog.fens.me/hadoop-maven-eclipse/ 感谢! 用Maven构建Hadoop项目 Hadoop家族系列文章,主要介绍Hadoop家族产品 ...
- Eclipse 中构建 Maven 项目的完整过程 - SpringBoot 项目
进行以下步骤的前提是你已经安装好本地maven库和eclipse中的maven插件了(有的eclipse中已经集成了maven插件) 一.Maven项目的新建 1.鼠标右键---->New--- ...
- 构建Springboot项目、实现简单的输出功能、将项目打包成可以执行的JAR包(详细图解过程)
1.构建SpringBoot项目 大致流程 1.新建工程 2.选择环境配置.jdk版本 3.选择 依赖(可省略.后续要手动在pom文件写) 4.项目名 1.1 图解建立过程 1.2 项目结构 友情提示 ...
- 基于dubbo构建分布式项目与服务模块
关于分布式服务架构的背景和需求可查阅http://dubbo.io/.不同于传统的单工程项目,本文主要学习如何通过maven和dubbo将构建分布项目以及服务模块,下面直接开始. 创建项目以及模块 ...
- 【转】使用Eclipse构建Maven项目 (step-by-step)
安装eclipse 及配置maven时,参考的资料!!! from:http://blog.csdn.net/qjyong/article/details/9098213 Maven这个个项目管理和构 ...
- 使用Maven构建Android项目
http://www.ikoding.com/build-android-project-with-maven/ 之前一直在做WEB前端项目,前段时间接手第一个Android项目,拿到代码之后,先试着 ...
- 使用Eclipse构建Maven项目 (step-by-step)
Maven这个个项目管理和构建自动化工具,越来越多的开发人员使用它来管理项目中的jar包.本文仅对Eclipse中如何安装.配置和使用Maven进行了介绍.完全step by step. 如果觉得本文 ...
- Yeoman自动构建js项目
Aug 19, 2013 Tags: bowergruntJavascriptjsnodejsyeomanyo Comments: 10 Comments Yeoman自动构建js项目 从零开始nod ...
随机推荐
- Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系
祝各位2017年事业辉煌!开年第一篇博客,继续探索Xamarin.Forms… 为什么我做Xamarin开发的时候中意于Prism.Forms框架?本章为你揭晓. 实例代码地址:https://git ...
- redux-amrc:用更少的代码发起异步 action
很多人说 Redux 代码多,开发效率低.其实 Redux 是可以灵活使用以及拓展的,经过充分定制的 Redux 其实写不了几行代码.今天先介绍一个很好用的 Redux 拓展-- redux-amrc ...
- 4.Android 打包时出现的Android Export aborted because fatal error were founds [closed]
Android 程序开发完成后,如果要发布到互联网上供别人使用,就需要将自己的程序打包成Android 安装包文件(Android Package,APK),其扩展名为.apk.使用run as 也能 ...
- [Nginx笔记]关于线上环境CLOSE_WAIT和TIME_WAIT过高
运维的同学和Team里面的一个同学分别遇到过Nginx在线上环境使用中会遇到TIME_WAIT过高或者CLOSE_WAIT过高的状态 先从原因分析一下为什么,问题就迎刃而解了. 首先是TIME_WAI ...
- JavaScript基础知识总结(三)
JavaScript语法 七.循环语句 1.while 语法: while (exp) { //statements; } 说明:while (变量<=结束值) { 需执行的代码 } 例子: / ...
- 【原】无脑操作:express + MySQL 实现CRUD
基于node.js的web开发框架express简单方便,很多项目中都在使用.这里结合MySQL数据库,实现最简单的CRUD操作. 开发环境: IDE:WebStorm DB:MySQL ------ ...
- MongoDB备份(mongodump)和恢复(mongorestore)
MongoDB提供了备份和恢复的功能,分别是MongoDB下载目录下的mongodump.exe和mongorestore.exe文件 1.备份数据使用下面的命令: >mongodump -h ...
- Hacker Rank: Two Strings - thinking in C# 15+ ways
March 18, 2016 Problem statement: https://www.hackerrank.com/challenges/two-strings/submissions/code ...
- Xamarin开发Android应用打包apk
Visual Studio中用Xamarin开发Android应用,生成apk文件有3种方法 1.debug时,代码目录下bin\Debug中会自动生成调试用***-Signed.apk文件,但是文件 ...
- Visual Studio Code v0.9.1 发布
微软的跨平台编辑器 Visual Studio Code v0.9.1 已经发布,官方博客上发布文章Visual Studio Code – October Update (0.9.1):http:/ ...