基于语法分析的CMD格式标准化工具
背景
新前端资源加载方式将代码依赖的打包编译转移到线下,那么线上就不需要独角兽对资源进行打包,线上静态资源服务可以变得更纯粹。而线下代码,可以使用标准的CommonJS方式书写,基于此的js的运行环境也不再局限于浏览器。
前端js/6v下的代码采用了seajs划分模块,遵循CMD规范,与CommonJS差别仅在于头尾多了define
的包装。如果要将CMD规范的代码迁移到CommonJS规范,仅需去除define
定义。如下:
CMD规范代码,基于seajs可在浏览器端运行。
// 所有模块都通过 define 来定义
define(function(require, exports, module) {
// 通过 require 引入依赖
var $ = require('jquery');
// 通过 exports 对外提供接口
exports.doSomething = ...
// 或者通过 module.exports 提供整个接口
module.exports = ...
});
CommonJS,可在node端运行。
// 通过 require 引入依赖
var fs = require('fs');
// 通过 exports 对外提供接口
exports.doSomething = ...
// 或者通过 module.exports 提供整个接口
module.exports = ...
但是,seajs又在返回模块上加了语法糖封装,支持以下更简化的写法。
// 所有模块都通过 define 来定义
define(function(require) {
// 通过 require 引入依赖
var $ = require('jquery');
// 等同于module.exports = ...
return ...;
});
这种写法有异于CommonJS,仅使用到了模块引用(require),模块导出无需模块定义(exports)或模块标识(module)的方式传递,而使用了直接return的方式。
所以,js/6v下的代码要迁移到CommonJS规范的代码需要2步:
- 把用语法糖书写的方式的代码CMD格式标准化;
- CMD标准格式的代码去
define
变成CommonJS规范。
单个文件操作起来并不复杂,以迁移询盘系统(gangesweb)为例,总文件1135个,仅js也有800多个,人肉修改是个苦力活。编辑器批量替换实现难度也很高。那么久只能通过编写工具来实现。
解决方案与使用方法
为此,用node写了个小工具cmdfix来解决背景问题中的第一步。(PS:第二步架构组有相应的工具解决,只针对标准化的CMD迁移CommonJS,所以还是需要通过cmdfix完成seajs代码CMD格式标准化处理)。
通过npm可安装:
npm install cmdfix -g
安装后可以通过命令行调用:
cmdfix <target> [ignore]
target:必选,扫描修正的目录或文件地址;ignore:可选,扫描目录下忽略的目录或文件,多个用逗号隔开。
例子:
cmdfix ./htdocs/js/6v/biz/gangesweb ./customer,./fastfeedback
扫描修正gangesweb下除了customer和fastfeedback以外的所有目录下的文件。
也可以把它作为可以node模块引入使用,如:
var cmd = require('cmdfix');
var result = cmd.fix({
"target": "", // 目标地址,扫描修正的目标目录或文件
"destination": "", // 修正后代码输出路径,为空则直接替换源文件
"suffix": [".js"], // 修正的代码文件后缀名
"ignore": [] // 忽略扫描的子目录或文件,地址相对于目标地址
}, function (scans, index) { // 进度回调,每处理完一个文件执行
// scans 所有需要处理的文件
// index 当前已经处理过的文件索引
});
/*
* 处理结果,包括:
* scans所有扫描处理的文件路径;
* cmds所有CMD规范的文件路径;
* fixed所有修正处理的文件路径;
*/
console.log(result);
基于语法分析的实现
要完成代码修正,最容易想到的就是用正则替换最后的return
为module.exports
和补全require, exports, module
。但是考虑上以下场景就发现没那么简单了:
// return一个带有return的函数
define(function(require) {
var $ = require('$');
// doSomething ...
return (function(){
// doSomething ...
return 'something';
});
});
// 用其他命名代替require。exports,module类同
define(function(r, e) {
var $ = r('$');
e.doSomething = function() {
// doSomething ...
}
});
// 工厂函数参数为0个、1个(即require)、2个(即require和exports)和3个(require,exports,module)的情况
define(function(require, exports) {
var $ = require('$');
exports.doSomething = function() {
// doSomething ...
}
});
// 多写了个空的return
define(function() {
// doSomething ...
return;
});
应对以上一些可能的场景,正则的替换处理就会变得特别复杂,而且还容易出错。
所以从保证工具准确率的角度上,使用基于语法分析的实现会更安全,具体的代码实现就不展开细节。
应用场景
首先当然就是6v下的应用代码的修正,这里以修正询盘系统(gangesweb)的代码为例:

耗时3秒不到扫描了885个js文件,其中CMD模块658个,修正了647个非标准化CMD格式的模块。
对修正后代码进行diff查看,准确率100%;
除了近期要迁移到git上的gangesweb代码,其他业务线迁移前需要对代码进行CMD标准化修正,都可以使用。
另外一个应用场景,就是可以通过node模块调用的方式,使用到架构组CMD到CommonJS迁移的工具中,弥补未做CMD标准化处理的缺失。甚至可以使用cmdfix的语法分析方案,改进迁移工具。