Chrome 10 週年

Chrome 10 Years

Chrome 10 週年 Google 真的是很精心的規劃要幫它慶祝啊,我猜大概是找了個很有狼性的 PM 來,最近一下就被發現兩個問題,第一個小一點點,就是 Clear browser data 的地方,全部清除的功能不會幫你清除 Google 的資料喔:

第二個問題比較大一點,灣區日報看到的 Why I'm done with Chrome,問題就是 Chrome 69 開始,登入網頁版的 Google 服務會自動把 Chrome 瀏覽器端的 Google 帳號也登入,然後根據 Google 工程師所說,這樣還不會觸發同步備份密碼和 autofill 等隱私資料(包括密碼、信用卡資料),不過在這狀態下你很可能不小心就觸發了,由於這個機制官方沒說過,介面上也沒明顯提示,就只有大頭那邊的頭像會變,幾乎都會被使用者忽略,而會不會自動備份隱私資料只是一部份,比較嚴重的點還是這個行為是破壞使用者的信任,事情鬧出來之後 Google 有官方回應 說下一版會有修改讓使用者可以關閉(不過預設還是開啟啦~~),登入後的介面也有相對應的修改讓狀態更清楚,另外就是刪除所有 Cookie 不會保留 Google 的了,以上這些修改都要等十月中的 Chrome 70。

最後就是,隨著這次事件才注意到有 ungoogled-chromium 這個專案啊。


保護專注力

SONY WH-1000XM3

前陣子 Ash Wu 寫了篇 如何在 Context Switch 間維持自己的生產力,講他這陣子的經驗,所以我也來分享我這陣子的經驗,不過不是 Context Switch,而是底層工程師如何保護自己的專注力。

之前在開放式辦公室工作,雖然我已經面對牆壁了,還是覺得很容易受到干擾,不論是環境還是工作模式造成的都有,所以我逐步做了一些調整來減少心流被打斷的機會,首先,硬體設備最常見的就是抗噪耳機了,之前是買 SONY 的 WH-1000XM2,最近剛上市 Mark 3 也更新過去了,抗噪耳機一開始會有點不習慣,像是本來打鍵盤的聲音都聽不到了之類的,不過不用很久就會習慣了,這筆開銷是比較大,不過要是花的起的話,我建議直接買三代,舒適度比較好,二代我戴久耳朵還是會不舒服;除了聲音設備外,螢幕弄大一點把視野佔滿也是個不錯的方式,前陣子網路上不少人都買了 40 吋的大螢幕來用的樣子,我自己是只有用到 27 吋的,其實遮蔽效果也沒比 24 吋的好很多就是。

第二個是我現在工作時的電腦不登入 SNS 了,包括 Facebook 和 Twitter,所以我不會查資料或是看 Email 的時候發現有小紅點就順手點過去看,其實一開始是因為在用 Toggl 追蹤時間,注意到去看 SNS 會造成時間計算不準確,所以乾脆都不登入了,也是沒很久就習慣了,當專心時,其實完全不會注意到有 SNS 等著我去看。

第三個是我會把能關的提示音都關掉,不過這是因為我耳機音樂的訊源就是工作用的電腦,所以電腦的 IM、收信軟體的提示音都會過去,如果耳機可以弄獨立音源給它其實也沒這問題,這時候就有點懷念 iPod 了;如果提示音都關了,還會被 Dock 上的紅色數字或是訊息通知窗干擾,那我還會更進一步把 IM 的程式都關掉,即使那是工作用的 IM 我也是會不客氣的關掉,可能過兩個小時後你會發現其實你不在線上也沒關係的,真的很要緊的事,其它同事會透過各種方式找到你的(然後你還會發現有時候其實被這樣聯絡也不是很要緊的事)。

除了電腦提示外,手機我也會關到完全靜音,完全靜音是指連震動都沒有,所以如果有來電的話就只有螢幕會亮而已,也因此我常常沒接到推銷電話,其實平常會打電話給我的人很少,大部分都是我事前會知道的。

以上的部分主要在於減少干擾,其實還有一些其它工作方式的細節可以幫助,不過還沒有太多想法整理出來,所以這次就先不提了。最後補充一下,Ash 也有提到的 workflowy(這是我的推薦連結)我也覺得很不錯,大概是遇到這個服務之前,有認真考慮過自己做一個這種東西自用,不過後來發現這個服務之後就再也沒想過這回事了。


Homebrew rollback to MySQL 5.7

最近 Mac 的 homebrew 預設的 MySQL 已經改到 8,然後產生了些相容性問題,像是 GUI 不支援之類的,所以提供一下方法回到 5.7

brew uninstall mysql
brew install mysql@5.7
brew link --force mysql@5.7

然後清空舊資料重建新的

rm -rf /usr/local/var/mysql/
mysqld --initialize

其中第二個步驟會產生新的 root 密碼,先複製下來,啟動服務並修改密碼:

brew services start mysql@5.7
mysqladmin -u root -p password

照指令操作,應該是先填前一步產生的密碼,然後輸入新密碼、再次輸入新密碼,這樣就 ok 了,剩下就是找個 GUI 或是匯入資料之類的。

參考資料:


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
  }
}

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


Tern 0.22 released

大約七月初的時候,我開始接手幫忙維護 Tern,Tern 是一個獨立的 JavaScript inference engine,用於協助撰寫 JavaScript 程式碼,就和之前介紹過的 Microsoft 的 LSP 後面的 Language Server 一樣,都是獨立於編輯器/IDE之外,不過 TernJS 是 2013 年就有開始發展的,所以是走自己的溝通介面;其實我幾年前也有幫忙貢獻過 TernJS,以前弄過我還有印象的有 Promise 支援、fetch 的定義、CoffeeScript plugin。

後來作者 Marijn暫停維護 Tern 跑去弄其他東西像是 AcornCodeMirror 還有 ProseMirror 等(這位很厲害,改天再來介紹),並公開找人接手,在一些文字內有找到他的說法是說現在這個架構有些問題處理不了,很難再發展下去了,總之所以就停了一年多沒更新了,我也是斷斷續續注意到這個狀況,不過在研究 LSP 的時候發現其實還蠻多東西是依賴 TernJS 的,讓他這樣荒廢下去好像有點可惜,認真考慮了一兩週後決定接手維護工作,考慮的點主要在於不知道能不能順利接手處理問題,因為 TernJS 的 code base 實在不容易理解,尤其是我沒有相關的 compiler、工具的訓練和開發經驗,以前那些貢獻其實都是花很大心力下去才弄出來的,幾乎是處於那種「程式碼會動了,但是我不知道為什麼」的狀態,不過這兩年相關的知識補了不少,還玩了好一陣子的 JavaScript AST,有覺得比較看的懂 TernJS 的程式碼了,就心一橫報名說要幫忙維護了,Marijn 看到我過去有發過一些 PR 後,很迅速的就開協作者權限給我了。

正式開始接手後,我就開始把要做的事情整理出來,我的目標是在保持現有架構之下,盡可能的繼續支援新語法,直到真的這個架構撐不住為止,所以一開始就是把一些落後的語法支援和定義補上,這次發佈的 0.22 版就是包括 0.21 之後的一些小 bugfix,還有我加入之後開始弄的 async/await、async iteration(包括for await of) 以及**支援,下一版我會開始一些內部的修改、還有看看 bug,不過 Emacs 相關的我現在是真的無法處理。

最後一段來說說目前感想吧,Tern 真是我目前為止看過最難理解的 code 了,不知道是不是會寫 compiler 的人腦袋都會轉換到常人無法理解的形狀,我目前為止看的第二辛苦的 code base 是 Kibana 的,不過 Kibana 單純只是東西很多,找入口找很久,Tern 難的點在於它用了很多 side effect 來做事,而且 code 內沒什麼文件說明,所以像下面這行我就花了很多時間才看懂實際上做什麼事:

infer(node.right, scope, new HasMethodCall(":Symbol.iterator", [], null,
                                           new HasMethodCall("next", [], null,
                                                             new GetProp("value", target))))

這行程式碼是先拿node.right:Symbol.iteratormethod 的執行結果,再看它的nextmethod 的執行結果,然後取最後這個結果的valueproperty 的資訊(可能的 type 之類的)塞給 target 物件,然後這行下面你又看不到 target 做何用,因為 target 物件是在上面已經有和其它會回傳的物件有建立關聯的;除此之外,這裡有個new GetProp,其它地方還有個AVal.getProp又是不同功能,一開始看的真的是黑人問號...


更之前的文章