seajs实用教程(三) 生产环境
2015-11-23 15:34:09

229
相信大家现在已经能够用seajs完成一个项目了,但是此时的项目离正式上线还有很多差距,还需做很多优化,譬如,优化请求次数,优化文件大小。
当你使用seajs愉快的编码的时候,你也就会发现模块实在是太多了,请求数目过多这一问题。在这章,我们来共同探讨一下怎么优化seajs工程,使之真正可以达到可以上线的标准。
在进行优化以前,我们先讨论一下seajs里边的模块ID,还记得前面说过CMD的模块定义吗?define(id?deps?,factory),这里边的模块ID,官方建议我们不要写,通过工具去生成,那么你知道这个id是怎么生成的,通过什么规则呢?
这节我们就来看看这个module ID。
先看看我们会在哪里都会用到module ID。三个地方,
define(id[1],['id[2]'],function(require){ var a = require("id"[3]); })
无论是define第一个参数【模块ID】还是第二个参数【依赖模块的ID】还是【require模块ID】,最终的比对标准是【解析后的文件URI】。 因此,这三处需要写ID 的地方可以以任意一种方式来写,只要最终解析为同一个URI,即被认为是同一个模块。
Sea.js 的一个基本约定原则:ID 和路径匹配原则。
所谓 ID 和路径匹配原则 是指,使用 seajs.use 或 require 进行引用的文件,如果是具名模块(即定义了 ID 的模块),会把 ID 和 seajs.use 的路径名进行匹配,如果一致,则正确执行模块返回结果。反之,则返回 null。例如:
define('path/module/a',[],function(require){ }) 在该例中,模块ID定义为了path/moudle/a,那么a模块应该正确放置的位置就base(在config中定义的)/path/moudle/a.js,假如a.js不在该位置,则返回了null。
至于为什么一定要使用一定要把 ID 定为文件路径,这一块请移步 https://github.com/seajs/seajs/issues/930
这节课我们来看看文件合并,主要是通过一个例子来看看。例子使用的是seajs官网提供的helloword。 先看看文件结构如下。
├── hello.html └── static └── js ├── lib │ ├── jquery │ │ ├── easing │ │ │ └── 1.3.0 │ │ │ ├── easing-debug.js │ │ │ ├── easing.js │ │ │ └── package.json │ │ └── jquery │ │ └── 1.10.1 │ │ ├── jquery-debug.js │ │ ├── jquery.js │ │ └── package.json │ └── seajs │ └── seajs │ ├── 2.1.0 │ │ ├── package.json │ │ ├── sea-debug.js │ │ ├── sea.js │ │ └── sea.js.map │ ├── 2.1.1 │ │ ├── package.json │ │ ├── sea-debug.js │ │ ├── sea.js │ │ └── sea.js.map │ └── 2.2.0 │ ├── package.json │ ├── sea-debug.js │ └── sea.js └── src ├── main.js ├── spinning.js └── style.css
看看hello.html中的代码
seajs.config({ base: "./static/js/", alias: { "jquery": "lib/jquery/jquery/1.10.1/jquery.js" }, map:[ ["jquery.js","jquery-debug.js"] ], debug:2 }); seajs.use("./static/js/src/main.js",function(){ console.log("init"); });
看这里边的map定义,jquery请求成了jquery-debug.js,
尝试下你把map去掉后你会发现程序运行不了。提示错误
spinning.js:6 Uncaught TypeError: $ is not a function
为什么会出错呢,查看下jquery.js代码,你会发现这句话
"function" == typeof define && define("jquery/jquery/1.10.1/jquery", [], function() { ... })
这里的jquery已经是具名模块了,id为"jquery/jquery/1.10.1/jquery",在spining.js中
var $ = require('jquery');
为什么报错,我猜你已经猜出,因为 Seajs 本身的约定: ID 和路径匹配原则,不了解的查看上一课时。
那么怎么修改呢?
- 移动文件位置: 把lib/jquery 移到上一级目录
- 修改jquery.js: 把里边的id jquery/jquery/1.10.1/jquery 变为lib/jquery/jquery/1.10.1/jquery
合并文件
我们先尝试下自己手动合并,将原理搞清楚。这个例子请求了两个js,main.js和spinging.js。 那么就需要把这两个文件放到一个文件中。
我们先在static/js/中新建一个目录dist目录,在文件中我们新建一个main.js文件,将src中的main.js,和spinging.js拷贝过来粘贴。
此时dist/main.js代码如下:
define(function(require) { var Spinning = require('../src/spinning'); var s = new Spinning('#container'); s.render(); }); define(function(require, exports, module) { var $ = require('jquery'); ... });
修改hello.html中seajs.use("./static/js/dist/main.js");
这样执行肯定会报错,错在哪里了哪?
错在了seajs.use已经分不清楚你要使用的是那个模块了,就相当于c语言里边找不到main函数了。怎么修改呢?为方便说明,这里的mian.js spinning.js指的是dist/main.js 里边分别copy过来的内容。
方法1:给main.js部分增加module_id,spining
define(‘dist/main’,function(require) { var Spinning = require('../src/spinning'); var s = new Spinning('#container'); s.render(); });
方法2:main代码不动,将spinning代码改动下,增加module_id
define('spinning',function(require, exports, module) { var $ = require('jquery'); ... });
大家看到这里也就明白了,给main代码里增加module_id,此时seajs.use也就会调用到该模块,该模块为主模块。
第二种方法里,除主模块意外所有模块增加moduleid的话,seajs.use使用的没有moduleid的模块。
注意:1. 在第一种方法里,模块id必须和路径对应起来。2.当给define函数增加deps,必须包含factory里边require引用的所有模块,否则会报错。
看到这里,看起来文件是合并到一起了,但是当右键查看审查元素中network,发现了还是去请求了../src/spinning.js。多了此请求。
看上面的源码也就发现了问题所在,需要修改main.js里边的 require('../src/spinning') 为 require('../src/spinning'),把spinning.js里边的module_id就可以了。再次查看确实没有请求spinning.js文件了。
在这里确实也打破了seajs的ID 和路径匹配原则,也就是该原则是可以被打破的。但是为什么不建议打破呢?
这样就很麻烦了,当多人开发的时候,大家都自己定义module_id,很可能重名,以后合并时候太容易出问题了。
最后看一下手动合并的最终代码
//main.js define('dist/main',['dist/spinning'],function(require) { var Spinning = require('dist/spinning'); var s = new Spinning('#container'); s.render(); }); //spinning.js define('dist/spinning',['jquery'],function(require, exports, module) { var $ = require('jquery'); ... });
当然这还没有结束,还需要在做下压缩代码。
手动合并让我们更加清楚知道生成module_id,合并的原理,下课时我们就来看看如何使用grunt来进行文件合并。
使用 Grunt 作为 seajs的 构建工具
使用构建工具来做 进行合并、调整、压缩工作,相信大家对grunt都有所了解,不了解的话百度之。
根据上一课时说的 seajs 工作原理,我们其实想让构建工具完成的是以下的几个事情
- 将入口文件拷贝到 产出目录
- 创建一个临时目录
- 将需要合并的js文件转为具名函数,并保持独立地保存在这个临时目录
- 将临时目录下独立的具名函数文件 合并为 1个 js 文件
- 将这个合并的 js 文件 拷贝到 我们的输出目录
- 压缩 这个 合并后的 文件
- 将这个临时目录删除
seajs 提供以下 grunt 的插件来 构建 项目
grunt-cmd-transport(用于将匿名函数转为 具名函数) grunt-cmd-concat(根据文件中的 id 自动 拷贝对应目录文件的内容到 同一文件下)
那么我们来尝试使用grunt。
package.json:
{ "name": "hello", "version": "1.0.0", "author": "jackness Lau", "devDependencies": { "grunt": "0.4.1", "grunt-cmd-transport": "0.1.1", "grunt-cmd-concat": "0.1.0", "grunt-contrib-copy": "^0.7.0", "grunt-contrib-uglify": "0.2.0", "grunt-contrib-clean": "0.4.0" } }
使用 npm install 获取需要使用的grunt插件
Gruntfile.js:
module.exports = function(grunt) { grunt.initConfig({ /** * step 1: * 将入口文件拷贝到 产出目录 */ copy: { dev:{ files:{ "static/js/dist/main.js":["static/js/src/main.js"], "static/js/dist/spinning.js":["static/js/src/spinning.js"] } } }, /** * step 2: * 创建一个临时目录 * 将需要合并的js文件转为具名函数,并保持独立地保存在这个临时目录 */ transport: { dev: { options: { // 是否采用相对地址 relative: true, // 生成具名函数的id的格式 默认值为 {{family}}/{{name}}/{{version}}/{{filename}} format: './static/js/{{filename}}' }, files: [{ // 相对路径地址 'cwd':'static/js/', // 需要生成具名函数的文件集合 'src':['dist/main.js','dist/spinning.js'], // 生成存放的文件目录。里面的目录结构与 src 里各个文件名带有的目录结构保持一致 'dest':'.build' }] } }, /** * step 3: * 将临时目录下独立的具名函数文件 合并为 1个 js 文件 * 将这个合并的 js 文件 拷贝到 我们的输出目录 */ concat: { dev: { options: { // 是否采用相对地址 relative: true }, files: { // 合并后的文件地址 'static/js/dist/main.js': ['.build/dist/main.js'] } } }, /** * step 4: * 压缩 这个 合并后的 文件 */ uglify: { dev: { files: { 'static/js/dist/main.min.js': ['static/js/dist/main.js'] } } }, /** * step 5: * 将这个临时目录删除 */ clean: { build: ['.build'] } }); grunt.loadNpmTasks('grunt-cmd-transport'); grunt.loadNpmTasks('grunt-cmd-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.registerTask('default', ['copy','transport', 'concat','uglify','clean']);
};
下来执行grunt 不出差错的话,会在static/js目录下生成一个dist目录,里面包含了合并但没压缩的spinning.js和合并且压缩好的main.min.js。
修改hello.html
seajs.use("./static/js/dist/main.min.js", function() { console.log("init"); });
这里使用的grunt-cmd-transform和grunt-cmd-concat版本上都有点老,大家可以尝试使用最新版本做下。