前端程式碼塊預處理工具jdists

百度FEX發表於2014-10-30

關於命名

dist s 就是 js 裡插入了一個 dist (分發),避免和其他元件命名衝突,同時特殊好記。 本工具專注於前端程式碼塊(js、css、html)預處理。

起因

一個頁面從開發到上線基本會經歷三個階段:

  • 本機開發除錯
    • 列印一些變數和執行狀態、模擬資料介面
  • 內網測試
    • 跳過某些步驟、使用內網環境
  • 公網上線
    • 移除除錯程式碼、使用線上環境。

其實前端程式碼和其他語言程式碼都需要編譯,目前已經有很多成熟的工具來完成編譯的事情。 比如:fis、grunt、gulp,基本都是依賴配置檔案,將專案程式碼進行編譯到相應版本。

問題

  • 程式碼和配置檔案是分離的,這樣開發維護起來不夠直觀。
  • 配置檔案通常是基於 JSON,還是不夠直觀。
  • 除錯程式碼容易遺忘,有導致線上事故的風險。

思考

那有沒有一種方法能將部分編譯邏輯寫在程式碼中,就像 C 語言預編譯巨集定義,程式碼本身就包含了編譯邏輯不依賴配置檔案就能執行:

#include "headfile"
#ifdef DEBUG
fprintf("variant=%d", variant);
#endif

這樣不僅利於維護也方便在本機開發時除錯,那麼接下來需求來了!

需求

  • 學習成本要很低。(使用 html 標記,這個大家再熟悉不過)
  • 支援 html、css、js 檔案格式。
  • 本機開發除錯時不依賴編譯器。(編譯邏輯寫在註釋中)
  • 不僅能 include 一個檔案,還能 include 一個檔案中的片段(程式碼塊)。
  • 能引入當前檔案的程式碼塊。
  • 能夠替換程式碼塊的內容。
  • 能 include 二進位制檔案,變為 base64 字串,方便轉成 dataUri。
  • 自動合併 css 或 js 檔案,能夠輸出合併檔案並能打 md5 戳。
  • 能夠處理 註釋模板,避免被壓縮工具移除
    var render = jhtmls.render(function () {
    /*!
    <div title="#{title}">#{content}</div>
    */});
  • 能夠擴充套件替換規則。

設計思路

解決好如何定義程式碼塊,其他問題基本就迎刃而解了。

用什麼方式來定義程式碼塊?

利用註釋 + html 標記,並且又能和普通註釋區分。

  • 在 html 中:
    <!--debug-->
    <div>測試版本</div>
    <!--/debug-->
  • 在 js 中:
    /*<debug>*/
    console.log('測試版本');
    /*</debug>*/
  • 在 css 中:
    .version {
      font-size: 12px;
    /*<debug>*/
      color: red;
    /*</debug>*/
    }

一些程式碼未必預設啟用,所以支援如下方式

  • 在 html 中:
    <!--release
    <div>線上版本</div>
    /release-->
  • 在 js 中:
    /*<release
    console.log('測試版本');
    /release>*/
  • 在 css 中:
    .version {
      font-size: 12px;
    /*<release
      color: red;
    /release>*/
    }

廢棄的方式

<!--debug begin-->
<div>測試版本</div>
<!--debug end-->
  • 使用 being / end 的方式,主要的問題是容易遺忘,該字首還是字尾

基本概念

名稱 含義 例子 備註
file 檔案 1.js,1.png 包括二進位制檔案
block 程式碼塊 只能是文字檔案
block::tag 標籤 <a>
block::attribute 程式碼塊屬性 <a encoding="md5">

標準程式碼塊

tag 功能 示例
include 引入檔案或程式碼塊 <!--include file="all.js" /-->
replace 將當前程式碼塊替換成檔案或程式碼塊
remove 將當前程式碼移除

程式碼屬性(attribute)

屬性名 含義 例子 備註
encoding 編碼 encoding=”base64″ 預設”original”,可擴充套件
file 檔名 file=”all.js” 預設當前檔案
type 型別 type=”comment” 預設”original”,”comment”:去掉包裹程式碼塊的註釋
trigger 觸發器 trigger=”release,LAN” 預設”release”,存在這些觸發器時才生效
js js 檔案 js=”dist/all.js?” 輸出的 js 檔名
css css 檔案 css=”dist/all.css?” 輸出的 css 檔名

編碼(encoding)

  • original:原文
  • string:作為字串
  • base64:base64 輸出
  • md5:內容 MD5 戳(小寫)
  • concat:合併本地 js 或 css 並可以知道輸出

可以通過 jdists.setEncoding(encoding, processor) 擴充套件

使用限制

  • 程式碼塊不能交叉,可以巢狀
  • 程式碼塊引用不能出現迴圈

實戰

開始使用

  • 依賴 npm 環境
  • 安裝 $npm install jdists -g
  • 命令格式 $jdists input1 [input2] [-output output] [-remove debug,test]
  • 命令引數
引數 簡寫 功能 備註
-output -o 指定輸出檔案 預設輸出到控制檯
-remove -r 指定移除的程式碼塊 預設 “debug,test”
-trigger -t 指定觸發器 預設 “release”
-version -v 列印當前版本

處理 js 中的 註釋模板

假設檔案 js/base.js 內容為:

var render = jhtmls.render(function() {/*!
<ul>
forEach(function(item) {
  <li>#{item.title}</li>
});
<ul>
*/});

如上可以省去拼接字串的工作,直觀好維護。但經過帶壓縮後就變成:

var render=jhtmls.render(function(){});

怎麼避免 註釋模板 被替換?

$jdists js/base.js -o dist/js/base.js

生成的檔案是:

var render = jhtmls.render('<ul>\nforEach(function(item) {\n  <li>#{item.title}</li>\n});\n<ul>');

jdists 預設會處理 註釋模板

釋出程式碼

假設檔案 js/net.js 內容為:

var ajax = ajax || {};
void function(exports) {
/*<replace
  exports.host = 'http://api.baidu.com/1.0/getuser';
/replace>*/
}(ajax);

$jdists js/net.js -o dist/js/net.js

生成的檔案是:

var ajax = ajax || {};
void function(exports) {
  exports.host = 'http://api.baidu.com/1.0/getuser';
}(ajax);

還有一種情況,是我們需要先編譯一個區域網版本

假設檔案 js/net.js 內容為:

var ajax = ajax || {};
void function(exports) {
/*<replace trigger="release"
  exports.host = 'http://api.baidu.com/1.0/getuser';
/replace>*/
/*<replace trigger="LAN"
  exports.host = 'http://http://192.168.1.67:8000/1.0/getuser';
/replace>*/
}(ajax);

$jdists js/net.js -o dist/js/net.js -t LAN

生成的檔案是:

var ajax = ajax || {};
void function(exports) {
/*<replace trigger="release"
  exports.host = 'http://api.baidu.com/1.0/getuser';
/replace>*/
  exports.host = 'http://192.168.1.67:8000/1.0/getuser';
}(ajax);

指定 trigger 的程式碼塊,會檢查是否命中配置的觸發器,如果沒有命中則不啟用功能。

合併靜態資源

還是依所見即所得的設計思路,開發期的 html 指明瞭依賴的靜態檔案,如:

index.html

<html>
<head>
  <!--replace encoding="concat" js="dist/all.js" css="dist/all.css"-->
  <link rel="stylesheet" type="text/css" href="base.css">
  <link rel="stylesheet" type="text/css" href="button.css">
  <script src="base.js"></script>
  <script src="replace.js"></script>
  <!--/replace-->
</head>
<body>...</body>
</html>

$jdists index.html -o dist/index.html

生成的檔案是:

<html>
<head>
  <script src="dist/all.js"></script>
  <link rel="stylesheet" type="text/css" href="dist/all.css">
</head>
<body>...</body>
</html>

同時將本地靜態資源分別合併到 dist/all.js 和 dist/all.css

打包元件

通過 jdists 可以將零散的程式碼和靜態資源,拼湊為一個完整的元件

void function() {
  var bar = document.getElementById('jfpss-bar');
  if (bar) {
    return;
  }

  /*<include components/jframes/src/jframes.js>*/
  ;
  /*<include components/jhtmls/src/jhtmls.js>*/
  ;
  /*<include src/jfpss.js>*/

  createStyle(function() {/*!<!--include src/tools.html style-->*/});

  var div = document.createElement('div');
  div.innerHTML = function() {/*!<!--include src/tools.html html-->*/};
  document.body.appendChild(div);

  /*<include src/tools.html js>*/
}();

這樣就可以用靜態頁面開發 UI 元件了。

相關文章