jdists 一款强大的代码块预处理工具
来源:互联网 发布:mac表情 编辑:程序博客网 时间:2024/06/01 07:29
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-->
实战
- 给源文件添加版权信息
../package.json
{ "name": "jdists", "version": "0.9.7", "description": "Code block processing tools", ... "author": { "name": "zswang", "url": "http://weibo.com/zswang" }, "license": "MIT", ...}
input
/*<jdists encoding="ejs" data="../package.json">*//** * @file <%- name %> scope * * <%- description %> * @author <% (author instanceof Array ? author : [author]).forEach(function (item) { %> * <%- item.name %> (<%- item.url %>) <% }); %> * @version <%- version %> <% var now = new Date() %> * @date <%- [ now.getFullYear(), now.getMonth() + 101, now.getDate() + 100 ].join('-').replace(/-1/g, '-') %> *//*</jdists>*/var colors = require('colors');var util = require('util');...
output
/** * @file jdists scope * * Code block processing tools * @author * zswang (http://weibo.com/zswang) * @version 0.9.7 * @date 2015-08-03 */var colors = require('colors');var util = require('util');...
- 代码混合加密
jdists 支持嵌套,这样就可以采用组合加密和嵌套加密的方式。让破解更困难
input
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title></title></head><body> <h1></h1> <h2></h2> <h3></h3></body><script>/*<jdists encoding="zero">*/ /*<jdists encoding="aaencode">*/ document.querySelector('h1').innerHTML = '编译后不可见 1'; /*</jdists>*/ /*<jdists encoding="jjencode">*/ document.querySelector('h2').innerHTML = '编译后不可见 2'; /*</jdists>*/ document.querySelector('h3').innerHTML = '编译后不可见 3';/*</jdists>*/</script></html>
output
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title></title></head><body> <h1></h1> <h2></h2> <h3></h3></body><script>(function(){}).constructor("".replace(/./g,function(a){return{"":0,"":1}[a]}).replace(/.{7}/g,function(a){return String.fromCharCode(parseInt(a,2))}))();</script></html>
- 预制默认插件
通常预制插件都是不断添加或移除,怎么才能不修代码就完成插件的预制?可以借助 glob 和一个模板引擎完成。
input
var colors = require('colors/safe');/*<remove>*/var defaultProcessors = { "ejs": require('../processor/processor-ejs'), "glob": require('../processor/processor-glob'), "jhtmls": require('../processor/processor-jhtmls'),};/*</remove>*//*<jdists encoding="glob" pattern="../processor/*.js" export="#processors" />*//*<jdists encoding="jhtmls" data="#processors">var path = require('path');!#{'var defaultProcessors = {'}forEach(function (process) { "!#{path.basename(process, '.js').replace(/^processor-/, '')}": require('#{process.replace(/\.js$/, '')}'),});!#{'};'}</jdists>*/
output
var colors = require('colors/safe');var defaultProcessors = { "aaencode": require('../processor/processor-aaencode'), "autoprefixer": require('../processor/processor-autoprefixer'), "base64": require('../processor/processor-base64'), "candy": require('../processor/processor-candy'), "clean-css": require('../processor/processor-clean-css'), "ejs": require('../processor/processor-ejs'), "glob": require('../processor/processor-glob'), "html": require('../processor/processor-html'), "indent": require('../processor/processor-indent'), "jade": require('../processor/processor-jade'), "jhtmls": require('../processor/processor-jhtmls'), "jjencode": require('../processor/processor-jjencode'), "jsdev": require('../processor/processor-jsdev'), "less": require('../processor/processor-less'), "md5": require('../processor/processor-md5'), "quoted": require('../processor/processor-quoted'), "regex": require('../processor/processor-regex'), "slice": require('../processor/processor-slice'), "svgo": require('../processor/processor-svgo'), "trim": require('../processor/processor-trim'), "uglify": require('../processor/processor-uglify'), "url": require('../processor/processor-url'), "yml2json": require('../processor/processor-yml2json'), "zero": require('../processor/processor-zero'),};
- 防止静态资源被搜索
通常静态资源的字符串是连续的,通过搜索关键字很容易定位并修改其内容。如果将连续的字符串分开赋值,那么定位就不能简单的搜索完成
input
#include<stdio.h>int main(void){ /*<jdists export="#encode"> function (content) { return content.replace(/char\s+(\w+)\[\s*(\d+)\s*\]\s*=\s*"(.*?)"/g, function (all, name, len, value) { var items = value.split('').concat([0]); items = items.map(function (item, index) { var value = item === 0 ? "'\\0'" : "'" + item + "'"; return '\t' + name + '[' + index + ']=' + value; }); return 'char ' + name + '[' + len + '];\n' + items.join(';\n'); } ); } </jdists>*/ /*<jdists encoding="#encode">*/ char _link1[100] = "http://legend.baidu.com/"; char _link2[100] = "http://shushuo.baidu.com/"; /*</jdists>*/ printf("link1: %s\n link2", _link2, _link2); return 0;}
output
#include<stdio.h>int main(void){ char _link1[100]; _link1[0]='h'; _link1[1]='t'; _link1[2]='t'; _link1[3]='p'; _link1[4]=':'; _link1[5]='/'; _link1[6]='/'; _link1[7]='l'; _link1[8]='e'; _link1[9]='g'; _link1[10]='e'; _link1[11]='n'; _link1[12]='d'; _link1[13]='.'; _link1[14]='b'; _link1[15]='a'; _link1[16]='i'; _link1[17]='d'; _link1[18]='u'; _link1[19]='.'; _link1[20]='c'; _link1[21]='o'; _link1[22]='m'; _link1[23]='/'; _link1[24]='\0'; char _link2[100]; _link2[0]='h'; _link2[1]='t'; _link2[2]='t'; _link2[3]='p'; _link2[4]=':'; _link2[5]='/'; _link2[6]='/'; _link2[7]='s'; _link2[8]='h'; _link2[9]='u'; _link2[10]='s'; _link2[11]='h'; _link2[12]='u'; _link2[13]='o'; _link2[14]='.'; _link2[15]='b'; _link2[16]='a'; _link2[17]='i'; _link2[18]='d'; _link2[19]='u'; _link2[20]='.'; _link2[21]='c'; _link2[22]='o'; _link2[23]='m'; _link2[24]='/'; _link2[25]='\0'; printf("link1: %s\n link2", _link2, _link2); return 0;}
- 引入其他代码处理工具
先将其他「代码处理工具」定义为「处理器」,然后按正常编码解析即可。
processor-jsdev.js
var jsdev = require('JSDev');/** * JSDev 编码 * * @see https://github.com/douglascrockford/JSDev * @param {string} content 文本内容 * @param {Object} attrs 属性 * @param {string} attrs.data 数据项 * @param {Object} scope 作用域 * @param {Function} scope.execImport 导入数据 */module.exports = function processor(content, attrs, scope) { if (!content) { return content; } var tags; if (attrs.tags) { /*jslint evil: true */ tags = scope.execImport(attrs.tags); if (/^\[[^]*\]$/.test(tags)) { // array tags = new Function( 'return (' + tags + ');' )(); } else { tags = tags.split(/\s*[,\n]\s*/); } } else { tags = null; } var comments; if (attrs.comments) { comments = scope.execImport(attrs.comments); } else { comments = null; } return jsdev(content, tags, comments);};
input
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>jsdev</title></head><body></body><script>/*<jdists encoding="uglify" trigger="#env === 'release'">*//*<jdists export="#env">debug</jdists>*//*<jdists export="#tags" trigger="#env === 'debug'">test_exposeenter:trace.enterexit:trace.exitunless:alert</jdists>*//*<jdists encoding="jsdev" tags="#tags" comments="Devel Edition" trigger="#env === 'debug'">*/// 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"*/}/*</jdists>*//*<jdists trigger="#env === 'debug'">var trace = { stack: [], enter: function (name) { this.stack.push(new Date); console.log('enter %s', name); }, exit: function (name) { var t = this.stack.pop(); if (t) { console.log('exit %s (%dms)', name, new Date() - t); } else { console.log('exit %s', name); } }};</jdists>*/new Constructor(1);new Constructor('1');/*</jdists>*/</script></html>
output
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>jsdev</title></head><body></body><script>/*<jdists encoding="uglify" trigger="#env === 'release'">*/// 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");}}var trace = { stack: [], enter: function (name) { this.stack.push(new Date); console.log('enter %s', name); }, exit: function (name) { var t = this.stack.pop(); if (t) { console.log('exit %s (%dms)', name, new Date() - t); } else { console.log('exit %s', name); } }};new Constructor(1);new Constructor('1');/*</jdists>*/</script></html>
如何使用
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
关键文件目录结果
[lib] --- 发布后的代码目录 jdists.js --- jdists 业务代码 scope.js --- jdists 作用域[processor] --- 预制编码器[processor-extend] --- 未预制的编码器,可能会常用的[src] --- 开发期代码[test] --- 测试目录 [fixtures] --- 测试用例 test.js --- 测试调度文件index.js --- jdists 声明cli.js --- jdists 控制台
项目地址:https://github.com/zswang/jdists
欢迎前往了解和交流
- jdists 一款强大的代码块预处理工具
- jdists 前端代码块预处理工具
- LANs.py:一款可以实现代码注入,无线渗透和WiFi用户监控的强大工具
- 介绍一款强大的文档搜索工具——grep
- R-Studio,一款强大的数据恢复工具
- TexturePacker是一款很强大的游戏图片制作工具
- RxTools一款强大实用的工具类集合
- TeamViewer——一款强大的远程控制工具
- 强大的代码生成工具MyGeneration
- 强大的代码生成工具MyGeneration
- 强大的代码生成工具MyGeneration
- 强大的代码阅读工具Understand
- 【推荐】强大的代码阅读工具Understand
- 扫描分析代码漏洞的强大工具
- SuperTextView:一款强大的TextView
- 代码阅读工具强大的代码阅读工具Understand
- Stimulsoft Reports.Silverlight是一款强大的基于Silverlight平台的报表创建工具控件
- 将Vim配置成为一款强大的编辑工具之 ctags的安装和使用
- 剑指Offer面试题51(Java版):数组中重复的数字
- Android Api Demos登顶之路(三十)Display Options
- 移动时代的前端加密
- cassandra nodejs driver maillist link
- Scala入门到精通——第二十四节 高级类型 (三)
- jdists 一款强大的代码块预处理工具
- POJ 2559 单调栈模板题
- OpenCV_局部图像特征的提取与匹配_源代码
- 【小熊刷题】Longest Substring Without Repeating Characters
- 连载六-------视图控制器
- css前缀含义
- Android相机、相册获取图片
- OpenCV SURF SIFT特征提取及RANSAC算法
- Codeforces Round #315 (Div. 2)569D Symmetric and Transitive(dp)