担心ECMAScript 6来的太晚? 不需要! nodejs

作者: Tulayang | 来源:发表于2015-01-03 22:15 被阅读2407次

    首先,我们需要一个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作为编译器。

    1. 安装

      npm install -g traceur

    2. 编写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();  
    
    1. 编译生成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();

    1. 运行生成的ECMAScript 5文件

      除了全局安装,你还需要另外复制一份,放到你的项目node_modules中,作为项目依赖。模块名是traceur。

      要运行这个ec5.js文件,还需要引入traceur模块。在文件上部,加上

      require('traceur');
      

      然后保存。运行
      $ node ec5.js

      OK,输出101. 现在在nodejs 0.10中,能够良好的运行这个文件。

    2. 总结一下过程

      编写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);
    });
    

    相关文章

      网友评论

      • Tulayang:@mdemo 其实我觉得6to5也是不错的,而且文档写的也很详细。
      • mdemo:不选择6to5的原因是什么

      本文标题:担心ECMAScript 6来的太晚? 不需要! nodejs

      本文链接:https://www.haomeiwen.com/subject/fngztttx.html