解放雙手-vscode擴展之根據Vue模板自動生成Scss結構

某次小組內開週會,提到開發效率的問題,有個小夥伴提到寫新頁面的時候,template大概佈局寫完後,對着 template結構寫 scss是件比較耗時耗力的事情,若是能做出一個自動依據 template結構生成 scss文件的 vscode插件就行了css

我當時也沒在乎,後來週會結束後以爲這事情能夠作一下,因而抽空看了下 vscode擴展的開發文檔,就上手 code了,作出來後效果還不錯,最起碼不用再作人工對着 templatescss這種沒技術含量的事情了,寫好了一大堆 template以後,一鍵轉換,仍是挺爽的 html

插件已經發布到 vscode擴展市場,能夠在 vscode插件市場查找安裝,或者 vscode上直接搜索 AutoScssStruct4Vue 安裝

插件的源碼已經上傳到 Github ,須要的可自取,若是有問題,直接提 issue 便可vue

模板解析

scss文件的關鍵就是選擇器,能在模板上體現出來的選擇器屬性有 classid和標籤名,因此必需要從模板中取得全部的選擇器git

模板的處理實際上就是字符串的處理,經過正則表達式從 <template>...</template>中提取所需的選擇器名,既然是 vue,我第一個就想到直接從 vue源碼中將 vue處理 template的部分抄過來,不事後來看了一下源碼,想法可行,可是性價比不高github

vuetemplate解析成 ast的部分,與其餘的一些處理邏輯耦合在一塊兒了,並且處理了不少我並不須要的東西,好比指令、組件這些,我只是想取個選擇器屬性而已,因此這個想法就放棄了正則表達式

再仔細一想,拋開 vue的那些東西來看(好比指令處理這些),其實處理 template跟處理普通的 html片斷沒太大區別,因而問題就變成了將html片斷轉爲 ast,這個就容易不少了api

不過仍是有些差異,畢竟 vuetemplate和普通 html片斷之間的處理方式仍是有點不一樣的,差別點在於 vue的一些特性,例如 v-bind語法,以及組件標籤數組

v-bind

如下以 class這個選擇器屬性爲例,id同此less

由於考慮到 v-bind語法,因此在 parse模板時,正則表達式須要將這個考慮進去,屬性匹配正則爲:編輯器

const attrRE = /((:|v-bind:)?[\w$\-]+)|(?<word>["'"]){1}[\s\S]*?\k<word>/g;
複製代碼

這個正則會將標籤的屬性全都匹配,由於vueclass屬性有三種寫法:

  • 字符串
  • 數組
  • 對象

因此轉換爲 ast後,可能的結構有:

{
  attrs: {
    class: 'box1 box2'
  },
  bindAttrs: {
    class: "['box1', name]"
  },
  // bindAttrs: {
  // class: "{isActive: 'name'}"
  // }
}
複製代碼

考慮到直觀性,在解析模板的時候,若是遇到是 v-bind的選擇器屬性,我會將此屬性記錄在 bindAttrs字段上,若是是普通的字符串尚需經,則記錄在 attrs屬性上,主要是爲了方便後續提取屬性值

對於 attrs上的選擇器屬性,這個沒什麼好說,直接字符串匹配便可:

// 獲取標籤的 選擇器名
seletorStr.split(/\s+/).filter(item => item)
複製代碼

至於 v-bind屬性的處理,其實也很好辦,對於 data字段,例以下面代碼段中的 name,這個 name變量究竟是什麼,只有在運行的時候動態獲取,字符串靜態處理的狀況不可能知道 name是什麼東西的,因此直接跳過

bindAttrs: {
  class: "['box1', name]"
}
複製代碼

那這就好辦了,對於 "['box1', name]"或者 "{isActive: 'name'}"字符串來講,我只須要取引號中間的字符串便可,即 box1name

最後,若是一個標籤既沒有 class也沒有 id,那麼就取這個標籤的標籤名做爲選擇器

組件標籤

相比於普通 html片斷,vue是有組件的概念的,除了內置組件還有自定義組件,這些組件能夠加選擇器屬性也能夠不加,能夠自閉合也能夠不自閉合,因此要根據這兩種狀況分別處理下

這裏暫且認爲某個標籤只要不是標準的 html標籤,那麼就是組件,組件能夠加 classid這種選擇器屬性,也能夠不加,若是加了選擇器屬性,則提取,不然就將組件的子元素當成組件不爲組件的父元素的子元素進行處理

可能有點繞,看個例子就明白了:

<div class="box1">
  <List>
    <p id="box2"></p>
  </List>
  <List class="box3">
    <p id="box4"></p>
  </List>
</div>
複製代碼

對於 #box2來講,其父元素是個組件,而且此組件沒有選擇器屬性,又不可能將組件的標籤名List當成選擇器,因此跳過 <List>,將 .box1當成是 #box2元素的父元素來進行處理

而對於 #box4來講,雖然它的父元素也是個組件,但這個組件有選擇器屬性 class,因此不跳過其父元素,最後生成的 scss結構以下:

.box1 {
  #box2 {}
  .box3 {
    #box4 {}
  }
}
複製代碼

Scss文件解析

template轉成 ast,獲取到 template結構及選擇器,接着按照層級轉爲 scss字符串便可

然而這是一次性操做,若是你後續還對 template進行修改,若是再按照前面來這麼一下,生成新的結構字符串,豈不是把你本身寫的 css規則給清除了?

好比:

<List class="box3">
  <p id="box4"></p>
</List>
複製代碼

對於上述 template來講,生成的 scss以下:

.box3 {
  #box4 {}
}
複製代碼

這是個結構,那你確定要添加規則的,好比:

.box3 {
  width: 100px;
  height: 200px;
  #box4 {
    color: #fff;
  }
}
複製代碼

而後你改了下 template,變成:

<List class="box3">
  <p id="box4"></p>
  <span></span>
</List>
複製代碼

再次生成:

.box3 {
  #box4 {}
  span {}
}
複製代碼

因此你以前寫的 css規則沒了

template的修改是很常見的,你不可能一上來就把一個組件的 template寫得明明白白,因此這是個常見的場景,須要避免問題的產生

先根據新 template生成新 Scss Ast,而後對比新舊 Scss Ast,根據兩者之間的差別修正新 ast,填補對應結構上的 css規則,這是一條可行之路,但考慮到scss文件就是由 template Ast映射而來的,因此選擇器的結構確定能對的上,直接對比 template astScss Ast之間的差別,在舊 Scss Ast上進行修正,保留原有 css規則也是可行的

生成新的 template以後,將之與舊 scss結構進行對比,由於考慮到 css規則寫法的放飛性,爲了不誤刪規則,我這裏在對比結構時,只會在舊版本的 scss上進行增長操做,而不會刪減內容,刪除操做由你本身來作,畢竟相比於寫,刪是一件很容易的事情

不可能直接對比 scss字符串的,還須要將 scss字符串轉爲 ast纔好

這比 template的轉譯還簡單,無非是字符串遍歷遞歸罷了,只不過因爲 css規則的寫法太爲所欲爲,因此須要注意的小點不少,好比由於同一級別下,一個選擇器規則可能會被同時應用在多個標籤上,可是這些標籤只須要一個 css規則就夠了,即存在標籤與 css規則之間的多對一關係,須要進行過濾

新舊 AST 結構對比

獲得新的 template ast以後,與舊 scss結構進行對比,對比同級別下的節點的差別,前面說了,對舊版 scss文件實行只增不減操做,因此若是發現新 ast相比於同級別舊 ast新增了節點,則在 Scss Ast的同級別上進行新增 Scss Ast節點的操做,若是發現 template刪除了某個節點,則跳過不作處理

if (matchIndex === -1) {
  // 沒找到,說明 template 中新增了標籤
  scssObj.children = scssObj.children.slice(0, childIndex + i).concat(
    {
      rule: '',
      selectorNames: selector,
      children: i == 0 ? trackChildren(templateObj) : []
    },
    scssObj.children.slice(childIndex)
  )
}
複製代碼

我這裏嚴格映射了子節點之間的前後順序,若是一個選擇器屬性對應的 template節點是其父元素的第 n個子元素,則在 Scss Ast中,此選擇器屬性也會是其父元素的第 n個子元素(不考慮重複選擇器的狀況下),這種順序將會被保留

對比修正完畢以後,將會獲得新 Scss Ast結構,而且結構上保留了已有的 css規則

Scss Ast 轉爲 scss 字符串

主要是 ast結構的遍歷操做,額外須要處理下換行縮進等問題,比較簡單

應用爲 vscode 插件

上述核心邏輯完成了,轉爲 vscode插件其實就是作交互了,對着 vscode插件開發文檔API便可

對於這個插件,我設置了兩個設置項

其一是插件運行時機,其二是 scss文件保存的位置

插件運行時機(excuteMode)

對於這個設置項,提供了兩個選項:當文件保存時,和當右鍵點選菜單欄時

當文件保存時,顧名思義,就是當你寫好了 template以後,保存當前文件,插件自動解析當前 vue文件中的 template,獲得 scss字符串

當右鍵點選菜單欄時,即你的鼠標在編輯器文檔上右鍵,出現一個菜單欄,點擊插件命令便可,此爲默認選項

scss文件保存的位置(scssFilePath)

你可能會在一個 .vue文件上同時寫 templatescript 以及 style,也可能將 scss寫到單獨的文件中,這也是很常見的場景,因此提供了兩種選擇,要麼默認將 scss字符串寫到當前 .vue文件的 style標籤內(若是不存在,則自動建立),要麼你指定 .scss文件的保存路徑(若是不存在,則自動建立)

總結

插件的開發邏輯其實很清晰,主要是須要考慮的場景不少,正則規則須要兼容各類爲所欲爲的寫法,雖然我這裏只考慮常見場景(不常見場景,例如類名用中文,這種寫法雖然符合規範,但我這裏不考慮),但常見場景的寫法依舊不少,稍有哪一種狀況沒考慮到就出 bug 若是有問題,直接提 issue 便可


這個插件當初製做的時候是以 scss 爲基準製做的,但理論上對於 less 也是適用的,由於這個其實就是選擇器層級嵌套的問題,不涉及到具體某個預處理器的語法,並且由於 scssless 之間寫法很類似,涉及到的正則其實都是同樣的, 因此若是你用的是 less,應該也是可使用的,對這方面有需求的同窗能夠先試用下,若是發現有什麼問題,請提 issue ,若是後續對於 less需求的人比較多,我會專門兼容一下 less

相關文章
相關標籤/搜索