|
InfoQ編輯注:本文來(lái)自前端工程師張?jiān)讫埖?a >博客,由作者本人推薦至InfoQ進(jìn)行分享。目前本系列已經(jīng)發(fā)布了三個(gè)部分,本處分享的是第二部分,前端開發(fā)體系建設(shè)日記。建議在閱讀本文前先閱讀本文作者和其團(tuán)隊(duì)之前分享的《前端工程精粹》系列一、二、三。 上周寫了一篇 文章 介紹前端集成解決方案的基本理論,很多同學(xué)看過(guò)之后大呼不過(guò)癮。
本打算繼續(xù)完善理論鏈,形成前端工程的知識(shí)結(jié)構(gòu)。但鑒于如今的快餐文化,po主決定還是先寫一篇實(shí)戰(zhàn)介紹,讓大家看到前端工程體系能為團(tuán)隊(duì)帶來(lái)哪些好處,調(diào)起大家的胃口再說(shuō)。
2014年02月12日 - 晴新到松鼠團(tuán)隊(duì)的第二天,小伙伴 @nino 找到我說(shuō)
po主不禁暗喜,好機(jī)會(huì),這是我專業(yè)啊,藍(lán)翔技校-前端集成解決方案學(xué)院-自動(dòng)化系-打包學(xué)專業(yè)的文憑不是白給的,于是自信滿滿的對(duì)nino說(shuō),有什么需求盡管提!
我倒吸一口涼氣,但表面故作鎮(zhèn)定的說(shuō):恩,確實(shí)不多,讓我們先來(lái)看看第一個(gè)需求。。。 還沒(méi)等我說(shuō)完,nino打斷我說(shuō)
松鼠公司的松鼠瀏覽器你知道吧,恩,它有很多個(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)目使用這套開發(fā)模式,希望能共用一些組件或者模
塊,產(chǎn)品也會(huì)公布一些api模塊給第三方使用,所以共享模塊功能也要加上。
總的來(lái)說(shuō),還要追加兩個(gè)部署需求:
我凝望著會(huì)議室白板上的這些需求,正打算爭(zhēng)辯什么,一扭頭發(fā)現(xiàn)nino已經(jīng)不見了。。。正在沮喪之際,小伙伴 @hinc 過(guò)來(lái)找我,跟他大概講了一下nino的需求,正想跟他抱怨工期問(wèn)題時(shí),hinc卻說(shuō)
3天時(shí)間,13項(xiàng)前端技術(shù)元素,靠譜么。。。 2014年02月13日 - 多云一覺(jué)醒來(lái),輕松了許多,但還有任務(wù)在身,不敢有半點(diǎn)怠慢。整理一下昨天的需求,我們來(lái)做一個(gè)簡(jiǎn)單的劃分。
這樣一套規(guī)范、框架、工具和倉(cāng)庫(kù)的開發(fā)體系,服從我之前介紹的 前端集成解決方案 的描述。前端界每天都團(tuán)隊(duì)在設(shè)計(jì)和實(shí)現(xiàn)這類系統(tǒng),它們其實(shí)是有規(guī)律可循的。百度出品的 fis 就是一個(gè)能幫助快速搭建前端集成解決方案的工具。使用fis我應(yīng)該可以在3天之內(nèi)完成這些任務(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)開發(fā)規(guī)范、部署規(guī)范、以及php、smarty等有著較深的綁定關(guān)系。 因此如果你的團(tuán)隊(duì)用的不是 php后端 && smarty模板 && modjs模塊化框架 && bingo框架 的話,請(qǐng)查看fis的文檔,或許不會(huì)有那么多困惑。
言歸正傳,讓我們基于 fis 開始實(shí)踐這套開發(fā)體系吧! 0. 開發(fā)概念定義前端開發(fā)體系設(shè)計(jì)第一步要定義開發(fā)概念。開發(fā)概念是指針對(duì)開發(fā)資源的分類概念。開發(fā)概念的確立,直接影響到規(guī)范的定制。比如,傳統(tǒng)的開發(fā)概念一般是按照文件類型劃分的,所以傳統(tǒng)前端項(xiàng)目會(huì)有這樣的目錄結(jié)構(gòu):
這樣確實(shí)很直接,任何智力健全的人都知道每個(gè)文件該放在哪里。但是這樣的開發(fā)概念劃分將給項(xiàng)目帶來(lái)較高的維護(hù)成本,并為項(xiàng)目臃腫埋下了工程隱患,理由是:
以我個(gè)人的經(jīng)驗(yàn),更傾向于具有一定語(yǔ)義的開發(fā)概念。綜合前面的需求,我為這個(gè)開發(fā)體系確定了3個(gè)開發(fā)資源概念:
1. 開發(fā)目錄設(shè)計(jì)基于開發(fā)概念的確立,接下來(lái)就要確定目錄規(guī)范了。我通常會(huì)給每種開發(fā)資源的目錄取一個(gè)有語(yǔ)義的名字,三種資源我們可以按照概念直接定義目錄結(jié)構(gòu)為: project - modules 存放模塊化資源 - pages 存放頁(yè)面資源 - static 存放非模塊化資源 這樣劃分目錄確實(shí)直觀,但結(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è)面和非模塊化資源比較合適,因此最終得到的開發(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)目的開發(fā)目錄結(jié)構(gòu)會(huì)和部署要求保持一致。這也為項(xiàng)目間模塊的復(fù)用帶來(lái)了成本,因?yàn)榇a中寫的url通常是部署后的路徑,遷移之后就可能失效了。
好了,去吃個(gè)午飯,下午繼續(xù)。。。 3. 配置fis連接開發(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)建配置了。雖然開發(fā)規(guī)范和部署規(guī)范十分復(fù)雜,但好在fis有一個(gè)非常強(qiáng)大的 roadmap.path 功能,專門用于分類文件、調(diào)整發(fā)布結(jié)構(gòu)、指定文件的各種屬性等功能實(shí)現(xiàn)。
閑話少說(shuō),我們先來(lái)看一下基本的配置,在 fis-conf.js 中添加代碼: fis.config.set('roadmap.path', [
{
reg : '**.md', //所有md后綴的文件
release : false //不發(fā)布
}
]);
在fis中,roadmap.pah是一個(gè)數(shù)組數(shù)據(jù),數(shù)組每個(gè)元素是一個(gè)對(duì)象,必須定義 reg 屬性,用以匹配項(xiàng)目文件路徑從而進(jìn)行分類劃分,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)有力工具:
在上面的配置中,版本號(hào)被寫到了匹配規(guī)則里,這樣非常不方便工程師在迭代的過(guò)程中升級(jí)項(xiàng)目版本。我們應(yīng)該將版本號(hào)、項(xiàng)目名稱等配置獨(dú)立出來(lái)管理。好在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個(gè),包括:
完成第一份配置之后,我們來(lái)看一下效果。 cd project fis release --dest ../release 進(jìn)入到項(xiàng)目目錄,然后使用fis release命令,對(duì)項(xiàng)目進(jìn)行構(gòu)建,用 --dest <path> 參數(shù)指定編譯結(jié)果的產(chǎn)出路徑,可以看到部署后的結(jié)果:
fis系統(tǒng)的強(qiáng)大之處在于當(dāng)你調(diào)整了部署規(guī)范之后,fis會(huì)識(shí)別所有資源定位標(biāo)記,將他們修改為對(duì)應(yīng)的部署路徑。
fis的文件系統(tǒng)設(shè)計(jì)決定了配置開發(fā)規(guī)范的成本非常低。fis構(gòu)建核心有三個(gè)超級(jí)正則,用于識(shí)別資源定位標(biāo)記,把用戶的開發(fā)規(guī)范和部署規(guī)范通過(guò)配置完整連接起來(lái),具體實(shí)現(xiàn)可以看這里。
接下來(lái),我們修改一下項(xiàng)目版本配置,再發(fā)布一下看看效果: fis.config.set('version', '1.0.1');
再次執(zhí)行: cd project fis release --dest ../release 得到:
至此,我們已經(jīng)基本解決了開發(fā)和部署直接的目錄規(guī)范問(wèn)題,這里我需要加快一些步伐,把其他目錄的部署規(guī)范也配置好,得到一個(gè)相對(duì)比較完整的結(jié)果: fis.config.set('roadmap.path', [
{
//md后綴的文件不發(fā)布
reg : '**.md',
release : false
},
{
//component_modules目錄下的代碼,由于component規(guī)范,已經(jīng)有了版本號(hào)
//我將它們直接發(fā)送到public/c目錄下就好了
reg : /^\/component_modules\/(.*)$/i,
release : '/public/c/$1'
},
{
//項(xiàng)目模塊化目錄沒(méi)有版本號(hào)結(jié)構(gòu),用全局版本號(hào)控制發(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)
//也不用對(duì)這些資源進(jìn)行壓縮(useOptimizer: false)
reg : '**',
useStandard : false,
useOptimizer : false
}
]);
fis.config.set('name', 'proj');
fis.config.set('version', '1.0.2');
我構(gòu)造了一個(gè)相對(duì)完整的目錄結(jié)構(gòu),然后進(jìn)行了一次構(gòu)建,效果還不錯(cuò):
不管部署規(guī)則多么復(fù)雜都不用擔(dān)心,有fis強(qiáng)大的資源定位系統(tǒng)幫你在開發(fā)規(guī)范和部署規(guī)范之間建立聯(lián)系,設(shè)計(jì)開發(fā)體系不在受制于工具的實(shí)現(xiàn)能力。
從前面的例子可以看出,開發(fā)使用相對(duì)路徑即可,fis會(huì)在構(gòu)建時(shí)會(huì)根據(jù)fis-conf.js中的配置完成開發(fā)路徑到部署路徑的轉(zhuǎn)換工作。這意味著在fis體系下開發(fā)的模塊將具有天然的可移植性,既能滿足不同項(xiàng)目的不同部署需求,又能允許開發(fā)中使用相對(duì)路徑進(jìn)行資源定位,工程師再不用把部署路徑寫到代碼中了。 愉快的一天就這么過(guò)去了,睡覺(jué)! 2014年02月14日 - 陰轉(zhuǎn)多云每到周五總是非常愜意的感覺(jué),不管這一周多么辛苦,周五就是一個(gè)解脫,更何況今天還是個(gè)特別的日子——情人節(jié)! 昨天主要解決了開發(fā)概念、開發(fā)目錄規(guī)范、部署目錄規(guī)范以及初步的fis-conf.js配置。今天要進(jìn)行前端開發(fā)體系設(shè)計(jì)的關(guān)鍵任務(wù)——模塊化框架。
模塊化框架肩負(fù)著模塊管理、資源加載、性能優(yōu)化(按需,請(qǐng)求合并)等多種重要職責(zé),同時(shí)它也是組件開發(fā)的基礎(chǔ)框架,因此模塊化框架設(shè)計(jì)的好壞直接影響到開發(fā)體系的設(shè)計(jì)質(zhì)量。 很遺憾的說(shuō),現(xiàn)在市面上已有的模塊化框架都沒(méi)能很好的處理模塊管理、資源加載和性能優(yōu)化三者之間的關(guān)系。這倒不是框架設(shè)計(jì)的問(wèn)題,而是由前端領(lǐng)域語(yǔ)言特殊性決定的??蚣茉O(shè)計(jì)者一般在思考模塊化框架時(shí),通常站在純前端運(yùn)行環(huán)境角度考慮,基本功能都是用原生js實(shí)現(xiàn)的,因此一個(gè)模塊化開發(fā)的關(guān)鍵問(wèn)題不能被很好的解決。這個(gè)關(guān)鍵問(wèn)題就是依賴聲明。 以 seajs 為例(無(wú)意冒犯),seajs采用運(yùn)行時(shí)分析的方式實(shí)現(xiàn)依賴聲明識(shí)別,并根據(jù)依賴關(guān)系做進(jìn)一步的模塊加載。比如如下代碼: define(function(require) {
var foo = require("foo");
//...
});
當(dāng)seajs要執(zhí)行一個(gè)模塊的factory函數(shù)之前,會(huì)先分析函數(shù)體中的require書寫,具體代碼在這里和這里,大概的代碼邏輯如下: Module.define = function (id, deps, factory) {
...
//抽取函數(shù)體的字符串內(nèi)容
var code = factory.toString();
//設(shè)計(jì)一個(gè)正則,分析require語(yǔ)句
var reg = /\brequire\s*\(([.*]?)\)/g;
var deps = [];
//掃描字符串,得到require所聲明的依賴
code.replace(reg, function(m, $1){
deps.push($1);
});
//加載依賴,完成后再執(zhí)行factory
...
};
由于框架設(shè)計(jì)是在“純前端實(shí)現(xiàn)”的約束條件下,使得模塊化框架對(duì)于依賴的分析必須在模塊資源加載完成之后才能做出識(shí)別。這將引起兩個(gè)性能相關(guān)的問(wèn)題:
第一個(gè)問(wèn)題還好,尤其是在gzip下差不多多少字節(jié),但是要配置js壓縮器保留require函數(shù)不壓縮。第二個(gè)問(wèn)題就比較麻煩了,雖然seajs有seajs-combo插件可以一定程度上減少請(qǐng)求,但仍然不能很好的解決這個(gè)問(wèn)題。舉個(gè)例子,有如下seajs模塊依賴關(guān)系樹:
采用seajs-combo插件之后,靜態(tài)資源請(qǐng)求的效果是這樣的: 工作過(guò)程是
雖然combo可以在依賴層級(jí)上進(jìn)行合并,但完成page.js的請(qǐng)求仍需要4個(gè)。很多團(tuán)隊(duì)在使用seajs的時(shí)候,為了避免這樣的串行依賴請(qǐng)求問(wèn)題,會(huì)自己實(shí)現(xiàn)打包方案,將所有文件直接打包在一起,放棄了模塊化的按需加載能力,也是一種無(wú)奈之舉。 原因很簡(jiǎn)單
歸根結(jié)底,這樣的結(jié)論是由前端領(lǐng)域語(yǔ)言的特點(diǎn)決定的。前端語(yǔ)言缺少三種編譯能力,前面講目錄規(guī)范和部署規(guī)范時(shí)其實(shí)已經(jīng)提到了一種能力,就是“資源定位的能力”,讓工程師使用開發(fā)路徑定位資源,編譯后可轉(zhuǎn)換為部署路徑。其他語(yǔ)言編寫的程序幾乎都沒(méi)有web這種物理上分離的資源部署策略,而且大多具都有類似'getResource(path)'這樣的函數(shù),用于在運(yùn)行環(huán)境下定位當(dāng)初的開發(fā)資源,這樣不管項(xiàng)目怎么部署,只要getResource函數(shù)運(yùn)行正常就行了??上岸苏Z(yǔ)言沒(méi)有這樣的資源定位接口,只有url這樣的資源定位符,它指向的其實(shí)并不是開發(fā)路徑,而是部署路徑。 這里可以簡(jiǎn)單列舉出前端語(yǔ)言缺少三種的語(yǔ)言能力:
以后我會(huì)在完善前端開發(fā)體系理論的時(shí)候在詳細(xì)介紹這三種語(yǔ)言能力的必要性和原子性,這里就暫時(shí)不展開說(shuō)明了。
要兼顧性能的同時(shí)解決模塊化依賴管理和加載問(wèn)題,其關(guān)鍵點(diǎn)在于
了解了原因,我們就要自己動(dòng)手設(shè)計(jì)模塊化框架了。不要害怕,模塊化框架其實(shí)很簡(jiǎn)單,思想、規(guī)范都是經(jīng)過(guò)很多前輩總結(jié)的結(jié)果,我們只要遵從他們的設(shè)計(jì)思想去實(shí)現(xiàn)就好了。 參照已有規(guī)范,我定義了三個(gè)模塊化框架接口:
利用構(gòu)建工具建立模塊依賴關(guān)系表,再將關(guān)系表注入到代碼中,調(diào)用require.config接口讓框架知道完整的依賴樹,從而實(shí)現(xiàn)require.async在異步加載模塊時(shí)能提前預(yù)知所有依賴的資源,一次性請(qǐng)求回來(lái)。 以上面的page.js依賴樹為例,構(gòu)建工具會(huì)生成如下代碼: 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);語(yǔ)句時(shí),框架查詢config.deps表,就能知道要發(fā)起一個(gè)這樣的combo請(qǐng)求: 從而實(shí)現(xiàn)按需加載和請(qǐng)求合并兩項(xiàng)性能優(yōu)化需求。 根據(jù)這樣的設(shè)計(jì)思路,我請(qǐng) @hinc 幫忙實(shí)現(xiàn)了這個(gè)框架,我告訴他,deps里不但會(huì)有js,還會(huì)有css,所以也要兼容一下。hinc果然是執(zhí)行能力非常強(qiáng)的小伙伴,僅一個(gè)下午的時(shí)間就搞定了框架的實(shí)現(xiàn),我們給這個(gè)框架取名為 scrat.js,僅有393行。 前面提到fis具有資源依賴聲明的編譯能力。因此只要工程師按照f(shuō)is規(guī)定的書寫方式在代碼中聲明依賴關(guān)系,就能在構(gòu)建的最后階段自動(dòng)獲得fis系統(tǒng)整理好的依賴樹,然后對(duì)依賴的數(shù)據(jù)結(jié)構(gòu)進(jìn)行調(diào)整、輸出,滿足框架要求就搞定了!fis規(guī)定的資源依賴聲明方式為:在html中聲明依賴,在js中聲明依賴,在css中聲明依賴。 接下來(lái),我要寫一個(gè)配置,將依賴關(guān)系表注入到代碼中。fis構(gòu)建是分流程的,具體構(gòu)建流程可以看這里。fis會(huì)在postpackager階段之前創(chuàng)建好完整的依賴樹表,我就在這個(gè)時(shí)候?qū)懸粋€(gè)插件來(lái)處理即可。 編輯fis-conf.js //postpackager插件接受4個(gè)參數(shù),
//ret包含了所有項(xiàng)目資源以及資源表、依賴樹,其中包括:
// ret.src: 所有項(xiàng)目文件對(duì)象
// ret.pkg: 所有項(xiàng)目打包生成的額外文件
// reg.map: 資源表結(jié)構(gòu)化數(shù)據(jù)
//其他參數(shù)暫時(shí)不用管
var createFrameworkConfig = function(ret, conf, settings, opt){
//創(chuàng)建一個(gè)對(duì)象,存放處理后的配置項(xiàng)
var map = {};
//依賴樹數(shù)據(jù)
map.deps = {};
//遍歷所有項(xiàng)目文件
fis.util.map(ret.src, function(subpath, file){
//文件的依賴數(shù)據(jù)就在file對(duì)象的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)建的時(shí)候發(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自動(dòng)建立了依賴關(guān)系,這是fis默認(rèn)進(jìn)行的依賴聲明。有了這個(gè)表,我們就可以把它注入到代碼中了。我們?yōu)轫?yè)面準(zhǔn)備一個(gè)替換用的鉤子,比如約定為__FRAMEWORK_CONFIG__,這樣用戶就可以根據(jù)需要在合適的地方獲取并使用這些數(shù)據(jù)。模塊化框架的配置一般都是寫在非模塊化文件中的,比如html頁(yè)面里,所以我們應(yīng)該只針對(duì)views目錄下的文件做這樣的替換就可以。所以我們需要給views下的文件進(jìn)行一個(gè)標(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目錄下的文件加一個(gè)isViews屬性標(biāo)記,用以標(biāo)記文件分類
//我們可以在插件中拿到文件對(duì)象的這個(gè)值
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)知道了這個(gè)foo.js依賴于bar.js、bar.css以及foo.css,因此可以發(fā)起兩個(gè)combo請(qǐng)求去加載所有依賴的js、css文件,完成后再執(zhí)行回調(diào)。 現(xiàn)在模塊的id有一些問(wèn)題,因?yàn)槟K發(fā)布會(huì)有版本號(hào)信息,因此模塊id也應(yīng)該攜帶版本信息,從前面的依賴樹生成配置代碼中我們可以看到模塊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)記文件分類
//我們可以在插件中拿到文件對(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="/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都會(huì)被修改為我們指定的模式,這就是以文件為中心的編譯系統(tǒng)的威力。
接下來(lái)還有一個(gè)問(wèn)題,就是模塊名太長(zhǎng),開發(fā)中寫這么長(zhǎng)的模塊名非常麻煩。我們可以借鑒流行的模塊化框架中常用的縮短模塊名手段——?jiǎng)e名(alias)——來(lái)降低開發(fā)中模塊引用的成本。此外,目前的配置其實(shí)會(huì)針對(duì)所有文件生成依賴關(guān)系表,我們的開發(fā)概念定義只有components和component_modules目錄下的文件才是模塊化的,因此我們可以進(jìn)一步的對(duì)文件進(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){
//判斷一下文件名和文件夾是否同名,如果同名則建立一個(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è)小小的需求,就是希望能像寫nodejs一樣開發(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é)睡到中午才起,這么好的天氣寫碼豈不是很loser?! 2014年02月16日 - 小雨居然浪費(fèi)了一天,剩下的時(shí)間不多了,今天要抓緊?。。?! 讓我們來(lái)回顧一下已經(jīng)完成了哪些工作:
剩下的幾個(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 中可以這樣寫: //依賴聲明
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也非常簡(jiǎn)單,fis在 parser 階段處理非標(biāo)準(zhǔn)語(yǔ)言,這個(gè)階段可以把非標(biāo)準(zhǔn)的js(coffee/前端模板)、css(less/sass/stylus)、html(markdown)語(yǔ)言轉(zhuǎn)換為標(biāo)準(zhǔn)的js、css或html。處理之后那些文件還能和標(biāo)準(zhǔn)語(yǔ)言一起經(jīng)歷預(yù)處理、語(yǔ)言能力擴(kuò)展、后處理、校驗(yàn)、測(cè)試、壓縮等階段。 所以,要支持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)過(guò)編譯插件函數(shù)處理
fis.config.set('modules.parser.styl', stylusParser);
//告訴fis,styl后綴的文件,被當(dāng)做css處理,編譯后后綴也是css
fis.config.set('roadmap.ext.styl', 'css');
這樣我們項(xiàng)目中的*.styl后綴的文件都會(huì)被編譯為css內(nèi)容,并且會(huì)在后面的流程中被當(dāng)做css內(nèi)容處理,比如壓縮、csssprite等。 文件監(jiān)聽、自動(dòng)刷新都是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)聽、自動(dòng)刷新,則使用: fis release -wL 壓縮、打包、文件監(jiān)聽、自動(dòng)刷新、發(fā)布到output目錄,則使用: fis release -opwLd output
另外,fis是命令行工具,各種內(nèi)置的插件也是完全獨(dú)立無(wú)環(huán)境依賴的,可以與ci平臺(tái)直接對(duì)接,并在各個(gè)主流操作系統(tǒng)下運(yùn)行正常。 利用fis的內(nèi)置的各種編譯功能,我們離目標(biāo)又近了許多:
剩下兩個(gè),我們可以通過(guò)擴(kuò)展fis的命令行插件來(lái)實(shí)現(xiàn)。fis有11個(gè)編譯流程擴(kuò)展點(diǎn),還有一個(gè)命令行擴(kuò)展點(diǎn)。要擴(kuò)展命令行插件很簡(jiǎn)單,只要我們將插件安裝到與fis同級(jí)的node_modules目錄下即可。比如: node_modules - fis - fis-command-say 那么執(zhí)行 fis say 這個(gè)命令,就能調(diào)用到那個(gè)fis-command-say插件了。剩下的這個(gè)component模塊安裝,我就利用了這個(gè)擴(kuò)展點(diǎn),結(jié)合component開源的 component-installer 包,我就可以把component整合當(dāng)前開發(fā)體系中,這里我們需要?jiǎng)?chuàng)建一個(gè)npm包來(lái)提供擴(kuò)展,而不能直接在fis-conf.js中擴(kuò)展命令行,插件代碼我就不貼了,可以看 這里。 眼前我們有了一個(gè)差不多100行的fis-conf.js文件,還有幾個(gè)插件,如果我把這樣一個(gè)零散的系統(tǒng)交付團(tuán)隊(duì)使用,那么大家使用的步驟差不多是這樣的:
這種情況讓團(tuán)隊(duì)用起來(lái)會(huì)有很多問(wèn)題。首先,安裝過(guò)程太過(guò)麻煩,其次如果項(xiàng)目多,那么fis-conf.js不能同步升級(jí),這是非常嚴(yán)重的問(wèn)題。grunt的gruntfile.js也是如此。如果說(shuō)有一個(gè)項(xiàng)目用了某套grunt配置感覺(jué)很爽,那么下個(gè)項(xiàng)目也想用這套方案,復(fù)制gruntfile.js是必須的操作,項(xiàng)目用的多了,同步gruntfile的成本就變得非常高了。 因此,fis提供了一種“包裝”的能力,它允許你將fis作為內(nèi)核,包裝出一個(gè)新的命令行工具,這個(gè)工具內(nèi)置了一些fis的配置,并且把所有命令行調(diào)用的參數(shù)傳遞給fis內(nèi)核去處理。 我準(zhǔn)備把這套系統(tǒng)打包為一個(gè)新的工具,給它取名為 scrat,也是一只松鼠。這個(gè)新工具的目錄結(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({...});
將這個(gè)npm包發(fā)布出來(lái),我們就有了一個(gè)全新的開發(fā)工具,這個(gè)工具可以解決前面說(shuō)的13項(xiàng)技術(shù)問(wèn)題,并提供一套完整的集成解決方案,而你的團(tuán)隊(duì)使用的時(shí)候,只有兩個(gè)步驟:
使用新工具的命令、參數(shù)幾乎和fis完全一樣: scrat release [options] scrat server start scrat install <name@version> [options] 而scrat這個(gè)工具所內(nèi)置的配置將變成規(guī)范文檔描述給團(tuán)隊(duì)同學(xué),這套系統(tǒng)要比grunt那種松散的構(gòu)建系統(tǒng)組成方式更容易被多個(gè)團(tuán)隊(duì)、多個(gè)項(xiàng)目同時(shí)共享。
2014年02月17日 - 多云終于到了周一,交付了一個(gè)新的開發(fā)工具——scrat,及其使用 文檔。 然而,過(guò)去的三天,為了構(gòu)造這套前端開發(fā)體系,都寫了哪些代碼呢?
一共 960行 代碼,用了4人/天。 總結(jié)
如果說(shuō)只是實(shí)現(xiàn)一個(gè)簡(jiǎn)單的編譯+壓縮+文件監(jiān)+聽自動(dòng)刷新的常規(guī)構(gòu)建系統(tǒng),基于fis應(yīng)該不超過(guò)1小時(shí)就能完成一個(gè),但要實(shí)踐完整的前端集成解決方案,確實(shí)需要點(diǎn)時(shí)間。 如之前一篇 文章 所講,前端集成解決方案有8項(xiàng)技術(shù)要素,除了組件倉(cāng)庫(kù),其他7項(xiàng)對(duì)于企業(yè)級(jí)前端團(tuán)隊(duì)來(lái)說(shuō),應(yīng)該都需要完整實(shí)現(xiàn)的。即便暫時(shí)不想實(shí)現(xiàn),也會(huì)隨著業(yè)務(wù)發(fā)展而被迫慢慢完善,這個(gè)完善過(guò)程是普適的。 對(duì)于前端集成解決方案的實(shí)踐,可以總結(jié)出這些設(shè)計(jì)步驟:
我們可以看看業(yè)界已有團(tuán)隊(duì)提出的各種解決方案,無(wú)不以這種思路來(lái)設(shè)計(jì)和發(fā)展的:
縱觀這些公司出品的前端集成解決方案,深入剖析其中的框架、規(guī)范、工具和流程,都可以發(fā)現(xiàn)一些共通的影子,設(shè)計(jì)思想殊途同歸,不約而同的朝著一種方向前進(jìn),那就是前端集成解決方案。嘗試將前端工程孤立的技術(shù)要素整合起來(lái),解決常見的領(lǐng)域問(wèn)題。
在這里我不能給出肯定或者否定的答復(fù)。 因?yàn)閱渭儚恼Z(yǔ)言的角度來(lái)說(shuō),html、js、css(甚至有人認(rèn)為css是數(shù)據(jù)結(jié)構(gòu),而非語(yǔ)言)確實(shí)是最簡(jiǎn)單最容易上手的開發(fā)語(yǔ)言,不用模塊化、不用工具、不用壓縮,任何人都可以快速上手,完成一兩個(gè)功能簡(jiǎn)單的頁(yè)面。所以說(shuō),在一般情況下,前端開發(fā)非常簡(jiǎn)單。
但正是由于前端語(yǔ)言這種靈活松散的特點(diǎn),使得前端項(xiàng)目規(guī)模在達(dá)到一定規(guī)模后,工程問(wèn)題凸顯,成為發(fā)展瓶頸,各種技術(shù)要素彼此之間開始出現(xiàn)關(guān)聯(lián),要用模塊化開發(fā),就必須對(duì)應(yīng)某個(gè)模塊化框架,用這個(gè)框架就必須對(duì)應(yīng)某個(gè)構(gòu)建工具,要用這個(gè)工具,就必須對(duì)應(yīng)某個(gè)包管理工具……這個(gè)時(shí)候,完整實(shí)踐前端集成解決方案就成了不二選擇。
所以會(huì)出現(xiàn)一些框架或工具在小項(xiàng)目中使用的好好的,一旦放到團(tuán)隊(duì)里使用就非常困難的情況。 前端入門雖易工程不易,且行寫珍惜! |
|
|
來(lái)自: 集微筆記 > 《雜亂技術(shù)知識(shí)》