Intrinsic Size 媒體寬高比

HTML 文件在編寫插入<img>時,通常都會順便加上寬高的資訊,早期這樣做除了指定圖片呈現時的大小外,還有一個好處是提升頁面繪製的速度,不用在圖片讀取好、知道實際寬高時,又重新排版重畫版面,不過這個狀況在用 CSS 設定動態寬度時,就又回到原點了,像是設定width: 100%;這種,因為沒辦法設定相對於圖片寬度的高度值,所以瀏覽器只能自己先隨便決定一個高度,等圖片抓好再重排一次。

一直以來這問題都沒好的方案處理,在排版上比較多人採用的是外面多一層 block 元素然後用padding-top來把空間先佔好,不過這也只算是個替代方案,真的要處理應該還是要從 HTML 或是 CSS 下手,然後前兩天才終於看到有個不錯的方案 Chrome 要來實做了,叫做Intrinsic Size

<img intrinsicsize="250 x 200" src="cat.jpg">

就是一個長乘寬的概念,中間那個乘號其實是小寫的x,然後提供的長寬資訊其實是等於預先給的 naturalWidth、naturalHeight,為什麼不用 aspect ratio 呢?在提案的文件裡面其實都有寫到,基本上就是這種設計提供的資訊更多,有更多好處,例如都沒設定寬高時,可以拿intrinsicsize來先用之類的,intrinsicsize目前設計只能用在圖片和影片上,Chrome預計在 71 的時候推出這個功能,也已經做好有貓測試網頁了,感覺一切都箭在弦上了,不過這新提案在 WICG 上幾乎沒討論,提案者是 WICG 也是 Chromium 成員就是,另外就是另外三家的 web platform status 都還查不到,並且,其實也有一個 CSS 的aspect-ratio提案,所以到底會怎樣還很難說啊(不過我覺得是會變標準啦)。


Extensible Web at 2018

2013 年有一份 Extensible Web 宣言,簽署人同意致力於開發 Web Platform 更底層的介面,讓開發者可以自己擴展 Web Platform,而構成 Web 介面的基本三要素其實就是 HTML、CSS、JavaScript。

其實我在當時就覺得很奇妙,我可以想像的到開發者自己擴充 HTML 標籤,不過卻想像不出來到底 JavaScript 和 CSS 要如何讓開發者擴充,沒想到今天回過頭來看,整個網路標準的發展方向真的是有在朝這方向前進,首先來看 HTML 的部分,其實就是Web Component,包括了 Custom Element、Shadow DOM、Template、HTML Import 等標準,透過這些新的 Web API,開發者很容易就可以做出可重複利用,有自訂行為的自製標籤了。

JavaScript 的部分,其實在上一篇文章介紹那一串 ES6 新功能的最後,有提到 meta programming 的新功能,雖然不是完完全全想擴充什麼就擴充什麼,但是 JavaScript 確實是慢慢的有些可以讓開發者比較深入底層的介面可以用。

最後 CSS 的部分,就是 CSS Houdini 了,Houdini 其實就是史上最偉大魔術師胡迪尼,從名稱就可以感受到這個東西有多 magic 了,其實 Houdini 不是一個標準,而是一個W3C Working Group,他們主要的目標就是建構出一整套 API 讓 CSS 可以擴充,而目前也已經有些成果了,我自己是看了去年一場演講的影片才對 Houdini 有了初步的認識:

現在最主要是已經有 Chrome 把 Paint API 實做出來,所以已經可以用 Canvas 畫圖了,雖然整個計畫離完成還要很久,不過看到 CSS 真的可以開始擴充了,還是覺得很 Magic~

Image Source:https://imgur.com/gallery/YsbKHg1


ECMAScript 2015 新功能間的關係

ECMAScript 2015,

這篇想說的是 2015 年的那個 ECMAScript 6(後面簡稱 ES6),也就是之前 ECMAScript Harmony 計畫的主要成果,那版 ES6 其實是這幾年來改動最多的一版,新增了很多的新功能和語法,而這一堆新功能很多是環環相扣的,我以前曾經在 Facebook 上提過,不過那邊的東西容易就消失在網路上,所以還是另開一篇文章來記錄,剛好也可以做為下一篇文章的參考資料。

首先要從Map/Set這兩個新的資料型態說起,ECMAScript 一直以來都只有少少的資料型態,直到 ES6 才加了些新的,其中比較容易注意到的就是 Map 和 Set 了,其實這兩種資料型態以前就是直接用 object 來 array 來做,兩邊蠻接近的,最主要的差異則是 Map 的 key 可以是任意型態, 而以前 object 的 key 只能是字串,Set 是 unique 的,array 則否,另外就是在適合的情境下,現在 Map/Set 的效能不一定會比較差。

Map、Set 其實都算是 collection 的資料,所以會需要有個方法可以遍歷所有元素,像是 array 的forEach或是 for...in 語法,不過 for...in 先拿到的東西是 key,還需要拿 key 去取元素實際的值:

for (let k in arr) {
  let v = arr[k];
}

一直以來,其實開發者社群都希望有個語法能直接取 collection 內的元素,所以像是 CoffeeScript 就是把for...in換成直接拿到 value,於是 ES6 就有了個for...of語法,可以遍歷 collection 類型的資料並直接取得值:

for (let v of arr) {
    
}

ES6 的這個語法,其實底層是透過iteration protocols這些內部協定來運作的,包括了 iterable protocol 和 iterator protocol,for...of其實就是透過 iterable protocol 去拿物件的 iterator,利用 iterator 來遍歷元素,所以自己寫的物件也可以實做 iterable protocol,然後就可以讓該物件支援for...of語法了;除此之外,iterator 是不能重複使用的,所以其實每次for..of,都是拿一個該物件的新的 iterator,而為了可以簡單產生這個 iterator,又有了generator function,generator function 每次執行都會回傳一個新的 iterator(精確一點說是 generator object,同時是 iterator 也是 iterable),正好適合這個情境。

Iterable protocol 的定義其實很簡單,就是定義怎樣把 generator function 放在物件裡的方式,實際上是利用 ES6 另一個新的資料型態:Symbol來達成的,為什麼不直接定個屬性名稱給它呢?最主要就是不要讓這些內部 protocol 的東西在for...in操作的時候被遍歷到,所以定義了新的 Symbol 型別,利用它的特性來把內部 protocol 做了一定程度的保護,iterable protocol 就是把 generator function 用一個預先定義好的 Symbol 來儲存,這個 Symbol 又稱為Symbol.iterator,這種預先定義好的 Symbol 則統稱為 Well-Known Symbols,ES6 其實定義了好幾個,不是只有 iterable 用的到,透過定義這些 Well-Known Symobls,可以介入改變一些 JavaScript 比較基礎的運作。

Well-Known Symobls 可以做到的事情,其實有點像是改變程式語言的運作,而這種類型的機制又稱為meta programming,除了 Well-Known Symobls 之外,ES6 其實還提供了ProxyReflect,這兩個東西應該比較多人知道 Proxy 是幹嘛的,對 Reflect 比較陌生,其實 Reflect 有點像是為了 JavaScript 一些設計不好的地方,想了解詳細一點推薦可以看主要的 polyfill [harmony-reflect][] 的 Wiki 頁:Why should I use this library?

最後總結一下,這串從 Map/Set 開始,接著連到for...of語法、Iterator、Generator、Symbol、Well-Known Symobls 最後到 meta programming 的 Proxy 和 Reflect,其實也差不多佔了 1/4 的 ES6 新功能。


Web Platform

這篇記錄一下現在主要四個瀏覽器核心對於新標準的支援計畫的狀態網站,不然每次都要重新查。

Chrome 的應該是最多人看過,其實看過這個就能了解現在幾乎是無法從 0 開始開發一個瀏覽器了,Google Chrome 從 WebKit 的基礎開始,到現在也花了十年。


eslint-plugin-pep8-blank-lines

eslint-plugin-pep8-blank-lines,

我的第二個 ESLint plugin 終於進 beta 了,這是我自己期望很久的檢查規範,上一次介紹 ESLint plugin 的時候就有說到接下來想處理空行,其實 ESLint 內建的 rule 已經有蠻多是用來檢查空行的了,不過沒有一個能符合我想要的規範,我想要的規範其實很簡單,就是希望能在大一點的物件中間能多一點空行,比較有段落的感覺,這樣閱讀起來感覺也比較好(如上圖),剛好我這兩年寫了一點 Python,有用 Flake8 做語法檢查,其中的PEP8 coding style 中關於空行的規範,就符合我想要的樣子,而且很簡單,這個規範是在大部分地方都允許最多一行空行,但是最上層(top level)的 function, class 前後要兩行空行。

於是這個 ESLint plugin 的主要目標,就是把 PEP8 這部分的規範搬過來,一開始想的實做方式有兩個,其一是參考padding-line-between-statements的作法,比較兩個相鄰 token/node 間的 line number,另一個則是用sourceCode來一行一行看,不過同時也要知道該行的 context 是什麼才能判斷,所以也是跑不掉要進去看 AST,加上我想要玩玩看 JavaScript 的 AST,所以最終我是選擇第一種作法,不過不是用 ESLint 內建的 walker,而是在Program:exit的時候才用自己寫的 walker 進去看 AST;而經過一輪重構後,現在的架構其實是靈活度很高的,我實際上做出了一個比 padding-line-between-statements 還要更多功能的規範定義格式,然後根據這個格式寫出我想要的空行規範,只是目前還沒開介面出來給使用者輸入自訂的空行規範就是了;其實我自己覺得這個 plugin 實做的理想型式應該還是要用實做方案二,並搭配使用 ESLint 的 AST walker,實際上 padding-line-between-statements 也就是這樣做,會這樣想最主要的原因是現在的實做只看 AST,但是 AST 其實不能 100% 表達原來的程式碼,這也是這次開發經驗中我最大的體悟,所以其實一些奇怪地方的空行就會很難抓到,例如await 1這兩個 token 中間如果有空行就會跳過,不過會在這種地方放空行的情形應該都是蠻少見的,所以目前也沒打算繼續改下去,短期內都會以處理 bug 為主,過陣子應該會試著加上 fix 的功能,總之歡迎測試並回報問題,雖然有寫測試,不過還沒什麼實際跑在真實的程式碼上,目前唯一的就是它自己的 code base 本身是有用吧,另外就是使用時如果是搭配其它 style 可能會需要把其它 style 的空行規則關掉,例如搭配standardjs時的.eslintrc範例:

{
  "extends": "standard",
 
  "plugins": [
    "pep8-blank-lines",
    "no-parameter-e"
  ],
 
  "rules": {
    "semi": [2, "always"],
    "no-extra-semi": 2,
    "comma-dangle": ["error", "always-multiline"],
    "no-multiple-empty-lines": 0,
    "pep8-blank-lines/pep8-blank-lines": 2,
    "no-parameter-e/no-parameter-e": 2
  }
}

這組其實也是我目前在用的設定啦~


➡ 看看其它文章