开源项目的build.js

摘要

build.js 用于生成待发布的文件, 一般会编译成三种,包括纯 ES2015 的模块, commonjs 的模块, umd 的模块。

viser

项目路径 https://github.com/viserjs/viser

const fs = require('fs');
const nodeExec = require('child_process').exec;
const prettyBytes = require('pretty-bytes');
const gzipSize = require('gzip-size');
const ora = require('ora');   // 控制台输出时,提示信息颜色
const _ = require('lodash');
const colors = require('colors');

function exec(shell, extraEnv) {   // 异步执行cli 命令
  return new Promise((resolve, reject) => {     //  child_process.exec(cmd, [options], callback)      直接执行系统命令
    nodeExec(shell, {
      stdio: 'inherit',
      env: Object.assign({}, process.env, extraEnv),    
    }, (error, stdOut) => {
      if (error) {
        reject(error.toString());
      } else {
        resolve(_.trim(stdOut.toString()));
      }
    });
  });
}

async function spinner(message, fn) {
  const oraSpinner = ora(colors.green(message)).start();   // 打印正在执行的命令, 转圈圈显示正在执行

  try {
    await fn(oraSpinner);
    oraSpinner.succeed(colors.gray.dim(message));   // 正常执行,打印绿色命令
  } catch (error) {
    oraSpinner.fail(colors.red(error.toString()));    // 执行失败,打印红色命令
    process.exit(0);    // 结束进程
  }
}

async function build() {   // 异步同时执行编译多种目标格式
  await spinner('Building TSC', async () => {   // 执行 tsconfig.json 编译, 至于 es6 还是 es2015 由 tsconfig.json 决定
    await exec('tsc');
  });

  await spinner('Building CommonJS modules', async (oraSpinner) => {
    await exec('rimraf lib && babel ./es -d lib', {   // 编译成 commonjs 模块
      NODE_ENV: 'commonjs',
      BABEL_ENV: 'cjs'
    });
  });

  await spinner('Building UMD Min modules', async () => {
    await exec('webpack --config webpack.config.js', {   // 编译成 umd 模块
      BABEL_ENV: 'umd',
    });
  });

  const size = gzipSize.sync(
    fs.readFileSync('umd/viser.min.js')
  );

  console.log(`gzipped, the UMD build is ${prettyBytes(size)}`);
}

build();

echarts

项目地址:
因为项目源码使用 amd 形式写的,以下这个脚本就是为了编译成 commonjs。直接用webpack 编译成umd ,并没有写build文件。

var glob = require('glob');    // 文件中文本匹配
var fsExtra = require('fs-extra');   // fs 以外的文件操作方法
var esprima = require('esprima');   // ECMAScript parser   语法解析器

function run(cb) {
    glob('**/*.js', {        // 查找__dirname + '/../src/' 下文件中所有包含 **/*.js  的文本
        cwd: __dirname + '/../src/'
    }, function (err, files) {
        files.forEach(function (filePath) {
            var code = parse(fsExtra.readFileSync(
                __dirname + '/../src/' + filePath, 'utf-8'));
            code = code.replace(/require\(([\'"])zrender\//g, 'require($1zrender/lib/');
            fsExtra.outputFileSync(
                __dirname + '/../lib/' + filePath,
                code, 'utf-8');
        });

        cb && cb();
    });
}

if (require.main === module) {
    run();
}
else {
    module.exports = run;
}

var MAGIC_DEPS = {
    'exports' : true,
    'module' : true,
    'require' : true
};

var SIMPLIFIED_CJS = ['require', 'exports', 'module'];

// Convert AMD-style JavaScript string into node.js compatible module
function parse (raw){   // 解析 var echarts = require('../echarts');    var zrUtil = require('zrender/core/util');
    var output = '';
    var ast = esprima.parse(raw, {
        range: true,
        raw: true
    });

    var defines = ast.body.filter(isDefine);

    if ( defines.length > 1 ){
        throw new Error('Each file can have only a single define call. Found "'+ defines.length +'"');
    } else if (!defines.length){
        return raw;
    }

    var def = defines[0];
    var args = def.expression['arguments'];
    var factory = getFactory( args );
    var useStrict = getUseStrict( factory );

    // do replacements in-place to avoid modifying the code more than needed
    if (useStrict) {
        output += useStrict.expression.raw +';\n';
    }
    output += raw.substring( 0, def.range[0] ); // anything before define
    output += getRequires(args, factory); // add requires
    output += getBody(raw, factory.body, useStrict); // module body

    output += raw.substring( def.range[1], raw.length ); // anything after define

    return output;
}


function getRequires(args, factory){   // 获取依赖
    var requires = [];
    var deps = getDependenciesNames( args );
    var params = factory.params.map(function(param, i){
        return {
            name : param.name,
            // simplified cjs doesn't have deps
            dep : (deps.length)? deps[i] : SIMPLIFIED_CJS[i]
        };
    });

    params.forEach(function(param){
        if ( MAGIC_DEPS[param.dep] && !MAGIC_DEPS[param.name] ) {
            // if user remaped magic dependency we declare a var
            requires.push( 'var '+ param.name +' = '+ param.dep +';' );
        } else if ( param.dep && !MAGIC_DEPS[param.dep] ) {
            // only do require for params that have a matching dependency also
            // skip "magic" dependencies
            requires.push( 'var '+ param.name +' = require(\''+ param.dep +'\');' );
        }
    });

    return requires.join('\n');
}


function getDependenciesNames(args){  // 获取依赖名字
    var deps = [];
    var arr = args.filter(function(arg){
        return arg.type === 'ArrayExpression';
    })[0];

    if (arr) {
        deps = arr.elements.map(function(el){
            return el.value;
        });
    }

    return deps;
}


function isDefine(node){
    return node.type === 'ExpressionStatement' &&
           node.expression.type === 'CallExpression' &&
           node.expression.callee.type === 'Identifier' &&
           node.expression.callee.name === 'define';
}


function getFactory(args){
    return args.filter(function(arg){
        return arg.type === 'FunctionExpression';
    })[0];
}


function getBody(raw, factoryBody, useStrict){
    var returnStatement = factoryBody.body.filter(function(node){
        return node.type === 'ReturnStatement';
    })[0];

    var body = '';
    var bodyStart = useStrict ? useStrict.expression.range[1] + 1 : factoryBody.range[0] + 1;

    if (returnStatement) {
        body += raw.substring( bodyStart, returnStatement.range[0] );
        // "return ".length === 7 so we add "6" to returnStatement start
        body += 'module.exports ='+ raw.substring( returnStatement.range[0] + 6, factoryBody.range[1] - 1 );
    } else {
        // if using exports or module.exports or just a private module we
        // simply return the factoryBody content
        body = raw.substring( bodyStart, factoryBody.range[1] - 1 );
    }

    return body;
}


function getUseStrict(factory){
    return factory.body.body.filter(isUseStrict)[0];
}


function isUseStrict(node){
    return node.type === 'ExpressionStatement' &&
            node.expression.type === 'Literal' &&
            node.expression.value === 'use strict';
}

其他

现在很多开源项目,比如 Vue,Ember,Preact,D3,Three.js,Moment 以及其他许多知名的库都是 rollup 打包 , rollup 会打包成新的标准化格式,纯 es6 模块, 当然也可以指定编译成 commonjs amd 格式的模块。

有篇文章这么定义两个打包工具: 对于应用使用 webpack,对于类库使用 Rollup

posted @ 2018-04-17 23:45  空城夕  阅读(392)  评论(0编辑  收藏  举报