標籤: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技術來調試最佳化後的代碼。進行如下操作。
- 修改build.js,增加如下配置
generateSourceMaps: true,preserveLicenseComments: false,optimize: "uglify2",
- 重新構建
node r.js -o build.js
開啟瀏覽器支援
這裡最好用firefox瀏覽器,chrome從本地檔案開啟html不能正常使用sourcemap。直接用firefox瀏覽就可以了。
firefox支援sourcemap
可以看到可以載入非最佳化的代碼,有人會問,這不要請求多次嗎?最佳化一份,非最佳化一份,這樣不是效能更差勁。其實只有你調試的時候,開啟了這個功能才會請求對應的sourcemap檔案,所以對使用者來說並不浪費。
寫一個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支援發布
這篇文章是來講模組化的,和發布沒啥關係,但是都寫到這裡了,就把程式發布出去吧,後面藉著這篇文章討論工程化的時候,可以在看看這篇文章的流程如何提高。
發布的方法無非這麼幾種:
- windows server的話直接遠程過去,copy一下就好。web deploy這種工具也很好用。
- linux使用ftp到遠程,再去copy一下。
- 使用rsync。
我們看一下第三種吧。我們用r.js最佳化了以後怎麼發布到伺服器上呢。我們按照Deployment-Techniques這個文章推薦的方法說一說。這個發布方法是在這些考慮下提出的。
- 構建後的代碼不提交到版本控制。理由主要是為了好維護,提交前build一下很容易忘記,而且提交最佳化後的代碼如果衝突了很難diff,merge。
- 使用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和打包發布