JavaScript模組化-require.js,r.js和打包發布

來源:互聯網
上載者:User

標籤:sync   doc   https   grunt   sequence   風格   r.js   example   衝突   

在JavaScript模組化和閉包和JavaScript-Module-Pattern-In-Depth這兩篇文章中,提到了模組化的基本思想,但是在實際項目中模組化和項目人員的分工,組建化開發,打包發布,效能最佳化,工程化管理都有密切的關係,這麼重要的事情,在JavaScript大行其道的今天,不可能沒有成熟的解決方案,所以從我的實踐經驗出發,從模組化講到工程化,分享一下自己的經驗。

這篇文章主要是講require.js和r.js在項目中的使用,不會涉及到工程化問題,對此熟悉的看官可以略過此文。對於require.js基本用法不熟悉的朋友,可以看看這個blog:asynchronous_module_definition

JavaScript的模組化

流行的模組化解決方案現在有很多,主要分為以下幾種規範

  • AMD:今天討論的主題,AMD 規範是JavaScript開發的一次重要嘗試,它以簡單而優雅的方式統一了JavaScript的模組定義和載入機制,並迅速得到很多架構的認可和採納。這對開發人員來說是一個好訊息,通過AMD我們降低了學習和使用各種架構的門檻,能夠以一種統一的方式去定義和使用模組,提高開發效率,降低了應用維護成本。
  • CommonJS:node.js的方式,在前端需要打包工具配合使用。在後端比較好用。
  • CMD & sea.js: 國內牛人搞的。LABjs、RequireJS、SeaJS 哪個最好用?為什嗎?

JavaScript的模組化需要解決下面幾個問題

  • 定義模組
  • 管理模組依賴
  • 載入模組
  • 載入最佳化
  • 代碼調試支援

為了直觀的理解一下流行了很久的require.js和r.js是如何解決這些問題的,我們從一個例子入手吧。下載example-multipage-shim

代碼結構

我們看一下基於requirejs的多頁面項目的一個基本結構:


example-multipage-shim檔案結構


下面我們看看如何解決js模組化的問題的。

定義模組

看一下base.js

define(function () {    function controllerBase(id) {        this.id = id;    }    controllerBase.prototype = {        setModel: function (model) {            this.model = model;        },        render: function (bodyDom) {            bodyDom.prepend(‘<h1>Controller ‘ + this.id + ‘ says "‘ +                      this.model.getTitle() + ‘"</h2>‘);        }    };    return controllerBase;});

使用define就可以定義了。不需要我們自己手動匯出全域變數啦。

管理模組依賴

看一下c1.js

define([‘./Base‘], function (Base) {    var c1 = new Base(‘Controller 1‘);    return c1;});

可以看到通過[‘./Base‘]注入依賴。
在看一下main1.js

define(function (require) {    var $ = require(‘jquery‘),        lib = require(‘./lib‘),        controller = require(‘./controller/c1‘),        model = require(‘./model/m1‘),        backbone = require(‘backbone‘),        underscore = require(‘underscore‘);    //A fabricated API to show interaction of    //common and specific pieces.    controller.setModel(model);    $(function () {        controller.render(lib.getBody());        //Display backbone and underscore versions        $(‘body‘)            .append(‘<div>backbone version: ‘ + backbone.VERSION + ‘</div>‘)            .append(‘<div>underscore version: ‘ + underscore.VERSION + ‘</div>‘);    });});

也可以通過require的方式(CommonJS風格)去載入相依模組

載入模組

看一下如何啟動,看看page1.html

<!DOCTYPE html><html>    <head>        <title>Page 1</title>        <script src="js/lib/require.js"></script>        <script>            //Load common code that includes config, then load the app            //logic for this page. Do the requirejs calls here instead of            //a separate file so after a build there are only 2 HTTP            //requests instead of three.            requirejs([‘./js/common‘], function (common) {                //js/common sets the baseUrl to be js/ so                //can just ask for ‘app/main1‘ here instead                //of ‘js/app/main1‘                requirejs([‘app/main1‘]);            });        </script>    </head>    <body>        <a href="page2.html">Go to Page 2</a>    </body></html>

我們看到首先用script標籤引入require.js,然後使用requirejs載入模組,而這些模組本來也要用script標籤引用的,所以說requirejs協助我們管理檔案載入的事情了。可以使用data-main屬性去載入,詳細說明可以看文檔了。
我們看一下運行效果。


運行效果


可以看到requirejs協助我們家在了所有模組,我們可以更好的組織JavaScript代碼了。

最佳化載入

我們模組化代碼以後,並不想增加請求的次數,這樣會使網頁的效能降低(這裡是非同步載入,但是瀏覽器非同步請求的過多,還是有問題的),所以我們想合并一下代碼。
使用r.js:

node r.js -o build.js

使用r.js


看看結果:


構建後


構建後我們的代碼都經過處理了。

看看運行效果。


減少了請求


可見可以通過r.js協助我們最佳化請求(通過合并檔案)。

如何配置
  • requirejs如何配置,我們看看common.js
    requirejs.config({  baseUrl: ‘js/lib‘, 從這個位置載入模組  paths: {      app: ‘../app‘   },  shim: {      backbone: {          deps: [‘jquery‘, ‘underscore‘],          exports: ‘Backbone‘      },      underscore: {          exports: ‘_‘      }  }});
屬性 意義
baseUrl 載入模組的位置
app:‘../app‘ 像這樣的‘app/sub‘,在app目錄下找sub模組
shim 全域匯出的庫,在這裡封裝

可以查看中文說明書看看更詳細的說明。

  • r.js如何配置,我們看看build.js
    這裡面有很全的配置說明example.build.js,過一下我們自己是怎麼配置的。
{    appDir: ‘../www‘,    mainConfigFile: ‘../www/js/common.js‘,    dir: ‘../www-built‘,    modules: [        //First set up the common build layer.        {            //module names are relative to baseUrl            name: ‘../common‘,            //List common dependencies here. Only need to list            //top level dependencies, "include" will find            //nested dependencies.            include: [‘jquery‘,                      ‘app/lib‘,                      ‘app/controller/Base‘,                      ‘app/model/Base‘            ]        },        //Now set up a build layer for each main layer, but exclude        //the common one. "exclude" will exclude nested        //the nested, built dependencies from "common". Any        //"exclude" that includes built modules should be        //listed before the build layer that wants to exclude it.        //The "page1" and "page2" modules are **not** the targets of        //the optimization, because shim config is in play, and        //shimmed dependencies need to maintain their load order.        //In this example, common.js will hold jquery, so backbone        //needs to be delayed from loading until common.js finishes.        //That loading sequence is controlled in page1.html.        {            //module names are relative to baseUrl/paths config            name: ‘app/main1‘,            exclude: [‘../common‘]        },        {            //module names are relative to baseUrl            name: ‘app/main2‘,            exclude: [‘../common‘]        }    ]}

我們主要看modules下面定義的數組,實際上就是一個個檔案的依賴關係,r.js會一用這裡的關係,合并檔案。詳細的配置意義可以看文檔

提示:r.js還可以最佳化css。

如何調試

前面代碼被最佳化了以後,調試起來就痛苦了,這裡我們可以使用sourcemap技術來調試最佳化後的代碼。進行如下操作。

  1. 修改build.js,增加如下配置
    generateSourceMaps: true,preserveLicenseComments: false,optimize: "uglify2",
  2. 重新構建
    node r.js -o build.js
  3. 開啟瀏覽器支援
    這裡最好用firefox瀏覽器,chrome從本地檔案開啟html不能正常使用sourcemap。直接用firefox瀏覽就可以了。


    firefox支援sourcemap


    可以看到可以載入非最佳化的代碼,有人會問,這不要請求多次嗎?最佳化一份,非最佳化一份,這樣不是效能更差勁。其實只有你調試的時候,開啟了這個功能才會請求對應的sourcemap檔案,所以對使用者來說並不浪費。

  4. 寫一個server讓chrome也支援
    chrome本身是支援source map的,就是從硬碟直接開啟檔案的許可權有特殊處理。以file://開頭的路徑很多事情做不了。所以我們做一個簡單的server吧。

在tools目錄下增加一個server.js檔案

var http = require(‘http‘),    url = require(‘url‘),    path = require(‘path‘),    fs = require(‘fs‘),    port = process.argv[2] || 8888,    types = {        ‘html‘: ‘text/html‘,        ‘js‘: ‘application/javascript‘    };http.createServer(function (request, response) {    var uri = url.parse(request.url).pathname,        filename = path.join(__dirname, ‘..‘, uri);        console.log(filename);    fs.exists(filename, function (exists) {        if (!exists) {            response.writeHead(404, {‘Content-Type‘: ‘text/plain‘});            response.write(‘404 Not Found\n‘);            response.end();            return;        }        var type = filename.split(‘.‘);        type = type[type.length - 1];        response.writeHead(200, { ‘Content-Type‘: types[type] + ‘; charset=utf-8‘ });        fs.createReadStream(filename).pipe(response);    });}).listen(parseInt(port, 10));console.log(‘Static file server running at\n  => http://localhost:‘ + port + ‘/\nCTRL + C to shutdown‘);

開啟chrome支援sourcemap


開啟chrome的支援

使用node啟動server


啟動node server

瀏覽器中調試


chrome需要server支援發布

這篇文章是來講模組化的,和發布沒啥關係,但是都寫到這裡了,就把程式發布出去吧,後面藉著這篇文章討論工程化的時候,可以在看看這篇文章的流程如何提高。
發布的方法無非這麼幾種:

  1. windows server的話直接遠程過去,copy一下就好。web deploy這種工具也很好用。
  2. linux使用ftp到遠程,再去copy一下。
  3. 使用rsync。

我們看一下第三種吧。我們用r.js最佳化了以後怎麼發布到伺服器上呢。我們按照Deployment-Techniques這個文章推薦的方法說一說。這個發布方法是在這些考慮下提出的。

  1. 構建後的代碼不提交到版本控制。理由主要是為了好維護,提交前build一下很容易忘記,而且提交最佳化後的代碼如果衝突了很難diff,merge。
  2. 使用r.js在server上產生構建後的代碼也不好,因為r.js會刪除目錄再重新建立,所以如果項目很大,有一段時間服務就會有很多404錯誤。

所以我們想到了用累加式更新的方法去同步資料夾。主要依賴rsync這個命令了。
文章推薦使用grunt工具來打包,然後再跑一個命令去同步資料夾。我們看看代碼。

/** * Gruntfile.js */module.exports = function(grunt) {    // Do grunt-related things in here    var requirejs = require("requirejs"),        exec = require("child_process").exec,        fatal = grunt.fail.fatal,        log = grunt.log,        verbose = grunt.verbose,        FS = require(‘fs‘),        json5 = FS.readFileSync("./build.js", ‘utf8‘),        JSON5 = require(‘json5‘),        // Your r.js build configuration        buildConfigMain = JSON5.parse(json5);    // Transfer the build folder to the right location on the server    grunt.registerTask(        "transfer",        "Transfer the build folder to ../website/www-built and remove it",        function() {            var done = this.async();            // Delete the build folder locally after transferring            exec("rsync -rlv --delete --delete-after ../www-built ../website && rm -rf ../www-built",                function(err, stdout, stderr) {                    if (err) {                        fatal("Problem with rsync: " + err + " " + stderr);                    }                    verbose.writeln(stdout);                    log.ok("Rsync complete.");                    done();                });        }    );    // Build static assets using r.js    grunt.registerTask(        "build",        "Run the r.js build script",        function() {            var done = this.async();            log.writeln("Running build...");            requirejs.optimize(buildConfigMain, function(output) {                log.writeln(output);                log.ok("Main build complete.");                done();            }, function(err) {                fatal("Main build failure: " + err);            });            // This is run after the build completes            grunt.task.run(["transfer"]);        }    );};

運行結果
可以看到建立了一個website檔案夾,並把構建的中間檔案同步到此檔案夾下面了,而website檔案是可以在遠程伺服器的,是不是很方便呢?


發布結果

上面的改動可以從這裡下載到,大家可以把玩一下requirejs-deploy-demo

總結

可以看到,通過require.js,r.js可以很好的進行模組話的開發;使用grunt,rsync,我們可以完成構建和發布的功能。



沈寅
連結:http://www.jianshu.com/p/7186e5f2f341
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

JavaScript模組化-require.js,r.js和打包發布

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.