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

2018-02-24 15:49 更新

原文出處:https://github.com/fouber/blog/issues/2
作者:fouber

目錄

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

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

干貨?fuck things?在哪里!

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

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

2014年02月12日 - 晴

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

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

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

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

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

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

還沒(méi)等我說(shuō)完,nino打斷我說(shuō)

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

松鼠公司的松鼠瀏覽器你知道吧,恩,它有很多個(gè)版本的樣子。
我希望代碼發(fā)布后能按照版本部署,不要彼此覆蓋。

舉個(gè)例子,代碼部署結(jié)構(gòu)可能是這樣的:

  release/
    - public/
      - 項(xiàng)目名
        - 1.0.0/
        - 1.0.1/
        - 1.0.2/
        - 1.0.2-alpha/
        - 1.0.2-beta/

讓歷史瀏覽器瀏覽歷史版本,沒(méi)事還能做個(gè)灰度發(fā)布,ABTest啥的,多好!

此外,我們將來(lái)會(huì)有多個(gè)項(xiàng)目使用這套開(kāi)發(fā)模式,希望能共用一些組件或者模
塊,產(chǎn)品也會(huì)公布一些api模塊給第三方使用,所以共享模塊功能也要加上。

總的來(lái)說(shuō),還要追加兩個(gè)部署需求:

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

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

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

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

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

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

2014年02月13日 - 多云

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

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

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

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

不幸的是,現(xiàn)在fis官網(wǎng)所介紹的?并不是?fis,而是一個(gè)叫?fis-plus?的項(xiàng)目,該項(xiàng)目并不像字面理解的那樣是fis的加強(qiáng)版,而是在fis的基礎(chǔ)上定制的一套面向百度前端團(tuán)隊(duì)的解決方案,以php為后端語(yǔ)言,跟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)開(kāi)發(fā)規(guī)范、部署規(guī)范、以及php、smarty等有著較深的綁定關(guān)系。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

project
  - modules       存放模塊化資源
  - pages         存放頁(yè)面資源
  - static        存放非模塊化資源

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

project
  - components    存放模塊化資源
  - pages         存放頁(yè)面資源
  - static        存放非模塊化資源

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

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

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

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

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

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

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

托nino的福,咱們的部署策略將會(huì)非常復(fù)雜,根據(jù)要求,一個(gè)完整的部署結(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ā)布出來(lái),得到這樣的部署結(jié)構(gòu):

release
  - public
    - component_modules   模塊化資源都部署到這個(gè)目錄下
      - 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?這個(gè)名字太長(zhǎng)了,如果部署到這樣的路徑下,url會(huì)很長(zhǎng),這也是一個(gè)優(yōu)化點(diǎn),因此最終決定部署結(jié)構(gòu)為:

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

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

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

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

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

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

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

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

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

閑話(huà)少說(shuō),我們先來(lái)看一下基本的配置,在?fis-conf.js?中添加代碼:

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

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

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

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

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)有力工具:

fis release

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

<!doctype html>
<html>
<head>
    <title>hello</title>
</head>
<body>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/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)知道了這個(gè)foo.js依賴(lài)于bar.js、bar.css以及foo.css,因此可以發(fā)起兩個(gè)combo請(qǐng)求去加載所有依賴(lài)的js、css文件,完成后再執(zhí)行回調(diào)。

現(xiàn)在模塊的id有一些問(wèn)題,因?yàn)槟K發(fā)布會(huì)有版本號(hào)信息,因此模塊id也應(yīng)該攜帶版本信息,從前面的依賴(lài)樹(shù)生成配置代碼中我們可以看到模塊id其實(shí)也是文件的一個(gè)屬性,因此我們可以在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)目名/版本號(hào)/文件路徑】
        id : '${name}/${version}/$1',
        release : '/public/c/${name}/${version}/$1'
    },
    {
        reg : /^\/views\/(.*)$/,
        //給views目錄下的文件加一個(gè)isViews屬性標(biāo)記,用以標(biāo)記文件分類(lèi)
        //我們可以在插件中拿到文件對(duì)象的這個(gè)值
        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="https://atts.w3cschool.cn/attachments/image/cimg/logo.png"/>
    <script type="text/javascript" src="https://atts.w3cschool.cn/attachments/image/cimg/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都會(huì)被修改為我們指定的模式,這就是以文件為中心的編譯系統(tǒng)的威力。

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

接下來(lái)還有一個(gè)問(wèn)題,就是模塊名太長(zhǎng),開(kāi)發(fā)中寫(xiě)這么長(zhǎng)的模塊名非常麻煩。我們可以借鑒流行的模塊化框架中常用的縮短模塊名手段——?jiǎng)e名(alias)——來(lái)降低開(kāi)發(fā)中模塊引用的成本。此外,目前的配置其實(shí)會(huì)針對(duì)所有文件生成依賴(lài)關(guān)系表,我們的開(kāi)發(fā)概念定義只有components和component_modules目錄下的文件才是模塊化的,因此我們可以進(jìn)一步的對(duì)文件進(jìn)行分類(lèi),得到這樣配置規(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目錄下的文件才需要建立依賴(lài)樹(shù)或別名
        if(file.isComponents || file.isComponentModules){
            //判斷一下文件名和文件夾是否同名,如果同名則建立一個(gè)別名
            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');了。

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

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

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

最煎熬的一天終于過(guò)去了,睡一覺(jué),擁抱一下周末。

2014年02月15日 - 超晴

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

2014年02月16日 - 小雨

居然浪費(fèi)了一天,剩下的時(shí)間不多了,今天要抓緊啊?。?!

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

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

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

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

無(wú)需配置,既可以在js中嵌入資源,比如 foo.js 中可以這樣寫(xiě):

//依賴(lài)聲明
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){
//依賴(lài)聲明
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);
};

//把圖片的b
以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)