Sea.js 開(kāi)發(fā)實(shí)戰(zhàn)

2020-04-26 10:01 更新

開(kāi)發(fā)實(shí)戰(zhàn)

venus-in-cmd

Venus是一個(gè)javascript類庫(kù),是一個(gè)canvas的wrapper,為了學(xué)習(xí)spm,我們使用cmd的模式來(lái)重構(gòu)這個(gè)類庫(kù)。

安裝spm-init

spm提供了初始cmd模塊的腳手架,我們可以使用下面的命令來(lái)安裝這個(gè)腳手架:

$ spm plugin install init

初始化一個(gè)cmd項(xiàng)目

運(yùn)行:

$ spm init

就可以初始化一個(gè)cmd模塊的項(xiàng)目,回答一些spm的問(wèn)題,就能在當(dāng)前目錄生成必要的文件和文件夾:

|~examples/
| `-index.md
|~src/
| `-venus.js
|~tests/
| `-venus-spec.js
|-LICENSE
|-Makefile
|-package.json
`-README.md

我們?cè)?code>src中添加venus的代碼。

編寫(xiě)cmd模塊

或者將現(xiàn)有的模塊轉(zhuǎn)化為cmd模塊。

本例中的Venus本來(lái)就已經(jīng)存在,那我們?nèi)绾螌⑵滢D(zhuǎn)成cmd模塊呢?

在Venus的源碼中我驚喜地發(fā)現(xiàn)這段代碼:

// File: vango.js

/*
 * wrapper for browser,nodejs or AMD loader evn
 */
(function(root, factory) {
    if (typeof exports === "object") {
        // Node
        module.exports = factory();
    // AMD loader
    } else if (typeof define === "function" && define.amd) {
        define(factory);
    } else {
        // Browser
        root.Vango = factory();
    }
})(this, function() {
    // Factory for build Vango
})

這段代碼可以令vango.js支持瀏覽器(通過(guò)script直接引入)、node環(huán)境以及AMD加載器。

于是事情就簡(jiǎn)單了,因?yàn)槲覀兛梢院芎?jiǎn)單地將一個(gè)Node模塊轉(zhuǎn)成CMD模塊,添加如下的wrapper即可:

// File: vango.js
define(function (require, exports, module) {
    (function(root, factory) {
        if (typeof exports === "object") {
            // Node
            module.exports = factory();
        // AMD loader
        } else if (typeof define === "function" && define.amd) {
            define(factory);
        } else {
            // Browser
            root.Vango = factory();
        }
    })(this, function() {
        // Factory for build Vango
        // return Vango
    })
})

UMD

上面那段有點(diǎn)黑魔法的代碼還有一個(gè)更復(fù)雜的形式,即Universal Module Definition

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define('backbone', ['jquery', 'underscore'], function (jQuery, _) {
      return factory(jQuery, _);
    });
  } else if (typeof exports === 'object') {
  // Node.js
    module.exports = factory(require('jquery'), require('underscore'));
  } else {
    // Browser globals
    root.Backbone = factory(root.jQuery, root._);
  }
}(this, function (jQuery, _) {
  var Backbone = {};
  // Backbone code that depends on jQuery and _
  return Backbone;

}));

當(dāng)然并不是所有的CMD模塊都得這么寫(xiě),你可以按照自己的方式,使用require、exportsmodule這三個(gè)關(guān)鍵字,遵循CMD的規(guī)范即可。

最后src有兩個(gè)文件,venus.js就很簡(jiǎn)單了:

define(function (require, exports, module) {
    var Vango = require('./vango');
    exports.Vango = Vango;
})

我們的venus的cmd版本搞定了,vango.js作為vango具體實(shí)現(xiàn),而venus.js只是這些將這些畫(huà)家暴露出來(lái)。

構(gòu)建

作為標(biāo)準(zhǔn)的cmd模塊,我們可以使用spm-build來(lái)構(gòu)建,別忘了之前提到的,你可以使用spm plugin install build來(lái)安裝。

在項(xiàng)目的根目錄下運(yùn)行spm build

$ spm build
           Task: "clean:build" (clean) task

           Task: "spm-install" task

           Task: "transport:src" (transport) task
      transport: 2 files

           Task: "concat:css" (concat) task
       concated: 0 files

           Task: "transport:css" (transport) task
      transport: 0 files

           Task: "concat:js" (concat) task
       concated: 2 files

           Task: "copy:build" (copy) task

           Task: "cssmin:css" (cssmin) task

           Task: "uglify:js" (uglify) task
           file: ".build/dist/venus.js" created.

           Task: "clean:dist" (clean) task

           Task: "copy:dist" (copy) task
         copied: 2 files

           Task: "clean:build" (clean) task
       cleaning: ".build"...

           Task: "spm-newline" task
         create: dist/venus-debug.js
         create: dist/venus.js

           Done: without errors.

從構(gòu)建的log中可以看出,spm完全就是使用grunt來(lái)構(gòu)建的,涉及到多個(gè)grunt task。你完全可以自己編寫(xiě)Gruntfile.js來(lái)實(shí)現(xiàn)自定義的構(gòu)建過(guò)程。

venus就被構(gòu)建好了,spm在目錄中生成了一個(gè)dist文件夾:

|~dist/
  |-venus-debug.js
  `-venus.js

venus-debug.js中的內(nèi)容為:

define("island205/venus/1.0.0/venus-debug", [ "./vango-debug" ], function(require, exports, module) {
    var Vango = require("./vango-debug");
    exports.Vango = Vango;
});

define("island205/venus/1.0.0/vango-debug", [], function(require, exports, module) {
    // Vango's code
})

venus.js的內(nèi)容與之一樣,只是經(jīng)過(guò)了壓縮,去掉了模塊名最后的-debug

spmsrc中的vango.js和venus.js根據(jù)依賴打到了一起。作為包的主模塊,venus.js被放到了最前面。

這是Sea.js的默認(rèn)約定,打包后的模塊文件其中的一個(gè)define即為該包的主模塊(ID 和路徑相匹配的那一個(gè)),也就是說(shuō),你通過(guò)require('island205/venus/1.0.0/venus'),雖然Sea.js加載的是整個(gè)打包的模塊,但是會(huì)把的一個(gè)factory的exports作為venus暴露的接口。

發(fā)布

如果你用過(guò)npm,那你對(duì)spm的發(fā)布功能應(yīng)該不會(huì)陌生了。spm也像npm一樣,有一個(gè)公共倉(cāng)庫(kù),我們可以通過(guò)spm plublish將venus發(fā)布到倉(cāng)庫(kù)中,與大家共享。

$ spm publish
        publish: island205/venus@1.0.0
          found: readme in markdown.
        tarfile: venus-1.0.0.tar.gz
        execute: git rev-parse HEAD
           yuan: Authorization required.
           yuan: `spm login` first

如果你碰到上面這種情況,你需要登錄下。

$ spm publish
        publish: island205/venus@1.0.0
          found: readme in markdown.
        tarfile: venus-1.0.0.tar.gz
        execute: git rev-parse HEAD
      published: island205/venus@1.0.0

接下來(lái)我們使用venus編寫(xiě)一個(gè)名為pixelegos的網(wǎng)頁(yè)程序,你可以使用這個(gè)程序來(lái)生成一些頭像的位圖。例如,spmjs的頭像(這是github為spmjs生成的隨機(jī)頭像):

spmjs

pixelegos

pixelegos完成后的樣子:

pixelegos

準(zhǔn)備

創(chuàng)建一個(gè)名為pixelegos的文件夾,初始化一個(gè)npm項(xiàng)目:

$ mkdir pixelegos && cd pixelegos && npm init

在目錄中多了一個(gè)packege.json文件,在這個(gè)文件中包含了一些pixelegos的信息,之后還會(huì)保存一些node module和spm的配置。

安裝依賴

本項(xiàng)目中需要依賴的cmd模塊包括backbone、seajsvenus、zepto。我們運(yùn)行下面的命令安裝這些依賴:

$ spm install seajs/seajs gallery/backbone zepto/zepto island205/venus

pixelegos目錄下增加了一個(gè)sea-modules目錄,上面的cmd依賴都安裝在這個(gè)目錄中,由于backone依賴于underscore,spm自動(dòng)安裝了依賴。

├── gallery
│   ├── backbone
│   │   └── 1.0.0
│   │       ├── backbone-debug.js
│   │       ├── backbone.js
│   │       └── package.json
│   └── underscore
│       └── 1.4.4
│           ├── package.json
│           ├── underscore-debug.js
│           └── underscore.js
├── island205
│   └── venus
│       └── 1.0.0
│           ├── package.json
│           ├── venus-debug.js
│           └── venus.js
├── seajs
│   └── seajs
│       └── 2.1.1
│           ├── package.json
│           ├── sea-debug.js
│           ├── sea.js
│           └── sea.js.map
└── zepto
    └── zepto
        └── 1.0.0
            ├── package.json
            ├── zepto-debug.js
            └── zepto.js

開(kāi)始

新建一些html、css、js文件,結(jié)構(gòu)如下:

├── index.css
├── index.html
├── js
│   ├── canvas.js
│   ├── config.js
│   ├── menu.js
│   ├── pixelegos.js
│   └── tool.js
├── package.json
└── sea-modules
    ├── gallery
    ├── island205
    ├── seajs
    └── zepto

給index.html添加如下內(nèi)容:

<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport' />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="/index.css" />
    <script type="text/javascript" src="/attachments/image/wk/helloseajs/sea-debug.js"></script>
    <script type="text/javascript" src="/config.js"></script>
    <script type="text/javascript">
        seajs.use('/js/pixelegos')
    </script>
</head>
<body>
</body>
</html>

其中,config.js在開(kāi)發(fā)時(shí)用來(lái)配置alias,pixelegos作為整個(gè)程序的啟動(dòng)模塊。

// config.js
seajs.config({
    alias: {
        '$': 'zepto/zepto/1.0.0/zepto',
        "backbone": "gallery/backbone/1.0.0/backbone",
        "venus": "island205/venus/1.0.0/venus"
    }
})
// pixelegos.js
define(function (require, exports, module) {
    var Menu = require('./menu')
    var Tool = require('./tool')
    var Canvas = require('./canvas')
    var $ = require('$')

    $(function() {
        var menu = new Menu()
        var tool = new Tool()
        var canvas = new Canvas()
        tool.on('select', function(color) {
            canvas.color = color
        })
        tool.on('erase', function() {
            canvas.color = 'white'
        })
    })
})

在其他js文件中分別基于backbone實(shí)現(xiàn)一些pixelegos的組件。例如:

// menu.js
define(function (require, exports, module) {
    var Backbone = require('backbone')
    var $ = require('$')

    var Menu = Backbone.View.extend({
        el: $('header'),
        show: false,
        events: {
            'click .menu-trigger': 'toogle'
        },
        initialize: function() {
            this.menu = this.$el.next()
            this.render()
        },
        toogle: function(e) {
            e.preventDefault()
            this.show = ! this.show
            this.render()
        },
        render: function() {
            if (this.show) {
                this.menu.css('height', 172)
            } else {
                this.menu.css('height', 0)
            }
        }
    })

    module.exports = Menu
})

menu.js依賴于backbone和$(在config.js將zepto alias為了$),實(shí)現(xiàn)了頂部的菜單。

當(dāng)當(dāng)當(dāng)當(dāng)

當(dāng)當(dāng)當(dāng)當(dāng),巴拉巴拉,我們敲敲打打完成了pixelegos得功能,我們已經(jīng)可以畫(huà)出那只Octocat了!

構(gòu)建發(fā)布

終于來(lái)到了我們的重點(diǎn),關(guān)于cmd模塊的構(gòu)建。

有童靴覺(jué)得spm提供出來(lái)的構(gòu)建工具很難用,搞不懂。我用下來(lái)確實(shí)些奇怪的地方,等我慢慢到來(lái)吧。

spm為自定義構(gòu)建提供了兩個(gè)工具:

  • grunt-cmd-transport:將cmd模塊轉(zhuǎn)換成具名模塊,即將define(function (require, exports, module) {})轉(zhuǎn)換為define(id, deps, function(require, exports, module) {}),可基于package.json中的spm配置來(lái)替換被alias掉的路徑等等。本身還可以將css或者h(yuǎn)tml文件轉(zhuǎn)換為cmd包。
  • grunt-cmd-concat:根據(jù)依賴樹(shù),將多個(gè)具名的cmd模塊打包到一起。

接下來(lái)就是用這些工具將我們零散的js打包成一個(gè)名為pixelegos.js的文件。

grunt

grunt是目前JavaScript最炙手可熱的構(gòu)建工具,我們先來(lái)安裝下:

" 在全部安裝grunt的命令行接口
$ npm install grunt-cli -g

" 安裝需要用的grunt task
$ npm install grunt grunt-cmd-concat grunt-cmd-transport grunt-contrib-concat grunt-contrib-jshint grunt-contrib-uglify  --dev-save

整個(gè)打包的流程為:

  1. 將業(yè)務(wù)代碼transport成cmd的具名模塊
  2. concat所有的文件到一個(gè)文件pixelegos.js
  3. uglify

編寫(xiě)Gruntfile.js文件

第一步先把js文件夾中的業(yè)務(wù)js轉(zhuǎn)換成具名模塊:

transport : {
    options: {
        idleading: '/dist/',
        alias: '<%= pkg.spm.alias %>',
        debug: false
    },
    app:{
        files:[{
            cwd: 'js/',
            src: '**/*',
            dest: '.build'
        }]
    }
}

這是一些transport的配置,即將js/中的js transport到.build中間文件夾中。

接下來(lái),將.build中的文件合并到一起(包含sea-modules中的依賴項(xiàng)。):

concat : {
    options : {
        include : 'all'
    },
    app: {
        files: [
            {
                expand: true,
                cwd: '.build/',
                src: ['pixelegos.js'],
                dest: 'dist/',
                ext: '.js'
            }
        ]
    }
}

這里我們只對(duì)pixelegos.js進(jìn)行concat,因?yàn)樗莂pp的入口文件,將include配置成all,只需要concat這個(gè)文件,就能將所有的依賴項(xiàng)打包到一起。include還可以配置成其他值:

  • self,相當(dāng)于不做concat,只是copy該文件
  • relative,只concat通過(guò)想對(duì)路徑依賴的模塊

既然我們已經(jīng)transport和concat好了文件,那我們直接使用整個(gè)文件就行了,于是我們的發(fā)布頁(yè)面可寫(xiě)成:

<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport' />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="/index.css" />
    <script type="text/javascript" src="/attachments/image/wk/helloseajs/sea.js"></script>
    <script type="text/javascript">
        seajs.use('/dist/pixelegos')
    </script>
</head>
<body>
...
</body>
</html>

當(dāng)我運(yùn)行index-product.html時(shí)我遇到了坑。在backbone包中并沒(méi)有指明$依賴的具體包,導(dǎo)致打包后的js無(wú)法找到$.js文件。原本以為backbone中的$會(huì)被業(yè)務(wù)級(jí)的配置所替換,但是事實(shí)并非如此。如何解決?

我們必須使用seajs.config接口提供一個(gè)dom的engine,在js/中創(chuàng)建engine.js文件:

// engine.js
seajs.config({
    alias: {
        '$': 'zepto/zepto/1.0.0/zepto'
    }
})

接下來(lái)把這個(gè)文件和pixelegos.js concat在一起:

normalconcat: {
    app: {
        src: ['js/engine.js', 'dist/pixelegos.js'],
        dest: 'dist/pixelegos.js'
    }
}

由于grunt-contrib-concat和grunt-cmd-concat產(chǎn)生了task name的沖突,可以通過(guò)grunt.renameTask來(lái)修改task名。

下一步,uglify!

uglify : {
    app : {
        files: [
            {
                expand: true,
                cwd: 'dist/',
                src: ['**/*.js', '!**/*-debug.js'],
                dest: 'dist/',
                ext: '.js'
            }
        ]
    }
}

大功告成,完整的Gruntfile.js如下:

module.exports = function (grunt) {
    grunt.initConfig({
        pkg : grunt.file.readJSON("package.json"),
        transport : {
            options: {
                idleading: '/dist/',
                alias: '<%= pkg.spm.alias %>',
                debug: false
            },
            app:{
                files:[{
                    cwd: 'js/',
                    src: '**/*',
                    dest: '.build'
                }]
            }
        },
        concat : {
            options : {
                include : 'all'
            },
            app: {
                files: [
                    {
                        expand: true,
                        cwd: '.build/',
                        src: ['pixelegos.js'],
                        dest: 'dist/',
                        ext: '.js'
                    }
                ]
            }
        },
        normalconcat: {
            app: {
                src: ['js/engine.js', 'dist/pixelegos.js'],
                dest: 'dist/pixelegos.js'
            }
        },
        uglify : {
            app : {
                files: [
                    {
                        expand: true,
                        cwd: 'dist/',
                        src: ['**/*.js', '!**/*-debug.js'],
                        dest: 'dist/',
                        ext: '.js'
                    }
                ]
            }
        },
        clean:{
            app:['.build', 'dist']
        }
    })

     grunt.loadNpmTasks('grunt-cmd-transport')
     grunt.loadNpmTasks('grunt-contrib-concat')
     grunt.renameTask('concat', 'normalconcat')
     grunt.loadNpmTasks('grunt-cmd-concat')
     grunt.loadNpmTasks('grunt-contrib-uglify')
     grunt.loadNpmTasks('grunt-contrib-clean')

     grunt.registerTask('build', ['clean', 'transport:app', 'concat:app', 'normalconcat:app', 'uglify:app'])
     grunt.registerTask('default', ['build'])
}

總結(jié)

我們使用spm將一個(gè)非cmd模塊venus轉(zhuǎn)成了標(biāo)準(zhǔn)的cmd模塊venus-in-cmd,然后我們用它結(jié)合多個(gè)cmd模塊構(gòu)建了一個(gè)簡(jiǎn)單的網(wǎng)頁(yè)程序。很有成就,有沒(méi)有!接下來(lái)我們要進(jìn)入hard模式了,我們來(lái)看看,Sea.js是如何實(shí)現(xiàn)的?只有了解了它的內(nèi)部是如何運(yùn)作的,在使用它的過(guò)程才能游刃有余!

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)