乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      前端開發(fā)體系建設(shè)日記

       集微筆記 2014-06-24

      InfoQ編輯注:本文來自前端工程師張?jiān)讫埖?a >博客,由作者本人推薦至InfoQ進(jìn)行分享。目前本系列已經(jīng)發(fā)布了三個部分,本處分享的是第二部分,前端開發(fā)體系建設(shè)日記。建議在閱讀本文前先閱讀本文作者和其團(tuán)隊(duì)之前分享的《前端工程精粹》系列、


      上周寫了一篇 文章 介紹前端集成解決方案的基本理論,很多同學(xué)看過之后大呼不過癮。

      干貨 fuck things 在哪里!

      本打算繼續(xù)完善理論鏈,形成前端工程的知識結(jié)構(gòu)。但鑒于如今的快餐文化,po主決定還是先寫一篇實(shí)戰(zhàn)介紹,讓大家看到前端工程體系能為團(tuán)隊(duì)帶來哪些好處,調(diào)起大家的胃口再說。

      ps: 寫完才發(fā)現(xiàn)這篇文章真的非常非常長,涵蓋了前端開發(fā)中的很多方面,希望大家能有耐心看完,相信一定會有所斬獲。。。

      2014年02月12日 - 晴

      新到松鼠團(tuán)隊(duì)的第二天,小伙伴 @nino 找到我說

      nino: 視頻項(xiàng)目打算重新梳理一下,希望能引入新的技術(shù)體系,解決現(xiàn)有的一些問題。

      po主不禁暗喜,好機(jī)會,這是我專業(yè)啊,藍(lán)翔技校-前端集成解決方案學(xué)院-自動化系-打包學(xué)專業(yè)的文憑不是白給的,于是自信滿滿的對nino說,有什么需求盡管提!

      nino: 我的需求并不多,就這么幾條~~

      1. 模塊化開發(fā)。最好能像寫nodejs一樣寫js,很舒服。css最好也能來個模塊化管理!
      2. 性能要好。模塊那么多,得有按需加載,請求不能太多。
      3. 組件化開發(fā)。一個組件的js、css、模板最好都在一個目錄維護(hù),維護(hù)起來方便。
      4. handlebars 作為前端模板引擎。這個模板引擎不錯,logic-less(輕邏輯)。
      5. stylus 寫css挺方便,給我整一個。
      6. 圖片base64嵌入。有些小圖可能要以base64的形式嵌入到頁面、js或者css中使用。嵌入之前記得壓縮圖片以減小體積。
      7. js/css/圖片壓縮應(yīng)該都沒問題吧。
      8. 要能與公司的ci平臺集。工具里最好別依賴什么系統(tǒng)庫,ci的機(jī)器未必支持。
      9. 開發(fā)體驗(yàn)要好。文件監(jiān)聽,瀏覽器自動刷新(livereload)一個都不能少。
      10. 我們用nodejs作為服務(wù)器,本地要能預(yù)覽,最好再能抓取線上數(shù)據(jù),方便調(diào)試。

      我倒吸一口涼氣,但表面故作鎮(zhèn)定的說:恩,確實(shí)不多,讓我們先來看看第一個需求。。。

      還沒等我說完,nino打斷我說

      nino: 橋豆麻袋(稍等),還有一個最重要的需求!

      松鼠公司的松鼠瀏覽器你知道吧,恩,它有很多個版本的樣子。
      我希望代碼發(fā)布后能按照版本部署,不要彼此覆蓋。
      
      舉個例子,代碼部署結(jié)構(gòu)可能是這樣的:
      
        release/
          - public/
            - 項(xiàng)目名
              - 1.0.0/
              - 1.0.1/
              - 1.0.2/
              - 1.0.2-alpha/
              - 1.0.2-beta/
      
      讓歷史瀏覽器瀏覽歷史版本,沒事還能做個灰度發(fā)布,ABTest啥的,多好!
      
      此外,我們將來會有多個項(xiàng)目使用這套開發(fā)模式,希望能共用一些組件或者模
      塊,產(chǎn)品也會公布一些api模塊給第三方使用,所以共享模塊功能也要加上。
      

      總的來說,還要追加兩個部署需求:

      1. 按版本部署,采用非覆蓋式發(fā)布
      2. 允許第三方引用項(xiàng)目公共模塊

      nino: 怎么樣,不算復(fù)雜吧,這個項(xiàng)目很趕,3天搞定怎么樣?

      我凝望著會議室白板上的這些需求,正打算爭辯什么,一扭頭發(fā)現(xiàn)nino已經(jīng)不見了。。。正在沮喪之際,小伙伴 @hinc 過來找我,跟他大概講了一下nino的需求,正想跟他抱怨工期問題時,hinc卻說

      hinc: 恩,這正是我們需要的開發(fā)體系,不過我這里還有一個需求。。。

      1. 我們之前積累了一些業(yè)務(wù)可以共用的模塊,放在了公司內(nèi)的gitlab上,采用 component 作為發(fā)布規(guī)范,能不能再加上這個組件倉庫的支持?

      3天時間,13項(xiàng)前端技術(shù)元素,靠譜么。。。

      2014年02月13日 - 多云

      一覺醒來,輕松了許多,但還有任務(wù)在身,不敢有半點(diǎn)怠慢。整理一下昨天的需求,我們來做一個簡單的劃分。

      • 規(guī)范
        • 開發(fā)規(guī)范
          • 模塊化開發(fā),js模塊化,css模塊化,像nodejs一樣編碼
          • 組件化開發(fā),js、css、handlebars維護(hù)在一起
        • 部署規(guī)范
          • 采用nodejs后端,基本部署規(guī)范應(yīng)該參考 express 項(xiàng)目部署
          • 按版本號做非覆蓋式發(fā)布
          • 公共模塊可發(fā)布給第三方共享
      • 框架
        • js模塊化框架,支持請求合并,按需加載等性能優(yōu)化點(diǎn)
      • 工具
        • 可以編譯stylus為css
        • 支持js、css、圖片壓縮
        • 允許圖片壓縮后以base64編碼形式嵌入到css、js或html中
        • 與ci平臺集成
        • 文件監(jiān)聽、瀏覽器自動刷新
        • 本地預(yù)覽、數(shù)據(jù)模擬
      • 倉庫
        • 支持component模塊安裝和使用

      這樣一套規(guī)范、框架、工具和倉庫的開發(fā)體系,服從我之前介紹的 前端集成解決方案 的描述。前端界每天都團(tuán)隊(duì)在設(shè)計(jì)和實(shí)現(xiàn)這類系統(tǒng),它們其實(shí)是有規(guī)律可循的。百度出品的 fis 就是一個能幫助快速搭建前端集成解決方案的工具。使用fis我應(yīng)該可以在3天之內(nèi)完成這些任務(wù)。

      ps: 這不是一篇關(guān)于fis的軟文,如果這樣的一套系統(tǒng)基于grunt實(shí)現(xiàn)相信會有非常大量的開發(fā)工作,3天完成幾乎是不可能的任務(wù)。

      不幸的是,現(xiàn)在fis官網(wǎng)所介紹的 并不是 fis,而是一個叫 fis-plus 的項(xiàng)目,該項(xiàng)目并不像字面理解的那樣是fis的加強(qiáng)版,而是在fis的基礎(chǔ)上定制的一套面向百度前端團(tuán)隊(duì)的解決方案,以php為后端語言,跟smarty有較強(qiáng)的綁定關(guān)系,有著 19項(xiàng) 技術(shù)要素,密切配合百度現(xiàn)行技術(shù)選型。絕大多數(shù)非百度前端團(tuán)隊(duì)都很難完整接受這19項(xiàng)技術(shù)選型,尤其是其中的部署、框架規(guī)范,跟百度前端團(tuán)隊(duì)相關(guān)開發(fā)規(guī)范、部署規(guī)范、以及php、smarty等有著較深的綁定關(guān)系。

      因此如果你的團(tuán)隊(duì)用的不是 php后端 && smarty模板 && modjs模塊化框架 && bingo框架 的話,請查看fis的文檔,或許不會有那么多困惑。

      ps: fis是一個構(gòu)建系統(tǒng)內(nèi)核,很好的抽象了前端集成解決方案所需的通用工具需求,本身不與任何后端語言綁定。而基于fis實(shí)現(xiàn)的具體解決方案就會有具體的規(guī)范和技術(shù)選型了。

      言歸正傳,讓我們基于 fis 開始實(shí)踐這套開發(fā)體系吧!

      0. 開發(fā)概念定義

      前端開發(fā)體系設(shè)計(jì)第一步要定義開發(fā)概念。開發(fā)概念是指針對開發(fā)資源的分類概念。開發(fā)概念的確立,直接影響到規(guī)范的定制。比如,傳統(tǒng)的開發(fā)概念一般是按照文件類型劃分的,所以傳統(tǒng)前端項(xiàng)目會有這樣的目錄結(jié)構(gòu):

      • js:放js文件
      • css:放css文件
      • images:放圖片文件
      • html:放html文件

      這樣確實(shí)很直接,任何智力健全的人都知道每個文件該放在哪里。但是這樣的開發(fā)概念劃分將給項(xiàng)目帶來較高的維護(hù)成本,并為項(xiàng)目臃腫埋下了工程隱患,理由是:

      1. 如果項(xiàng)目中的一個功能有了問題,維護(hù)的時候要在js目錄下找到對應(yīng)的邏輯修改,再到css目錄下找到對應(yīng)的樣式文件修改一下,如果圖片不對,還要再跑到images目錄下找對應(yīng)的開發(fā)資源。
      2. images下的文件不知道哪些圖片在用,哪些已經(jīng)廢棄了,誰也不敢刪除,文件越來越多。

      ps: 除非你的團(tuán)隊(duì)只有1-2個人,你的項(xiàng)目只有很少的代碼量,而且不用關(guān)心性能和未來的維護(hù)問題,否則,以文件為依據(jù)設(shè)計(jì)的開發(fā)概念是應(yīng)該被拋棄的。

      以我個人的經(jīng)驗(yàn),更傾向于具有一定語義的開發(fā)概念。綜合前面的需求,我為這個開發(fā)體系確定了3個開發(fā)資源概念:

      • 模塊化資源:js模塊、css模塊或組件
      • 頁面資源:網(wǎng)站html或后端模板頁面,引用模塊化框架,加載模塊
      • 非模塊化資源:并不是所有的開發(fā)資源都是模塊化的,比如提供模塊化框架的js本身就不能是一個模塊化的js文件。嚴(yán)格上講,頁面也屬于一種非模塊化的靜態(tài)資源。

      ps: 開發(fā)概念越簡單越好,前面提到的fis-plus也有類似的開發(fā)概念,有組件或模塊(widget),頁面(page),測試數(shù)據(jù)(test),非模塊化靜態(tài)資源(static)。有的團(tuán)隊(duì)在模塊之中又劃分出api模塊和ui模塊(組件)兩種概念。

      1. 開發(fā)目錄設(shè)計(jì)

      基于開發(fā)概念的確立,接下來就要確定目錄規(guī)范了。我通常會給每種開發(fā)資源的目錄取一個有語義的名字,三種資源我們可以按照概念直接定義目錄結(jié)構(gòu)為:

      project
        - modules       存放模塊化資源
        - pages         存放頁面資源
        - static        存放非模塊化資源
      

      這樣劃分目錄確實(shí)直觀,但結(jié)合前面hinc說過的,希望能使用component倉庫資源,因此我決定將模塊化資源目錄命名為components,得到:

      project
        - components    存放模塊化資源
        - pages         存放頁面資源
        - static        存放非模塊化資源
      

      而nino又提到過模塊資源分為項(xiàng)目模塊和公共模塊,以及hinc提到過希望能從component安裝一些公共組件到項(xiàng)目中,因此,一個components目錄還不夠,想到nodejs用node_modules作為模塊安裝目錄,因此我在規(guī)范中又追加了一個 component_modules 目錄,得到:

      project
        - component_modules    存放外部模塊資源
        - components           存放項(xiàng)目模塊資源
        - pages                存放頁面資源
        - static               存放非模塊化資源
      

      nino說過今后大多數(shù)項(xiàng)目采用nodejs作為后端,express是比較常用的nodejs的server框架,express項(xiàng)目通常會把后端模板放到 views 目錄下,把靜態(tài)資源放到 public 下。為了迎合這樣的需求,我將page、static兩個目錄調(diào)整為 views 和 public,規(guī)范又修改為:

      project
        - component_modules    存放外部模塊資源
        - components           存放項(xiàng)目模塊資源
        - views                存放頁面資源
        - public               存放非模塊化資源
      

      考慮到頁面也是一種靜態(tài)資源,而public這個名字不具有語義性,與其他目錄都有概念沖突,不如將其與views目錄合并,views目錄負(fù)責(zé)存放頁面和非模塊化資源比較合適,因此最終得到的開發(fā)目錄結(jié)構(gòu)為:

      project
        - component_modules    存放外部模塊資源
        - components           存放項(xiàng)目模塊資源
        - views                存放頁面以及非模塊化資源
      

      2. 部署目錄設(shè)計(jì)

      托nino的福,咱們的部署策略將會非常復(fù)雜,根據(jù)要求,一個完整的部署結(jié)果應(yīng)該是這樣的目錄結(jié)構(gòu):

      release
        - public
          - 項(xiàng)目名
            - 1.0.0    1.0.0版本的靜態(tài)資源都構(gòu)建到這里
            - 1.0.1    1.0.1版本的靜態(tài)資源都構(gòu)建到這里
            - 1.0.2    1.0.2版本的靜態(tài)資源都構(gòu)建到這里
            ...
        - views
          - 項(xiàng)目名
            - 1.0.0    1.0.0版本的后端模板都構(gòu)建到這里
            - 1.0.1    1.0.1版本的后端模板都構(gòu)建到這里
            - 1.0.2    1.0.2版本的后端模板都構(gòu)建到這里
            ...
      

      由于還要部署一些可以被第三方使用的模塊,public下只有項(xiàng)目名的部署還不夠,應(yīng)改把模塊化文件單獨(dú)發(fā)布出來,得到這樣的部署結(jié)構(gòu):

      release
        - public
          - component_modules   模塊化資源都部署到這個目錄下
            - module_a
              - 1.0.0
                - module_a.js
                - module_a.css
                - module_a.png
              - 1.0.1
              - 1.0.2
              ...
          - 項(xiàng)目名
            - 1.0.0    1.0.0版本的靜態(tài)資源都構(gòu)建到這里
            - 1.0.1    1.0.1版本的靜態(tài)資源都構(gòu)建到這里
            - 1.0.2    1.0.2版本的靜態(tài)資源都構(gòu)建到這里
            ...
        - views
          - 項(xiàng)目名
            - 1.0.0    1.0.0版本的后端模板都構(gòu)建到這里
            - 1.0.1    1.0.1版本的后端模板都構(gòu)建到這里
            - 1.0.2    1.0.2版本的后端模板都構(gòu)建到這里
            ...
      

      由于 component_modules 這個名字太長了,如果部署到這樣的路徑下,url會很長,這也是一個優(yōu)化點(diǎn),因此最終決定部署結(jié)構(gòu)為:

      release
        - public
          - c                   模塊化資源都部署到這個目錄下
            - 公共模塊
              - 版本號
            - 項(xiàng)目名
              - 版本號
          - 項(xiàng)目名
            - 版本號             非模塊化資源都部署到這個目錄下
        - views
          - 項(xiàng)目名
            - 版本號             后端模板都構(gòu)建到這個目錄下
      

      插一句,并不是所有團(tuán)隊(duì)都會有這么復(fù)雜的部署要求,這和松鼠團(tuán)隊(duì)的業(yè)務(wù)需求有關(guān),但我相信這個例子也不會是最復(fù)雜的。每個團(tuán)隊(duì)都會有自己的運(yùn)維需求,前端資源部署經(jīng)常牽連到公司技術(shù)架構(gòu),因此很多前端項(xiàng)目的開發(fā)目錄結(jié)構(gòu)會和部署要求保持一致。這也為項(xiàng)目間模塊的復(fù)用帶來了成本,因?yàn)榇a中寫的url通常是部署后的路徑,遷移之后就可能失效了。

      解耦開發(fā)規(guī)范和部署規(guī)范是前端開發(fā)體系的設(shè)計(jì)重點(diǎn)。

      好了,去吃個午飯,下午繼續(xù)。。。

      3. 配置fis連接開發(fā)規(guī)范和部署規(guī)范

      我準(zhǔn)備了一個樣例項(xiàng)目:

      project
        - views
          - logo.png
          - index.html
        - fis-conf.js
        - README.md
      

      fis-conf.js是fis工具的配置文件,接下來我們就要在這里進(jìn)行構(gòu)建配置了。雖然開發(fā)規(guī)范和部署規(guī)范十分復(fù)雜,但好在fis有一個非常強(qiáng)大的 roadmap.path 功能,專門用于分類文件、調(diào)整發(fā)布結(jié)構(gòu)、指定文件的各種屬性等功能實(shí)現(xiàn)。

      所謂構(gòu)建,其核心任務(wù)就是將文件按照某種規(guī)則進(jìn)行分類(以文件后綴分類,以模塊化/非模塊化分類,以前端/后端代碼分類),然后針對不同的文件做不同的構(gòu)建處理。

      閑話少說,我們先來看一下基本的配置,在 fis-conf.js 中添加代碼:

      fis.config.set('roadmap.path', [
          {
              reg : '**.md',   //所有md后綴的文件
              release : false  //不發(fā)布
          }
      ]);
      

      以上配置,使得項(xiàng)目中的所有md后綴文件都不會發(fā)布出來。release是定義file對象發(fā)布路徑的屬性,如果file對象的release屬性為false,那么在項(xiàng)目發(fā)布階段就不會被輸出出來。

      在fis中,roadmap.pah是一個數(shù)組數(shù)據(jù),數(shù)組每個元素是一個對象,必須定義 reg 屬性,用以匹配項(xiàng)目文件路徑從而進(jìn)行分類劃分,reg屬性的取值可以是路徑通配字符串或者正則表達(dá)式。fis有一個內(nèi)部的文件系統(tǒng),會給每個源碼文件創(chuàng)建一個 fis.File 對象,創(chuàng)建File對象時,按照roadmap.path的配置逐個匹配文件路徑,匹配成功則把除reg之外的其他屬性賦給File對象,fis中各種處理環(huán)節(jié)及插件都會讀取所需的文件對象的屬性值,而不會自己定義規(guī)范。有關(guān)roadmap.path的工作原理可以看這里 以及 這里。

      ok,讓md文件不發(fā)布很簡單,那么views目錄下的按版本發(fā)布要求怎么實(shí)現(xiàn)呢?其實(shí)也是非常簡單的配置:

      fis.config.set('roadmap.path', [
          {
              reg : '**.md',   //所有md后綴的文件
              release : false  //不發(fā)布
          },
          {
              //正則匹配【/views/**】文件,并將views后面的路徑捕獲為分組1
              reg : /^\/views\/(.*)$/i,
              //發(fā)布到 public/proj/1.0.0/分組1 路徑下
              release : '/public/proj/1.0.0/$1'
          }
      ]);
      

      roadmap.path數(shù)組的第二元素?fù)?jù)采用正則作為匹配規(guī)則,正則可以幫我們捕獲到分組信息,在release屬性值中引用分組是非常方便的。正則匹配 + 捕獲分組,成為目錄規(guī)范配置的強(qiáng)有力工具:

      在上面的配置中,版本號被寫到了匹配規(guī)則里,這樣非常不方便工程師在迭代的過程中升級項(xiàng)目版本。我們應(yīng)該將版本號、項(xiàng)目名稱等配置獨(dú)立出來管理。好在roadmap.path還有讀取其他配置的能力,修改上面的配置,我們得到:

      //開發(fā)部署規(guī)范配置
      fis.config.set('roadmap.path', [
          {
              reg : '**.md',   //所有md后綴的文件
              release : false  //不發(fā)布
          },
          {
              reg : /^\/views\/(.*)$/i,
              //使用${xxx}引用fis.config的其他配置項(xiàng)
              release : '/public/${name}/${version}/$1'
          }
      ]);
      
      //項(xiàng)目配置,將name、version獨(dú)立配置,統(tǒng)管全局
      fis.config.set('name', 'proj');
      fis.config.set('version', '1.0.0');
      

      fis的配置系統(tǒng)非常靈活,除了 文檔 中提到的配置節(jié)點(diǎn),其他配置用戶可以隨便定義使用。比如配置的roadmap是系統(tǒng)保留的,而name、version都是用戶自己隨便指定的。fis系統(tǒng)保留的配置節(jié)點(diǎn)只有6個,包括:

      1. project(系統(tǒng)配置)
      2. modules(構(gòu)建流程配置)
      3. settings(插件配置)
      4. roadmap(目錄規(guī)范與域名配置)
      5. deploy(部署目標(biāo)配置)
      6. pack(打包配置)

      完成第一份配置之后,我們來看一下效果。

      cd project
      fis release --dest ../release
      

      進(jìn)入到項(xiàng)目目錄,然后使用fis release命令,對項(xiàng)目進(jìn)行構(gòu)建,用 --dest <path> 參數(shù)指定編譯結(jié)果的產(chǎn)出路徑,可以看到部署后的結(jié)果:

      ps: fis release會將處理后的結(jié)果發(fā)布到源碼目錄之外的其他目錄里,以保持源碼目錄的干凈。

      fis系統(tǒng)的強(qiáng)大之處在于當(dāng)你調(diào)整了部署規(guī)范之后,fis會識別所有資源定位標(biāo)記,將他們修改為對應(yīng)的部署路徑。

      fis的文件系統(tǒng)設(shè)計(jì)決定了配置開發(fā)規(guī)范的成本非常低。fis構(gòu)建核心有三個超級正則,用于識別資源定位標(biāo)記,把用戶的開發(fā)規(guī)范和部署規(guī)范通過配置完整連接起來,具體實(shí)現(xiàn)可以看這里

      不止html,fis為前端三種領(lǐng)域語言都準(zhǔn)備了資源定位識別標(biāo)記,更多文檔可以看這里:在html中定位資源,在js中定位資源,在css中定位資源

      接下來,我們修改一下項(xiàng)目版本配置,再發(fā)布一下看看效果:

      fis.config.set('version', '1.0.1');
      

      再次執(zhí)行:

      cd project
      fis release --dest ../release
      

      得到:

      至此,我們已經(jīng)基本解決了開發(fā)和部署直接的目錄規(guī)范問題,這里我需要加快一些步伐,把其他目錄的部署規(guī)范也配置好,得到一個相對比較完整的結(jié)果:

      fis.config.set('roadmap.path', [
          {
              //md后綴的文件不發(fā)布
              reg : '**.md',
              release : false
          },
          {
              //component_modules目錄下的代碼,由于component規(guī)范,已經(jīng)有了版本號
              //我將它們直接發(fā)送到public/c目錄下就好了
              reg : /^\/component_modules\/(.*)$/i,
              release : '/public/c/$1'
          },
          {
              //項(xiàng)目模塊化目錄沒有版本號結(jié)構(gòu),用全局版本號控制發(fā)布結(jié)構(gòu)
              reg : /^\/components\/(.*)$/i,
              release : '/public/c/${name}/${version}/$1'
          },
          {
              //views目錄下的文件發(fā)布到【public/項(xiàng)目名/版本】目錄下
              reg : /^\/views\/(.*)$/,
              release : '/public/${name}/${version}/$1'
          },
          {
              //其他文件就不屬于前端項(xiàng)目了,比如nodejs的后端代碼
              //不處理這些文件的資源定位替換(useStandard: false)
              //也不用對這些資源進(jìn)行壓縮(useOptimizer: false)
              reg : '**',
              useStandard : false,
              useOptimizer : false
          }
      ]);
      
      fis.config.set('name', 'proj');
      fis.config.set('version', '1.0.2');
      

      我構(gòu)造了一個相對完整的目錄結(jié)構(gòu),然后進(jìn)行了一次構(gòu)建,效果還不錯:

      不管部署規(guī)則多么復(fù)雜都不用擔(dān)心,有fis強(qiáng)大的資源定位系統(tǒng)幫你在開發(fā)規(guī)范和部署規(guī)范之間建立聯(lián)系,設(shè)計(jì)開發(fā)體系不在受制于工具的實(shí)現(xiàn)能力。

      你可以盡情發(fā)揮想象力,設(shè)計(jì)出最優(yōu)雅最合理的開發(fā)規(guī)范和最高效最貼合公司運(yùn)維要求的部署規(guī)范,最終用fis的roadmap.path功能將它們連接起來,實(shí)現(xiàn)完美轉(zhuǎn)換。

      fis的roadmap功能實(shí)際上提供了項(xiàng)目代碼與部署規(guī)范解耦的能力。

      從前面的例子可以看出,開發(fā)使用相對路徑即可,fis會在構(gòu)建時會根據(jù)fis-conf.js中的配置完成開發(fā)路徑到部署路徑的轉(zhuǎn)換工作。這意味著在fis體系下開發(fā)的模塊將具有天然的可移植性,既能滿足不同項(xiàng)目的不同部署需求,又能允許開發(fā)中使用相對路徑進(jìn)行資源定位,工程師再不用把部署路徑寫到代碼中了。

      愉快的一天就這么過去了,睡覺!

      2014年02月14日 - 陰轉(zhuǎn)多云

      每到周五總是非常愜意的感覺,不管這一周多么辛苦,周五就是一個解脫,更何況今天還是個特別的日子——情人節(jié)!

      昨天主要解決了開發(fā)概念、開發(fā)目錄規(guī)范、部署目錄規(guī)范以及初步的fis-conf.js配置。今天要進(jìn)行前端開發(fā)體系設(shè)計(jì)的關(guān)鍵任務(wù)——模塊化框架。

      模塊化框架是前端開發(fā)體系中最為核心的環(huán)節(jié)。

      模塊化框架肩負(fù)著模塊管理、資源加載、性能優(yōu)化(按需,請求合并)等多種重要職責(zé),同時它也是組件開發(fā)的基礎(chǔ)框架,因此模塊化框架設(shè)計(jì)的好壞直接影響到開發(fā)體系的設(shè)計(jì)質(zhì)量。

      很遺憾的說,現(xiàn)在市面上已有的模塊化框架都沒能很好的處理模塊管理、資源加載和性能優(yōu)化三者之間的關(guān)系。這倒不是框架設(shè)計(jì)的問題,而是由前端領(lǐng)域語言特殊性決定的。框架設(shè)計(jì)者一般在思考模塊化框架時,通常站在純前端運(yùn)行環(huán)境角度考慮,基本功能都是用原生js實(shí)現(xiàn)的,因此一個模塊化開發(fā)的關(guān)鍵問題不能被很好的解決。這個關(guān)鍵問題就是依賴聲明。

      seajs 為例(無意冒犯),seajs采用運(yùn)行時分析的方式實(shí)現(xiàn)依賴聲明識別,并根據(jù)依賴關(guān)系做進(jìn)一步的模塊加載。比如如下代碼:

      define(function(require) {
        var foo = require("foo");
        //...
      });
      

      當(dāng)seajs要執(zhí)行一個模塊的factory函數(shù)之前,會先分析函數(shù)體中的require書寫,具體代碼在這里這里,大概的代碼邏輯如下:

      Module.define = function (id, deps, factory) {
          ...
          //抽取函數(shù)體的字符串內(nèi)容
          var code = factory.toString();
          //設(shè)計(jì)一個正則,分析require語句
          var reg = /\brequire\s*\(([.*]?)\)/g;
          var deps = [];
          //掃描字符串,得到require所聲明的依賴
          code.replace(reg, function(m, $1){
              deps.push($1);
          });
          //加載依賴,完成后再執(zhí)行factory
          ...
      };
      

      由于框架設(shè)計(jì)是在“純前端實(shí)現(xiàn)”的約束條件下,使得模塊化框架對于依賴的分析必須在模塊資源加載完成之后才能做出識別。這將引起兩個性能相關(guān)的問題:

      1. require被強(qiáng)制為關(guān)鍵字而不能被壓縮。否則factory.toString()的分析將沒有意義。
      2. 依賴加載只能串行進(jìn)行,當(dāng)一個模塊加載完成之后才知道后面的依賴關(guān)系。

      第一個問題還好,尤其是在gzip下差不多多少字節(jié),但是要配置js壓縮器保留require函數(shù)不壓縮。第二個問題就比較麻煩了,雖然seajs有seajs-combo插件可以一定程度上減少請求,但仍然不能很好的解決這個問題。舉個例子,有如下seajs模塊依賴關(guān)系樹:

      ps: 圖片來源 @raphealguo

      采用seajs-combo插件之后,靜態(tài)資源請求的效果是這樣的:

      1. http://www./page.js
      2. http://www./a.js,b.js
      3. http://www./c.js,d.js,e.js
      4. http://www./f.js

      工作過程是

      1. 框架先請求了入口文件page.js
      2. 加載完成后分析factory函數(shù),發(fā)現(xiàn)依賴了a.js和b.js,然后發(fā)起一個combo請求,加載a.js和b.js
      3. a.js和b.js加載完成后又進(jìn)一步分析源碼,才發(fā)現(xiàn)還依賴了c.js、d.js和e.js,再發(fā)起請求加載這三個文件
      4. 完成c、d、e的加載之后,再分析,發(fā)現(xiàn)f.js依賴,再請求
      5. 完成f.js的請求,page.js的依賴全部滿足,執(zhí)行它的factory函數(shù)。

      雖然combo可以在依賴層級上進(jìn)行合并,但完成page.js的請求仍需要4個。很多團(tuán)隊(duì)在使用seajs的時候,為了避免這樣的串行依賴請求問題,會自己實(shí)現(xiàn)打包方案,將所有文件直接打包在一起,放棄了模塊化的按需加載能力,也是一種無奈之舉。

      原因很簡單

      以純前端方式來實(shí)現(xiàn)模塊依賴加載不能同時解決性能優(yōu)化問題。

      歸根結(jié)底,這樣的結(jié)論是由前端領(lǐng)域語言的特點(diǎn)決定的。前端語言缺少三種編譯能力,前面講目錄規(guī)范和部署規(guī)范時其實(shí)已經(jīng)提到了一種能力,就是“資源定位的能力”,讓工程師使用開發(fā)路徑定位資源,編譯后可轉(zhuǎn)換為部署路徑。其他語言編寫的程序幾乎都沒有web這種物理上分離的資源部署策略,而且大多具都有類似'getResource(path)'這樣的函數(shù),用于在運(yùn)行環(huán)境下定位當(dāng)初的開發(fā)資源,這樣不管項(xiàng)目怎么部署,只要getResource函數(shù)運(yùn)行正常就行了。可惜前端語言沒有這樣的資源定位接口,只有url這樣的資源定位符,它指向的其實(shí)并不是開發(fā)路徑,而是部署路徑。

      這里可以簡單列舉出前端語言缺少三種的語言能力:

      • 資源定位的能力:使用開發(fā)路徑進(jìn)行資源定位,項(xiàng)目發(fā)布后轉(zhuǎn)換成部署路徑
      • 依賴聲明的能力:聲明一個資源依賴另一個資源的能力
      • 資源嵌入的能力:把一個資源的編譯內(nèi)容嵌入到另一個文件中

      以后我會在完善前端開發(fā)體系理論的時候在詳細(xì)介紹這三種語言能力的必要性和原子性,這里就暫時不展開說明了。

      fis最核心的編譯思想就是圍繞這三種語言能力設(shè)計(jì)的。

      要兼顧性能的同時解決模塊化依賴管理和加載問題,其關(guān)鍵點(diǎn)在于

      不能運(yùn)行時去分析模塊間的依賴關(guān)系,而要讓框架提前知道依賴樹。

      了解了原因,我們就要自己動手設(shè)計(jì)模塊化框架了。不要害怕,模塊化框架其實(shí)很簡單,思想、規(guī)范都是經(jīng)過很多前輩總結(jié)的結(jié)果,我們只要遵從他們的設(shè)計(jì)思想去實(shí)現(xiàn)就好了。

      參照已有規(guī)范,我定義了三個模塊化框架接口:

      • 模塊定義接口:define(id, factory);
      • 異步加載接口:require.async(ids, callback);
      • 框架配置接口:require.config(options);

      利用構(gòu)建工具建立模塊依賴關(guān)系表,再將關(guān)系表注入到代碼中,調(diào)用require.config接口讓框架知道完整的依賴樹,從而實(shí)現(xiàn)require.async在異步加載模塊時能提前預(yù)知所有依賴的資源,一次性請求回來。

      以上面的page.js依賴樹為例,構(gòu)建工具會生成如下代碼:

      require.config({
          deps : {
              'page.js' : [ 'a.js', 'b.js' ],
              'a.js'    : [ 'c.js' ],
              'b.js'    : [ 'd.js', 'e.js' ],
              'c.js'    : [ 'f.js' ],
              'd.js'    : [ 'f.js' ]
          }
      });
      

      當(dāng)執(zhí)行require.async('page.js', fn);語句時,框架查詢config.deps表,就能知道要發(fā)起一個這樣的combo請求:

      http://www./f.js,c.js,d.js,e.js,a.js,b.js,page.js

      從而實(shí)現(xiàn)按需加載和請求合并兩項(xiàng)性能優(yōu)化需求。

      根據(jù)這樣的設(shè)計(jì)思路,我請 @hinc 幫忙實(shí)現(xiàn)了這個框架,我告訴他,deps里不但會有js,還會有css,所以也要兼容一下。hinc果然是執(zhí)行能力非常強(qiáng)的小伙伴,僅一個下午的時間就搞定了框架的實(shí)現(xiàn),我們給這個框架取名為 scrat.js,僅有393行。

      前面提到fis具有資源依賴聲明的編譯能力。因此只要工程師按照fis規(guī)定的書寫方式在代碼中聲明依賴關(guān)系,就能在構(gòu)建的最后階段自動獲得fis系統(tǒng)整理好的依賴樹,然后對依賴的數(shù)據(jù)結(jié)構(gòu)進(jìn)行調(diào)整、輸出,滿足框架要求就搞定了!fis規(guī)定的資源依賴聲明方式為:在html中聲明依賴,在js中聲明依賴,在css中聲明依賴。

      接下來,我要寫一個配置,將依賴關(guān)系表注入到代碼中。fis構(gòu)建是分流程的,具體構(gòu)建流程可以看這里。fis會在postpackager階段之前創(chuàng)建好完整的依賴樹表,我就在這個時候?qū)懸粋€插件來處理即可。

      編輯fis-conf.js

      //postpackager插件接受4個參數(shù),
      //ret包含了所有項(xiàng)目資源以及資源表、依賴樹,其中包括:
      //   ret.src: 所有項(xiàng)目文件對象
      //   ret.pkg: 所有項(xiàng)目打包生成的額外文件
      //   reg.map: 資源表結(jié)構(gòu)化數(shù)據(jù)
      //其他參數(shù)暫時不用管
      var createFrameworkConfig = function(ret, conf, settings, opt){
          //創(chuàng)建一個對象,存放處理后的配置項(xiàng)
          var map = {};
          //依賴樹數(shù)據(jù)
          map.deps = {};
          //遍歷所有項(xiàng)目文件
          fis.util.map(ret.src, function(subpath, file){
              //文件的依賴數(shù)據(jù)就在file對象的requires屬性中,直接賦值即可
              if(file.requires && file.requires.length){
                  map.deps[file.id] = file.requires;
              }
          });
          console.log(map.deps);
      };
      //在modules.postpackager階段處理依賴樹,調(diào)用插件函數(shù)
      fis.config.set('modules.postpackager', [createFrameworkConfig]);
      

      我們準(zhǔn)備一下項(xiàng)目代碼,看看構(gòu)建的時候發(fā)生了什么:

      執(zhí)行fis release查看命令行輸出,可以看到consolog.log的內(nèi)容為:

      {
          deps: {
              'components/bar/bar.js': [
                  'components/bar/bar.css'
              ],
              'components/foo/foo.js': [
                  'components/bar/bar.js',
                  'components/foo/foo.css'
              ]
          }
      }
      

      可以看到j(luò)s和同名的css自動建立了依賴關(guān)系,這是fis默認(rèn)進(jìn)行的依賴聲明。有了這個表,我們就可以把它注入到代碼中了。我們?yōu)轫撁鏈?zhǔn)備一個替換用的鉤子,比如約定為__FRAMEWORK_CONFIG__,這樣用戶就可以根據(jù)需要在合適的地方獲取并使用這些數(shù)據(jù)。模塊化框架的配置一般都是寫在非模塊化文件中的,比如html頁面里,所以我們應(yīng)該只針對views目錄下的文件做這樣的替換就可以。所以我們需要給views下的文件進(jìn)行一個標(biāo)記,只有views下的html或js文件才需要進(jìn)行依賴樹數(shù)據(jù)注入,具體的配置為:

      fis.config.set('roadmap.path', [
          {
              reg : '**.md',
              release : false
          },
          {
              reg : /^\/component_modules\/(.*)$/i,
              release : '/public/c/$1'
          },
          {
              reg : /^\/components\/(.*)$/i,
              release : '/public/c/${name}/${version}/$1'
          },
          {
              reg : /^\/views\/(.*)$/,
              //給views目錄下的文件加一個isViews屬性標(biāo)記,用以標(biāo)記文件分類
              //我們可以在插件中拿到文件對象的這個值
              isViews : true,
              release : '/public/${name}/${version}/$1'
          },
          {
              reg : '**',
              useStandard : false,
              useOptimizer : false
          }
      ]);
      
      var createFrameworkConfig = function(ret, conf, settings, opt){
          var map = {};
          map.deps = {};
          fis.util.map(ret.src, function(subpath, file){
              if(file.requires && file.requires.length){
                  map.deps[file.id] = file.requires;
              }
          });
          //把配置文件序列化
          var stringify = JSON.stringify(map, null, opt.optimize ? null : 4);
          //再次遍歷文件,找到isViews標(biāo)記的文件
          //替換里面的__FRAMEWORK_CONFIG__鉤子
          fis.util.map(ret.src, function(subpath, file){
              //有isViews標(biāo)記,并且是js或者h(yuǎn)tml類文件,才需要做替換
              if(file.isViews && (file.isJsLike || file.isHtmlLike)){
                  var content = file.getContent();
                  //替換文件內(nèi)容
                  content = content.replace(/\b__FRAMEWORK_CONFIG__\b/g, stringify);
                  file.setContent(content);
              }
          });
      };
      fis.config.set('modules.postpackager', [createFrameworkConfig]);
      
      //項(xiàng)目配置
      fis.config.set('name', 'proj');     //將name、version獨(dú)立配置,統(tǒng)管全局
      fis.config.set('version', '1.0.3');
      

      我在views/index.html中寫了這樣的代碼:

      <!doctype html>
      <html>
      <head>
          <title>hello</title>
      </head>
      <body>
          <script type="text/javascript" src="scrat.js"></script>
          <script type="text/javascript">
              require.config(__FRAMEWORK_CONFIG__);
              require.async('components/foo/foo.js', function(foo){
                  //todo
              });
          </script>
      </body>
      </html>
      

      執(zhí)行 fis release -d ../release 之后,得到構(gòu)建后的內(nèi)容為:

      <!doctype html>
      <html>
      <head>
          <title>hello</title>
      </head>
      <body>
          <script type="text/javascript" src="/public/proj/1.0.3/scrat.js"></script>
          <script type="text/javascript">
              require.config({
                  "deps": {
                      "components/bar/bar.js": [
                          "components/bar/bar.css"
                      ],
                      "components/foo/foo.js": [
                          "components/bar/bar.js",
                          "components/foo/foo.css"
                      ]
                  }
              });
              require.async('components/foo/foo.js', function(foo){
                  //todo
              });
          </script>
      </body>
      </html>
      

      在調(diào)用 require.async('components/foo/foo.js') 之際,模塊化框架已經(jīng)知道了這個foo.js依賴于bar.js、bar.css以及foo.css,因此可以發(fā)起兩個combo請求去加載所有依賴的js、css文件,完成后再執(zhí)行回調(diào)。

      現(xiàn)在模塊的id有一些問題,因?yàn)槟K發(fā)布會有版本號信息,因此模塊id也應(yīng)該攜帶版本信息,從前面的依賴樹生成配置代碼中我們可以看到模塊id其實(shí)也是文件的一個屬性,因此我們可以在roadmap.path中重新為文件賦予id屬性,使其攜帶版本信息:

      fis.config.set('roadmap.path', [
          {
              reg : '**.md',
              release : false,
              isHtmlLike : true
          },
          {
              reg : /^\/component_modules\/(.*)$/i,
              //追加id屬性
              id : '$1',
              release : '/public/c/$1'
          },
          {
              reg : /^\/components\/(.*)$/i,
              //追加id屬性,id為【項(xiàng)目名/版本號/文件路徑】
              id : '${name}/${version}/$1',
              release : '/public/c/${name}/${version}/$1'
          },
          {
              reg : /^\/views\/(.*)$/,
              //給views目錄下的文件加一個isViews屬性標(biāo)記,用以標(biāo)記文件分類
              //我們可以在插件中拿到文件對象的這個值
              isViews : true,
              release : '/public/${name}/${version}/$1'
          },
          {
              reg : '**',
              useStandard : false,
              useOptimizer : false
          }
      ]);
      

      重新構(gòu)建項(xiàng)目,我們得到了新的結(jié)果:

      <!doctype html>
      <html>
      <head>
          <title>hello</title>
      </head>
      <body>
          <img src="/public/proj/1.0.4/logo.png"/>
          <script type="text/javascript" src="/public/proj/1.0.4/scrat.js"></script>
          <script type="text/javascript">
              require.config({
                  "deps": {
                      "proj/1.0.4/bar/bar.js": [
                          "proj/1.0.4/bar/bar.css"
                      ],
                      "proj/1.0.4/foo/foo.js": [
                          "proj/1.0.4/bar/bar.js",
                          "proj/1.0.4/foo/foo.css"
                      ]
                  }
              });
              require.async('proj/1.0.4/foo/foo.js', function(foo){
                  //todo
              });
          </script>
      </body>
      </html>
      

      you see?所有id都會被修改為我們指定的模式,這就是以文件為中心的編譯系統(tǒng)的威力。

      以文件對象為中心構(gòu)建系統(tǒng)應(yīng)該通過配置指定文件的各種屬性。插件并不自己實(shí)現(xiàn)某種規(guī)范規(guī)定,而是讀取file對象的對應(yīng)屬性值,這樣插件的職責(zé)單一,規(guī)范又能統(tǒng)一起來被用戶指定,為完整的前端開發(fā)體系設(shè)計(jì)奠定了堅(jiān)實(shí)規(guī)范配置的基礎(chǔ)。

      接下來還有一個問題,就是模塊名太長,開發(fā)中寫這么長的模塊名非常麻煩。我們可以借鑒流行的模塊化框架中常用的縮短模塊名手段——別名(alias)——來降低開發(fā)中模塊引用的成本。此外,目前的配置其實(shí)會針對所有文件生成依賴關(guān)系表,我們的開發(fā)概念定義只有components和component_modules目錄下的文件才是模塊化的,因此我們可以進(jìn)一步的對文件進(jìn)行分類,得到這樣配置規(guī)范:

      fis.config.set('roadmap.path', [
          {
              reg : '**.md',
              release : false,
              isHtmlLike : true
          },
          {
              reg : /^\/component_modules\/(.*)$/i,
              id : '$1',
              //追加isComponentModules標(biāo)記屬性
              isComponentModules : true,
              release : '/public/c/$1'
          },
          {
              reg : /^\/components\/(.*)$/i,
              id : '${name}/${version}/$1',
              //追加isComponents標(biāo)記屬性
              isComponents : true,
              release : '/public/c/${name}/${version}/$1'
          },
          {
              reg : /^\/views\/(.*)$/,
              isViews : true,
              release : '/public/${name}/${version}/$1'
          },
          {
              reg : '**',
              useStandard : false,
              useOptimizer : false
          }
      ]);
      

      然后我們?yōu)橐恍┠Kid建立別名:

      var createFrameworkConfig = function(ret, conf, settings, opt){
          var map = {};
          map.deps = {};
          //別名收集表
          map.alias = {};
          fis.util.map(ret.src, function(subpath, file){
              //添加判斷,只有components和component_modules目錄下的文件才需要建立依賴樹或別名
              if(file.isComponents || file.isComponentModules){
                  //判斷一下文件名和文件夾是否同名,如果同名則建立一個別名
                  var match = subpath.match(/^\/components\/(.*?([^\/]+))\/\2\.js$/i);
                  if(match && match[1] && !map.alias.hasOwnProperty(match[1])){
                      map.alias[match[1]] = file.id;
                  }
                  if(file.requires && file.requires.length){
                      map.deps[file.id] = file.requires;
                  }
              }
          });
          var stringify = JSON.stringify(map, null, opt.optimize ? null : 4);
          fis.util.map(ret.src, function(subpath, file){
              if(file.isViews && (file.isJsLike || file.isHtmlLike)){
                  var content = file.getContent();
                  content = content.replace(/\b__FRAMEWORK_CONFIG__\b/g, stringify);
                  file.setContent(content);
              }
          });
      };
      fis.config.set('modules.postpackager', [createFrameworkConfig]);
      

      再次構(gòu)建,在注入的代碼中就能看到alias字段了:

      require.config({
          "deps": {
              "proj/1.0.5/bar/bar.js": [
                  "proj/1.0.5/bar/bar.css"
              ],
              "proj/1.0.5/foo/foo.js": [
                  "proj/1.0.5/bar/bar.js",
                  "proj/1.0.5/foo/foo.css"
              ]
          },
          "alias": {
              "bar": "proj/1.0.5/bar/bar.js",
              "foo": "proj/1.0.5/foo/foo.js"
          }
      });
      

      這樣,代碼中的 require('foo'); 就等價(jià)于 require('proj/1.0.5/foo/foo.js');了。

      還剩最后一個小小的需求,就是希望能像寫nodejs一樣開發(fā)js模塊,也就是要求實(shí)現(xiàn)define的自動包裹功能,這個可以通過文件編譯的 postprocessor 插件完成。配置為:

      //在postprocessor對所有js后綴的文件進(jìn)行內(nèi)容處理:
      fis.config.set('modules.postprocessor.js', function(content, file){
          //只對模塊化js文件進(jìn)行包裝
          if(file.isComponents || file.isComponentModules){
              content = 'define("' + file.id + 
                        '", function(require,exports,module){' +
                        content + '});';
          }
          return content;
      });
      

      所有在components目錄和component_modules目錄下的js文件都會被包裹define,并自動根據(jù)roadmap.path中的id配置進(jìn)行模塊定義了。

      最煎熬的一天終于過去了,睡一覺,擁抱一下周末。

      2014年02月15日 - 超晴

      周末的天氣非常好哇,一覺睡到中午才起,這么好的天氣寫碼豈不是很loser?!

      2014年02月16日 - 小雨

      居然浪費(fèi)了一天,剩下的時間不多了,今天要抓緊?。。。?/p>

      讓我們來回顧一下已經(jīng)完成了哪些工作:

      • 規(guī)范
        • 開發(fā)規(guī)范
          • 模塊化開發(fā),js模塊化,css模塊化,像nodejs一樣的模塊化開發(fā)
          • 組件化開發(fā),js、css、handlebars維護(hù)在一起
        • 部署規(guī)范
          • 采用nodejs后端,基本部署規(guī)范應(yīng)該參考 express 項(xiàng)目部署
          • 按版本號做非覆蓋式發(fā)布
          • 公共模塊可發(fā)布給第三方共享
      • 框架
        • js模塊化框架,支持請求合并,按需加載等性能優(yōu)化點(diǎn)
      • 工具
        • 可以編譯stylus為css
        • 支持js、css、圖片壓縮
        • 允許圖片壓縮后以base64編碼形式嵌入到css、js或html中
        • 與ci平臺集成
        • 文件監(jiān)聽、瀏覽器自動刷新
        • 本地預(yù)覽、數(shù)據(jù)模擬
      • 倉庫
        • 支持component模塊安裝和使用

      剩下的幾個需求中有些是fis默認(rèn)支持的,比如base64內(nèi)嵌功能,圖片會先經(jīng)過編譯流程,得到壓縮后的內(nèi)容fis再對其進(jìn)行base64化的內(nèi)嵌處理。由于fis的內(nèi)嵌功能支持任意文件的內(nèi)嵌,所以,這個語言能力擴(kuò)展可以同時解決前端模板和圖片base64內(nèi)嵌需求,比如我們有這樣的代碼:

      project
        - components
          - foo
            - foo.js
            - foo.css
            - foo.handlebars
            - foo.png
      

      無需配置,既可以在js中嵌入資源,比如 foo.js 中可以這樣寫:

      //依賴聲明
      var bar =  require('../bar/bar.js');
      //把handlebars文件的字符串形式嵌入到j(luò)s中
      var text = __inline('foo.handlebars');
      var tpl = Handlebars.compile(text);
      exports.render = function(data){
          return tpl(data);
      };
      
      //把圖片的base64嵌入到j(luò)s中
      var data = __inline('foo.png');
      exports.getImage = function(){
          var img = new Image();
          img.src = data;
          return img;
      };
      

      編譯后得到:

      define("proj/1.0.5/foo/foo.js", function(require,exports,module){
      //依賴聲明
      var bar =  require('proj/1.0.5/bar/bar.js');
      //把handlebars文件的字符串形式嵌入到j(luò)s中
      var text = "<h1>{{title}}</h1>";
      var tpl = Handlebars.compile(text);
      exports.render = function(data){
          return tpl(data);
      };
      
      //把圖片的base64嵌入到j(luò)s中
      var data = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoA...';
      exports.getImage = function(){
          var img = new Image();
          img.src = data;
          return img;
      };
      
      });
      

      支持stylus也非常簡單,fis在 parser 階段處理非標(biāo)準(zhǔn)語言,這個階段可以把非標(biāo)準(zhǔn)的js(coffee/前端模板)、css(less/sass/stylus)、html(markdown)語言轉(zhuǎn)換為標(biāo)準(zhǔn)的js、css或html。處理之后那些文件還能和標(biāo)準(zhǔn)語言一起經(jīng)歷預(yù)處理、語言能力擴(kuò)展、后處理、校驗(yàn)、測試、壓縮等階段。

      所以,要支持stylus的編譯,只要在fis-conf.js中添加這樣的配置即可:

      //依賴開源的stylus包
      var stylus = require('stylus');
      //編譯插件只負(fù)責(zé)處理文件內(nèi)容
      var stylusParser = function(content, file, conf){
          return stylus(content, conf).render();
      };
      //配置編譯流程,styl后綴的文件經(jīng)過編譯插件函數(shù)處理
      fis.config.set('modules.parser.styl', stylusParser);
      //告訴fis,styl后綴的文件,被當(dāng)做css處理,編譯后后綴也是css
      fis.config.set('roadmap.ext.styl', 'css');
      

      這樣我們項(xiàng)目中的*.styl后綴的文件都會被編譯為css內(nèi)容,并且會在后面的流程中被當(dāng)做css內(nèi)容處理,比如壓縮、csssprite等。

      文件監(jiān)聽、自動刷新都是fis內(nèi)置的功能,fis的release命令集合了所有編譯所需的參數(shù),

        fis release -h
      
        Usage: release [options]
      
        Options:
      
          -h, --help             output usage information
          -d, --dest <names>     release output destination
          -m, --md5 [level]      md5 release option
          -D, --domains          add domain name
          -l, --lint             with lint
          -t, --test             with unit testing
          -o, --optimize         with optimizing
          -p, --pack             with package
          -w, --watch            monitor the changes of project
          -L, --live             automatically reload your browser
          -c, --clean            clean compile cache
          -r, --root <path>      set project root
          -f, --file <filename>  set fis-conf file
          -u, --unique           use unique compile caching
          --verbose              enable verbose output
      

      這些參數(shù)是可以隨意組合的,比如我們想文件監(jiān)聽、自動刷新,則使用:

      fis release -wL
      

      壓縮、打包、文件監(jiān)聽、自動刷新、發(fā)布到output目錄,則使用:

      fis release -opwLd output
      

      構(gòu)建工具不需要那么多命令,或者develop、release等不同狀態(tài)的配置文件,應(yīng)該從命令行切換編譯參數(shù),從而實(shí)現(xiàn)開發(fā)/上線構(gòu)建模式的切換。

      另外,fis是命令行工具,各種內(nèi)置的插件也是完全獨(dú)立無環(huán)境依賴的,可以與ci平臺直接對接,并在各個主流操作系統(tǒng)下運(yùn)行正常。

      利用fis的內(nèi)置的各種編譯功能,我們離目標(biāo)又近了許多:

      • 規(guī)范
        • 開發(fā)規(guī)范
          • 模塊化開發(fā),js模塊化,css模塊化,像nodejs一樣的模塊化開發(fā)
          • 組件化開發(fā),js、css、handlebars維護(hù)在一起
        • 部署規(guī)范
          • 采用nodejs后端,基本部署規(guī)范應(yīng)該參考express項(xiàng)目部署
          • 按版本號做非覆蓋式發(fā)布
          • 公共模塊可發(fā)布給第三方共享
      • 框架
        • js模塊化框架,支持請求合并,按需加載等性能優(yōu)化點(diǎn)
      • 工具
        • 可以編譯stylus為css
        • 支持js、css、圖片壓縮
        • 允許圖片壓縮后以base64編碼形式嵌入到css、js或html中
        • 與ci平臺集成
        • 文件監(jiān)聽、瀏覽器自動刷新
        • 本地預(yù)覽、數(shù)據(jù)模擬
      • 倉庫
        • 支持component模塊安裝和使用

      剩下兩個,我們可以通過擴(kuò)展fis的命令行插件來實(shí)現(xiàn)。fis有11個編譯流程擴(kuò)展點(diǎn),還有一個命令行擴(kuò)展點(diǎn)。要擴(kuò)展命令行插件很簡單,只要我們將插件安裝到與fis同級的node_modules目錄下即可。比如:

      node_modules
        - fis
        - fis-command-say
      

      那么執(zhí)行 fis say 這個命令,就能調(diào)用到那個fis-command-say插件了。剩下的這個component模塊安裝,我就利用了這個擴(kuò)展點(diǎn),結(jié)合component開源的 component-installer 包,我就可以把component整合當(dāng)前開發(fā)體系中,這里我們需要創(chuàng)建一個npm包來提供擴(kuò)展,而不能直接在fis-conf.js中擴(kuò)展命令行,插件代碼我就不貼了,可以看 這里。

      眼前我們有了一個差不多100行的fis-conf.js文件,還有幾個插件,如果我把這樣一個零散的系統(tǒng)交付團(tuán)隊(duì)使用,那么大家使用的步驟差不多是這樣的:

      1. 安裝fis,npm install -g fis
      2. 安裝component安裝用的命令行插件,npm insatll -g fis-command-component
      3. 安裝stylus編譯插件,npm install -g fis-parser-stylus
      4. 下載一份配置文件,fis-conf.js,修改里面的name、version配置

      這種情況讓團(tuán)隊(duì)用起來會有很多問題。首先,安裝過程太過麻煩,其次如果項(xiàng)目多,那么fis-conf.js不能同步升級,這是非常嚴(yán)重的問題。grunt的gruntfile.js也是如此。如果說有一個項(xiàng)目用了某套grunt配置感覺很爽,那么下個項(xiàng)目也想用這套方案,復(fù)制gruntfile.js是必須的操作,項(xiàng)目用的多了,同步gruntfile的成本就變得非常高了。

      因此,fis提供了一種“包裝”的能力,它允許你將fis作為內(nèi)核,包裝出一個新的命令行工具,這個工具內(nèi)置了一些fis的配置,并且把所有命令行調(diào)用的參數(shù)傳遞給fis內(nèi)核去處理。

      我準(zhǔn)備把這套系統(tǒng)打包為一個新的工具,給它取名為 scrat,也是一只松鼠。這個新工具的目錄結(jié)構(gòu)是這樣的:

      scrat
        - bin
          - scrat
        - node_modules
          - fis
          - fis-parser-handlebars
          - fis-lint-jshint
          - scrat-command-install
          - scrat-command-server
          - scrat-parser-stylus
        - index.js
        - package.json
      

      其中,index.js的內(nèi)容為:

      //require一下fis模塊
      var fis = module.exports = require('fis');
      
      //聲明命令行工具名稱
      fis.cli.name = 'scrat';
      
      //定義插件前綴,允許加載scrat-xxx-xxx插件,或者fis-xxx-xxx插件,
      //這樣可以形成scrat自己的插件系統(tǒng)
      fis.require.prefixes = [ 'scrat', 'fis' ];
      
      //把前面的配置都寫在這里統(tǒng)一管理
      //項(xiàng)目中就不用再寫了
      fis.config.merge({...});
      

      將這個npm包發(fā)布出來,我們就有了一個全新的開發(fā)工具,這個工具可以解決前面說的13項(xiàng)技術(shù)問題,并提供一套完整的集成解決方案,而你的團(tuán)隊(duì)使用的時候,只有兩個步驟:

      1. 安裝這個工具,npm install -g scrat
      2. 項(xiàng)目配置只有兩項(xiàng),name和version

      使用新工具的命令、參數(shù)幾乎和fis完全一樣:

      scrat release [options]
      scrat server start
      scrat install <name@version> [options]
      

      而scrat這個工具所內(nèi)置的配置將變成規(guī)范文檔描述給團(tuán)隊(duì)同學(xué),這套系統(tǒng)要比grunt那種松散的構(gòu)建系統(tǒng)組成方式更容易被多個團(tuán)隊(duì)、多個項(xiàng)目同時共享。

      熬了一個通宵,基本算是完成了。。。

      2014年02月17日 - 多云

      終于到了周一,交付了一個新的開發(fā)工具——scrat,及其使用 文檔。

      然而,過去的三天,為了構(gòu)造這套前端開發(fā)體系,都寫了哪些代碼呢?

      • 基于fis的一套規(guī)范及插件配置,274行;
      • scrat install命令行插件,用于安裝component模塊,74行;
      • scrat server命令行插件,用于啟動nodejs的服務(wù)器,203行
      • 編譯stylus的插件,10行
      • 編譯handlebars的插件,6行
      • 一個模塊化框架 scrat.js,393行

      一共 960行 代碼,用了4人/天。

      總結(jié)

      不可否認(rèn),為大規(guī)模前端團(tuán)隊(duì)設(shè)計(jì)集成解決方案需要花費(fèi)非常多的心思。

      如果說只是實(shí)現(xiàn)一個簡單的編譯+壓縮+文件監(jiān)+聽自動刷新的常規(guī)構(gòu)建系統(tǒng),基于fis應(yīng)該不超過1小時就能完成一個,但要實(shí)踐完整的前端集成解決方案,確實(shí)需要點(diǎn)時間。

      如之前一篇 文章 所講,前端集成解決方案有8項(xiàng)技術(shù)要素,除了組件倉庫,其他7項(xiàng)對于企業(yè)級前端團(tuán)隊(duì)來說,應(yīng)該都需要完整實(shí)現(xiàn)的。即便暫時不想實(shí)現(xiàn),也會隨著業(yè)務(wù)發(fā)展而被迫慢慢完善,這個完善過程是普適的。

      對于前端集成解決方案的實(shí)踐,可以總結(jié)出這些設(shè)計(jì)步驟:

      1. 設(shè)計(jì)開發(fā)概念,定義開發(fā)資源的分類(模塊化/非模塊化)
      2. 設(shè)計(jì)開發(fā)目錄,降低開發(fā)、維護(hù)成本(開發(fā)規(guī)范)
      3. 根據(jù)運(yùn)維和業(yè)務(wù)要求,設(shè)計(jì)部署規(guī)范(部署規(guī)范)
      4. 設(shè)計(jì)工具,完成開發(fā)目錄和部署目錄的轉(zhuǎn)換(開發(fā)-部署轉(zhuǎn)換)
      5. 設(shè)計(jì)模塊化框架,兼顧性能優(yōu)化(開發(fā)框架)
      6. 擴(kuò)展工具,支持開發(fā)框架的構(gòu)建需求(框架構(gòu)建需求)
      7. 流程整合(開發(fā)、測試、聯(lián)調(diào)、上線等流程接入)

      我們可以看看業(yè)界已有團(tuán)隊(duì)提出的各種解決方案,無不以這種思路來設(shè)計(jì)和發(fā)展的:

      • seajs開發(fā)體系,支付寶團(tuán)隊(duì)前端開發(fā)體系,以 spm 為構(gòu)建和包管理工具
      • fis-plus,百度絕大多數(shù)前端團(tuán)隊(duì)使用的開發(fā)體系,以fis為構(gòu)建工具內(nèi)核,以lights為包管理工具
      • edp,百度ecomfe前端開發(fā)體系,以 edp 為構(gòu)建和包管理工具
      • modjs,騰訊AlloyTeam團(tuán)隊(duì)出品的開發(fā)體系
      • yeoman,google出品的解決方案,以grunt為構(gòu)建工具,bower為包管理工具

      縱觀這些公司出品的前端集成解決方案,深入剖析其中的框架、規(guī)范、工具和流程,都可以發(fā)現(xiàn)一些共通的影子,設(shè)計(jì)思想殊途同歸,不約而同的朝著一種方向前進(jìn),那就是前端集成解決方案。嘗試將前端工程孤立的技術(shù)要素整合起來,解決常見的領(lǐng)域問題。

      或許有人會問,不就是寫頁面么,用得著這么復(fù)雜?

      在這里我不能給出肯定或者否定的答復(fù)。

      因?yàn)閱渭儚恼Z言的角度來說,html、js、css(甚至有人認(rèn)為css是數(shù)據(jù)結(jié)構(gòu),而非語言)確實(shí)是最簡單最容易上手的開發(fā)語言,不用模塊化、不用工具、不用壓縮,任何人都可以快速上手,完成一兩個功能簡單的頁面。所以說,在一般情況下,前端開發(fā)非常簡單。

      在規(guī)模很小的項(xiàng)目中,前端技術(shù)要素彼此不會直接產(chǎn)生影響,因此無需集成解決方案。

      但正是由于前端語言這種靈活松散的特點(diǎn),使得前端項(xiàng)目規(guī)模在達(dá)到一定規(guī)模后,工程問題凸顯,成為發(fā)展瓶頸,各種技術(shù)要素彼此之間開始出現(xiàn)關(guān)聯(lián),要用模塊化開發(fā),就必須對應(yīng)某個模塊化框架,用這個框架就必須對應(yīng)某個構(gòu)建工具,要用這個工具,就必須對應(yīng)某個包管理工具……這個時候,完整實(shí)踐前端集成解決方案就成了不二選擇。

      當(dāng)前端項(xiàng)目達(dá)到一定規(guī)模后,工程問題將成為主要瓶頸,原來孤立的技術(shù)要素開始彼此產(chǎn)生影響,需要有人從比較高的角度去梳理、尋找適合自己團(tuán)隊(duì)的集成解決方案。

      所以會出現(xiàn)一些框架或工具在小項(xiàng)目中使用的好好的,一旦放到團(tuán)隊(duì)里使用就非常困難的情況。

      前端入門雖易工程不易,且行寫珍惜!

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多