glob

最近在搞 jsctags-oasis 這個專案,因此認真的研究了一下 glob,glob 這東西其實有在使用 CLI 的話,一定是使用過的,例如:

ls *.js

後面的*.js就是 glob,應該可以稱為一種表達式吧,沒有正規表示式(Regular Expression)強大,是專用於匹配檔案的,現在也已經是內建於 Linux shell 內的功能了,所以其實只要man glob.7或是man 7 glob就可以找到官方文件了(不過 macOS 上沒有),然後 glob 和正規表示式相比,有個很關鍵的差異就是 glob 是有判斷路徑階層的,也就是其實?*雖然是任意字元,但是/不屬於任意字元,/又被稱為 path separator,如果要找不同層子目錄的檔案,就要把路徑寫好,不然比對時不會如願找到想要的目標,而這個差異其實也說明了為什麼ls subfolder/*只會印出該層子目錄下的檔案,而不是把第二第三層子目錄下的東西也都印出來,雖然有**這個寫法,不過我是在 nodejs 開始蓬勃發展之後才在 node-glob 文件上看到的。

其實我第一次看到 glob 這個單字也是node-glob,不過當時以為 node-glob 和命令列的那套不相容,只是借用名字而已,因為那個**/*.js的語法我以前沒看過,一直以為是 node-glob 自己做的,直到這次研究才發現其實**bash提供的擴充語法,bash 的 extglob 提供了一些更接近正規表示式的語法:

?(pattern-list)
       Matches zero or one occurrence of the given patterns
*(pattern-list)
       Matches zero or more occurrences of the given patterns
+(pattern-list)
       Matches one or more occurrences of the given patterns
@(pattern-list)
       Matches one of the given patterns
!(pattern-list)
       Matches anything except one of the given patterns

另外還有很多設定可以調整 glob 的行為,其中一樣叫做globstar的,就是讓**可以 recursive 的 match 子目錄的檔案,這個功能是在bash 4.0 alpha版的時候新增的,到今天其實也已經超過十年了。

至於為什麼會研究起 glob 呢?是因為我在做 jsctags-oasis 時,要盡量的支援Exuberant Ctags支援的參數,其中做到exclude的時候,一開始偷懶用了 node-glob 的 ignore,但是實際上要拿vim-gutentags來用時卻行為不如預期,為了能正確支援就研究起這實際上怎麼串起來的,首先是 vim-gutentags 會拿 Vim 那邊的wildignore送給 ctags,wildignore 使用的表達式是 Vim 自己的filepattern,和 glob 有點接近,像是*都是正規表示式的.*,還有?都是正規表示式的.,不過*有特別說到:

Unusual: includes path separators

這行為就和 glob 不一樣了,所以假設 ctags 的exclude也是用 glob 表示式,那是不是表示 vim-gutentags 這邊實做有不正確呢?結果我發現 Exuberant Ctags 的文件是這樣說的:

each pattern specified using this option will be compared against both the complete path (e.g. some/path/base.ext) and the base name (e.g. base.ext) of the file, thus allowing patterns which match a given file name irrespective of its path, or match only a specific path. If appropriate support is available from the runtime library of your C compiler, then pattern may contain the usual shell wildcards (not regular expressions) common on Unix (be sure to quote the option parameter to protect the wildcards from being expanded by the shell before being passed to ctags; also be aware that wildcards can match the slash character, '/').

這時候就要感謝那時期的文件都有寫得很詳細,不用花時間去看程式碼,這邊的說明就是說會比對 basename (檔名加附檔名)和完整的 pathname,另外對於 wildcard 的支援則是看系統,是用shell wildcards,其實就是 glob 表達式,不過照這樣說,應該就和 Vim filepattern 不一樣了,研究許久才注意到關鍵的地方就在上面那段文件的最後一句,提到 wildcards 也會 match 到/字元,也就是最前面提到的 path separator,結果就是, Vim filepattern 和 Exuberant Ctags 的exclude用的表示式基本上是相容的,但是也因為特性就無法用 node-glob 的ignore來支援了。所以我就照著說明自己實做了比對的部分,然後有用到一個叫globrex的 npm package,這個是tiny-glob底層用的工具,算是個偷吃步,不管 path separator 直接把 glob 轉成正規表示式的作法,根據原始碼,它會直接把*轉換成.*,這樣就會 match 到/字元了,本來是偷吃步的作法,卻意外的剛好合用,理論上這樣就可以正確的支援 ctags 的exclude才是吧。


addEventListener 的第三個參數

addEventListener

2007 年我寫過一篇一樣標題的addEventListener 的第三個參數,介紹了事件發生時, DOM Node 的 capture 和 bubbling,事隔十多年,前陣子定睛一看,發現 DOM spec 有變,第三個參數除了可以收 boolean 型別的 useCapture 之外,還可以收options物件,又稱為EventListenerOptions,而這個 options 物件現在支援三個屬性,分別是:

capture,就是以前的第三個參數 useCapture,Boolean 型別。

once,新的選項,也是 Boolean 型別,用途就像是 jQuery 的one一樣,想不到現在也直接在 DOM 層原生支援了

passive,也是新選項,一樣是 Boolean 型別,用途是告訴瀏覽器,這個事件 handler function 會不會呼叫event.preventDefault來停止瀏覽器的原生行為,我最初其實是在 Google 的關於scroll performance 的文件看到的,就是如果你是 scroll event,以前會因為瀏覽器要判斷會不會被preventDefault,所以讓 scroll 的效能變差,加上這個選項可以直接告訴瀏覽器說沒有要 preventDefault 後,原生的事件行為就可以不管 event handler 直接處理了,如果裡面硬是執行event.preventDefault的話,那就會被忽略掉,然後看瀏覽器可能會有警告訊息出現在 console。

Passive Event 的效果也有人做了影片可以看(來源):

EventListenerOptions 這個東西大概是在 2015 開始討論的,然後2016進到 WICG 討論,瀏覽器開始實做,一開始就是只有passivecaptureonce則是後來才加上的,所以可以看到 MDN 的瀏覽器支援度表格,once還要比較新一點的瀏覽器才支援,像是 Chrome 51 就支援passive,然後要到 55 才支援once,如果再仔細看,會發現最後一列是touchstarttouchmove事件如果是在 document 層的話,預設改為 passive 事件,這是 2017 年 Chrome 主導修改的行為,Firefox 也有跟進,主要就是希望能讓這些事件處理預設效能好一點,這部分的行為修改其實到現在都還沒標準化,目前還是在 WICG 那邊有個open issue,除了 touch 事件外,其實連 document 層的 wheel 事件也在Chrome 73,也就是現在的穩定版本也預設改為 passive 事件了,然後也是有 WICG 的open issue,MDN 的表則是還沒有。

EventListenerOptions 也是有 polyfill 和工具 package 的,首先來說一下 polyfill 吧,我知道的有dom4,其實搜尋一下還蠻容易找到其它的,不過差異沒很大,feature detection 的方式幾乎都是用 Object 的 getter 來看送進去的 options 物件的passive屬性有沒有被讀取過,有的話就表示瀏覽器有支援,然候 polyfill 其實也只有行為上的補完,不會真的讓效能提升,不過 polyfill 在現在的支援度下來看也是不太需要了。Package 的話有個default-passive-events會幫忙把 scroll、wheel、mouse、touch 等等有需要的事件都改為預設passive: true,如果是新專案先加一下似乎不錯,不過感覺上 Google 是很想都改掉的樣子,現在其實也只差 mouse 事件而已,應該是還怕影響太大不敢下手吧。


Lab Gradient

Gradient

一早起來就看到這篇文章視覺上的完美漸層 Chromatic,介紹了一個 Sketch plugin 可以用不同色彩系統的漸層來產生更好的視覺效果,之前其實也有注意到這個問題好幾次,就是覺得 CSS gradient 的效果不好,也有注意到一些其他的漸層方法,不過一直沒記錄下來,所以趁這時候把一些資源記錄一下,該篇文章的作者 Samuel 是推薦 Lab 色彩系統的漸層。

目前因為 CSS 就是只有 RGB 漸層,所以要用其他系統的漸層就只能用模擬的,SASS 的話有chromatic-sass,PostCSS 則是postcss-easing-gradients,這套背後用的則是首篇文章也有介紹的chroma.js來轉換的,不過它其實主要是在做 easing gradient 的,然後還有一些線上的模擬工具可以讓人直接看看效果,第一個其實是 easing gradient 的工具,其實就是在 easing gradient 標準提案時有人做來讓人體驗的,另一個介面比較不 fancy,但是我覺得比較實用的Lch and Lab colour and gradient picker


使用 VSCode 讀程式碼

VSCode peek window

偶爾看到些有趣的 library 會對它的原理和實做方式有興趣,就會花時間看看他們的程式碼,像是曾經介紹過的immer,或是最近還在看的lit-html,然後最近發現VSCode已經有把閱讀程式碼需要的功能都做好,而且因為原生對 JavaScript 支援很好,看 JavaScript 專案的時候很方便,不用特別安裝或設定什麼直接就可以開始。

閱讀程式碼其實也沒需要什麼特殊功能,就是看到不知道是什麼的東西(constant, variable, function, class ...)時,能不能快速移動到定義的地方,看完後還可以回到原處這類的 reference 和導覽的功能,在 VSCode 當中,這兩個功能就是F12移動到定義處和Ctrl+-移動回到上個位置,簡單一點的使用只要記得這兩個快速鍵就可以了,然後其實還有幾個進階的功能:

  1. Opt+F12Peek Definition,用 peek window 預覽定義,peek window 就是像上圖那樣一個浮在現在視窗上面的子視窗,通常右邊都會列出一些項目讓你可以挑選(peek),這個指令似乎也可以列出多個定義的位置,不過我還不確定是怎樣的情形會一個變數有多個地方定義它就是。
  2. Cmd+KF12Open Definition to the Side,這是兩段式的指令,先按Cmd+K然後再按F12,就會垂直分割出一個新視窗,然後新開的視窗就是所尋找的定義的程式碼。
  3. Shift+F12Peek References,這也是開 peek window,不過不一樣的點是它是列出專案內其他有用到這個東西的地方,有時候看定義不太理解可以直接看看怎麼使用,也還蠻有幫助的。

大概就這樣,Vim 的話其實也有內建的導覽功能,不過要產生對應的 reference 資訊還要些工具幫助,等研究研究後再來介紹嘍。


CSS 屬性排序

CSS Box

上週在弄Stylelint的設定,然後理所當然的處理到屬性排序的問題,以前我是用 CSScomb 的zen這組設定, 這組排序規則是從Zen Coding那邊來的,它的規則其實應該就是Concentric CSS的規則,concentric 是同心的的意思,同心圓的那個同心,這組規則的基本原則就是從外到內,從外部的定位、排版方式、到邊距、外框、內距、寬高等 box model 的屬性,然後才到內文的屬性,這組也是我第一次知道的有系統的 CSS 屬性排序規則,而且我覺得這個規則很直覺,而且也是很有邏輯的設計,所以就一直用到現在了。

最近這次要弄 Stylelint 設定時順便又研究了一下現在主流的排序規則,發現現在 lint tool 如果有內建排序規則的話,一定會有的其實是照字母順序排序,我自己是覺得在 CSS 領域照字母排序根本是 anti-pattern 啦,不過一直都有聽說有些大型企業的專案會用這種排序規則。事實上在 CSS-Tricks 的Poll Results: How do you order your CSS properties?的這篇文章,裡面就有 14% 的人是使用字母排序,當年還是 2012,2017 年 SitePoint 的調查結果其實沒有很大改變,變成 13%,所以雖然我覺得 anti-pattern 但其實還是有一定比例的人真的這樣用,其實要說的話也還是有好處啦,因為其它的屬性排序規則都是有個基本原則,實際上要仔細排列所有屬性的時候都還是有可能會有些地方有灰色地帶。

最高比例的排序方法則是照屬性類型分類,照屬性分類其實算是比較籠統的說法,Concentric CSS 的排序規則也算是照屬性分類的,同樣符合這樣條件的另外還有 Nicolas 的Idomatic CSS和 Jonathan Snook 的SMACSS的排序規則,其實這兩種方法的大方向也和 Concentric 的很接近,雖然是分成幾個大類別,但是大類別的排序基本上一樣是從外到內的方向,然後根據 npm 的安裝數字,目前使用度最高的應該是 SMACSS 的排序建議了吧,細部的完整排序可以在 Stylelint plugin package 的repo那邊看到。而除了 Stylelint 之外,PostCSS 也有 plugin 叫css-declaration-sorter來幫你排,也內建了Concentric CSSSMACSS,而且該 plugin 還號稱:

  • Up-to-date CSS properties fetched from theMDN Web Platform.
  • Thought-out sorting orders out of the box, approved by their authors.

感覺很不錯的樣子,它們的完整排序清單也可以在 repo 內找到:SMACSSConcentric CSS,不過排序這東西應該還是要在 lint 的時候做啊,PostCSS 的產出物通常是 production 環境用的 code 了,只是 stylelint 那邊用的排序清單和這邊的又不同,不過其實StylelintVSCode都可以拿 PostCSS 的來用的樣子,還沒測試過就是~

最後提兩個 Concentric CSS 灰色地帶的問題:

  1. Grid、 column、 flex、 float 這幾個屬性你會怎麼排呢~?
  2. 如果box-sizing: border-box;設下去後,width/height 要放在 padding 前面還後面呢?

➡ 看看其它文章