{"id":13802762,"url":"https://github.com/zswang/jdists","last_synced_at":"2025-04-04T16:16:54.100Z","repository":{"id":21940193,"uuid":"25264653","full_name":"zswang/jdists","owner":"zswang","description":"A feature rich code block preprocessing tool.","archived":false,"fork":false,"pushed_at":"2018-08-18T01:34:41.000Z","size":495,"stargazers_count":318,"open_issues_count":7,"forks_count":40,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-05-14T06:21:33.163Z","etag":null,"topics":["block","code","javascript","jdists","processor","region"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zswang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-10-15T17:03:02.000Z","updated_at":"2023-12-27T04:11:16.000Z","dependencies_parsed_at":"2022-08-17T23:45:25.731Z","dependency_job_id":null,"html_url":"https://github.com/zswang/jdists","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zswang%2Fjdists","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zswang%2Fjdists/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zswang%2Fjdists/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zswang%2Fjdists/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zswang","download_url":"https://codeload.github.com/zswang/jdists/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246671052,"owners_count":20815090,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["block","code","javascript","jdists","processor","region"],"created_at":"2024-08-04T00:01:54.304Z","updated_at":"2025-04-04T16:16:54.080Z","avatar_url":"https://github.com/zswang.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jdists 强大的代码块预处理工具\n\n标签： jdists 教程\n\n---\n\n[![Build Status](https://img.shields.io/travis/zswang/jdists/master.svg)](https://travis-ci.org/zswang/jdists)\n[![NPM version](https://img.shields.io/npm/v/jdists.svg)](http://badge.fury.io/js/jdists)\n[![NPM download](https://img.shields.io/npm/dm/jdists.svg)](https://www.npmjs.com/package/jdists)\n[![Coverage Status](https://coveralls.io/repos/zswang/jdists/badge.svg?branch=master\u0026service=github)](https://coveralls.io/github/zswang/jdists?branch=master)\n\n![jdists logo](https://cloud.githubusercontent.com/assets/536587/9022251/4d33427c-38a1-11e5-98e5-37b6a1c69a85.png)\n\n## 背景\n\n### 软件发布流程\n\n![code pretreatment](https://cloud.githubusercontent.com/assets/536587/9024268/5275fe58-38f8-11e5-9306-89e6c1840f97.png)\n\n通常软件发布时会将源文件做一次「预处理」再编译成可执行文件，才发布到市场。\n\n### 「预处理」的目的主要是出于以下几点\n\n* 配置线上运行环境，如调试服务地址需变更为实现线上地址；\n* 减少执行程序的大小，移除没有使用的代码或资源并压缩；\n* 增加逆向工程的成本，给代码做混淆（包括改变标识符和代码结构），降低可读性；\n* 移除或增加调试功能，关闭或开启一些特权后门。\n\n\u003e 一些 IDE 已在「编译」时集成了「预处理」功能。\n\n## 什么是 jdists\n\njdists 是一款强大的代码块预处理工具。\n\n### 什么是「代码块」(code block)？\n\n通常就是注释或注释包裹的代码片段，用于表达各种各样的含义。\n\n\u003e 举个栗子\n\n+ TODO 注释，表示代码中待完善的地方\n```js\n/* TODO 功能待开发 */\n```\n----\n+ [wiredep][1] 注释，表示引入 bower 组件依赖的 css 资源\n```html\n\t\u003c!-- bower:css --\u003e\n\t\u003clink rel=\"stylesheet\" href=\"bower_components/css/bootstrap.css\" /\u003e\n\t\u003c!-- endbower --\u003e\n```\n----\n+ [jshint.js][2] 顶部注释，表示版权声明\n```js\n/*!\n * JSHint, by JSHint Community.\n *\n * This file (and this file only) is licensed under the same slightly modified\n * MIT license that JSLint is. It stops evil-doers everywhere:\n *\n *   Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)\n * .........\n */\n```\n----\n+ jshint.js 另一部分注释，表示代码检查配置项\n```js\n/*jshint quotmark:double */\n/*global console:true */\n/*exported console */\n```\n总之，本文所指「代码块」就是有特殊意义的注释。\n\n### 什么是「代码块预处理」？\n\n指在代码编译之前，将代码文件按代码块粒度做一次编码或解析。\n\n\u003e 举个栗子，原本无效的代码片段，经过编码后变成了有效代码。\n\n预处理前：\n```js\n/*\u003cjdists\u003e\nconsole.log('Hello World!');\n\u003c/jdists\u003e*/\n```\n\n预处理后：\n```js\nconsole.log('Hello World!');\n```\n\n### 市面上还有哪一些「代码块预处理工具」？\n\n市面上有不少，这里只列两个比较典型的。\n\n+ 已被普遍使用的 [JSDoc][3]，功能是将代码中的注释抽离成 API 文档。\n\n```js\n/**\n * Represents a book.\n * @constructor\n * @param {string} title - The title of the book.\n * @param {string} author - The author of the book.\n */\nfunction Book(title, author) {\n}\n```\n----\n+ [JSDev][4] 是由 JSON 之父 Douglas Crockford 编写。jdists 与 JSDev 的功能类似，但 jdists 功能要复杂很多。\n\nC command line example:\n\n```shell\n\t jsdev -comment \"Devel Edition.\" \u003cinput \u003eoutput test_expose enter:trace.enter exit:trace.exit unless:alert\n```\n\nJavaScript:\n```js\n\t\toutput = JSDEV(input, [\n\t\t\t\t\"test_expose\",\n\t\t\t\t\"enter:trace.enter\",\n\t\t\t\t\"exit:trace.exit\",\n\t\t\t\t\"unless:alert\"\n\t\t] , [\"Devel Edition.\"]);\n```\ninput:\n```js\n\t\t// This is a sample file.\n\n\t\tfunction Constructor(number) {\n\t\t\t\t/*enter 'Constructor'*/\n\t\t\t\t/*unless(typeof number !== 'number') 'number', \"Type error\"*/\n\t\t\t\tfunction private_method() {\n\t\t\t\t\t\t/*enter 'private_method'*/\n\t\t\t\t\t\t/*exit 'private_method'*/\n\t\t\t\t}\n\t\t\t\t/*test_expose\n\t\t\t\t\t\tthis.private_method = private_method;\n\t\t\t\t*/\n\t\t\t\tthis.priv = function () {\n\t\t\t\t\t\t/*enter 'priv'*/\n\t\t\t\t\t\tprivate_method();\n\t\t\t\t\t\t/*exit 'priv'*/\n\t\t\t\t}\n\t\t\t\t/*exit \"Constructor\"*/\n\t\t}\n```\n\noutput:\n\n```js\n\t\t// Devel Edition.\n\t\t// This is a sample file.\n\n\t\tfunction Constructor(number) {\n\t\t\t\t{trace.enter('Constructor');}\n\t\t\t\tif (typeof number !== 'number') {alert('number', \"Type error\");}\n\t\t\t\tfunction private_method() {\n\t\t\t\t\t\t{trace.enter('private_method');}\n\t\t\t\t\t\t{trace.exit('private_method');}\n\t\t\t\t}\n\t\t\t\t{\n\t\t\t\t\t\tthis.private_method = private_method;\n\t\t\t\t}\n\t\t\t\tthis.priv = function () {\n\t\t\t\t\t\t{trace.enter('priv');}\n\t\t\t\t\t\tprivate_method();\n\t\t\t\t\t\t{trace.exit('priv');}\n\t\t\t\t}\n\t\t\t\t{trace.exit(\"Constructor\");}\n\t\t}\n```\n\nlightly minified:\n\n```js\n\t\tfunction Constructor(number) {\n\t\t\t\tfunction private_method() {\n\t\t\t\t}\n\t\t\t\tthis.priv = function () {\n\t\t\t\t\t\tprivate_method();\n\t\t\t\t}\n\t\t}\n```\n\n### 预处理以「代码块」为粒度有什么优势？\n \n* 处理速度快，按需对代码块部分进行指定编码；\n* 控制力更强，可以控制每个字符的变化；\n* 不干扰编译器，编译器天然忽略注释。\n\n### 现有「代码块预处理工具」存在什么问题？\n\n+ 不容易学习和记忆。`begin` 还是 `start`，前缀还是后缀？\n```\n\u003c!-- 乐居广告脚本 begin--\u003e\n/* jshint ignore:start */\n/* TODO 待开发功能 */\n```\n\n+ 是否存在闭合不明显。什么时候生效，什么时候失效？\n```\n/*jshint unused:true, eqnull:true*/\n/*test_expose\n\t\tthis.private_method = private_method;\n\t*/\n```\n\n+ 没有标准，不能跨语言。JSDev 和 JSDoc 不能用于其他主流语言，如 Python、Lua 等。\n\n## 代码预处理的思考\n\n问题也就是：怎么定义、怎么处理、什么情况下触发。\n\n### 怎么定义「代码块」？\n\n本人拟订了一个基于「XML 标签」+「多行注释」的代码块规范： [CBML][5]\n\n![CBML](https://cloud.githubusercontent.com/assets/536587/9024562/a4dbd27a-3908-11e5-9c2c-50156a04d398.png)\n\n优势：\n\n* 学习成本低，XML、多行注释都是大家熟知的东西；\n* 标签是否闭合很明显；\n* 支持多种主流编程语言。\n\n### 怎么处理「代码块」？\n\n处理的步骤无外乎就是：输入、编码、输出\n\n![processor](https://cloud.githubusercontent.com/assets/536587/9024576/3bdbae70-3909-11e5-9b3e-f4ba83b5e842.png)\n\n经过解析 CBML 的语法树，获取 `tag` 和 `attribute` 两个关键信息。\n\n如果 `tag` 值为 `\u003cjdists\u003e` 就开始按 jdists 的规则进行处理。\n\n\u003e 整个处理过程由四个关键属性决定：\n\u003e 1. `import=` 指定输入媒介\n\u003e 2. `export=` 指定输出媒介\n\u003e 3. `encoding=` 指定编码集合\n\u003e 4. `trigger=` 指定触发条件\n\n举个例子\n```js\n/*\u003cjdists export=\"template.js\" trigger=\"@version \u003c '1.0.0'\"\u003e\n\tvar template = /*\u003cjdists encoding=\"base64,quoted\" import=\"main.html?template\" /\u003e*/\n/*\u003c/jdists\u003e\n```\n\n这里有两个代码块，还是一个嵌套结构\n\n* 外层代码块属性 `export=\"template.js\"` 指定内容导出到文件 `template.js`（目录相对于当前代码块所在的文件）。\n* 外层代码块属性  `trigger=\"@version \u003c '1.0.0'\"` 指定命令行参数 `version` 小于 `'1.0.0'` 才触发。\n* 内层代码块属性 `encoding=\"base64,quoted\"` 表示先给内容做一次 `base64` 编码再做一次 `quoted` 即，编码成字符串字面量。\n\n### 什么情况下触发？\n\n有两个触发条件：\n\n1. 当 `tag` 值为 `\u003cjdists\u003e` 或者是被配置为 `jdists` 标签\n2. 当属性 `trigger=` 表达式判断为 `true`\n\n## jdists 基本概念\n\n### 代码块 block\n\n由 tag 标识的代码区域\n\n代码块主要有如下三种形式：\n* 空内容代码块，没有包裹任何代码\n```js\n/*\u003cjdists import=\"main.js\" /\u003e*/\n```\n\n* 有效内容代码块，包裹的内容是编译器会解析\n```js\n/*\u003cjdists encoding=\"uglify\"\u003e*/\n\tfunction format(template, json) {\n\t\tif (typeof template === 'function') { // 函数多行注释处理\n\t\t\ttemplate = String(template).replace(\n\t\t\t\t/[^]*\\/\\*!?\\s*|\\s*\\*\\/[^]*/g, // 替换掉函数前后部分\n\t\t\t\t''\n\t\t\t);\n\t\t}\n\t\treturn template.replace(/#\\{(.*?)\\}/g, function(all, key) {\n\t\t\t\treturn json \u0026\u0026 (key in json) ? json[key] : \"\";\n\t\t});\n\t}\n/*\u003c/jdists\u003e*/\n```\n\n* 无效内容代码块，包裹的内容也在注释中\n```js\n/*\u003cjdists\u003e\nconsole.log('version: %s', version);\n\u003cjdists\u003e*/\n```\n\n### 标签 tag\n\n* `\u003cjdists\u003e` | 自定义\n\n### 属性 attribute\n\n* `import=` 指定输入媒介\n* `export=` 指定输出媒介\n* `encoding=` 指定编码集合\n* `trigger=` 指定触发条件\n\n### 媒介 medium\n\n* `\u0026content` 默认为 \"\u0026\"\n* `file` 文件\n\t\t\u003e 如：\n\t\t\u003e `main.js`\n\t\t\u003e `index.html`\n\n* `#variant` 变量\n\t\t\u003e 如：\n\t\t\u003e `#name`\n\t\t\u003e `#data`\n\n* `[file]?block` *readonly* 代码块，默认 `file` 为当前文件\n\t\t\u003e 如：\n\t\t\u003e `filename?tagName`\n\t\t\u003e `filename?tagName[attrName=attrValue]`\n\t\t\u003e `filename?tagName[attrName=attrValue][attrName2=attrValue2]`\n\n* `@argument` *readonly* 控制台参数\n\t\t\u003e 如：\n\t\t\u003e `@output`\n\t\t\u003e `@version`\n\n* `:environment` *readonly* 环境变量\n\t\t\u003e 如：\n\t\t\u003e `:HOME`\n\t\t\u003e `:USER`\n\n* `[...]`、`{...}` *readonly* 字面量\n\t\t\u003e 如：\n\t\t\u003e `[1, 2, 3, 4]`\n\t\t\u003e `{title: 'jdists'}`\n\n* `'string'` *readonly* 字符串\n\t\t\u003e 如：\n\t\t\u003e `'zswang'`\n\n### 触发器 trigger\n\n触发器有两种表达式\n\n* 触发器名列表与控制台参数 `--trigger` 是否存在交集，存在则被触发\n\n\u003e 当 `$ jdists ... --trigger release` 触发\n\n```html\n\u003c!--remove trigger=\"release\"--\u003e\n\u003clabel\u003erelease\u003c/label\u003e\n\u003c!--/remove--\u003e\n```\n\n* 将变量、属性、环境变量表达式替换后的字面量结果是否为 true\n\n\u003e 当 `$ jdists ... --version 0.0.9` 触发\n\n```html\n\u003c!--remove trigger=\"@version \u003c '1.0.0'\"--\u003e\n\u003clabel\u003e1.0.0+\u003c/label\u003e\n\u003c!--/remove--\u003e\n```\n\n## 如何扩展 jdists\n\n可以参考项目中 processor 目录，中自带编码器的写法\n\n举个栗子\n```js\nvar ejs = require('ejs');\n\n/**\n * ejs 模板渲染\n *\n * @param {string} content 文本内容\n * @param {Object} attrs 属性\n * @param {string} attrs.data 数据项\n * @param {Object} scope 作用域\n * @param {Function} scope.execImport 导入数据\n * @param {Function} scope.compile 二次编译 jdists 文本\n */\nmodule.exports = function processor(content, attrs, scope) {\n\tif (!content) {\n\t\treturn content;\n\t}\n\tvar render = ejs.compile(content);\n\tvar data;\n\tif (attrs.data) {\n\t\t/*jslint evil: true */\n\t\tdata = new Function(\n\t\t\t'return (' +\n\t\t\tscope.execImport(attrs.data) +\n\t\t\t');'\n\t\t)();\n\t}\n\telse {\n\t\tdata = null;\n\t}\n\treturn scope.compile(render(data));\n};\n```\n\n详情参考：[jdists Scope](https://github.com/zswang/jdists/wiki/Scope)\n\n## 用例\n\n### 代码编译成 dataurl\n\n通过块导入\n\n```html\n\u003c!--remove--\u003e\n\u003cscript\u003e\n/*\u003cjdists encoding=\"base64\" id=\"code\"\u003e*/\nconsole.log('hello world!');\n/*\u003c/jdists\u003e*/\n\u003c/script\u003e\n\u003c!--/remove--\u003e\n\n\u003c!--jdists\u003e\n\u003cscript src=\"data:application/javascript;base64,/*\u003cjdists import=\"?[id=code]\" /\u003e*/\"\u003e\u003c/script\u003e\n\u003c/jdists--\u003e\n```\n\n通过变量导入\n\n```html\n\u003c!--remove--\u003e\n\u003cscript\u003e\n/*\u003cjdists encoding=\"base64\" export=\"#code\"\u003e*/\nconsole.log('hello world!');\n/*\u003c/jdists\u003e*/\n\u003c/script\u003e\n\u003c!--/remove--\u003e\n\n\u003c!--jdists\u003e\n\u003cscript src=\"data:application/javascript;base64,/*\u003cjdists import=\"#code\" /\u003e*/\"\u003e\u003c/script\u003e\n\u003c/jdists--\u003e\n```\n\n## 实战\n\n* [给源文件添加版权信息](https://github.com/zswang/jdists/wiki/%5Bcase%5DBuild-copyright)\n* [代码混合加密](https://github.com/zswang/jdists/wiki/%5Bcase%5DCode-mixed-encryption)\n* [预制默认插件](https://github.com/zswang/jdists/wiki/%5Bcase%5DPrefabricated-default-plugin)\n* [防止静态资源被搜索](https://github.com/zswang/jdists/wiki/%5Bcase%5DTo-prevent-the-reverse-engineering)\n* [引入其他代码处理工具](https://github.com/zswang/jdists/wiki/%5Bcase%5DThe-introduction-of-third-party-code-processing-tools)\n\n## 如何使用\n\njdists 依赖 node v0.10.0 以上的环境\n\n### 安装 \n\n`$ npm install jdists [-g]`\n\n### 命令行\n\n```\nUsage:\n\n\t\tjdists \u003cinput list\u003e [options]\n\nOptions:\n\n\t\t-r, --remove                 Remove block tag name list (default \"remove,test\")\n\t\t-o, --output                 Output file (default STDOUT)\n\t\t-v, --version                Output jdists version\n\t\t-t, --trigger                Trigger name list (default \"release\")\n\t\t-c, --config                 Path to config file (default \".jdistsrc\")\n```\n\n### JS\n\n```js\nvar content = jdists.build(filename, {\n\t\tremove: 'remove,debug',\n\t\ttrigger: 'release'\n});\n```\n\n### 问题反馈和建议\n\nhttps://github.com/zswang/jdists/issues\n\n## 开发\n\n### 复制项目代码\n\n`$ git clone https://github.com/zswang/jdists.git`\n\n### 初始化依赖\n\n`$ npm install`\n\n### 执行测试用例\n\n`$ npm test`\n\n### 预处理\n\n`$ npm run dist`\n\n### 代码覆盖率\n\n`$ npm run cover`\n\n## 关键文件目录结果\n\n```\n[lib]                 --- 发布后的代码目录\n\t\tjdists.js         --- jdists 业务代码\n\t\tscope.js          --- jdists 作用域\n[processor]           --- 预制编码器\n[processor-extend]    --- 未预制的编码器，可能会常用的\n[src]                 --- 开发期代码\n[test]                --- 测试目录\n\t\t[fixtures]        --- 测试用例\n\t\ttest.js           --- 测试调度文件\nindex.js              --- jdists 声明\ncli.js                --- jdists 控制台\n```\n\n[1]: https://github.com/taptapship/wiredep\n[2]: https://github.com/jshint/jshint\n[3]: https://github.com/jsdoc3/jsdoc\n[4]: https://github.com/douglascrockford/JSDev\n[5]: https://github.com/cbml/cbml\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzswang%2Fjdists","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzswang%2Fjdists","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzswang%2Fjdists/lists"}