首先,我們需要一個ECMAScript 6 -> ECMAScript5的編譯器,目前非常之多。這里推薦Google出品的traceur-compiler。
這里有一個編譯器列表: es6-tools。
原理
使用這些編譯器,會幫助你把現在用ECMAScript 6語法編寫的javascript文件,編譯生成ECMAScript 5語法的靜態文件。
如果你是coffeescript的使用者,那就很熟悉這種方式,類似coffeescript語法編譯成javascript文件。
因為是靜態文件,所以不會存在運行時再次編譯的性能問題。
另外,也提供了運行時編譯,幫助你直接測試ECMAScript 6語法編寫的文件。
traceur-compiler 使用入門
首先我想說,需求決定了技術的需要。因為一個項目的復雜度,我急于需要yield 和生成器來簡化異步判斷。因為對express和koa那種java式的純面向對象深深的反感之久,(對JAVA C++ 和純面向對象的批判,已經是卡內基·梅隆大學"反模塊化的又是反并行的"的家常便飯),我在長久的積累中,短時間編寫出了ROCORE,并迅速測試優化,迭代進了0.2.17版本(事實上我現在使用的版本又有了新的改變,一個完全采用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文件,你會看到如下的代碼:
"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();
-
運行生成的ECMAScript 5文件
除了全局安裝,你還需要另外復制一份,放到你的項目node_modules中,作為項目依賴。模塊名是traceur。
要運行這個ec5.js文件,還需要引入traceur模塊。在文件上部,加上
require('traceur');
然后保存。運行
$ node ec5.js
OK,輸出101. 現在在nodejs 0.10中,能夠良好的運行這個文件。
-
總結一下過程
編寫ECMAScript 6文件 -> 編譯,生成ECMAScript 5文件 -> 修改ECMAScript 5文件,在最上邊加入require('traceur')保存 -> 運行ECMAScript 5文件。
在實際使用時,只需要在app.js(或者server.js)主文件中加入require('traceur')既可以。
批量編譯
我們的項目文件可是有大量的文件,總不能一個一個動手編譯吧?!OK。這里有一個批量編譯的腳本程序,復制然后保存到一個js文件里,設置要編譯的主目錄和要輸出的目標目錄,ta會把主目錄的所有js文件編譯到目標目錄,并且支持遞歸內部文件,非js文件被原樣不變的復制到目標目錄。
另外,建議只編譯復制lib目錄中的文件,其他目錄的文件手動復制到目標目錄。
// 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);
}
});
運行時編譯和單元測試
在項目開發過程,我們可不想寫完一個文件就編譯一次,所以我們還有個法子,在開發的時候使用運行時編譯。等全部開發完,測試完,然后一次編譯,上線。
在項目建立一個test-traceur.js文件:
// 這段代碼請原樣復制
require('traceur').require.makeDefault(function(filename) {
return filename.indexOf('node_modules') === -1;
});
// 這里引入需要測試的目標文件
require('./test/my.js');
然后,運行測試:
$ node test-traceur.js
程序就會自動編譯my.js和my.js所依賴的文件,并且運行。
test-traceur.js文件里邊的寫法是必須這樣的,只能引入一個測試文件,這是由trace-compiler設定的,具體可以參看其文檔。
我有一個批量測試腳本,如果你需要的話:
建立兩個文件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為要測試目錄,運行
$ node test-all.js
在測試目錄的文件就會依次運行。(他們和他們依賴的文件完全可以是ECMAScrip 6語法的文件)
最后,推薦你使用ROCORE簡化你的異步開發
即便你不想改變你的express或者其他,你仍然能從ROCORE獲得幫助,比如里邊的scc和mcc這兩個隨時可用的異步工具:
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);
});