天天看點

CSS預處理語言的子產品化實踐

編寫css是前端工作中,一項普通而又頻繁的勞動,由于css并不是一門語言,是以在程式設計上顯得有些簡陋。對于小型項目來說,css的量還不至于龐大,問題沒有凸顯,而如果要開發和持續維護一個較為大型的項目,那就需要對css進行管理和規範了,否則會發生不可挽回的後果(吓唬誰呢??)。

背景

上一節【從css談子產品化】我們通過規範的限制,将css的編寫方式進行了優化和改進,形成一種可持續發展的路線。但還是遺留了一些問題:備援。雖然我們通過定義公共子產品和私有子產品,來委婉地分擔common的體積,但common的體積還是太大了,而且從設計上考慮,我們應該盡量多地提煉公共子產品,以便更好地實作複用。最理想的情況是,所有的子產品都寄存在一個公共的庫裡,哪裡需要用到就從庫中直接調過來。這個美好的願望不是不可實作的,借助預處理語言,我們可以很輕易地完成這事情。

預處理語言是一種類css的語言,我們知道css本身不是語言,而預處理語言的誕生,就是為填補這一部分語言功能。它實作了變量、函數、混合的定義,以及檔案的引用、合并、壓縮功能,使得css也能面向對象,應付複雜龐大的業務。

目前流行的預處理語言主要有兩種:less和sass。作為學習,兩者都可以入門一下,而作為工作,盡量熟悉一種。我比較常用sass,是以以下内容都是以sass為基本語言做介紹,兩者在特性上有很多相似的地方,是以大家不必擔心實作上有什麼千差萬别。

sass

基本文法可以到官網(英語)或者w3cplus sass guide(中文)檢視學習,我們這裡隻簡單地過一遍,講一些我們需要用到的内容,不會面面俱到。

sass有兩種字尾名檔案:一種字尾名為sass,不使用大括号和分号;另一種就是我們這裡使用的scss檔案,這種和我們平時寫的css檔案格式差不多,使用大括号和分号。而本教程中所說的所有sass檔案都指字尾名為scss的檔案。在此也建議使用字尾名為scss的檔案,以避免sass字尾名的嚴格格式要求報錯。 ——摘自w3cplus sass guide

1、嵌套(非常重要的特性)

sass的嵌套包括兩種:一種是選擇器的嵌套;另一種是屬性的嵌套。我們一般說起或用到的都是選擇器的嵌套。 ——摘自w3cplus sass guide 選擇器嵌套 所謂選擇器嵌套指的是在一個選擇器中嵌套另一個選擇器來實作繼承,進而增強了sass檔案的結構性和可讀性。在選擇器嵌套中,可以使用&表示父元素選擇器。 ——摘自w3cplus sass guide

// index.scss 

.g-index { 

  ... 

  .g-hd { 

    ... 

    .m-nav { ... } 

  } 

  .g-bd { 

    .m-news { ... } 

  .g-ft { 

    .m-copy_right { ... } 

  .m-dialog { 

    display: none; 

    &.z-active {  // 留意此處&的用法 

      display: block; 

    } 

編譯後:

/* index.css */ 

.g-index { ... } 

.g-index .g-hd { ... } 

.g-index .g-hd .m-nav { ... } 

.g-index .g-bd { ... } 

.g-index .g-bd .m-news { ... } 

.g-index .g-ft { ... } 

.g-index .g-ft .m-copy_right { ... } 

.g-index .m-dialog { 

  display: none; 

.g-index .m-dialog.z-active {  // 留意此處&的編譯結果 

  display: block; 

是不是爽歪歪?再也不用一遍一遍地去複制和修改一大堆的選擇器,也不需要去整理它們之間的關系,隻需要嵌套一下,所有的關系就如同直接看dom一樣簡單明了了!解放雙手,解放雙眼,同時還提高效率。值得留意的是,我們書寫sass的時候,應該盡量保持sass的嵌套順序與dom一緻,注意,是嵌套順序一緻,而不是層次一緻,因為并不是dom裡的所有元素都需要寫樣式。

我們再來提一個場景,說明sass的嵌套寫法利于維護,假設<code>g-bd</code>下原本有個子產品<code>m-article_box</code>,現在我們要把<code>m-article_box</code>從<code>g-bd</code>遷移到<code>g-hd</code>中(當然這個需求有些不合理~),我們來看原始代碼:

&lt;!-- index.html --&gt; 

&lt;!doctype html&gt; 

&lt;html&gt; 

&lt;head&gt; 

  &lt;title&gt;index&lt;/title&gt; 

&lt;/head&gt; 

&lt;body&gt; 

  &lt;div class="g-index"&gt; 

    &lt;div class="g-bd"&gt; 

      &lt;div class="m-article_box"&gt; 

        &lt;div class="hd"&gt; 

          最新文章 

        &lt;/div&gt; 

        &lt;div class="bd"&gt; 

          &lt;div class="list"&gt; 

            &lt;div class="item"&gt; 

              &lt;img class="cover" /&gt; 

              &lt;div class="info"&gt; 

                &lt;div class="title"&gt; 

                  &lt;a href="#"&gt;文章标題&lt;/a&gt; 

                &lt;/div&gt; 

                &lt;div class="desc"&gt;文章簡介&lt;/div&gt; 

              &lt;/div&gt; 

            &lt;/div&gt; 

          &lt;/div&gt; 

        &lt;div class="ft"&gt; 

          &lt;div class="page"&gt; 

            &lt;a href="#" class="pg"&gt;1&lt;/a&gt; 

            &lt;a href="#" class="pg"&gt;2&lt;/a&gt; 

            &lt;a href="#" class="pg"&gt;3&lt;/a&gt; 

            &lt;a href="#" class="pg"&gt;4&lt;/a&gt; 

      &lt;/div&gt; 

    &lt;/div&gt; 

  &lt;/div&gt; 

&lt;/body&gt; 

&lt;/html&gt; 

.g-bd { ... } 

.g-bd .m-article_box { ... } 

.g-bd .m-article_box .hd { ... } 

.g-bd .m-article_box .bd { ... } 

.g-bd .m-article_box .bd .list { ... } 

.g-bd .m-article_box .bd .list .item { ... } 

.g-bd .m-article_box .bd .list .item .cover { ... } 

.g-bd .m-article_box .bd .list .item .info { ... } 

.g-bd .m-article_box .bd .list .item .info .title { ... } 

.g-bd .m-article_box .bd .list .item .info .desc { ... } 

.g-bd .m-article_box .ft { ... } 

.g-bd .m-article_box .ft .page { ... } 

.g-bd .m-article_box .ft .page .pg { ... } 

按照css的方式,我們就要把所有跟<code>m-article_box</code>有關的部分,從<code>g-bd</code>全部複制到<code>g-hd</code>裡去。這還是在子產品的書寫符合規範的情況下,如果這個子產品書寫不符合規範,沒有把全部結構都挂在<code>m-article_box</code>類下,那真的就是災難了~而現在使用sass的話,我們隻需要把<code>m-article_box</code>的區塊整個從<code>g-bd</code>剪切到<code>g-hd</code>就完事了(這裡為了突出修改的工作量大,我特地把整個子產品結構都寫全了——才不是為了湊字數。。。):

// 修改前 

.g-hd { ... } 

.g-bd { 

  .m-article_box { 

    .hd { ... } 

    .bd { 

      .list { 

        .item { 

          .cover { 

            ... 

          } 

          .info { 

            .title { 

              ... 

            } 

            .desc { 

        } 

      } 

    .ft { 

      .page { 

        .pg { ... } 

// 修改後 

.g-hd { 

非常友善,而且不容易出錯。

2、變量(variable)

咱們直接上代碼:

$fontsize: 16px; 

$grey: #ccc; 

.m-nav { 

  font-size: $fontsize; 

  color: $grey; 

編譯結果:

  font-size: 16px; 

  color: #ccc; 

寫過代碼的人都熟悉 參數 的用法吧,太簡單太直白了不想說太多,自己意會吧。

3、函數(function)

// pixels to rems 

@function rem($px) { 

    @return $px / 640 * 16rem; 

太簡單了直白了不想說太多,自己意會吧。

4、混合(mixin)

混合,顧名思義,就是混合的意思。。。也就是我們可以事先定義一段代碼塊,在需要使用到的地方,直接引用(include),而在引用之前,這段代碼都不會出現在編譯檔案中,也就是不會生成任何内容。

這也是非常重要的一個特性!我們知道common的體積非常大,而體積大的根本原因是它存放了許許多多的子產品。我們設想一下,如果将每一個子產品都打包成mixin,那common不就減肥成功了?!多年的頑疾終于看到希望,沒有比這更讓人驚喜的了!我們這就上車:

/* common.css */ 

.m-nav { ... } 

.m-news { ... } 

.m-copy_right { ... } 

改造後

// common.scss 

@mixin m-nav { 

  .m-nav { ... } 

@mixin m-news { 

  .m-news { ... } 

@mixin m-copy_right { 

  .m-copy_right { ... } 

  @include m-nav; 

  @include m-news; 

  @include m-copy_right; 

5、import

這個屬性很眼熟?沒錯,事實上,css本身就有這個屬性實作,我們可以在css檔案中直接使用<code>import</code>來引入其他檔案。那麼css的<code>import</code>和sass的<code>import</code>有什麼差別?從含義和用法上來說,沒有差別,差別在于工作原理。css的import是阻塞的,而sass的import在編譯後,其實是合并檔案,最後隻産出一個css檔案,而css則沒有合并,該多少個檔案就還是多少個檔案。

注意:

隻有import一個.sass/.scss檔案的時候,才可以省去字尾名,如果是直接import一個.css檔案,要補全檔案名;

import之後的分号<code>;</code>不要漏寫,會報錯;

sass如果import的是一個.css檔案的話,那它的作用就跟css原生的import作用一樣,隻有import一個sass檔案的時候,才是合并檔案。

如下:

@import 'common'; 

@import 'a.css'; 

/* index.scss */ 

@import url('a.css'); 

css的import之是以沒有被普遍使用是有原因的。我們可以大概猜到它的工作原理:a.css import

b.css以後,當浏覽器加載到頁面中的a.css的時候,已經準備按照a.css的内容來渲染頁面了,剛解析到第一行,發現a.css居然還import了一個b.css,于是它不得不先放下a.css(既阻塞a.css),去加載b.css,直到b.css加載完,并且優先解析它,然後才開始回來解析a.css——鬼知道b.css會不會又import了c.css……這直接導緻了渲染工作滞後,引發性能問題。

說實話我還不如直接用兩個link标簽去同步加載a.css和b.css,效率會高一些。

是以css的import基本是被抛棄了的屬性。

sass的import主要的好處就是把檔案合并了,減少了請求。原本需要link好幾個css檔案的頁面,現在隻需要一個。

子產品化

終于要開始幹點正事了,首先我們來回顧一下,上一節我們以規範為基礎建構的子產品化項目,遺留了一些什麼問題。

備援 體積龐大的common;

使用<code>cm-</code>子產品差別<code>m-</code>子產品,使得後期開發過程中,<code>m-</code>子產品向<code>cm-</code>子產品轉變過程比較繁瑣;

……

好像,問題也不是特别多,我們一個一個解決。

為了友善,在這裡我們把每個頁面所對應的scss檔案叫做 頁面scss;把變量、函數、混合等(沒有被引用或者執行的情況下)編譯後不産生實際内容的代碼叫做 定義類代碼 ,那麼相對應的其他内容就是 實際内容代碼。

1、mixin.scss

我們知道,一方面,在common中過多地添加子產品最終會導緻common的體積過大,使得資源備援,另一方面,為了友善維護,我們又希望盡量多地把子產品公有化。

這是一對沖突,僅靠css本身是無法解決的,但sass可以!如果我們使用mixin來代替直接書寫子產品,由于mixin并不直接生成代碼,而是通過主動引用,才能生成對應内容,那麼理論上,common就可以無限多地存放子產品而不必占用一點空間!

(注意,這裡說的是理論上,實際應用中,檔案太過龐大的話,免不了還是要受到命名沖突的限制的,不過這問題不大。)

說幹就幹,我們把common中的子產品全部打包成mixin:

調用方式如下:

@import 'common'; // 記得先引入common 

原本我們會在每個需要用到公共子產品的頁面中,先引用common,然後再引用頁面css,而現在,我們隻需要在頁面scss中直接<code>@import common;</code>就可以了。

使用common:

  &lt;link rel="stylesheet" type="text/css" href="./style/common.css"&gt; &lt;link rel="stylesheet" type="text/css" href="./style/index.css"&gt; &lt;/head&gt; &lt;body&gt; ... &lt;/body&gt; &lt;/html&gt; 

改造後:

  &lt;link rel="stylesheet" type="text/css" href="./style/index.css"&gt; &lt;/head&gt; &lt;body&gt; ... &lt;/body&gt; &lt;/html&gt; 

很完美,

——至少目前為止是這樣。

這些東西目前我們也堆積在common當中,而且合情合理,因為它們都是全局的樣式。但是對比起mixin來說,這些實際内容代碼顯得很少量,有種被淹沒的感覺,使得整個common看上去就像隻有mixin。但是這些實際内容代碼的作用卻又非常重要。為了使common的構成更加直覺,我們把mixin全部都抽離出來,單獨存放一個叫做mixin.scss的檔案中,然後在common引用它,這樣,mixin的管理更加的規範,而且common的結構也更加清晰了。

抽離mixin還有另外一個重要原因,後面會講到的,我們希望mixin作為一個純粹定義類代碼檔案,随處可以引用而不會生成多餘的代碼。

原本我們會在每個需要用到公共子產品的頁面中,先引用common,然後再引用頁面css,而現在,我們隻需要在頁面scss中直接<code>@import mixin;</code>就可以了。

使用mixin:

@import 'common';  // 引入common,如果有需要,common裡一樣可以引入mixin 

@import 'mixin';  // 引入mixin 

  &lt;link rel="stylesheet" type="text/css" href="./style/index.css"&gt; 

2、common.scss

好,抽離了mixin之後,我們現在來重新看回common,common裡應該是些什麼樣的内容。上面的内容我們稍稍提到了一點,我們來展開一下。

2.1、css reset(normalize)

我們知道浏覽器千差萬别,各浏覽器的預設樣式也是不盡相同,最常見的比如body的預設内邊距,p标簽的預設内邊距,以及ul/ol等等。這些不統一的預設樣式經常讓我們感到頭疼,是以就有人提出一開始寫樣式就先把它們消除的想法,于是就催生了後來非常流行的<code>reset.css</code>。

起初的reset.css很簡單,大概是這樣的:

html, body, h1, h2, h3, h4, h5, h6, div, dl, dt, dd, ul, ol, li, p { 

  margin: 0; 

  padding: 0; 

沒錯,就是把幾乎所有會用到的标簽都給去了内邊距和外邊距,簡單粗暴,這樣所有的标簽就都統一了,而且在不同的浏覽器下也是統一的。

其他的部分每個人有各自的補充,比如有人會把<code>h1~h6</code>的所有字号給定義一遍,以保證在不同浏覽器下他們有統一的大小;有人會給<code>a</code>标簽設定統一的字型顔色和hover效果,諸如此類等等。

很好,沒毛病。我們把這些統稱為<code>css reset</code>,然後再統一封裝到一個叫做<code>reset.css</code>的檔案中,然後每個頁面都引用。

這種方式一直以來都挺實用,而且大家也都這麼用,沒出過什麼問題。隻是後來有人提出,這種方式太過粗暴(居然還心疼浏覽器了)。。。而且會降低頁面渲染的性能,最重要的是,這使得我們原本設計出來的表達各種含義的标簽兒們,變得毫無特點了。。。

說的好有道理,如果你家裡所有人名字不一樣但是都長一個樣,還有啥意思。

于是,就出現了<code>normalize.css</code>,normalize的目的同樣是為了統一各個浏覽器下各不相同的預設樣式,不過它并不是簡單粗暴地全部抹平,而是根據規範,來人為地把那些不符合規範的預設樣式“扶正”,進而達到統一各個浏覽器預設樣式,同時保留各個标簽原有特點的目的。

我們不能說reset與normalize這兩種思想孰好孰壞,隻能說各有各的特點和作用,它們的存在都是為了解決同樣的問題。

2.2、插件

一般來說,一個ui插件都會至少包括一個css檔案,像bootstrap、datepicker等等。假設我們項目中需要以bootstrap為基礎架構,實作快速開發,那麼這時候我們就需要在項目中全局引入bootstrap.min.css,當然,還有bootstrap.min.js。說到全局暴露,我們第一時間想到的是common,沒錯,我們可以在common中引入。

有人問,插件的.css檔案怎麼import?額,改一下擴充名為.scss就可以了,scss是相容原生css文法的~

是以最終,我們的common大概是這樣子的:

@import './reset'; 

@import './bootstrap.min'; 

@import './mixin'; 

事實上,如果我們不需要使用到 mixin.scss 中的變量和mixin的話,我們可以不引用它。

那麼我們的頁面scss應該是這樣的:

@import './common'; 

幹淨,整潔。

3、mixin編寫規範

每添加一個新角色,我們就要及時給它設定規範,好讓它按照我們的期望工作别添亂。我們接下來來歸納一下mixin的書寫規範。

場景一:項目裡有mixin.scss、a.scss(假設這是某個功能檔案)、index.scss三個檔案,mixin中定義了一個變量<code>$fontsize: 16px;</code>,a中定義了一個變量<code>$color: #ccc;</code>,我們在index中同時引用這兩個檔案,那麼我們在index中是可以直接使用<code>$fontsize</code>和<code>$color</code>這兩個變量的——我的意思是,盡管在index中我們并沒有看到這兩個變量的聲明和定義,但它們就這麼存在了。

這是好事還是壞事呢?直覺告訴我,這可能有問題。沒錯,這是不是跟我們之前讨論過的 污染

很像?隻不過我們之前是引用了common之後,index什麼都還沒寫就已經被占用了很多子產品名,而現在是因為引用了其他檔案,而占用了index的很多變量名。另外,就維護的角度來看,這也是有問題的,如果我不事先告訴你,或者你不事先看過一遍mixin和a,你知道index中的<code>$color</code>是哪裡來的嗎?假設我們需要字型大小,你知道去哪個檔案修改嗎?另外,你怎麼保證同時引用mixin與a的時候,他們之間有沒有可能存在同名的變量?那誰覆寫誰呢?這些問題看起來很小,但是當你項目規模大的時候,這可能是無法挽回的災難(吓唬誰呢???)。

場景二:假設我們的項目有一個主題色,邊框、tab背景、導航欄背景,以及字型顔色等等,都是這個主題色,為了友善使用,不想總是用取色器去取值,于是我們在mixin中定義了一個全局變量<code>$color: #ff9900</code>,然後就可以愉快地到處使用了!

整個網站開發完了,一個月後,設計師突然過來跟你說:“老闆說,這個主題色要改改,有點土,咱們換個大紅。”,于是你一臉不情願然而内心卻竊喜地打開mixin,把$color的值改成了大紅,然後得瑟地對設計師說:“幸好我早有準備,搞定了,你看看吧。”,儲存,打開頁面一看,設計師和你的臉都綠了,頁面怎麼這麼醜,有些字原本是主題色,但背景是紅色,而現在一改,整塊都變成紅的,内容都看不清了,有些邊框原本就是紅色的,但是字型是原本的主題色,然而現在一改,邊框跟字型都變成紅的了。

設計師:“不不不,我隻是想把背景顔色改一下。”

你:“你不是說改主題色嗎?那就是所有的地方啊。”

設計師:“不用,改背景就好了。”

你:“不行啊。。。”

設計師:“為什麼不行,不就是改個背景顔色嗎?怎麼設定的就怎麼改回來呀。”

你:“不是你想的那麼簡單。。。”

好吧我就是吓唬你的,你要是特能折騰那麼這些都不叫事兒。

是以我們需要對(全局)變量進行管理,就像我們當初管理mixin那樣,不能想在哪裡定義就在哪裡定義,也不能動不動就修改一個全局變量:

全局變量隻在mixin中定義,其他scss檔案定義的變量(無論是暴露到全局還是局部)都隻看作局部變量,不在目前檔案以外的地方使用(即便是在能引用到的情況下,也避免使用);

需要使用全局變量的地方直接import mixin;

一般來說,定義全局變量應該慎重,全局變量的數量應該盡量少;

盡可能不改動,如果需求變動,除非是對用途十分确定的情況,否則請新增一個全局變量來逐漸替換需要修改的地方;

不要使用太過籠統的名詞來作為全局變量,比如<code>color</code>,建議直接是用色值的描述,比如<code>$orange: #ff9900</code>,這使得我們在維護上更友善擴充,如果色值需要修改,但是又不是所有的地方都需要修改,那麼我們可以新定義一個變量來擴充它,比如<code>$red: red</code>。

這些點說起來都有點飄忽,事實上也确實很難說明白為什麼要這麼做,畢竟都是經驗總結,是以大家不妨先熟悉使用sass一段時間之後,再來細細思考這些問題。

注意,以上講的這些都不是死規定,在某些時候,這個規範是需要根據實際項目而做調整的,就比如我們之後要講到的spa。十全十美的項目是不存在的,也不存在能适用所有項目的開發模式,因地制宜才能更好地解決問題。而且我們目前提到的問題都不是緻命的,緻命的問題在上一節我們制定規範的時候已經避開了。

調用子產品

問題,在哪裡調用子產品?

答,頁面scss。

在頁面scss中調用子產品是一個好習慣,它使得我們在每個頁面所用到的子產品既是一緻的又是互相隔離的,不像在common中直接引用子產品那樣,使得一個頁面scss還沒有内容的時候就已經被很多子產品名污染了。

再提個問題,在頁面scss的哪裡調用子產品?

例一,根類外:

@include m-nav; 

@include m-news; 

@include m-copy_right; 

例二,根類内:

目前為止,這兩種方式都是可以的,至于我為什麼用“目前為止”這個詞,那是因為我們後面将要講到的spa,如果用例一的方式是有問題的。是以我比較鼓勵使用例二的方式。當然,我說了,目前為止例一也是沒問題的。

性能優化

目前為止,我們的子產品化工作已經算是完成了,其實已經可以收工了。不過我們還是可以稍微做一下優化。

1、緩存

我們需要考慮一個問題:緩存。

緩存是我們web開發中最常見的情況之一,很多時候我們都需要跟緩存打交道,特别是在做性能優化的時候。

一般來說,靜态資源在被加載到浏覽器之後,浏覽器會把它本地緩存下來,以便下次請求同個資源的時候可以快速響應,不需要再去遠端伺服器加載。

我們就css來說,假設我們按照原來的方式,使用多個link去加載reset、bootstrap、common、index這幾個檔案的話,這幾個檔案都會被緩存下來,以使得下次再通路這個頁面,這個頁面的加載速度會快很多。

如果是從index頁面跳轉到about頁面呢?你會發現也很快,因為about頁面的全局css(reset、bootstrap、common)和index頁面是一樣的,而它們在你通路index的時候,已經加載過了,得益于緩存的作用,之後的頁面打開都快。

我們現在的方式是,一個頁面所用到的所有css檔案都被合并成一個,也就不存在相同的檔案可以利用緩存這樣的優勢了。

那我們有辦法改進嗎?有的!我們隻需要把common獨立出來,那麼common就可以做為被緩存的公共檔案了。最終我們從一個頁面隻引用一個檔案變成了一個頁面引用兩個檔案,即common和頁面css:

注意,不同于之前,我們這裡的index.scss不再引入common.scss,是以我們最終是得到了兩個css檔案,而common.css是在所有頁面中通過link标簽引入的。

如此一來,我們就實作了既能夠合并檔案,減少請求數,又可以利用緩存,提高加載速度。

2、壓縮

代碼壓縮是優化工作中最基本的一步,css的壓縮空間是很大的,尤其是我們這種 垂直的書寫方式 ,壓縮起來是相當高效的。

在sass中這很簡單,sass在編譯的時候提供了幾種模式,其中的<code>compressed</code>模式是最高效的壓縮模式,記得在編譯打包的時候選擇compressed模式就行了。

總結

總的來說,預處理語言在使我們程式設計更加美好的同時,也使得規範更加的完善。在css本身無法實作的情況下,我們通過工具來完成了子產品化開發。

我不會講如何去安裝和配置sass環境,因為這些w3cplus sass guide有詳細的介紹了,建議使用nodejs的方式,不會搗鼓nodejs/npm的前端不是好前端。

最後,我們回到一開始提到的問題——為什麼要子產品化?現在我們可以先從css的工作來回答,從某種意義上講,子產品化提高了我們程式設計能力和解決問題的能力,使得建構一個龐大而可擴充可維護的項目成為可能,使得我們能夠以架構的思維和眼光去搭建整個項目。

下一節本想就着這一篇的内容直接說spa的,但是發現spa已經是元件化的内容了,也就是說它不單單是css的内容,需要更多的知識點來搭台,是以還是先放着,下一節,我們來了解【html與模闆引擎的子產品化實踐】(近期推出,敬請留意)

作者:jack_lo

來源:51cto

繼續閱讀