├── .gitignore
├── README.md
├── examples
├── demo1
│ ├── base.js
│ ├── base.tpl
│ ├── base.tpl.js
│ └── index.html
├── demo2
│ └── index.html
├── index.html
├── lib
│ ├── dialog
│ │ ├── base.css
│ │ ├── base.js
│ │ ├── base.tpl
│ │ └── base.tpl.js
│ └── jquery.js
└── seajs
│ ├── sea-config.js
│ ├── sea-style.js
│ └── sea.js
├── index.js
├── lib
├── alias.js
├── cache.js
├── debug.js
├── default.js
├── load
│ ├── css.js
│ ├── css_js.js
│ ├── file.js
│ ├── js.js
│ └── tpl_js.js
├── main.js
├── mime.json
├── url.js
└── util.js
├── nginx.conf
├── package.json
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /demo
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # node-combo
2 |
3 | nodejs的静态合并
4 |
5 | ## 故事
6 |
7 | > [http://www.xuexb.com/html/250.html](http://www.xuexb.com/html/250.html)
8 |
9 | 由于项目迭代开发,可能一直在改版, 如果使用`grunt`之类的工作进行合并,又有学习成本和维护成本, 但如果不压吧, 感觉B格又不高, 于是想想有没有一种可以在请求的时候去合并,压缩的, 起初看到大搜车的`ads`, 后来知道了美丽说也是这种模式, 于是就想试试用`nodejs`写一个类似这么个东东...然后...
10 |
11 | > 只是想学学`nodejs`, 只是把我的想法实践出来罢了!
12 |
13 | ## 期望
14 |
15 | 最终要完成合并`cmd`模块, 普通文件, `tpl->js`, `css->js`, 配合`CDN`缓存使用达到上线不费劲的效果!
16 |
17 | ## 介绍
18 |
19 | 她是一个可以被动式合并你设置的资源, 让你可以抛弃那些略为复杂的打包, 上线机制, 从而达到**开发人员只需要关心自己代码**的境界!
20 |
21 | ### 普通的合并
22 |
23 | 这种通常可以满足你的需求了,可以把多个文件进行合并(前提是一个链接只能合并一种文件类型), 比如 `demo.com/??test/a.css,test/b.css`, 也或者 `demo.com/??test/a.js,test/b.js`, 但由于文件路径很长的原因导致整个链接可能很长,很长...
24 |
25 | 然后就在考虑是不是可以以子目录的方式进行合并?
26 |
27 | ### 子目录合并
28 |
29 | 这种可以使你先进入深的目录,再动态的合并文件, 比如 `demo.com/test/test2/test3??a.css,b.css,c.css` ,是不是略爽?
30 |
31 | 但如果一个链接里我目录既然深, 还会跨目录呢?
32 |
33 | ### 跨目录合并
34 |
35 | 先上"比如": `demo.com/test/test2/test3/??a.css,b.css,/biede/c.css`, 这种就可以解决跨目录的问题, 但我在想很反感, 通过`seajs`的`alias`我在考虑链接能不能也"别名", 于是...
36 |
37 | ### 别名加载
38 |
39 | 例子:
40 |
41 | ``` js
42 | alias: {
43 | 'global': 'src/??seajs/sea,seajs/sea-style,seajs/sea-config,lib/dialog/base'
44 | },
45 | ```
46 |
47 | `demo.com/??global` 就相当于 `demo.com/src/??seajs/sea,seajs/sea-style,seajs/sea-config,lib/dialog/base`
48 |
49 | ### cmd和非cmd
50 |
51 | 会判断js文件内是否有`define`关键字, 如果有则视为`cmd`文件, 可以添加`file_no_cmd`来显式的设置不是`cmd`
52 |
53 | ### 无缓存加载
54 |
55 | 正如你所想, 添加`?`, 如:
56 |
57 | * `demo.com/??a.css,b.css?v=123`
58 | * `demo.com/??a.css,b.css?34234234`
59 | * `demo.com/src/xxoo??a.css,b.css?v=123`
60 | * `demo.com/src/xxoo/??a,b?v=123&type=css`
61 | * `demo.com/src/xxoo/??a,b?v=123&fdsfsd=1`
62 | * `demo.com/??别名?v=123`
63 |
64 | ## demo
65 |
66 | ### 合并css
67 |
68 | * `demo.com/??test/a.css,test/b.css` 普通的合并
69 | * `demo.com/test/??a.css,b.css` 以子目录合并
70 | * `demo.com/test/??a,b?type=css` 添加文件类型,可以替代文件后缀
71 | * `demo.com/??global` 别名方式
72 | * `demo.com/??global?dfdfdf` 别名方式
73 | * `demo.com/test/??a.css,b.css,/test2/c.css` 跨目录合并
74 | * `demo.com/test/??a.css,b.css?4654` 添加时间缀
75 | * `demo.com/test/??a.css,b?4654` 在没有显式的设置过`type`时省略的会补上第一个的类型
76 |
77 |
78 | ### 合并js
79 |
80 | **注:js会判断是否为`cmd`,如果是则会添加id并抽取依赖, uri是以子目录为基础来设置**
81 |
82 | * `demo.com/??test/a.js,test/b.js` 普通的合并
83 | * `demo.com/test/??a.js,b.js` 以子目录合并
84 | * `demo.com/test/??a,b?type=js` 添加文件类型,可以替代文件后缀,默认`type`为`js`,如果没有显式的设置过`type`,则是以第一个文件的后缀为标准
85 | * `demo.com/test/??a,b` 默认文件类型
86 | * `demo.com/??global` 别名方式
87 |
88 | ## 请求处理流程
89 |
90 | 在捕获到页面请求后,经过这些处理:
91 |
92 | 1. 判断是否有`??`存在,如果没有则认为需要`nginx`等服务器处理
93 | 2. 判断`??`后的字符是否为别名(配置的`alias`), 如果有,则拿别名的值替换整个`url`
94 | 3. 判断是否开启`cache`, 如果有则使用`md5`的`url`读取缓存文件是否存在,如果存在,则直接输出并中断
95 | 4. 对`url`进行分组,生成文件路径数组
96 | 5. 遍历路径数组,判断文件是否存在,存在则调用相应的处理文件进行处理
97 | 6. 处理文件,如`cmd`的生成
98 | 7. 如果开启`cache`则写入缓存
99 | 8. 数据输出
100 |
101 |
102 | ## Nginx代理
103 |
104 | 建议使用`nginx`做`http`代理,如果请求中包含`??`才交给`node-combo`处理,配置如下:
105 |
106 | ```
107 | server {
108 | listen 80;
109 | server_name 127.0.0.1;
110 |
111 |
112 | location / {
113 | root 目标路径;
114 | index index.html index.htm;
115 | autoindex on;
116 | }
117 |
118 | if ($request_uri ~* "\?\?"){
119 | rewrite (.*) /__combo;
120 | }
121 |
122 |
123 | location /__combo {
124 | proxy_set_header Connection "";
125 | proxy_set_header Host $http_host;
126 | proxy_set_header X-NginX-Proxy true;
127 | proxy_pass http://127.0.0.1:90$request_uri; 这里是node-combo跑的路径
128 | proxy_redirect off;
129 | }
130 | }
131 | ```
132 |
133 | ## 配置
134 |
135 | ``` js
136 | {
137 | base: './',//根路径
138 | cache: true,//是否开启缓存
139 | cache_path: './__cache',//缓存目录
140 | alias: {},//别名
141 | port: 80,//端口
142 | }
143 | ```
144 |
145 | ## 使用
146 |
147 | `npm install node-combo`
148 |
149 | ``` js
150 | var combo = require('node-combo');
151 |
152 | combo( [option] );
153 | ```
154 |
155 | ## bug
156 |
157 | [提交](https://github.com/xuexb/node-combo/issues)
158 |
159 | ## changelog
160 |
161 | ### 2.0.2
162 | 修改依赖
163 |
164 | ### 2.0.1
165 |
166 | * 文档完善
167 | * 支持在没有显式的设置`type`时使用第1个文件后缀为类型
168 |
169 | ### 2.0.0
170 |
171 | * 完成基本的功能并发布到`npm`上
172 |
173 | ## todo
174 |
175 | * demo
176 | * tpl->js的支持
177 | * css->js的支持
178 | * debug功能
179 | * cache管理
180 | * 压缩开关
181 | * jsHint
182 | * Etag?? CDN??
--------------------------------------------------------------------------------
/examples/demo1/base.js:
--------------------------------------------------------------------------------
1 | define(function(require) {
2 | var $ = require('../lib/jquery'),
3 | Dialog = require('../lib/dialog/base');
4 |
5 | var html = require('./base.tpl');
6 |
7 | var demo = {
8 | 'default': function() {
9 | new Dialog({
10 | content: '我是测试的',
11 | title: '测试',
12 | width: 400,
13 | height: 200
14 | });
15 | },
16 | alert: function() {
17 | Dialog.alert('我是alert');
18 | },
19 | confirm: function() {
20 | Dialog.confirm('确认?', function() {
21 |
22 | });
23 | },
24 | success: function() {
25 | Dialog.success('你成功了');
26 | },
27 | error: function() {
28 | Dialog.error('一会再试吧');
29 | }
30 | }
31 |
32 | $('h1').text('加载完成,耗时:'+ (new Date().getTime() - __load) + 'ms');
33 |
34 | $('.box').html(html).on('click', 'button', function() {
35 | var fn = demo[this.getAttribute('data-type')];
36 |
37 | if(!fn){
38 | fn = demo['default'];
39 | }
40 |
41 | fn();
42 | });
43 | });
--------------------------------------------------------------------------------
/examples/demo1/base.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/demo1/base.tpl.js:
--------------------------------------------------------------------------------
1 | define([], '\
2 | \
3 | \
4 | \
5 | \
6 | ');
--------------------------------------------------------------------------------
/examples/demo1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 默认异步加载
6 |
9 |
10 |
11 |
12 |
13 | 加载中...
14 |
15 |
16 |
17 |
18 |
19 | 注: 因seajs加载tpl跨域问题,要先把tpl文件转换为js
20 |
21 |
22 |
23 | 这个demo只是加载一个自己的js,这个js里加载jquery和一个弹层插件(包括js,css,tpl),完成页面逻辑
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
--------------------------------------------------------------------------------
/examples/demo2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 同步加载
6 |
9 |
10 |
11 |
12 |
13 | 加载中...
14 |
15 |
16 |
17 |
18 |
19 |
20 | 同步demo1版本
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo
6 |
7 |
8 |
9 | Demo
10 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/lib/dialog/base.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 |
4 | /*弹出层*/
5 |
6 | .ui-dialog-tips-icon,.ui-dialog-close{
7 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAA8CAYAAABmdppWAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzBBMjAxMUE1OTkzMTFFNDg5RjZBQjYxMUQ3OEQxN0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzBBMjAxMUI1OTkzMTFFNDg5RjZBQjYxMUQ3OEQxN0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3MEEyMDExODU5OTMxMUU0ODlGNkFCNjExRDc4RDE3RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3MEEyMDExOTU5OTMxMUU0ODlGNkFCNjExRDc4RDE3RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqIotjEAAAIVSURBVHja7Je7SgNBFIY3cUtJ6SUkYgq7IEGxEOwiIhJQEzVgYWNjaTS1TyAx4BtYqrlYBNGYJxAtItgqGi+1IAhq9JzlDAzrzOy4m0JhD/yQuX3ssnv+PxsoP68akoqCZkFZUAL0CroAHYOqoJboUFAwFwadge5Au6AJUDeoFzRDc/e0J+wExAMPoKThXEnaOyMD4kLN+H3VeCgDRlzCeGiEB5YN71VlwEHQmAtAETTAjUdBMQRmXMI2QTnbfAaBaZewbQFwHoFxwQtdlMB2FDCs4SB1AF8bdKgogOUVMKtM0BWon5tjmxH6RWMtGFQTgaegKdsCO4SQOuhEA4ZVwVsuSRZzBKlrwrBKCLwFnSug75owdKIb1inpDnTKHN966G0pD7AU88egrcGXXMCyvLHY/fCAXKOhAWpQE+zb30N7oWlOkmksUAyM0Nol6Ah0SA/z54u9NX0tu4JP0Bv9blNHvdDch+xQIJ4YF2XKnkYM4C2vgB79TBFmisoTRfbvmCkyT1TZv5UppiRTeE/EWtewfytTTIUx2KGGA8zKFFOQKSqoky8KM8VTiTJFFkwsEgzFVUozRZVyKmilq6cv+sQ9RScYNn6I1kI05mtNlikFxQNg4VUQZQpzmwj9K/VS2F0tP1M6kCnNoVhnM6W9uOxnip8pfqb8/0yRfafIYMz+87RH+zvFfaaQ2/iZ8ocy5VuAAQCfH9Kq9FtUMgAAAABJRU5ErkJggg==");
8 | background-repeat:no-repeat;
9 | }
10 |
11 |
12 | .ui-dialog-outer,
13 | .ui-dialog-outer table,
14 | .ui-dialog-outer p{
15 | padding:0;margin:0;
16 | }
17 |
18 | /*弹出层*/
19 | .ui-dialog-outer { text-align:left; outline:none;font-size:14px;position:relative;}
20 | .ui-dialog-border{ border:0 none; margin:0; border-collapse:collapse;}
21 | /*.ui-dialog-header, .ui-dialog-footer { padding:0; }*/
22 | .ui-dialog-iframe .ui-dialog-content{height:100%;background:#fff url('http://static.jiapai.cc/res/img/loading-32-32.gif') center center no-repeat;}
23 | .ui-dialog-iframe iframe{width: 100%;height: 100%;}
24 | /*标题栏*/
25 | .ui-dialog-header{ position:relative;/* height:36px; */ z-index:100;}
26 | .ui-dialog-title { overflow:hidden;cursor:default;height:36px; line-height:36px;color:#333;padding:0 20px;background-color:#f2f2f2;}
27 | .ui-dialog-close{display:block; position: absolute;overflow:hidden;right: 10px;top:8px;height:20px;width: 20px;text-indent:-9999em;cursor:pointer;background-position:0 -20px;}
28 | .ui-dialog-close:hover{text-decoration:none;}
29 | /*loading*/
30 | .ui-dialog-loading {width:80px;height: 50px;overflow: hidden;background: url('http://static.jiapai.cc/res/img/loading-32-32.gif') no-repeat 30px center;line-height: 50px;padding-left: 80px;font-size: 16px;}
31 | /*按钮组*/
32 | .ui-dialog-buttons{padding:20px; text-align:center; white-space:nowrap;font-size:0;}
33 | .ui-dialog-button{user-select:none;-moz-user-select: none;-webkit-user-select: none; -ms-user-select: none;margin-left:20px;cursor: pointer; display: inline-block; text-align: center;height:30px;line-height:30px;padding:0 20px;width:auto;background-color:#adaeb0;color:#fff;font-size:14px;border-radius:3px;text-decoration:none;}
34 | .ui-dialog-buttons a:first-child{margin-left:0;}
35 | .ui-dialog-button:hover{background-color:#999;text-decoration:none;}
36 | /*无效按钮*/
37 | .ui-dialog-button-disabled,.ui-dialog-button-disabled:hover,.ui-dialog-button-disabled:active{cursor:default;color:#666;background-color:#ddd;filter:alpha(opacity=50); opacity:.5;text-decoration:none;}
38 | /*高亮*/
39 | .ui-dialog-button-on{background-color:#fe555a;color:#fff;}
40 | .ui-dialog-button-on:hover{background-color:#e64348;color:#fff;}
41 | /*遮罩*/
42 | .ui-dialog-mask { background-color:#000; filter:alpha(opacity=30); opacity:0.3; }
43 | /*内部一个边框*/
44 | .ui-dialog-inner{background-color:#fff;border:1px solid #bab0b8; }
45 | /*没有标题*/
46 | .ui-dialog-noTitle .ui-dialog-title { display:none; }
47 | /*视觉*/
48 | @media screen and (min-width:0) {
49 | .ui-dialog-outer { -webkit-transform: scale(0); transform: scale(0); -webkit-transition: -webkit-transform .2s ease-in-out; transition: transform .2s ease-in-out; }
50 | .ui-dialog-show .ui-dialog-outer{ -webkit-transform: scale(1); transform: scale(1); }
51 | }
52 | /*扩展开始*/
53 | /*alert*/
54 | .ui-dialog-alert .ui-dialog-content{text-align:center;padding-top:30px;padding-bottom:20px;font-size:14px;}
55 | .ui-dialog-alert.ui-dialog-noTitle .ui-dialog-content{padding-top:40px;}
56 | .ui-dialog-alert .ui-dialog-inner{border:none;}
57 |
58 |
59 | /*简单提示*/
60 | .ui-dialog-tips .ui-dialog-inner{
61 | border-color:#898989;
62 | }
63 | .ui-dialog-tips .ui-dialog-content{padding:18px 30px 18px 46px;color:#333;}
64 | .ui-dialog-tips .ui-dialog-header{display:none;}
65 | .ui-dialog-tips-icon{position:absolute;left:16px;top:20px;overflow:hidden;display:block;width:20px;height:20px;}
66 | .ui-dialog-tips-success-icon{background-position:0 0;}
67 | .ui-dialog-tips-error-icon{background-position:0 -40px;}
68 |
69 | /*fix*/
70 | .ui-dialog-content,.ui-dialog-inner{position:relative;}
--------------------------------------------------------------------------------
/examples/lib/dialog/base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 弹出层插件
3 | * @namespace dialog
4 | */
5 |
6 |
7 |
8 | define(function(require) {
9 | 'use strict';
10 |
11 | var jQuery = require('../jquery');
12 |
13 | var _templates = require('./base.tpl');
14 |
15 | require('./base.css');
16 |
17 |
18 | return (function(window, document, $, undefined) {
19 |
20 | var _count = 0,
21 | $window = $(window),
22 | $document = $(document),
23 | _expando = 'Dialog' + (+new Date());
24 |
25 | /**
26 | * 弹出层构架函数
27 | */
28 | var Dialog = function(config, ok, cancel) {
29 | var api;
30 | config = config || {}; //如果没有配置参数
31 |
32 | // 合并
33 | config = $.extend(true, {}, Dialog.defaults, config);
34 |
35 | //如果已经弹出, id为唯一标识
36 | config.id = config.id || _expando + _count;
37 | api = Dialog.list[config.id];
38 | if (api) { //如果缓存里有
39 | return api.zIndex(); //去操作焦点.focus();//置顶该实例并返回
40 | }
41 |
42 |
43 | //如果是写的string
44 | if (typeof config === 'string') {
45 | config = {
46 | content: config
47 | }
48 | }
49 |
50 |
51 | //
52 | if (!$.isArray(config.button)) { //如果参数的button不是数组则让其为数组,因为后面要进行push操作追加
53 | config.button = [];
54 | }
55 |
56 |
57 | // 确定按钮
58 | if (ok !== undefined) {
59 | config.ok = ok;
60 | }
61 |
62 | if (config.ok) { //如果有确认按钮则追加到button数组里
63 | config.button.push({
64 | id: 'ok',
65 | value: config.okValue,
66 | callback: config.ok,
67 | focus: true, //确认按钮默认为聚焦状态
68 | highlight: true //高亮
69 | });
70 | }
71 |
72 |
73 | // 取消按钮
74 | if (cancel !== undefined) {
75 | config.cancel = cancel;
76 | }
77 |
78 | if (config.cancel) { //如果有取消按钮则追加到button数组里
79 | config.button.push({
80 | id: 'cancel',
81 | value: config.cancelValue,
82 | callback: config.cancel
83 | });
84 | }
85 |
86 | // 更新 zIndex 全局配置
87 | Dialog.defaults.zIndex = config.zIndex; //把参数里的zindex更新到全局对象里
88 |
89 | _count++; //让标识+1,防止重复
90 | Dialog.list[config.id] = this;
91 | return this._create(config);
92 | }
93 |
94 | Dialog.version = '6.0'; //版本
95 |
96 | Dialog.prototype = { //采用jQuery无需new返回新实例
97 | /**
98 | * 内部触发事件
99 | */
100 | _trigger: function(type) {
101 | var self = this,
102 | listeners = self._getEventListener(type),
103 | i = 0,
104 | len = listeners.length;
105 |
106 | for (;i < len; i++) {
107 | listeners[i].call(self);
108 | }
109 | },
110 |
111 | /**
112 | * 添加事件
113 | * @param {String} 事件类型
114 | * @param {Function} 监听函数
115 | */
116 | on: function(type, callback) {
117 | var self = this;
118 |
119 | if (type.indexOf('button:') === 0) {
120 | type = type.slice(7);
121 | if (!self._callback[type]) {
122 | self._callback[type] = {};
123 | }
124 | self._callback[type].callback = callback;
125 | } else {
126 | self._getEventListener(type).push(callback);
127 | }
128 | return self;
129 | },
130 |
131 |
132 | /**
133 | * 删除事件
134 | * @param {String} 事件类型
135 | * @param {Function} 监听函数
136 | */
137 | off: function(type, callback) {
138 | var self = this,
139 | listeners,
140 | i;
141 |
142 | if (type.indexOf('button:') === 0) {
143 | delete self._callback[type.slice(7)];
144 | } else {
145 | listeners = self._getEventListener(type);
146 | if ('function' === typeof callback) {
147 | for (i = 0; i < listeners.length; i++) {
148 | if (callback === listeners[i]) {
149 | listeners.splice(i--, 1);
150 | }
151 | }
152 | } else {
153 | listeners.length = 0;
154 | }
155 | }
156 |
157 |
158 |
159 | return self;
160 | },
161 |
162 |
163 | // 获取事件缓存
164 | _getEventListener: function(type) {
165 | var listener = this._listener;
166 | if (!listener[type]) {
167 | listener[type] = [];
168 | }
169 | return listener[type];
170 | },
171 |
172 | _create: function(config) {
173 | var self = this;
174 |
175 | self._listener = {}; //v6事件空间
176 | self._callback = {}; //按钮事件空间
177 | self._dom = {}; //jQuery对象
178 | // self.visibled = true;
179 | // self.locked = false;//
180 | // self.closed = false;//设置关闭标识
181 | self.config = config; //把参数引用到对象上
182 |
183 | self._createHTML(config); //输出html
184 |
185 |
186 | //添加对iframe的支持
187 | if (config.url) {
188 | self._$('wrap').addClass('ui-dialog-iframe');
189 |
190 | self._dom.iframe = $('')
191 | .attr({
192 | src: config.url,
193 | name: config.id,
194 | // width: '100%',
195 | // height: '100%',
196 | allowtransparency: 'yes',
197 | frameborder: 'no',
198 | scrolling: 'no'//ie7下有滚动条
199 | }).on('load', function() {
200 | self.iframeAuto();
201 | });
202 |
203 | config.content = self._dom.iframe[0];
204 | self.on('close', function() {
205 | // 重要!需要重置iframe地址,否则下次出现的对话框在IE6、7无法聚焦input
206 | // IE删除iframe后,iframe仍然会留在内存中出现上述问题,置换src是最容易解决的方法
207 | this._$('iframe').attr('src', 'about:blank').remove();
208 | });
209 | }
210 |
211 |
212 |
213 | if(config.skin){
214 | self._$('wrap').addClass(config.skin);
215 | }
216 |
217 | self._$('wrap').css('position', config.fixed ? 'fixed' : 'absolute'); //定位
218 |
219 | self._$('close').click(function() {
220 | self._click('cancel');
221 | // self._click('close');
222 | });
223 |
224 | self.button.apply(self, config.button); //处理按钮组
225 |
226 | self.title(config.title) //设置标题
227 | .content(config.content) //设置内容
228 | .width(config.width) //设置宽高
229 | .height(config.height) //设置宽高
230 | .time(config.time) //设置自动关闭
231 | .position() //重置位置
232 | .zIndex() //置顶
233 | ._addEvent(); //绑定事件
234 |
235 |
236 |
237 | self[config.visible ? 'show' : 'hide'](); //去焦点.focus();//是否显示
238 |
239 | if(config.lock){
240 | self.lock(); //如果有遮罩
241 | }
242 |
243 |
244 | if(config.initialize){
245 | config.initialize.call(self); //如果有初始化参数则call下
246 | }
247 |
248 | return self; //返回实例
249 | },
250 |
251 | /**
252 | * 设置iframe自适应
253 | */
254 | iframeAuto: function() {
255 | var self = this,
256 | config = self.config,
257 | $iframe = self._dom.iframe,
258 | test;
259 |
260 | if ($iframe && (!config.width || !config.height)) {
261 | try {
262 | // 跨域测试
263 | test = $iframe[0].contentWindow.frameElement;
264 | } catch (e) {}
265 |
266 | if (test) {
267 |
268 | if (!config.width) {
269 | self.width($iframe.contents().width());
270 | }
271 | if (!config.height) {
272 | self.height($iframe.contents().height());
273 | }
274 | }
275 | }
276 | return self;
277 | },
278 |
279 | /**
280 | * 设置内容
281 | * @param {String} 内容 (可选)
282 | */
283 | content: function(message) {
284 | this._$('content').html(message); //设置内容不解释
285 | // this._reset();//重置下位置
286 | return this.position();
287 | },
288 |
289 |
290 | /**
291 | * 设置标题
292 | * @param {(String|Boolean)} 标题内容. 为 false 则隐藏标题栏
293 | */
294 | title: function(content) {
295 |
296 | var className = 'ui-dialog-noTitle'; //没有标题时的class
297 |
298 | if (content === false) { //如果参数为false才不显示标题
299 | this._$('title').hide().html('');
300 | this._$('wrap').addClass(className);
301 | } else {
302 | this._$('title').show().html(content);
303 | this._$('wrap').removeClass(className);
304 | }
305 |
306 | return this;
307 | },
308 |
309 |
310 |
311 | /** @inner 位置居中 */
312 | position: function() {
313 |
314 | return this.__center();
315 | },
316 |
317 | /**
318 | * 内部居中
319 | */
320 | __center: function() {
321 | var wrap = this._$('wrap')[0],
322 | fixed = this.config.fixed, //判断是否为fixed定位
323 | dl = fixed ? 0 : $document.scrollLeft(), //如果不是则找到滚动条
324 | dt = fixed ? 0 : $document.scrollTop(), //同上
325 | ww = $window.width(), //窗口的宽
326 | wh = $window.height(), //窗口的高
327 | ow = wrap.offsetWidth, //当前弹层的宽
328 | oh = wrap.offsetHeight, //同上
329 | left = (ww - ow) / 2 + dl,
330 | top = (wh - oh) / 2 + dt; //(wh - oh) * 382 / 1000 + dt;//项目不让使用黄金比例
331 |
332 | wrap.style.left = Math.max(parseInt(left), dl) + 'px';
333 | wrap.style.top = Math.max(parseInt(top), dt) + 'px';
334 |
335 | return this;
336 | },
337 |
338 |
339 |
340 | width: function(value) {
341 | var self = this;
342 | //如果是获取宽
343 | if (value === undefined) {
344 | return self._$('wrap').width();
345 | }
346 | self._$('content').width(value);
347 | return self.position();
348 | },
349 |
350 | height: function(value) {
351 | var self = this;
352 | //如果是获取高
353 | if (value === undefined) {
354 | return self._$('wrap').height();
355 | }
356 | self._$('content').height(value);
357 | return self.position();
358 | },
359 |
360 | button: function() {
361 |
362 |
363 | var self = this,
364 | buttons = self._$('buttons')[0], //dom下
365 | callback = self._callback, //0000事件空间
366 | ags = [].slice.call(arguments);
367 |
368 | var i = 0,
369 | val, value, id, isNewButton, button, className;
370 |
371 | for (; i < ags.length; i++) {
372 |
373 | val = ags[i]; //当前的按钮对象
374 |
375 | id = val.id || value; //找到id
376 |
377 |
378 | value = val.value || (callback[id] && callback[id].value) || '确定'; //如果没有设置value
379 |
380 |
381 | isNewButton = !callback[id]; //是否已经存在
382 | button = isNewButton ? document.createElement('a') : callback[id].elem; //如果已存在则拿dom,否则创建dom
383 | button.href = '#'; //你懂的
384 | className = 'ui-dialog-button'; //按钮class
385 |
386 | if (isNewButton) { //如果为新按钮
387 | callback[id] = {};
388 | }
389 |
390 | //写入到按钮和空间上
391 | button.innerHTML = callback[id].value = value;
392 |
393 |
394 | //如果禁用
395 | if (val.disabled) {
396 | className += ' ui-dialog-button-disabled'; //禁用的class
397 | } else {
398 | //如果有回调
399 | if (val.callback) {
400 | callback[id].callback = val.callback;
401 | }
402 | }
403 |
404 | if (val.focus) { //如果为聚焦
405 | if(self._focus){
406 | self._focus.removeClass('ui-dialog-button-focus'); //移除老聚焦的按钮
407 | }
408 | className += ' ui-dialog-button-focus'; //给当前添加聚焦
409 | self._focus = $(button);
410 | }
411 |
412 | if (val.highlight) { //如果高亮
413 | className += ' ui-dialog-button-on';
414 | }
415 |
416 | if (val.className) { //如果配置的按钮有class则追加下
417 | className += ' ' + val.className;
418 | }
419 |
420 |
421 |
422 | button.className = className;
423 |
424 |
425 | button.setAttribute(_expando + 'callback', id); //为了委托事件用
426 |
427 | if (isNewButton) { //如果为新按钮则追加到dom
428 | callback[id].elem = button;
429 | buttons.appendChild(button);
430 | }
431 | }
432 |
433 | buttons.style.display = ags.length ? '' : 'none';
434 |
435 | if(self._focus){
436 | self._focus.focus(); //只操作按钮的焦点,而不管窗口的焦点,否则ie6有严重bug
437 | }
438 |
439 | return self;
440 | },
441 |
442 |
443 | /** 显示对话框 */
444 | show: function() {
445 | //this.dom.wrap.show();
446 | var self = this;
447 |
448 | if(self.visibled === true){
449 | return self;
450 | }
451 |
452 | self.visibled = true;
453 |
454 | self._$('wrap').show();
455 | self._$('wrap').addClass('ui-dialog-show');
456 |
457 | if (self._dom.mask) {
458 | self._dom.mask.show();
459 | }
460 |
461 | self._trigger('show');
462 |
463 | return self;
464 | },
465 |
466 |
467 | /** 隐藏对话框 */
468 | hide: function() {
469 | //this.dom.wrap.hide();
470 | var self = this;
471 |
472 | if(self.visibled === false){
473 | return self;
474 | }
475 |
476 | self.visibled = false;
477 |
478 | self._$('wrap').hide();
479 | self._$('wrap').removeClass('ui-dialog-show');
480 |
481 | if (self._dom.mask) {
482 | self._dom.mask.hide();
483 | }
484 |
485 |
486 |
487 |
488 | self._trigger('hide');
489 |
490 | return self;
491 | },
492 |
493 |
494 | /** 关闭对话框 */
495 | close: function() {
496 | var self = this,
497 | key,
498 | beforeunload;
499 |
500 | if (self.closed) {
501 | return self;
502 | }
503 |
504 | beforeunload = self.config.beforeunload;
505 |
506 | if (beforeunload && beforeunload.call(self) === false) {
507 | return self;
508 | }
509 |
510 |
511 | if (Dialog.focus === self) {
512 | Dialog.focus = null;
513 | }
514 |
515 |
516 |
517 |
518 | self._trigger('close');
519 |
520 |
521 | self.time().unlock(); //._removeEvent();//jQuery会自己卸载
522 | delete Dialog.list[self.config.id];
523 | self._$('wrap').remove();
524 |
525 |
526 |
527 | //采用v6的删除方法,减少资源
528 | for (key in self) {
529 | delete self[key];
530 | }
531 |
532 | self.closed = true;
533 |
534 |
535 | return self;
536 | },
537 |
538 |
539 | /**
540 | * 定时关闭
541 | * @param {Number} 单位毫秒, 无参数则停止计时器
542 | */
543 | time: function(time) {
544 |
545 | var self = this,
546 | timer = self._timer;
547 |
548 | if(timer){
549 | clearTimeout(timer);
550 | }
551 |
552 | if (time) {
553 | self._timer = setTimeout(function() {
554 | self._click('close');
555 | }, time);
556 | }
557 |
558 |
559 | return self;
560 | },
561 |
562 | /** 置顶对话框 */
563 | zIndex: function() {
564 |
565 | var /*dom = this.dom,*/
566 | self = this,
567 | top = Dialog.focus,
568 | index = Dialog.defaults.zIndex++;
569 |
570 | // 设置叠加高度
571 | self._$('wrap').css('zIndex', index);
572 |
573 | if(self._dom.mask){
574 | self._dom.mask.css('zIndex', index - 1);
575 | }
576 |
577 | // 设置最高层的样式
578 | if(top){
579 | top._$('wrap').removeClass('ui-dialog-focus');
580 | }
581 | Dialog.focus = self;
582 | self._$('wrap').addClass('ui-dialog-focus');
583 |
584 | return self;
585 | },
586 |
587 |
588 | /** 设置屏锁 */
589 | // 需要优化
590 | lock: function() {
591 |
592 | var self = this,
593 | config = self.config,
594 | div,
595 | css;
596 |
597 | if (self.locked) {
598 | return self;
599 | }
600 |
601 | div = document.createElement('div');
602 | css = {
603 | position: 'fixed',
604 | left: 0,
605 | top: 0,
606 | width: '100%',
607 | height: '100%',
608 | overflow: 'hidden',
609 | display: 'none'
610 | }
611 |
612 |
613 |
614 | div.className = 'ui-dialog-mask user-select-none';
615 |
616 | if(config.backgroundColor){
617 | css['backgroundColor'] = config.backgroundColor;
618 | }
619 | if(config.backgroundOpacity){
620 | css['opacity'] = config.backgroundOpacity;
621 | }
622 |
623 |
624 |
625 | document.body.appendChild(div);
626 |
627 |
628 | if(self.visibled){
629 | css.display = 'block';
630 | }
631 |
632 | self._dom.mask = $(div).css(css);/*.on('dblclick', function() {
633 | self._click('close');
634 | })*/
635 |
636 | self.locked = true;
637 |
638 |
639 |
640 | self.zIndex()._$('wrap').addClass('ui-dialog-lock');
641 | return self;
642 | },
643 |
644 |
645 | /** 解开屏锁 */
646 | unlock: function() {
647 | var self = this;
648 | if (!self.locked) {
649 | return self;
650 | }
651 |
652 | self._$('mask').remove();
653 |
654 | self._$('wrap').removeClass('ui-dialoge-lock');
655 | self.locked = false;
656 |
657 | //删除元素引用
658 | delete self._dom.mask;
659 |
660 | return self;
661 | },
662 |
663 |
664 | // 获取元素
665 | _createHTML: function() {
666 | var $wrap = $('').css({
667 | position: 'absolute',
668 | left: '-9999em',
669 | top: 0,
670 | outline: 'none'
671 | }).attr({
672 | 'role': this.config.lock ? 'alertdialog' : 'dialog',
673 | 'tabindex': -1
674 | });
675 |
676 | $wrap[0].innerHTML = Dialog._templates;
677 | document.body.appendChild($wrap[0]);
678 | // $(document.body).append($wrap); //插入到前面ie67有问题
679 | // document.body.insertBefore(wrap, document.body.firstChild);
680 | this._dom.wrap = $wrap;
681 | },
682 |
683 | //获取jQuery对象
684 | _$: function(i) {
685 | var dom = this._dom;
686 | return dom[i] || (dom[i] = dom.wrap.find('[data-dom=' + i + ']'));
687 | },
688 |
689 |
690 | // 按钮回调函数触发
691 | _click: function(id) {
692 | var self = this,
693 | fn = self._callback[id] && self._callback[id].callback;
694 |
695 | return typeof fn !== 'function' || fn.call(self) !== false ?
696 | self.close() : self;
697 | },
698 |
699 |
700 |
701 | // 事件代理
702 | _addEvent: function() {
703 |
704 | var self = this;
705 | /*,
706 | dom = this.dom;*/
707 |
708 | // 优化事件代理
709 | self._$('buttons').on('click', 'a', function() {
710 | var callbackID,
711 | $this = $(this);
712 |
713 | if (!$this.hasClass('ui-dialog-button-disabled')) {
714 | callbackID = $this.attr(_expando + 'callback');
715 | if(callbackID){
716 | self._click(callbackID);
717 | }
718 | }
719 |
720 | return !1;
721 | });
722 |
723 | self._$('wrap').on('mousedown', function() {
724 | self.zIndex();
725 | });
726 | }
727 | /*,
728 |
729 |
730 | // 卸载事件代理 不用卸载, 因为在remove的时候会自动卸载
731 | _removeEvent: function() {
732 | this._$('wrap').unbind();
733 | }*/
734 |
735 | }
736 |
737 |
738 |
739 | // 快捷方式绑定触发元素
740 | // $.fn.dialog = $.fn.Dialog = function() {
741 | // var config = arguments;
742 | // this[this.live ? 'live' : 'bind']('click', function() {
743 | // Dialog.apply(this, config);
744 | // return false;
745 | // });
746 | // return this;
747 | // };
748 |
749 |
750 |
751 | /** 最顶层的对话框API */
752 | Dialog.focus = null;
753 |
754 |
755 |
756 | /**
757 | * 根据 ID 获取某对话框 API
758 | * @param {String} 对话框 ID
759 | * @return {Object} 对话框 API (实例)
760 | */
761 | Dialog.get = function(id) {
762 | var iframe,
763 | list = Dialog.list,
764 | key;
765 |
766 | // 从 iframe 传入 window 对象
767 | if (id && id.frameElement) {
768 | iframe = id.frameElement;
769 | for (key in list) {
770 | if (list[key]._dom.iframe && list[key]._dom.iframe[0] === iframe) {
771 | return list[key];
772 | }
773 | }
774 | } else if (id) {
775 | return list[id];
776 | } else {
777 | return list;
778 | }
779 | }
780 |
781 | Dialog.list = {};
782 |
783 |
784 |
785 | //// 全局快捷键
786 | //$(document).bind('keydown', function (event) {
787 | // var target = event.target,
788 | // nodeName = target.nodeName,
789 | // rinput = /^input|textarea$/i,
790 | // api = Dialog.focus,
791 | // keyCode = event.keyCode;
792 |
793 | // if (!api || rinput.test(nodeName) && target.type !== 'button') {
794 | // return;
795 | // };
796 | //
797 | // // ESC
798 | // keyCode === 27 && api._click('cancel');
799 | //});
800 |
801 |
802 | // 锁屏限制tab
803 | // function focusin(event) {
804 | // var api = Dialog.focus;
805 | // if (api && api.locked && !api.dom.wrap[0].contains(event.target)) {
806 | // event.stopPropagation();
807 | // api.dom.outer[0].focus();
808 | // }
809 | // }
810 |
811 | // if ($.fn.live) {
812 | // $('body').live('focus', focusin);
813 | // } else if (document.addEventListener) {
814 | // document.addEventListener('focus', focusin, true);
815 | // } else {
816 | // $(document).bind('focusin', focusin);
817 | // }
818 |
819 |
820 |
821 | // 浏览器窗口改变后重置对话框位置
822 | $window.bind('resize', function() {
823 | var id;
824 | for (id in Dialog.list) {
825 | Dialog.list[id].position();
826 | }
827 | });
828 |
829 |
830 |
831 | // 可用 dialog._$(name) 获取 data-dom 的jQuery对象
832 | // 已知dom: inner,title,close,content,footer,buttons,header
833 | Dialog._templates = _templates;
834 |
835 |
836 |
837 | /**
838 | * 默认配置
839 | */
840 | Dialog.defaults = {
841 |
842 | // 消息内容
843 | content: '加载中
',
844 |
845 | // 标题
846 | title: '\u63D0\u793A',
847 |
848 | // 自定义按钮
849 | button: null,
850 |
851 | // 确定按钮回调函数
852 | ok: null,
853 |
854 | // 取消按钮回调函数
855 | cancel: null,
856 |
857 | // 对话框初始化后执行的函数
858 | initialize: null,
859 |
860 | // 对话框关闭前执行的函数
861 | beforeunload: null,
862 |
863 | // 确定按钮文本
864 | okValue: '\u786E\u5B9A',
865 |
866 | // 取消按钮文本
867 | cancelValue: '\u53D6\u6D88',
868 |
869 | // 内容宽度
870 | width: '',
871 |
872 | // 内容高度
873 | height: '',
874 |
875 | // 皮肤名(多皮肤共存预留接口)
876 | skin: '',
877 |
878 | // 自动关闭时间(毫秒)
879 | time: 0,
880 |
881 | // 初始化后是否显示对话框
882 | visible: true,
883 |
884 | // 是否锁屏
885 | lock: false,
886 |
887 | // 是否固定定位
888 | fixed: false,
889 |
890 | // 对话框叠加高度值(重要:此值不能超过浏览器最大限制)
891 | zIndex: 3e4,
892 |
893 | backgroundColor: '',
894 |
895 | backgroundOpacity: ''
896 | }
897 |
898 |
899 |
900 | Dialog.alert = function(str, fn) {
901 | var options = {};
902 |
903 |
904 | if ('string' === typeof str) {
905 | options = {
906 | content: str,
907 | beforeunload: fn
908 | }
909 | } else if($.isPlainObject(str)){
910 | options = str;
911 | }
912 |
913 | return new Dialog($.extend({
914 | skin: 'ui-dialog-alert',
915 | width: 380,
916 | fixed: 1,
917 | lock: 1,
918 | ok: 1,
919 | title: !1
920 | }, options));
921 | }
922 |
923 |
924 | Dialog.confirm = function(str, fn, cancel) {
925 | var options = {};
926 |
927 |
928 | if ('string' === typeof str) {
929 | options = {
930 | content: str,
931 | ok: fn,
932 | cancel: cancel
933 | }
934 | } else if($.isPlainObject(str)){
935 | options = str;
936 | }
937 |
938 | return new Dialog($.extend({
939 | skin: 'ui-dialog-alert',
940 | width: 380,
941 | fixed: 1,
942 | lock: 1,
943 | ok: 1,
944 | title: !1,
945 | cancel: options.cancel === undefined ? 1 : options.cancel
946 | }, options));
947 | }
948 |
949 |
950 | /**
951 | * 成功、错误、警告
952 | * @memberOf dialog
953 | * @function
954 | * @param {(object | string)} options dialog参数对象或者字符串
955 | * @param {number} num 自动关闭的倒计时, ms
956 | * @return {object} dialog对象
957 | * @example
958 | * 1: dialog.success("删除成功");
959 | * 2: dialog.error("分类不存在",4000);
960 | * 3: dialog.warning("您不是管理员");
961 | */
962 | $.each(['success', 'error', 'warning'], function(i, that) {
963 | Dialog[that] = function(options, num) {
964 | if ('string' === typeof options) {
965 | options = {
966 | content: options,
967 | time: 'function' === typeof num ? 0 : num,
968 | beforeunload: 'function' === typeof num ? num : 0
969 | }
970 | }
971 | options = options || {};
972 | options.time = options.time || 2 * 1e3;
973 |
974 | options.content = '' + options.content + '
\
975 | ';
976 | options.skin = 'ui-dialog-tips';
977 | options.fixed = 1;
978 | options.title = options.cancel = false;
979 | return new Dialog(options);
980 | };
981 | });
982 |
983 |
984 | return Dialog;
985 | }(window, document, jQuery));
986 | });
--------------------------------------------------------------------------------
/examples/lib/dialog/base.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 | |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/lib/dialog/base.tpl.js:
--------------------------------------------------------------------------------
1 | define([], '\
2 |
\
3 |
\
4 |
\
5 | \
6 | \
7 | \
8 | \
9 | \
13 | \
14 | \
17 | \
18 | | \
19 |
\
20 | \
21 |
\
22 |
');
--------------------------------------------------------------------------------
/examples/seajs/sea-config.js:
--------------------------------------------------------------------------------
1 | // file_no_cmd
2 |
3 | /**
4 | * seajs配置
5 | * @description 对时间缀的支持
6 | */
7 | ;
8 | (function(seajs) {
9 | 'use strict';
10 | var version = (document.getElementById('seajsnode') || {}).src;
11 |
12 | if (version && version.indexOf('v=') > 0) {
13 | version = version.substr(version.indexOf('v=') - 1);
14 | seajs.config({
15 | map: [
16 | function(uri) {
17 | return uri + version;
18 | }
19 | ]
20 | });
21 | }
22 | })(window.seajs);
--------------------------------------------------------------------------------
/examples/seajs/sea-style.js:
--------------------------------------------------------------------------------
1 | // file_no_cmd
2 |
3 | /* jshint ignore:start */
4 |
5 | (function() {
6 | /**
7 | * The Sea.js plugin for embedding style text in JavaScript code
8 | */
9 |
10 | var RE_NON_WORD = /\W/g
11 | var doc = document
12 | var head = document.getElementsByTagName('head')[0] ||
13 | document.documentElement
14 | var styleNode
15 |
16 | seajs.importStyle = function(cssText, id) {
17 | if (id) {
18 | // Convert id to valid string
19 | id = id.replace(RE_NON_WORD, '-')
20 |
21 | // Don't add multiple times
22 | if (doc.getElementById(id)) return
23 | }
24 |
25 | var element
26 |
27 | // Don't share styleNode when id is spectied
28 | if (!styleNode || id) {
29 | element = doc.createElement('style')
30 | id && (element.id = id)
31 |
32 | // Adds to DOM first to avoid the css hack invalid
33 | head.appendChild(element)
34 | } else {
35 | element = styleNode
36 | }
37 |
38 | // IE
39 | if (element.styleSheet !== undefined) {
40 |
41 | // http://support.microsoft.com/kb/262161
42 | if (doc.getElementsByTagName('style').length > 31) {
43 | throw new Error('Exceed the maximal count of style tags in IE')
44 | }
45 |
46 | element.styleSheet.cssText += cssText
47 | }
48 | // W3C
49 | else {
50 | element.appendChild(doc.createTextNode(cssText))
51 | }
52 |
53 | if (!id) {
54 | styleNode = element
55 | }
56 | }
57 | })();
58 |
59 | /* jshint ignore:end */
--------------------------------------------------------------------------------
/examples/seajs/sea.js:
--------------------------------------------------------------------------------
1 | // file_no_cmd
2 |
3 | /*! Sea.js 2.2.1 | seajs.org/LICENSE.md */
4 | /* jshint ignore:start */
5 | !function(a,b){function c(a){return function(b){return{}.toString.call(b)=="[object "+a+"]"}}function d(){return A++}function e(a){return a.match(D)[0]}function f(a){for(a=a.replace(E,"/");a.match(F);)a=a.replace(F,"/");return a=a.replace(G,"$1/")}function g(a){var b=a.length-1,c=a.charAt(b);return"#"===c?a.substring(0,b):".js"===a.substring(b-2)||a.indexOf("?")>0||".css"===a.substring(b-3)||"/"===c?a:a+".js"}function h(a){var b=v.alias;return b&&x(b[a])?b[a]:a}function i(a){var b=v.paths,c;return b&&(c=a.match(H))&&x(b[c[1]])&&(a=b[c[1]]+c[2]),a}function j(a){var b=v.vars;return b&&a.indexOf("{")>-1&&(a=a.replace(I,function(a,c){return x(b[c])?b[c]:a})),a}function k(a){var b=v.map,c=a;if(b)for(var d=0,e=b.length;e>d;d++){var f=b[d];if(c=z(f)?f(a)||a:a.replace(f[0],f[1]),c!==a)break}return c}function l(a,b){var c,d=a.charAt(0);if(J.test(a))c=a;else if("."===d)c=f((b?e(b):v.cwd)+a);else if("/"===d){var g=v.cwd.match(K);c=g?g[0]+a.substring(1):a}else c=v.base+a;return 0===c.indexOf("//")&&(c=location.protocol+c),c}function m(a,b){if(!a)return"";a=h(a),a=i(a),a=j(a),a=g(a);var c=l(a,b);return c=k(c)}function n(a){return a.hasAttribute?a.src:a.getAttribute("src",4)}function o(a,b,c){var d=S.test(a),e=L.createElement(d?"link":"script");if(c){var f=z(c)?c(a):c;f&&(e.charset=f)}p(e,b,d,a),d?(e.rel="stylesheet",e.href=a):(e.async=!0,e.src=a),T=e,R?Q.insertBefore(e,R):Q.appendChild(e),T=null}function p(a,c,d,e){function f(){a.onload=a.onerror=a.onreadystatechange=null,d||v.debug||Q.removeChild(a),a=null,c()}var g="onload"in a;return!d||!V&&g?(g?(a.onload=f,a.onerror=function(){C("error",{uri:e,node:a}),f()}):a.onreadystatechange=function(){/loaded|complete/.test(a.readyState)&&f()},b):(setTimeout(function(){q(a,c)},1),b)}function q(a,b){var c=a.sheet,d;if(V)c&&(d=!0);else if(c)try{c.cssRules&&(d=!0)}catch(e){"NS_ERROR_DOM_SECURITY_ERR"===e.name&&(d=!0)}setTimeout(function(){d?b():q(a,b)},20)}function r(){if(T)return T;if(U&&"interactive"===U.readyState)return U;for(var a=Q.getElementsByTagName("script"),b=a.length-1;b>=0;b--){var c=a[b];if("interactive"===c.readyState)return U=c}}function s(a){var b=[];return a.replace(X,"").replace(W,function(a,c,d){d&&b.push(d)}),b}function t(a,b){this.uri=a,this.dependencies=b||[],this.exports=null,this.status=0,this._waitings={},this._remain=0}if(!a.seajs){var u=a.seajs={version:"2.2.1"},v=u.data={},w=c("Object"),x=c("String"),y=Array.isArray||c("Array"),z=c("Function"),A=0,B=v.events={};u.on=function(a,b){var c=B[a]||(B[a]=[]);return c.push(b),u},u.off=function(a,b){if(!a&&!b)return B=v.events={},u;var c=B[a];if(c)if(b)for(var d=c.length-1;d>=0;d--)c[d]===b&&c.splice(d,1);else delete B[a];return u};var C=u.emit=function(a,b){var c=B[a],d;if(c)for(c=c.slice();d=c.shift();)d(b);return u},D=/[^?#]*\//,E=/\/\.\//g,F=/\/[^/]+\/\.\.\//,G=/([^:/])\/\//g,H=/^([^/:]+)(\/.+)$/,I=/{([^{]+)}/g,J=/^\/\/.|:\//,K=/^.*?\/\/.*?\//,L=document,M=e(L.URL),N=L.scripts,O=L.getElementById("seajsnode")||N[N.length-1],P=e(n(O)||M);u.resolve=m;var Q=L.head||L.getElementsByTagName("head")[0]||L.documentElement,R=Q.getElementsByTagName("base")[0],S=/\.css(?:\?|$)/i,T,U,V=+navigator.userAgent.replace(/.*(?:AppleWebKit|AndroidWebKit)\/(\d+).*/,"$1")<536;u.request=o;var W=/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g,X=/\\\\/g,Y=u.cache={},Z,$={},_={},ab={},bb=t.STATUS={FETCHING:1,SAVED:2,LOADING:3,LOADED:4,EXECUTING:5,EXECUTED:6};t.prototype.resolve=function(){for(var a=this,b=a.dependencies,c=[],d=0,e=b.length;e>d;d++)c[d]=t.resolve(b[d],a.uri);return c},t.prototype.load=function(){var a=this;if(!(a.status>=bb.LOADING)){a.status=bb.LOADING;var c=a.resolve();C("load",c);for(var d=a._remain=c.length,e,f=0;d>f;f++)e=t.get(c[f]),e.statusf;f++)e=Y[c[f]],e.status=bb.EXECUTING)return c.exports;c.status=bb.EXECUTING;var e=c.uri;a.resolve=function(a){return t.resolve(a,e)},a.async=function(b,c){return t.use(b,c,e+"_async_"+d()),a};var f=c.factory,g=z(f)?f(a,c.exports={},c):f;return g===b&&(g=c.exports),delete c.factory,c.exports=g,c.status=bb.EXECUTED,C("exec",c),g},t.resolve=function(a,b){var c={id:a,refUri:b};return C("resolve",c),c.uri||u.resolve(c.id,b)},t.define=function(a,c,d){var e=arguments.length;1===e?(d=a,a=b):2===e&&(d=c,y(a)?(c=a,a=b):c=b),!y(c)&&z(d)&&(c=s(""+d));var f={id:a,uri:t.resolve(a),deps:c,factory:d};if(!f.uri&&L.attachEvent){var g=r();g&&(f.uri=g.src)}C("define",f),f.uri?t.save(f.uri,f):Z=f},t.save=function(a,b){var c=t.get(a);c.statusf;f++)b[f]=Y[d[f]].exec();c&&c.apply(a,b),delete e.callback},e.load()},t.preload=function(a){var b=v.preload,c=b.length;c?t.use(b,function(){b.splice(0,c),t.preload(a)},v.cwd+"_preload_"+d()):a()},u.use=function(a,b){return t.preload(function(){t.use(a,b,v.cwd+"_use_"+d())}),u},t.define.cmd={},a.define=t.define,u.Module=t,v.fetchedList=_,v.cid=d,u.require=function(a){var b=t.get(t.resolve(a));return b.status data.update_time + config.expiration_time) {
31 | fs.unlinkSync(url);
32 | return false;
33 | }
34 | } else {
35 | return false;
36 | }
37 |
38 | //输出文件
39 | response.writeHead(200, {
40 | 'Content-Type': data.mime
41 | });
42 | response.end(data.code);
43 | return true;
44 | }
45 |
46 | /**
47 | * 写入文件
48 | * @param {string} url 链接
49 | * @param {string} code 代码
50 | * @param {string} type 类型,有js,css
51 | * @param {string} mime 文件类型
52 | */
53 | cache.write = function(url, code, type, mime, config) {
54 | var data = {
55 | url: url,
56 | code: code,
57 | update_time: new Date().getTime(),
58 | type: type,
59 | mime: mime
60 | }
61 |
62 | //创建cache目录
63 | util.mkdir(config.cache_path);
64 |
65 | //写入文件
66 | fs.writeFileSync(path.resolve(config.cache_path, md5(url) + '.json'), JSON.stringify(data));
67 | }
68 |
69 | module.exports = cache;
--------------------------------------------------------------------------------
/lib/debug.js:
--------------------------------------------------------------------------------
1 | //调试
--------------------------------------------------------------------------------
/lib/default.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | base: "./",//根路径
3 | port: 88,//端口
4 | alias: {},//别名
5 | nginx: true,//是否nginx代理
6 |
7 | cache: true,//是否开启缓存文件,如果为false下面2个则成为浮云
8 | cache_path: "./__cache/",//缓存文件的路径,基于base
9 | expiration_time: 1000 * 60 * 60 * 24 * 10,//文件过期时间,单位ms
10 | }
--------------------------------------------------------------------------------
/lib/load/css.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 加载样式文件
3 | */
4 |
5 | var fs = require('fs');
6 | var cssmin = require('cssmin');
7 |
8 |
9 | function load(data) {
10 | var code = fs.readFileSync(data.path).toString();
11 |
12 | code = cssmin(code);
13 |
14 | return code;
15 | }
16 |
17 | module.exports = load;
--------------------------------------------------------------------------------
/lib/load/css_js.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 加载样式文件转换为cmd
3 | */
4 |
5 | var css = require('./css');
6 |
7 | function load(data) {
8 | var code = css(data);
9 |
10 | code = code.replace(/\\/g, '\\\\').replace(/\'/g, '\\\'');
11 |
12 | code = 'define(\''+ data.uri +'\',[], function(){seajs.importStyle(\''+ code +'\');});';
13 |
14 | return code;
15 | }
16 |
17 | module.exports = load;
--------------------------------------------------------------------------------
/lib/load/file.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var url = require('url');
3 | var path = require('path');
4 |
5 |
6 | module.exports = function (request_url, config){
7 | var result = {
8 | code: '404',
9 | type: 'html',
10 | }
11 |
12 | request_url = url.parse(request_url).pathname;
13 |
14 | console.log(request_url);
15 |
16 | //如果是文件
17 | if (fs.existsSync(request_url)) {
18 | result.type = path.extname(request_url) || '.text';
19 | result.type = result.type.substr(1);
20 | console.log(result.type)
21 | result.code = fs.readFileSync(request_url).toString() || '404';
22 | }
23 |
24 | return result;
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/lib/load/js.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 加载js文件
3 | * 判断是否为cmd,如果是则提取id,并出生依赖
4 | */
5 |
6 | var fs = require('fs');
7 | var ast = require('cmd-util').ast;
8 |
9 |
10 |
11 | function load(url_data) {
12 | var path = url_data.path;
13 | var data = fs.readFileSync(path).toString();
14 |
15 | //如果为加载器
16 | //判断是否为非cmd
17 | if (data.indexOf('define') > -1 && data.indexOf('file_no_cmd') === -1) {
18 | data = cmd(data, url_data.uri) || '';
19 | } else {
20 | data = min(data);
21 | }
22 |
23 |
24 | return data;
25 | }
26 |
27 |
28 | /**
29 | * 处理cmd
30 | * @param {string} data 代码
31 | */
32 | function cmd(data, uri) {
33 | var astCache, meta, deps,
34 | options = {};
35 |
36 | try {
37 | astCache = ast.getAst(data);
38 | } catch (e) {
39 | console.log(e);
40 | }
41 |
42 | meta = ast.parseFirst(astCache);
43 |
44 | //如果不是cmd
45 | if (!meta) {
46 | return null;
47 | }
48 |
49 |
50 |
51 | deps = [];
52 | // 只处理当前文件写的依赖
53 | if (meta.dependencies) {
54 | deps = meta.dependencies;
55 | }
56 |
57 | //生成文件
58 | astCache = ast.modify(astCache, {
59 | id: meta.id || uri, //cmd的id
60 | dependencies: deps
61 | });
62 |
63 | data = astCache.print_to_string({
64 | beautify: false,
65 | comments: false
66 | });
67 |
68 | return data;
69 | }
70 |
71 |
72 | /**
73 | * 压缩
74 | * @param {string} data 代码
75 | */
76 | function min(data) {
77 | return require('uglify-js').minify(data, {
78 | fromString: true,
79 | compress: {
80 | hoist_vars: true
81 | }
82 | }).code;
83 | }
84 |
85 | module.exports = load;
86 |
--------------------------------------------------------------------------------
/lib/load/tpl_js.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 |
4 | function load(data) {
5 | var code = fs.readFileSync(data.path).toString();
6 |
7 | code = code.replace(/\\/g, '\\\\').replace(/\'/g, '\\\'').replace(/[\r]+/g, '\\');
8 |
9 | code = 'define(\''+ data.uri +'\',[], \''+ code +'\');';
10 |
11 | return code;
12 | }
13 |
14 | module.exports = load;
--------------------------------------------------------------------------------
/lib/main.js:
--------------------------------------------------------------------------------
1 | var http = require('http');
2 | var fs = require('fs');
3 | var path = require('path');
4 |
5 |
6 | var config = require('./default');
7 | var cache = require('./cache');
8 | var MIME = require('./mime.json');
9 |
10 |
11 | function main(options) {
12 | //合并配置
13 | options = options || {};
14 | Object.keys(options).forEach(function(key) {
15 | config[key] = options[key];
16 | });
17 |
18 | //取得绝对路径
19 | config.base = path.resolve(path.resolve('./'), config.base);
20 |
21 | //得到缓存的绝对路径
22 | config.cache_path = path.resolve(config.base, config.cache_path);
23 |
24 | http.createServer(function(request, response) {
25 | var url = request.url.substr(1),
26 | result, url_data, alias;
27 |
28 | //如果不包含??
29 | if (url.indexOf('??') === -1) {
30 |
31 | //如果需要nginx处理的
32 | if (config.nginx) {
33 | response.writeHead(403, {
34 | 'Content-Type': MIME['text']
35 | });
36 | response.end('nginx');
37 | return false;
38 | }
39 |
40 | //加载普通文件
41 | result = require('./load/file')(url, config);
42 | response.writeHead(200, {
43 | 'Content-Type': MIME[result.type]
44 | });
45 | response.end(result.code);
46 | return false;
47 | }
48 |
49 | //处理别名
50 | url = require('./alias')(url, config);
51 |
52 | //如果有缓存
53 | if (config.cache) {
54 | //如果命中
55 | if (cache.check(url, config, response) === true) {
56 | return false;
57 | }
58 | }
59 |
60 | result = '';
61 |
62 | //路径数组
63 | url_data = require('./url')(url, config);
64 |
65 | //遍历路径
66 | url_data.files.forEach(function(value) {
67 | //如果文件存在
68 | if (fs.existsSync(value.path)) {
69 | result += require('./load/' + value.parse)(value);
70 | }
71 | });
72 |
73 | //如果为404
74 | if (result === '') {
75 | response.writeHead(404, {
76 | 'Content-Type': 'text/plain'
77 | });
78 | response.end('404
');
79 | } else {
80 | result = '/** node-combo */\n' + result;
81 |
82 | //如果需要缓存,则写入文件
83 | if (config.cache) {
84 | cache.write(url, result, url_data.type, MIME[url_data.type], config);
85 | }
86 |
87 | response.writeHead(200, {
88 | 'Content-Type': MIME[url_data.type]
89 | });
90 | response.end(result);
91 | }
92 | }).listen(config.port);
93 |
94 |
95 | console.log('启动端口: ' + config.port);
96 |
97 | //如果开启缓存
98 | if (config.cache) {
99 | console.log('根目录: ' + config.base);
100 | console.log('缓存目录: ' + config.cache_path);
101 | }
102 | }
103 |
104 |
105 | module.exports = main;
--------------------------------------------------------------------------------
/lib/mime.json:
--------------------------------------------------------------------------------
1 | {
2 | "css": "text/css",
3 | "gif": "image/gif",
4 | "html": "text/html",
5 | "ico": "image/x-icon",
6 | "jpeg": "image/jpeg",
7 | "jpg": "image/jpeg",
8 | "js": "text/javascript",
9 | "json": "application/json",
10 | "pdf": "application/pdf",
11 | "png": "image/png",
12 | "svg": "image/svg+xml",
13 | "swf": "application/x-shockwave-flash",
14 | "tiff": "image/tiff",
15 | "txt": "text/plain",
16 | "wav": "audio/x-wav",
17 | "wma": "audio/x-ms-wma",
18 | "wmv": "video/x-ms-wmv",
19 | "xml": "text/xml",
20 | "text": "text/plain"
21 | }
--------------------------------------------------------------------------------
/lib/url.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 处理url
3 | * @把地址栏的url解析,并生成绝对路径,cmdURI,扩展名的路径数组
4 | */
5 |
6 | var path = require('path');
7 | var util = require('./util');
8 |
9 |
10 | /**
11 | * 处理url
12 | * @param {string} str 请求的全路径
13 | * @param {object} config 配置文件
14 | * @return {object}
15 | */
16 | function url(str, config) {
17 | var data = {},
18 | index, Prefix;
19 |
20 | //为了查找时间缀
21 | str = str.replace('??', '@@');
22 | index = str.lastIndexOf('?');
23 | if (index > -1) {
24 | data.type = util.queryUrl('type', str.slice(index));
25 | str = str.substr(0, index);
26 | }
27 |
28 | //默认类型
29 | if (data.type !== 'js' && data.type !== 'css') {
30 | data.type = 'js';
31 | }
32 |
33 | //计算位置
34 | index = str.indexOf('@@');
35 |
36 | //得到路径前缀
37 | Prefix = str.substr(0, index) || '';
38 |
39 | //如果不是以/结尾,但这里要判断如果为空
40 | if (Prefix && Prefix.slice(-1) !== '/') {
41 | Prefix += '/';
42 | }
43 |
44 | //路径前缀
45 | data.prefix = Prefix;
46 |
47 | //文件url数组
48 | data.files = [];
49 | str.substr(index + 2).split(',').forEach(function(val) {
50 | var ext, uri;
51 |
52 | if (!val) {
53 | return;
54 | }
55 |
56 | //取扩展名
57 | ext = path.extname(val);
58 |
59 | //如果没有后缀则使用默认
60 | if (!ext) {
61 | ext = data.type;
62 | val += '.' + ext;
63 | } else {
64 | ext = ext.slice(1);
65 | //如果非法
66 | if (['js', 'css', 'tpl'].indexOf(ext) === -1) {
67 | return;
68 | }
69 | }
70 |
71 | //生成cmd使用uri
72 | uri = val[0] === '/' ? val.substr(1) : val;
73 | //只对js替换
74 | if(ext === 'js'){
75 | uri = uri.replace('.js', '');
76 | }
77 |
78 | //拼数据
79 | data.files[data.files.length] = {
80 | path: path.resolve(config.base, val[0] === '/' ? val.substr(1) : Prefix + val), //全路径
81 | uri: uri,
82 | ext: ext, //扩展名
83 | parse: data.type === ext && ext === 'js' ? 'js' : data.type === 'js' && ext === 'css' ? 'css_js' : data.type === 'js' && ext === 'tpl' ? 'tpl_js' : data.type === ext && ext === 'css' ? 'css' : 'js',
84 | }
85 | });
86 |
87 | return data;
88 | }
89 |
90 |
91 | module.exports = url;
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 常用你懂的
3 | */
4 |
5 | var util = {};
6 |
7 | var fs = require('fs');
8 | var path = require('path');
9 |
10 | /**
11 | * 解析URL
12 | */
13 | util.queryUrl = function(name, url) {
14 | var results,
15 | params, qrs2, i, len;
16 |
17 | if (typeof(url) !== 'string') {
18 | return '';
19 | }
20 |
21 | if (url[0] === '?') {
22 | url = url.substr(1);
23 | }
24 |
25 | if (name) {
26 | results = url.match(new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'));
27 | results = results === null ? '' : decodeURIComponent(results[2]);
28 | } else {
29 | results = {};
30 | if (url) {
31 | params = url.split('&');
32 | i = 0;
33 | len = params.length;
34 | for (i = 0; i < len; i++) {
35 | qrs2 = params[i].split('=');
36 | results[qrs2[0]] = (qrs2[1] === void 0 ? '' : decodeURIComponent(qrs2[1]));
37 | }
38 | }
39 | }
40 | return results;
41 | }
42 |
43 |
44 | /**
45 | * 递归创建目录
46 | * @thinkjs
47 | */
48 | util.mkdir = function(p, mode) {
49 | var pp;
50 |
51 | mode = mode || '0777';
52 |
53 | if (fs.existsSync(p)) {
54 | util.chmod(p, mode);
55 | return true;
56 | }
57 | pp = path.dirname(p);
58 | if (fs.existsSync(pp)) {
59 | fs.mkdirSync(p, mode);
60 | } else {
61 | mkdir(pp, mode);
62 | mkdir(p, mode);
63 | }
64 | return true;
65 | }
66 |
67 |
68 | /**
69 | * 设置文件权限
70 | * @thinkjs
71 | */
72 | util.chmod = function(p, mode) {
73 | mode = mode || '0777';
74 |
75 | if (!fs.existsSync(p)) {
76 | return true;
77 | }
78 | return fs.chmodSync(p, mode);
79 | };
80 |
81 | module.exports = util;
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 88;
3 | server_name 127.0.0.1;
4 |
5 |
6 | location / {
7 | root /Users/xieyaowu/work/web/html5;
8 | index index.html index.htm;
9 | autoindex on;
10 | }
11 |
12 | if ($request_uri ~* "\?\?"){
13 | rewrite (.*) /__combo;
14 | }
15 |
16 |
17 | location /__combo {
18 | proxy_set_header Connection "";
19 | proxy_set_header Host $http_host;
20 | proxy_set_header X-NginX-Proxy true;
21 | proxy_pass http://127.0.0.1:90$request_uri;
22 | proxy_redirect off;
23 | }
24 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-combo",
3 | "description": "nodejs的静态合并",
4 | "version": "2.1.0",
5 | "author": "xieliang",
6 | "devDependencies": {
7 | "cmd-util": "*",
8 | "cssmin": "*",
9 | "MD5": "*",
10 | "uglify-js": "*"
11 | },
12 | "dependencies": {
13 | "cmd-util": "*",
14 | "cssmin": "*",
15 | "MD5": "*",
16 | "uglify-js": "*"
17 | },
18 | "main": "index.js",
19 | "directories": {
20 | "test": "test"
21 | },
22 | "scripts": {
23 | "test": "npm install && node test.js"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/xuexb/node-combo.git"
28 | },
29 | "keywords": [
30 | "combo",
31 | "cmd"
32 | ],
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/xuexb/node-combo/issues"
36 | },
37 | "homepage": "https://github.com/xuexb/node-combo"
38 | }
39 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | var combo = require('./index');
2 |
3 | combo({
4 | cache: false,
5 | port: 80,
6 | nginx: false,
7 | });
--------------------------------------------------------------------------------