jdists v2.2.4
jdists 强大的代码块预处理工具
标签: jdists 教程
背景
软件发布流程
通常软件发布时会将源文件做一次「预处理」再编译成可执行文件,才发布到市场。
「预处理」的目的主要是出于以下几点
- 配置线上运行环境,如调试服务地址需变更为实现线上地址;
- 减少执行程序的大小,移除没有使用的代码或资源并压缩;
- 增加逆向工程的成本,给代码做混淆(包括改变标识符和代码结构),降低可读性;
- 移除或增加调试功能,关闭或开启一些特权后门。
一些 IDE 已在「编译」时集成了「预处理」功能。
什么是 jdists
jdists 是一款强大的代码块预处理工具。
什么是「代码块」(code block)?
通常就是注释或注释包裹的代码片段,用于表达各种各样的含义。
举个栗子
- TODO 注释,表示代码中待完善的地方
/* TODO 功能待开发 */
- wiredep 注释,表示引入 bower 组件依赖的 css 资源
<!-- bower:css -->
<link rel="stylesheet" href="bower_components/css/bootstrap.css" />
<!-- endbower -->
- jshint.js 顶部注释,表示版权声明
/*!
* JSHint, by JSHint Community.
*
* This file (and this file only) is licensed under the same slightly modified
* MIT license that JSLint is. It stops evil-doers everywhere:
*
* Copyright (c) 2002 Douglas Crockford (www.JSLint.com)
* .........
*/
- jshint.js 另一部分注释,表示代码检查配置项
/*jshint quotmark:double */
/*global console:true */
/*exported console */
总之,本文所指「代码块」就是有特殊意义的注释。
什么是「代码块预处理」?
指在代码编译之前,将代码文件按代码块粒度做一次编码或解析。
举个栗子,原本无效的代码片段,经过编码后变成了有效代码。
预处理前:
/*<jdists>
console.log('Hello World!');
</jdists>*/
预处理后:
console.log('Hello World!');
市面上还有哪一些「代码块预处理工具」?
市面上有不少,这里只列两个比较典型的。
- 已被普遍使用的 JSDoc,功能是将代码中的注释抽离成 API 文档。
/**
* Represents a book.
* @constructor
* @param {string} title - The title of the book.
* @param {string} author - The author of the book.
*/
function Book(title, author) {
}
- JSDev 是由 JSON 之父 Douglas Crockford 编写。jdists 与 JSDev 的功能类似,但 jdists 功能要复杂很多。
C command line example:
jsdev -comment "Devel Edition." <input >output test_expose enter:trace.enter exit:trace.exit unless:alert
JavaScript:
output = JSDEV(input, [
"test_expose",
"enter:trace.enter",
"exit:trace.exit",
"unless:alert"
] , ["Devel Edition."]);
input:
// This is a sample file.
function Constructor(number) {
/*enter 'Constructor'*/
/*unless(typeof number !== 'number') 'number', "Type error"*/
function private_method() {
/*enter 'private_method'*/
/*exit 'private_method'*/
}
/*test_expose
this.private_method = private_method;
*/
this.priv = function () {
/*enter 'priv'*/
private_method();
/*exit 'priv'*/
}
/*exit "Constructor"*/
}
output:
// Devel Edition.
// This is a sample file.
function Constructor(number) {
{trace.enter('Constructor');}
if (typeof number !== 'number') {alert('number', "Type error");}
function private_method() {
{trace.enter('private_method');}
{trace.exit('private_method');}
}
{
this.private_method = private_method;
}
this.priv = function () {
{trace.enter('priv');}
private_method();
{trace.exit('priv');}
}
{trace.exit("Constructor");}
}
lightly minified:
function Constructor(number) {
function private_method() {
}
this.priv = function () {
private_method();
}
}
预处理以「代码块」为粒度有什么优势?
- 处理速度快,按需对代码块部分进行指定编码;
- 控制力更强,可以控制每个字符的变化;
- 不干扰编译器,编译器天然忽略注释。
现有「代码块预处理工具」存在什么问题?
- 不容易学习和记忆。
begin
还是start
,前缀还是后缀?
<!-- 乐居广告脚本 begin-->
/* jshint ignore:start */
/* TODO 待开发功能 */
- 是否存在闭合不明显。什么时候生效,什么时候失效?
/*jshint unused:true, eqnull:true*/
/*test_expose
this.private_method = private_method;
*/
- 没有标准,不能跨语言。JSDev 和 JSDoc 不能用于其他主流语言,如 Python、Lua 等。
代码预处理的思考
问题也就是:怎么定义、怎么处理、什么情况下触发。
怎么定义「代码块」?
本人拟订了一个基于「XML 标签」+「多行注释」的代码块规范: CBML
优势:
- 学习成本低,XML、多行注释都是大家熟知的东西;
- 标签是否闭合很明显;
- 支持多种主流编程语言。
怎么处理「代码块」?
处理的步骤无外乎就是:输入、编码、输出
经过解析 CBML 的语法树,获取 tag
和 attribute
两个关键信息。
如果 tag
值为 <jdists>
就开始按 jdists 的规则进行处理。
整个处理过程由四个关键属性决定: 1.
import=
指定输入媒介 2.export=
指定输出媒介 3.encoding=
指定编码集合 4.trigger=
指定触发条件
举个例子
/*<jdists export="template.js" trigger="@version < '1.0.0'">
var template = /*<jdists encoding="base64,quoted" import="main.html?template" />*/
/*</jdists>
这里有两个代码块,还是一个嵌套结构
- 外层代码块属性
export="template.js"
指定内容导出到文件template.js
(目录相对于当前代码块所在的文件)。 - 外层代码块属性
trigger="@version < '1.0.0'"
指定命令行参数version
小于'1.0.0'
才触发。 - 内层代码块属性
encoding="base64,quoted"
表示先给内容做一次base64
编码再做一次quoted
即,编码成字符串字面量。
什么情况下触发?
有两个触发条件:
- 当
tag
值为<jdists>
或者是被配置为jdists
标签 - 当属性
trigger=
表达式判断为true
jdists 基本概念
代码块 block
由 tag 标识的代码区域
代码块主要有如下三种形式:
- 空内容代码块,没有包裹任何代码
/*<jdists import="main.js" />*/
- 有效内容代码块,包裹的内容是编译器会解析
/*<jdists encoding="uglify">*/
function format(template, json) {
if (typeof template === 'function') { // 函数多行注释处理
template = String(template).replace(
/[^]*\/\*!?\s*|\s*\*\/[^]*/g, // 替换掉函数前后部分
''
);
}
return template.replace(/#\{(.*?)\}/g, function(all, key) {
return json && (key in json) ? json[key] : "";
});
}
/*</jdists>*/
- 无效内容代码块,包裹的内容也在注释中
/*<jdists>
console.log('version: %s', version);
<jdists>*/
标签 tag
<jdists>
| 自定义
属性 attribute
import=
指定输入媒介export=
指定输出媒介encoding=
指定编码集合trigger=
指定触发条件
媒介 medium
&content
默认为 "&"file
文件> 如: > `main.js` > `index.html`
#variant
变量> 如: > `#name` > `#data`
[file]?block
readonly 代码块,默认file
为当前文件> 如: > `filename?tagName` > `filename?tagName[attrName=attrValue]` > `filename?tagName[attrName=attrValue][attrName2=attrValue2]`
@argument
readonly 控制台参数> 如: > `@output` > `@version`
:environment
readonly 环境变量> 如: > `:HOME` > `:USER`
[...]
、{...}
readonly 字面量> 如: > `[1, 2, 3, 4]` > `{title: 'jdists'}`
'string'
readonly 字符串> 如: > `'zswang'`
触发器 trigger
触发器有两种表达式
- 触发器名列表与控制台参数
--trigger
是否存在交集,存在则被触发
当
$ jdists ... --trigger release
触发
<!--remove trigger="release"-->
<label>release</label>
<!--/remove-->
- 将变量、属性、环境变量表达式替换后的字面量结果是否为 true
当
$ jdists ... --version 0.0.9
触发
<!--remove trigger="@version < '1.0.0'"-->
<label>1.0.0+</label>
<!--/remove-->
如何扩展 jdists
可以参考项目中 processor 目录,中自带编码器的写法
举个栗子
var ejs = require('ejs');
/**
* ejs 模板渲染
*
* @param {string} content 文本内容
* @param {Object} attrs 属性
* @param {string} attrs.data 数据项
* @param {Object} scope 作用域
* @param {Function} scope.execImport 导入数据
* @param {Function} scope.compile 二次编译 jdists 文本
*/
module.exports = function processor(content, attrs, scope) {
if (!content) {
return content;
}
var render = ejs.compile(content);
var data;
if (attrs.data) {
/*jslint evil: true */
data = new Function(
'return (' +
scope.execImport(attrs.data) +
');'
)();
}
else {
data = null;
}
return scope.compile(render(data));
};
详情参考:jdists Scope
用例
代码编译成 dataurl
通过块导入
<!--remove-->
<script>
/*<jdists encoding="base64" id="code">*/
console.log('hello world!');
/*</jdists>*/
</script>
<!--/remove-->
<!--jdists>
<script src="data:application/javascript;base64,/*<jdists import="?[id=code]" />*/"></script>
</jdists-->
通过变量导入
<!--remove-->
<script>
/*<jdists encoding="base64" export="#code">*/
console.log('hello world!');
/*</jdists>*/
</script>
<!--/remove-->
<!--jdists>
<script src="data:application/javascript;base64,/*<jdists import="#code" />*/"></script>
</jdists-->
实战
如何使用
jdists 依赖 node v0.10.0 以上的环境
安装
$ npm install jdists [-g]
命令行
Usage:
jdists <input list> [options]
Options:
-r, --remove Remove block tag name list (default "remove,test")
-o, --output Output file (default STDOUT)
-v, --version Output jdists version
-t, --trigger Trigger name list (default "release")
-c, --config Path to config file (default ".jdistsrc")
JS
var content = jdists.build(filename, {
remove: 'remove,debug',
trigger: 'release'
});
问题反馈和建议
https://github.com/zswang/jdists/issues
开发
复制项目代码
$ git clone https://github.com/zswang/jdists.git
初始化依赖
$ npm install
执行测试用例
$ npm test
预处理
$ npm run dist
代码覆盖率
$ npm run cover
关键文件目录结果
[lib] --- 发布后的代码目录
jdists.js --- jdists 业务代码
scope.js --- jdists 作用域
[processor] --- 预制编码器
[processor-extend] --- 未预制的编码器,可能会常用的
[src] --- 开发期代码
[test] --- 测试目录
[fixtures] --- 测试用例
test.js --- 测试调度文件
index.js --- jdists 声明
cli.js --- jdists 控制台
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago