珠峰培训

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版本上都有点老,大家可以尝试使用最新版本做下。