JavaScript模塊化-require.js,r.js和打包發布
在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和打包發布