首先辉浦,我們需要一個(gè)ECMAScript 6 -> ECMAScript5的編譯器,目前非常之多。這里推薦Google出品的traceur-compiler庄涡。
這里有一個(gè)編譯器列表: es6-tools弄屡。
原理
使用這些編譯器题禀,會(huì)幫助你把現(xiàn)在用ECMAScript 6語法編寫的javascript文件,編譯生成ECMAScript 5語法的靜態(tài)文件膀捷。
如果你是coffeescript的使用者迈嘹,那就很熟悉這種方式,類似coffeescript語法編譯成javascript文件全庸。
因?yàn)槭庆o態(tài)文件秀仲,所以不會(huì)存在運(yùn)行時(shí)再次編譯的性能問題。
另外壶笼,也提供了運(yùn)行時(shí)編譯神僵,幫助你直接測試ECMAScript 6語法編寫的文件。
traceur-compiler 使用入門
首先我想說覆劈,需求決定了技術(shù)的需要保礼。因?yàn)橐粋€(gè)項(xiàng)目的復(fù)雜度,我急于需要yield 和生成器來簡化異步判斷责语。因?yàn)閷?duì)express和koa那種java式的純面向?qū)ο笊钌畹姆锤兄茫?對(duì)JAVA C++ 和純面向?qū)ο蟮呐信谡希呀?jīng)是卡內(nèi)基·梅隆大學(xué)"反模塊化的又是反并行的"的家常便飯),我在長久的積累中坤候,短時(shí)間編寫出了ROCORE铝阐,并迅速測試優(yōu)化,迭代進(jìn)了0.2.17版本(事實(shí)上我現(xiàn)在使用的版本又有了新的改變铐拐,一個(gè)完全采用ECMAScript 6語法beta的0.3.1版本)徘键。
為了能夠迅速兼容nodejs 0.10练对,我找到了6to5, traceur-compiler,并選擇了traceur-compiler作為編譯器吹害。
-
安裝
npm install -g traceur
編寫ECMAScript 6語法的文件
編寫你的ECMAScript 6文件螟凭,例如ec6.js文件:
// ec6.js
let a = 1;
function* g() {
yield 100;
console.log(a + 100);
}
var it = g();
it.next();
it.next();
- 編譯生成ECMAScript 5語法的文件
使用traceur-compiler編譯文件,生成ECMAScript文件它呀,比如叫做ec5.js螺男。
$ traceur ec6.js --out ec5.js --modules=commonjs
打開得到的ec5.js文件,你會(huì)看到如下的代碼:
"use strict";
var $__0 = $traceurRuntime.initGeneratorFunction(g);
var __moduleName = "tt.js";
var a = 1;
function g() {
return $traceurRuntime.createGeneratorInstance(function($ctx) {
while (true)
switch ($ctx.state) {
case 0:
$ctx.state = 2;
return 100;
case 2:
$ctx.maybeThrow();
$ctx.state = 4;
break;
case 4:
console.log(a + 100);
$ctx.state = -2;
break;
default:
return $ctx.end();
}
}, $__0, this);
}
var it = g();
it.next();
it.next();
-
運(yùn)行生成的ECMAScript 5文件
除了全局安裝纵穿,你還需要另外復(fù)制一份下隧,放到你的項(xiàng)目node_modules中,作為項(xiàng)目依賴谓媒。模塊名是traceur淆院。
要運(yùn)行這個(gè)ec5.js文件,還需要引入traceur模塊句惯。在文件上部土辩,加上
require('traceur');
然后保存。運(yùn)行
$ node ec5.js
OK抢野,輸出101. 現(xiàn)在在nodejs 0.10中拷淘,能夠良好的運(yùn)行這個(gè)文件。
-
總結(jié)一下過程
編寫ECMAScript 6文件 -> 編譯指孤,生成ECMAScript 5文件 -> 修改ECMAScript 5文件启涯,在最上邊加入require('traceur')保存 -> 運(yùn)行ECMAScript 5文件。
在實(shí)際使用時(shí)恃轩,只需要在app.js(或者server.js)主文件中加入require('traceur')既可以结洼。
批量編譯
我們的項(xiàng)目文件可是有大量的文件,總不能一個(gè)一個(gè)動(dòng)手編譯吧详恼?!OK引几。這里有一個(gè)批量編譯的腳本程序昧互,復(fù)制然后保存到一個(gè)js文件里,設(shè)置要編譯的主目錄和要輸出的目標(biāo)目錄伟桅,ta會(huì)把主目錄的所有js文件編譯到目標(biāo)目錄敞掘,并且支持遞歸內(nèi)部文件,非js文件被原樣不變的復(fù)制到目標(biāo)目錄楣铁。
另外玖雁,建議只編譯復(fù)制lib目錄中的文件,其他目錄的文件手動(dòng)復(fù)制到目標(biāo)目錄盖腕。
// compile.js
var traceur = require('traceur');
var fs = require('fs');
var path = require('path');
var PATH_SOURCE = '/home/king/box-fork/dist';
var PATH_TARGET = '/home/king/box-fork-compile';
function readSync(paths, i, f) {
if (i >= 0 && i < paths.length) {
var stats = fs.statSync(paths[i]);
if (stats.isFile()) {
f('file', paths[i]);
return readSync(paths, ++i, f);
} else if (stats.isDirectory()) {
var newPaths = fs.readdirSync(paths[i]).map(function (pathname) {
return path.join(paths[i], pathname);
});
f('directory', paths[i]);
return readSync(paths.slice(0, i).concat(newPaths,
paths.slice(i + 1)),
i, f);
} else {
return readSync(paths.slice(0, i).concat(paths.slice(i + 1)),
i, f);
}
} else {
return paths;
}
}
readSync([PATH_SOURCE], 0, function (type, pathname) {
var newPath = path.join(PATH_TARGET, pathname.replace(new RegExp('^' + source.replace(/\//g,'\/').replace(/\\/g,'\\')), ''));
if (type === 'file') {
if (path.extname(pathname) !== '.js') {
console.log('copy %s %s', pathname, newPath);
fs.writeFileSync(newPath, fs.readFileSync(pathname));
return;
}
console.log('traceur %s %s', pathname, newPath);
var src = fs.readFileSync(pathname, {encoding:'utf8'});
var options = {};
var compiled = traceur.compile(src, options);
fs.writeFileSync(newPath, compiled, {encoding:'utf8'});
}
if (type === 'directory') {
console.log('mkdir %s', newPath);
fs.mkdirSync(newPath);
}
});
運(yùn)行時(shí)編譯和單元測試
在項(xiàng)目開發(fā)過程赫冬,我們可不想寫完一個(gè)文件就編譯一次浓镜,所以我們還有個(gè)法子,在開發(fā)的時(shí)候使用運(yùn)行時(shí)編譯劲厌。等全部開發(fā)完膛薛,測試完,然后一次編譯补鼻,上線哄啄。
在項(xiàng)目建立一個(gè)test-traceur.js文件:
// 這段代碼請(qǐng)?jiān)瓨訌?fù)制
require('traceur').require.makeDefault(function(filename) {
return filename.indexOf('node_modules') === -1;
});
// 這里引入需要測試的目標(biāo)文件
require('./test/my.js');
然后,運(yùn)行測試:
$ node test-traceur.js
程序就會(huì)自動(dòng)編譯my.js和my.js所依賴的文件风范,并且運(yùn)行咨跌。
test-traceur.js文件里邊的寫法是必須這樣的,只能引入一個(gè)測試文件硼婿,這是由trace-compiler設(shè)定的锌半,具體可以參看其文檔。
我有一個(gè)批量測試腳本加酵,如果你需要的話:
建立兩個(gè)文件test-traceur.js, test-all.js
test-traceur.js:
require('traceur').require.makeDefault(function(filename) {
return filename.indexOf('node_modules') === -1;
});
require(process.argv[3]);
- test-all.js:
var fs = require('fs');
var path = require('path');
var child_process = require('child_process');
var ROOT = path.join(__dirname, '../test');
var ROOT_TRACE = path.join(__dirname, 'test-traceur.js');
(function test(paths, i) {
if (i >= 0 && i < paths.length) {
var stats = fs.statSync(paths[i]);
if (stats.isFile()) {
if (path.extname(paths[i]) === '.js') {
console.log('\ntest: %s', paths[i]);
child_process.exec('node ' + ROOT_TRACE + ' -f ' + paths[i], function (err, stdout, stderr) {
if (err) {
throw err;
}
console.log('OK: %s', paths[i]);
test(paths, i + 1);
});
} else {
test(paths, i + 1);
}
} else if (stats.isDirectory()) {
var newPaths = fs.readdirSync(paths[i]).map(function (pathname) {
return path.join(paths[i], pathname);
});
test(paths.slice(0, i).concat(newPaths, paths.slice(i + 1)), i);
} else {
test(paths.slice(0, i).concat(paths.slice(i + 1)), i);
}
} else {
console.log('\ncomplete\n');
}
} ([ROOT], 0));
在test-all.js文件中拳喻,修改ROOT為要測試目錄,運(yùn)行
$ node test-all.js
在測試目錄的文件就會(huì)依次運(yùn)行猪腕。(他們和他們依賴的文件完全可以是ECMAScrip 6語法的文件)
最后冗澈,推薦你使用ROCORE簡化你的異步開發(fā)
即便你不想改變你的express或者其他,你仍然能從ROCORE獲得幫助陋葡,比如里邊的scc和mcc這兩個(gè)隨時(shí)可用的異步工具:
var R = require('rocore');
var fs = require('fs');
R.scc(function* (ynext) {
var [err, data] = yield fs.readFile(__dirname + 'my.conf', {encoding:'utf8'}, ynext);
if (err) throw err;
yield fs.writeFile(__dirname + 'new.conf', data, {encoding:'utf8'}, ynext);
console.log('done');
});
var R = require('rocore');
var pool = require('mysql').createPool(/*配置*/);
R.scc(function* (ynext) {
var {employee, order, position} = yield R.mcc(function* (ynext) {
yield pool.query('SELECT * FROM employee', ynext('employee'));
yield pool.query('SELECT * FROM order', ynext('order'));
yield pool.query('SELECT * FROM position', ynext('position'));
}, ynext);
console.log(employee);
console.log(order);
console.log(position);
});