UNPKG

13.3 kBMarkdownView Raw
1# jdists 强大的代码块预处理工具
2
3标签: jdists 教程
4
5---
6
7[![Build Status](https://img.shields.io/travis/zswang/jdists/master.svg)](https://travis-ci.org/zswang/jdists)
8[![NPM version](https://img.shields.io/npm/v/jdists.svg)](http://badge.fury.io/js/jdists)
9[![NPM download](https://img.shields.io/npm/dm/jdists.svg)](https://www.npmjs.com/package/jdists)
10[![Coverage Status](https://coveralls.io/repos/zswang/jdists/badge.svg?branch=master&service=github)](https://coveralls.io/github/zswang/jdists?branch=master)
11
12![jdists logo](https://cloud.githubusercontent.com/assets/536587/9022251/4d33427c-38a1-11e5-98e5-37b6a1c69a85.png)
13
14## 背景
15
16### 软件发布流程
17
18![code pretreatment](https://cloud.githubusercontent.com/assets/536587/9024268/5275fe58-38f8-11e5-9306-89e6c1840f97.png)
19
20通常软件发布时会将源文件做一次「预处理」再编译成可执行文件,才发布到市场。
21
22### 「预处理」的目的主要是出于以下几点
23
24* 配置线上运行环境,如调试服务地址需变更为实现线上地址;
25* 减少执行程序的大小,移除没有使用的代码或资源并压缩;
26* 增加逆向工程的成本,给代码做混淆(包括改变标识符和代码结构),降低可读性;
27* 移除或增加调试功能,关闭或开启一些特权后门。
28
29> 一些 IDE 已在「编译」时集成了「预处理」功能。
30
31## 什么是 jdists
32
33jdists 是一款强大的代码块预处理工具。
34
35### 什么是「代码块」(code block)?
36
37通常就是注释或注释包裹的代码片段,用于表达各种各样的含义。
38
39> 举个栗子
40
41+ TODO 注释,表示代码中待完善的地方
42```js
43/* TODO 功能待开发 */
44```
45----
46+ [wiredep][1] 注释,表示引入 bower 组件依赖的 css 资源
47```html
48 <!-- bower:css -->
49 <link rel="stylesheet" href="bower_components/css/bootstrap.css" />
50 <!-- endbower -->
51```
52----
53+ [jshint.js][2] 顶部注释,表示版权声明
54```js
55/*!
56 * JSHint, by JSHint Community.
57 *
58 * This file (and this file only) is licensed under the same slightly modified
59 * MIT license that JSLint is. It stops evil-doers everywhere:
60 *
61 * Copyright (c) 2002 Douglas Crockford (www.JSLint.com)
62 * .........
63 */
64```
65----
66+ jshint.js 另一部分注释,表示代码检查配置项
67```js
68/*jshint quotmark:double */
69/*global console:true */
70/*exported console */
71```
72总之,本文所指「代码块」就是有特殊意义的注释。
73
74### 什么是「代码块预处理」?
75
76指在代码编译之前,将代码文件按代码块粒度做一次编码或解析。
77
78> 举个栗子,原本无效的代码片段,经过编码后变成了有效代码。
79
80预处理前:
81```js
82/*<jdists>
83console.log('Hello World!');
84</jdists>*/
85```
86
87预处理后:
88```js
89console.log('Hello World!');
90```
91
92### 市面上还有哪一些「代码块预处理工具」?
93
94市面上有不少,这里只列两个比较典型的。
95
96+ 已被普遍使用的 [JSDoc][3],功能是将代码中的注释抽离成 API 文档。
97
98```js
99/**
100 * Represents a book.
101 * @constructor
102 * @param {string} title - The title of the book.
103 * @param {string} author - The author of the book.
104 */
105function Book(title, author) {
106}
107```
108----
109+ [JSDev][4] 是由 JSON 之父 Douglas Crockford 编写。jdists 与 JSDev 的功能类似,但 jdists 功能要复杂很多。
110
111C command line example:
112
113```shell
114 jsdev -comment "Devel Edition." <input >output test_expose enter:trace.enter exit:trace.exit unless:alert
115```
116
117JavaScript:
118```js
119 output = JSDEV(input, [
120 "test_expose",
121 "enter:trace.enter",
122 "exit:trace.exit",
123 "unless:alert"
124 ] , ["Devel Edition."]);
125```
126input:
127```js
128 // This is a sample file.
129
130 function Constructor(number) {
131 /*enter 'Constructor'*/
132 /*unless(typeof number !== 'number') 'number', "Type error"*/
133 function private_method() {
134 /*enter 'private_method'*/
135 /*exit 'private_method'*/
136 }
137 /*test_expose
138 this.private_method = private_method;
139 */
140 this.priv = function () {
141 /*enter 'priv'*/
142 private_method();
143 /*exit 'priv'*/
144 }
145 /*exit "Constructor"*/
146 }
147```
148
149output:
150
151```js
152 // Devel Edition.
153 // This is a sample file.
154
155 function Constructor(number) {
156 {trace.enter('Constructor');}
157 if (typeof number !== 'number') {alert('number', "Type error");}
158 function private_method() {
159 {trace.enter('private_method');}
160 {trace.exit('private_method');}
161 }
162 {
163 this.private_method = private_method;
164 }
165 this.priv = function () {
166 {trace.enter('priv');}
167 private_method();
168 {trace.exit('priv');}
169 }
170 {trace.exit("Constructor");}
171 }
172```
173
174lightly minified:
175
176```js
177 function Constructor(number) {
178 function private_method() {
179 }
180 this.priv = function () {
181 private_method();
182 }
183 }
184```
185
186### 预处理以「代码块」为粒度有什么优势?
187
188* 处理速度快,按需对代码块部分进行指定编码;
189* 控制力更强,可以控制每个字符的变化;
190* 不干扰编译器,编译器天然忽略注释。
191
192### 现有「代码块预处理工具」存在什么问题?
193
194+ 不容易学习和记忆。`begin` 还是 `start`,前缀还是后缀?
195```
196<!-- 乐居广告脚本 begin-->
197/* jshint ignore:start */
198/* TODO 待开发功能 */
199```
200
201+ 是否存在闭合不明显。什么时候生效,什么时候失效?
202```
203/*jshint unused:true, eqnull:true*/
204/*test_expose
205 this.private_method = private_method;
206 */
207```
208
209+ 没有标准,不能跨语言。JSDev 和 JSDoc 不能用于其他主流语言,如 Python、Lua 等。
210
211## 代码预处理的思考
212
213问题也就是:怎么定义、怎么处理、什么情况下触发。
214
215### 怎么定义「代码块」?
216
217本人拟订了一个基于「XML 标签」+「多行注释」的代码块规范: [CBML][5]
218
219![CBML](https://cloud.githubusercontent.com/assets/536587/9024562/a4dbd27a-3908-11e5-9c2c-50156a04d398.png)
220
221优势:
222
223* 学习成本低,XML、多行注释都是大家熟知的东西;
224* 标签是否闭合很明显;
225* 支持多种主流编程语言。
226
227### 怎么处理「代码块」?
228
229处理的步骤无外乎就是:输入、编码、输出
230
231![processor](https://cloud.githubusercontent.com/assets/536587/9024576/3bdbae70-3909-11e5-9b3e-f4ba83b5e842.png)
232
233经过解析 CBML 的语法树,获取 `tag``attribute` 两个关键信息。
234
235如果 `tag` 值为 `<jdists>` 就开始按 jdists 的规则进行处理。
236
237> 整个处理过程由四个关键属性决定:
238> 1. `import=` 指定输入媒介
239> 2. `export=` 指定输出媒介
240> 3. `encoding=` 指定编码集合
241> 4. `trigger=` 指定触发条件
242
243举个例子
244```js
245/*<jdists export="template.js" trigger="@version < '1.0.0'">
246 var template = /*<jdists encoding="base64,quoted" import="main.html?template" />*/
247/*</jdists>
248```
249
250这里有两个代码块,还是一个嵌套结构
251
252* 外层代码块属性 `export="template.js"` 指定内容导出到文件 `template.js`(目录相对于当前代码块所在的文件)。
253* 外层代码块属性 `trigger="@version < '1.0.0'"` 指定命令行参数 `version` 小于 `'1.0.0'` 才触发。
254* 内层代码块属性 `encoding="base64,quoted"` 表示先给内容做一次 `base64` 编码再做一次 `quoted` 即,编码成字符串字面量。
255
256### 什么情况下触发?
257
258有两个触发条件:
259
2601. `tag` 值为 `<jdists>` 或者是被配置为 `jdists` 标签
2612. 当属性 `trigger=` 表达式判断为 `true`
262
263## jdists 基本概念
264
265### 代码块 block
266
267由 tag 标识的代码区域
268
269代码块主要有如下三种形式:
270* 空内容代码块,没有包裹任何代码
271```js
272/*<jdists import="main.js" />*/
273```
274
275* 有效内容代码块,包裹的内容是编译器会解析
276```js
277/*<jdists encoding="uglify">*/
278 function format(template, json) {
279 if (typeof template === 'function') { // 函数多行注释处理
280 template = String(template).replace(
281 /[^]*\/\*!?\s*|\s*\*\/[^]*/g, // 替换掉函数前后部分
282 ''
283 );
284 }
285 return template.replace(/#\{(.*?)\}/g, function(all, key) {
286 return json && (key in json) ? json[key] : "";
287 });
288 }
289/*</jdists>*/
290```
291
292* 无效内容代码块,包裹的内容也在注释中
293```js
294/*<jdists>
295console.log('version: %s', version);
296<jdists>*/
297```
298
299### 标签 tag
300
301* `<jdists>` | 自定义
302
303### 属性 attribute
304
305* `import=` 指定输入媒介
306* `export=` 指定输出媒介
307* `encoding=` 指定编码集合
308* `trigger=` 指定触发条件
309
310### 媒介 medium
311
312* `&content` 默认为 "&"
313* `file` 文件
314 > 如:
315 > `main.js`
316 > `index.html`
317
318* `#variant` 变量
319 > 如:
320 > `#name`
321 > `#data`
322
323* `[file]?block` *readonly* 代码块,默认 `file` 为当前文件
324 > 如:
325 > `filename?tagName`
326 > `filename?tagName[attrName=attrValue]`
327 > `filename?tagName[attrName=attrValue][attrName2=attrValue2]`
328
329* `@argument` *readonly* 控制台参数
330 > 如:
331 > `@output`
332 > `@version`
333
334* `:environment` *readonly* 环境变量
335 > 如:
336 > `:HOME`
337 > `:USER`
338
339* `[...]``{...}` *readonly* 字面量
340 > 如:
341 > `[1, 2, 3, 4]`
342 > `{title: 'jdists'}`
343
344* `'string'` *readonly* 字符串
345 > 如:
346 > `'zswang'`
347
348### 触发器 trigger
349
350触发器有两种表达式
351
352* 触发器名列表与控制台参数 `--trigger` 是否存在交集,存在则被触发
353
354> 当 `$ jdists ... --trigger release` 触发
355
356```html
357<!--remove trigger="release"-->
358<label>release</label>
359<!--/remove-->
360```
361
362* 将变量、属性、环境变量表达式替换后的字面量结果是否为 true
363
364> 当 `$ jdists ... --version 0.0.9` 触发
365
366```html
367<!--remove trigger="@version < '1.0.0'"-->
368<label>1.0.0+</label>
369<!--/remove-->
370```
371
372## 如何扩展 jdists
373
374可以参考项目中 processor 目录,中自带编码器的写法
375
376举个栗子
377```js
378var ejs = require('ejs');
379
380/**
381 * ejs 模板渲染
382 *
383 * @param {string} content 文本内容
384 * @param {Object} attrs 属性
385 * @param {string} attrs.data 数据项
386 * @param {Object} scope 作用域
387 * @param {Function} scope.execImport 导入数据
388 * @param {Function} scope.compile 二次编译 jdists 文本
389 */
390module.exports = function processor(content, attrs, scope) {
391 if (!content) {
392 return content;
393 }
394 var render = ejs.compile(content);
395 var data;
396 if (attrs.data) {
397 /*jslint evil: true */
398 data = new Function(
399 'return (' +
400 scope.execImport(attrs.data) +
401 ');'
402 )();
403 }
404 else {
405 data = null;
406 }
407 return scope.compile(render(data));
408};
409```
410
411详情参考:[jdists Scope](https://github.com/zswang/jdists/wiki/Scope)
412
413## 用例
414
415### 代码编译成 dataurl
416
417通过块导入
418
419```html
420<!--remove-->
421<script>
422/*<jdists encoding="base64" id="code">*/
423console.log('hello world!');
424/*</jdists>*/
425</script>
426<!--/remove-->
427
428<!--jdists>
429<script src="data:application/javascript;base64,/*<jdists import="?[id=code]" />*/"></script>
430</jdists-->
431```
432
433通过变量导入
434
435```html
436<!--remove-->
437<script>
438/*<jdists encoding="base64" export="#code">*/
439console.log('hello world!');
440/*</jdists>*/
441</script>
442<!--/remove-->
443
444<!--jdists>
445<script src="data:application/javascript;base64,/*<jdists import="#code" />*/"></script>
446</jdists-->
447```
448
449## 实战
450
451* [给源文件添加版权信息](https://github.com/zswang/jdists/wiki/%5Bcase%5DBuild-copyright)
452* [代码混合加密](https://github.com/zswang/jdists/wiki/%5Bcase%5DCode-mixed-encryption)
453* [预制默认插件](https://github.com/zswang/jdists/wiki/%5Bcase%5DPrefabricated-default-plugin)
454* [防止静态资源被搜索](https://github.com/zswang/jdists/wiki/%5Bcase%5DTo-prevent-the-reverse-engineering)
455* [引入其他代码处理工具](https://github.com/zswang/jdists/wiki/%5Bcase%5DThe-introduction-of-third-party-code-processing-tools)
456
457## 如何使用
458
459jdists 依赖 node v0.10.0 以上的环境
460
461### 安装
462
463`$ npm install jdists [-g]`
464
465### 命令行
466
467```
468Usage:
469
470 jdists <input list> [options]
471
472Options:
473
474 -r, --remove Remove block tag name list (default "remove,test")
475 -o, --output Output file (default STDOUT)
476 -v, --version Output jdists version
477 -t, --trigger Trigger name list (default "release")
478 -c, --config Path to config file (default ".jdistsrc")
479```
480
481### JS
482
483```js
484var content = jdists.build(filename, {
485 remove: 'remove,debug',
486 trigger: 'release'
487});
488```
489
490### 问题反馈和建议
491
492https://github.com/zswang/jdists/issues
493
494## 开发
495
496### 复制项目代码
497
498`$ git clone https://github.com/zswang/jdists.git`
499
500### 初始化依赖
501
502`$ npm install`
503
504### 执行测试用例
505
506`$ npm test`
507
508### 预处理
509
510`$ npm run dist`
511
512### 代码覆盖率
513
514`$ npm run cover`
515
516## 关键文件目录结果
517
518```
519[lib] --- 发布后的代码目录
520 jdists.js --- jdists 业务代码
521 scope.js --- jdists 作用域
522[processor] --- 预制编码器
523[processor-extend] --- 未预制的编码器,可能会常用的
524[src] --- 开发期代码
525[test] --- 测试目录
526 [fixtures] --- 测试用例
527 test.js --- 测试调度文件
528index.js --- jdists 声明
529cli.js --- jdists 控制台
530```
531
532[1]: https://github.com/taptapship/wiredep
533[2]: https://github.com/jshint/jshint
534[3]: https://github.com/jsdoc3/jsdoc
535[4]: https://github.com/douglascrockford/JSDev
536[5]: https://github.com/cbml/cbml