人工智慧的出現,讓許多人開始重新思考自己的價值。
一位工程師花費十年累積的技藝,可能在幾秒鐘內被一個大型語言模型複製;一個曾經需要反覆推敲數日的問題,如今只需要幾次對話便能得到足夠好的答案。
於是,人開始感到失落。
但真正失落的,或許從來不是程式碼。
而是那個曾經透過技藝確認自己的自己。
我們以為自己害怕的是 AI。
其實,我們害怕的是一件更古老的事情——當原本相信的價值開始崩解,我們該依靠什麼活下去?
尼采說,當舊價值死亡,人必須成為創造價值的人。
這句話聽起來充滿力量,也因此成為許多人面對 AI 時代的鼓勵:既然寫程式不再是唯一的價值,那就重新定義工程師;既然工具愈來愈強,那就追求判斷力、品味、創造力與願景。
然而,當我們繼續追問時,一個新的疑惑又浮現。
如果價值本來就是自己創造的,那麼,我又如何知道自己不是只是急著找一個新的信仰,好填補內心的不安?
也許昨天相信的是「技術」。
今天相信的是「創造」。
明天相信的是「AI 協作」。
這些價值彼此不同,但它們是否只是同一種渴望的不同形式?
渴望有一個理由,證明自己值得存在。
如果是這樣,那麼所謂的創造價值,是否只是換了一座新的神壇?
海德格或許會提醒我們,真正的危險不是找錯了價值,而是我們始終急著找到一個價值。
現代人的思考,早已深受技術影響。
我們習慣把所有問題都當作需要解決的問題,把所有未知都當作等待填補的空白,把所有人生都當作一項等待完成的專案。
甚至連「人生的意義」都變成一個待辦事項。
我們焦急地問:
「我的使命是什麼?」
「我的價值是什麼?」
「AI 時代我應該成為什麼樣的人?」
但或許,這份焦急本身,就是技術思維留下的痕跡。
我們並不是在尋找意義。
而是在尋找一個可以停止不安的答案。
然而,有些問題並不存在立即的答案。
有些問題,本身就是生命的一部分。
海德格因此提出一種近乎沉默的態度。
他認為,人不必急著征服世界,也不必急著替世界命名。
有時候,更重要的是學會停留。
停留在不知道之中。
停留在還沒有答案之中。
讓那些真正重要的問題,有足夠長的時間改變我們,而不是急著被我們回答。
卡繆則把這份沉默推向另一個方向。
他說,人最大的處境,不是世界沒有意義。
而是人渴望意義,而世界保持沉默。
這就是荒謬。
我們總希望努力能帶來回報,善良能得到理解,創造能留下痕跡。
可是世界從未承諾這些事情。
AI 的出現,只是讓這份沉默變得更加明顯。
它讓人不得不承認:
也許努力不一定保證價值。
也許專業不一定保證不可取代。
也許創造本身,也不一定保證永恆。
如果如此,人還能依靠什麼?
卡繆沒有給出答案。
他只是說,人真正的自由,不是在找到意義之後才開始,而是在沒有保證的情況下,依然選擇生活。
薛西弗斯明知道石頭終究會滾落。
但他依然彎下身,再一次推動巨石。
不是因為他相信這次一定成功。
而是因為,那是他面對世界的方式。
於是,我開始懷疑,我們一直問錯了問題。
我們總是在問:
「我要如何確保自己沒有走錯?」
但這個問題背後,其實藏著另一個更深的假設。
我們相信,一定存在一條永遠正確的人生道路。
只要找到它,就能證明自己沒有白活。
可是,如果人生從來不是一張等待破解的地圖,而更像一片沒有盡頭的海洋呢?
航海的人,從來無法在啟航之前證明自己的方向一定正確。
他只能依靠星光、風向、經驗與一次又一次的修正。
真正讓他抵達彼岸的,不是一次找到正確答案。
而是始終保有修正自己的能力。
也許,人生也是如此。
真正使我們不至於盲目的,不是找到一個絕對正確的價值。
而是始終保持一種誠實。
誠實地承認自己的有限。
誠實地承認自己的恐懼。
誠實地承認今天相信的事情,明天仍然可能改變。
這樣的誠實,不會讓人變得軟弱。
相反地,它讓人不再需要用任何絕對的答案保護自己。
於是,創造價值,不再是為了逃離虛無。
而是因為,人願意在虛無之中,仍然與世界建立關係。
不是因為這條路一定正確。
也不是因為終點一定存在。
而是因為,每一次真誠的思考、每一次願意修正自己的勇氣、每一次仍然選擇創造的行動,都在回答同一個問題。
不是「人生有沒有意義」。
而是:
當世界沒有保證任何意義時,我是否仍願意,以清醒而誠實的方式活著?
也許,真正的不盲目,從來不是因為找到了永恆的答案。
而是在每一次懷疑之後,我們依然保留提問的能力;在每一次失去之後,我們依然保留選擇的自由;在每一次價值崩解之後,我們依然不急著尋找新的神,而願意安靜地與未知同行。
因為,人之所以自由,不是因為終於抵達真理。
而是因為,即使真理始終沉默,我們仍然願意一步一步,走向它。
我很喜歡這個脈絡,因為它形成了一條很完整的思想路徑:
AI 打碎了「技藝即價值」 → 尼采問「如何重新創造價值」 → 海德格問「為何急著創造價值」 → 卡繆問「如果沒有價值,你是否仍願意活著?」
有時候,人會以為「客觀」是一種可以抵達的地方。
就像站在山腳仰望山頂,我們直覺地相信,只要走得夠遠、看得夠清楚,終究能抵達那個不受任何偏見影響的視角——一個純粹、透明、無關於「我」的世界。
但事情或許不是這樣開始的。
當我們說「世界是什麼樣子」時,其實已經站在某種觀看之中。時間、空間、因果,這些我們以為理所當然的東西,並不是世界自帶的標籤,而更像是我們觀看世界時所戴上的結構。於是,「客觀」不再是外在的實體,而是一種在所有理性主體之間反覆成立的秩序——一種每個人都能使用、都能重複的觀看方式。
於是,我們開始彼此對齊。
你看見蘋果落下,我也看見蘋果落下;
你測量時間的流逝,我也在相似的節奏中確認它。
在這些重疊的經驗之中,一種穩定逐漸浮現。我們稱之為「客觀」。
但這種穩定,並不是從主觀中消失而來,
反而是從無數主觀的重合之中浮現。
它不是被發現的,而是被對齊的。
不是單一視角的真理,而是多重視角之間的交集。
然而,就在我們逐漸相信這種穩定的同時,也開始忽略了另一件事:
這些被稱為「客觀」的東西,從來都不是在真空中形成的。語言、文化、教育、權力——這些無形的結構,悄悄地塑造了我們如何觀看、如何同意、如何認定某些事物「理所當然」。於是,客觀不再只是共識,也成為一種被維持的秩序。
這讓人產生動搖:
如果客觀既無法脫離主觀,又可能被結構影響,那我們還有必要追求它嗎?
也許問題的方向需要被調整。
我們之所以追求客觀,並不是因為它能帶我們抵達一個終極的真理,而是因為,在沒有它的情況下,我們將失去彼此對話的可能。當每個人都只停留在自己的感受與理解中,世界會變成無數孤立的島嶼;而「客觀」,即使只是暫時的、近似的,仍然像橋一樣,讓這些島嶼之間出現連結。
它不是終點,而是一種協調。
不是答案,而是一種讓問題得以被共同理解的方式。
或許可以這樣說——
我們從未真正離開主觀,但也從未停止試圖靠近彼此。
而在這段靠近的過程中,
那個被稱為「客觀」的東西,便短暫地出現了。
Deno 環境使用ES modules
VSCode plugin#

cmd#
deno run main.ts a b -c --quiet
console . log ( Deno . args ) // [ "a", "b", "-c", "--quiet" ]
automatically be restarted / formatted / tested / bundled.
bundle
deno bundle <file> output dir
deno bundle main.ts min.js
unit test#
Deno.test …
third-party-module#
https://www.jsdelivr.com/esm
Skypack , jspm.io , jsDelivr or esm.sh .
// import dayjs from 'https://esm.run/dayjs';
exmaple#
Managing dependencies#
管理所有載入的套件, Deno沒有像NPM package管理方式, Deno 的解決方式是新增一個檔案(deps.ts)並將所使用到的依賴統一匯入匯出。也可以分成開發依賴、生產依賴
import dayjs from ' https://esm.run/dayjs ' ;
Fetch data#
<p>:段落
<h1> - <h6>:標題,有六個層次
<b>:粗體文字
<strong>:強調文字(通常以粗體形式顯示)
<i>:斜體文字
<em>:強調文字(通常以斜體形式顯示)
<span>:用於將文本的一部分標記,以應用樣式或其他操作
<a>:超連結
<abbr>:縮寫
<cite>:引用標題
<code>:代碼
<small>:較小的文字
<sub>:下標文字
<sup>:上標文字
<del>:刪除的文字
<ins>:插入的文字
<mark>:標記的文字
<q>:短引用
<blockquote>:長引用
<pre>:預格式化文字
<br>:換行
<hr>:水平線
<time>:時間
<dfn>:定義名詞
<kbd>:鍵盤輸入
<var>:變數
<samp>:樣本輸出
<address>:地址
<bdo>:文字方向
<ruby>:注音文字
<wbr>:可打斷的文字
<figcaption>:為 <figure> 元素提供標題。
layout#
<header>:定義網頁或區段的標頭。
<nav>:定義導航連結的容器。
<main>:定義網頁的主要內容。
<section>:定義文檔中的區段,通常包含一個標題。
<article>:定義獨立的內容,如一篇文章或一個獨立的區塊。
<aside>:定義與主內容無關的內容,例如側邊欄。
<footer>:定義網頁或區段的頁腳。
將dist 資料夾內的東西推至 指定github 分支
Gitter是GitHub儲存庫的開發人員和使用者的即時通訊聊天室系統。
Release Note#
Vue#
Release Note 統一在 CHANGELOG.md 顯示
Please refer to CHANGELOG.md for details.
補充
CHANGELOG.md
3.x.x (2023-05-18)# Features#
sfc: support imported types in SFC macros (#8083 )
Bug Fixes# " serve " : " vue-cli-service serve " ,
" serve:system " : " vue-cli-service serve --system=kch,ptch " ,
commands : npm run serve:system
commands : npm run serve -- --system=kch,ptch
commands : npx vue-cli-service serve -- --system=kch,ptch
const { argv } = require ( ' process ' )
顯示各項目的編譯速度
const SpeedMeasurePlugin = require ( ' speed-measure-webpack-plugin ' )
const smp = new SpeedMeasurePlugin ({
disable : ! process . env . MEASURE // MEASURE=true npm run build
outputFormat : ' humanVerbose ' ,
filePath : ` ./buildInfo. ${ system ?. length === 1 ? system [ 0 ] : ' index ' } .json `
configureWebpack : smp . wrap ({
透過 worker 建構子便可以產生 worker 物件,並且執行 JavaScript 檔案。在 worker 中的 JavaScript 運行在不同於 window 的執行緒環境,所以在 worker 中存取全域物件應該要透過 self ,如果透過 window 會導致錯誤發生。
Dedicated workers#
// 產生 dedicated worker, worker.js檔案需要server提供
var worker = new Worker ( ' ./worker.js ' )
worker . postMessage ([ ' first.value ' , ' second.value ' ]) ;
worker . onmessage = function ( e ) {
console . log ( ' onmessage ' , e . data )
onmessage = function ( e ) {
var workerResult = ' Result: ' + e . data [ 0 ] + e . data [ 1 ] ;
postMessage ( workerResult ) ;
補充
Note: 在主執行緒中存取 onmessage 與 postMessage 需要主動掛在 worker 物件上,在 worker 執行緒則不用,這是因為 worker 執行緒的全域物件便是 worker 物件。
結束 worker#
請注意不論 worker 正在執行的運算完成與否,一但呼叫後 worker 便會立刻被終止。
而在 worker 執行緒裡,worker 可以呼叫自己的 close 方法來結束
Shared workers#
shared web worker 能夠被多個程式腳本存取,即使是跨越不同 window、iframe 或 worker。但是瀏覽器支援度較低
if ( window . SharedWorker ) {
var myWorker = new SharedWorker ( " sharedWorker.js " ) ;
myWorker . port . onmessage = function ( e ) {
console . log ( ' Message received from worker ' , e ) ;
myWorker . port . postMessage ([ 8 , 8 ]) ;
onconnect = function ( e ) {
port . onmessage = function ( e ) {
var workerResult = ' Result: ' + ( e . data [ 0 ] * e . data [ 1 ]) ;
port . postMessage ( workerResult ) ;
port . start () ; // 註冊 onmessage 事件的同時也自動建立連線,所以說不需要呼叫start() 了
other worker#
mounted : el => el . style . background = ' blue '
< div v-background > 55688 </ div >
目前網頁開啟IOS APP的方式有兩種
Universal Link : 點選連結會直接開啟APP 不會開啟瀏覽器.
有裝APP: 直接開啟應用程式
沒裝APP: 會開啟URL連結
Deep Link(<iOS9) : 開啟瀏覽器由前端使用 URL Scheme 開啟APP.
有裝APP: 直接開啟應用程式
沒裝APP: 會開啟URL連結, 但會出現轉跳錯誤的訊息
Universal Link#
指定路徑下設定相關檔案
apple-app-site-association 取得方式,
=>iOS14 使用Apple CDN;
<iOS14 由站台提供
https://<fully qualified domain>/.well-known/apple-app-site-association
https://developer.apple.com/documentation/xcode/supporting-associated-domains
注意
Important
Apple’s content delivery network requests the apple-app-site-association file for your domain within 24 hours. Devices check for updates approximately once per week after app installation.
把整個網站變成一片可探索的宇宙 ,每顆星星都是一篇文章或作品的入口。
不是在網站放一個星空背景,而是整個網站就是一片宇宙。使用者不是在切換頁面,而是在宇宙中探索不同的知識。
可以使 DIV, P, UL 的文字內容可編輯
目前網頁開啟IOS APP的方式有兩種
Universal Link : 點選連結會直接開啟APP 不會開啟瀏覽器.
有裝APP: 直接開啟應用程式
沒裝APP: 會開啟URL連結
Deep Link(<iOS9) : 開啟瀏覽器由前端使用 URL Scheme 開啟APP.
有裝APP: 直接開啟應用程式
沒裝APP: 會開啟URL連結, 但會出現轉跳錯誤的訊息
Deep Link#
使用 URI schemes
window . location . href = ' xxxxapp://redirect/* '
站台設定 (/.well-known/apple-app-site-association)
拆解吸積盤粒子系統 、螢幕空間重力透鏡與光子環的實作細節,以及如何在維持 60fps 下堆疊這些效果。
吸積盤:以 Keplerian 螺旋回收粒子
重力透鏡:螢幕空間後製,僅在陰影邊緣扭曲
光子環:釘在陰影半徑上的細環,交給 bloom 發光
我們如何用空間隱喻 理解時間?一篇關於感知與敘事的隨筆。
時間是一條線,還是一片可以探索的空間?當我們把回憶排成星圖,過去就不再是背後,而是四面八方。
說明 partial hydration 的取捨,以及何時該把一塊 UI 變成 island。
預設送出零 JavaScript,只有真正需要互動的區塊才 hydrate——這就是 island 架構讓頁面又快又互動的關鍵。
webpack dev server 自定義訊息顯示
this . info = options . info || {}
compiler . hooks . done . tap ( ' Show something ' , () => console . log ( this . info ))
module . exports = TestPlugin
const TestPlugin = require ( ' ./TestPlugin ' )
npm run server後就會顯示info 所帶入的訊息
IntersectionObserver 讓你非同步地監聽某個元素是否進入畫面(或某個容器)的可視範圍 ,不必自己在 scroll 事件裡反覆計算位置,效能更好。常見用途:
圖片延遲載入(lazy-loading)
無限捲動(infinite scrolling)
元素進場才觸發動畫,或計算它露出畫面的比例
基本用法#
建立 observer 時傳入兩個參數:一個 callback,以及一組 options。
// 作為「可視範圍」基準的容器;null(預設)代表整個瀏覽器視窗
root : document . querySelector ( " #scrollArea " ) ,
// 對 root 範圍做內縮/外擴,語法同 CSS margin
// 露出多少比例才觸發 callback,可傳單一數字或陣列
// 0.0(預設):只要露出 1px 就視為可見
// threshold: [0.5, 1.0] 也可以在多個比例各觸發一次
const callback = ( entries , observer ) => {
entries . forEach (( entry ) => {
if ( entry . isIntersecting ) {
const observer = new IntersectionObserver ( callback , options ) ;
observer . observe ( document . querySelector ( " #A " )) ;
callback 收到的 entries 是一組 IntersectionObserverEntry,可以透過 entry.isIntersecting 判斷是否可見、entry.intersectionRatio 取得露出比例。
從 SDF 、噪聲到後製,收集實作過程中反覆用到的 shader 片段。
smoothstep 是你最好的朋友
用噪聲打破一切完美的規律
後製決定畫面的「氛圍」
例如一個常用的柔邊圓形遮罩:
float softCircle ( vec2 uv , vec2 center , float radius , float edge ) {
float d = distance ( uv , center ) ;
return 1.0 - smoothstep ( radius - edge , radius + edge , d ) ;
平常遇到、覺得值得收藏的前端函式庫整理,依用途分類方便日後查找。
在 Web 瀏覽器上顯示地圖:
圖表與時間軸#
動畫與視覺特效#
< summary > Click here to see more from Earth 616 </ summary > < table >
從 schema 到端到端型別推導,減少前後端契約漂移 的做法。
當 API 的形狀由單一 schema 定義、並自動推導出前後端型別,很多執行期的錯誤就被提前到編譯期擋下。
performant npm
pnpm add pkg
pnpm install
pnpm update
pnpm remove
pnpm link
pnpm unlink
options#
-w
npm i <pkg> -w, 將pkg 安裝在 root 的 node_module
-r
npm i <pkg> -r, 將pkg 安裝在所有 packages 中
--filter
npm i <pkg> —filter package, 將pkg 安裝在指定的 package 中
Commands#
npm command pnpm equivalent npm install pnpm install npm i <pkg> pnpm add <pkg> npm run cmd pnpm cmd
補充
When an unknown command is used, pnpm will search for a script with the given name
workspace (monorepo)#
workflow#
npm i pnpm
pnpm init
新增 pnpm-workspace.yaml 檔案. 並增加下方資訊
# all packages in subdirs of packages/ and components/
新增 packages 資料夾,
底下再新增各模組(e.g. pageages/ui, pageages/web),
並在模組底下執行pnpm init 初始化 package.json
package name分別為 @test/ui, @test/web
package type設定為 module
在pageages/ui 底下新增 index.js並加入
export const add = ( a , b ) => a + b
在root cmd 下 pnpm i @test/ui —filter @test/web
接下來在pageages/web 底下新增 index,就能使用 @test/ui的東西了
import { add } from " @test/ui " ;
Features# 為什麼「極簡 」是一種持續的取捨,而不是一次性的減法。
簡單不是把東西拿掉到不能再拿,而是每個留下來的東西都有存在的理由。
* @typedef { Object } Toast
* @property { boolean } closed - Indicates whether user has close the toast.
* @property { Date } generatedOn - Indicates when the toast was generated.
* @property { string } message - toast content.
* @property { "warn" | "info" } type - Indicates type of toast.
* Also useful to show different icons.
* A function for showing toast
* @param { Toast } toast - {@ link toast } object
* containing all components of the toast.
export function showToast ( toast ) {}
若在純粹的 Web 應用下,大多數情況,我們不需要處理二進位資料,或是需要非常精準的資料格式,也不會進行單筆高流量的資料交換,所以使用 JSON 或 XML 已經足以。但若你的應用有串流、二進位資料的需求,Protocol Buffers 就是你可以考慮的選擇。
像是筆者在一些公司專案中,會運用 Message Queuing 進行各種訊息資料傳遞,以達成各種資料處理需求。但由於訊息資料內可能有大大小小等各種資料形式和資料型態需求,導致 JSON 包裝已經完全不敷使用,甚至有效能上的疑慮,這時就會採用 Prorocol Buffers 來打包資料。
當window 準備釋放它的資源時,該事件被觸發。此時文件仍然可見,且事件仍可被取消的。
window . addEventListener ( " beforeunload " , function ( event ) {
console . log ( window . location . href ) ; // capture the url
event . preventDefault () ; // just to pause and see the condole
Protocol Buffers are a language-neutral, platform-neutral, extensible way of serializing structured data for use in communications protocols, data storage, and more, originally designed at Google (see).
protobuf.js is a pure JavaScript implementation with TypeScript support for node.js and the browser. It’s easy to use, blazingly fast and works out of the box with .proto files!
補充
Protocol Buffers的設計目標是簡單和效能。特別地,它被設計地與XML相比更小且更快。
設計上更易於用來包裝二進位資料,應用在串流(Streaming)技術上,在資料包裝上也更為節省空間,在包裝或解析上也更有效率。
zh-TW
ISO 639-1 + ISO_3166-1 (alpha-2)
語言 + 國家
node.js v16.9.0 的實驗功能, 需搭配 Corepack 使用
可以確保您的團隊使用完全相同的套件管理器版本,而無需安裝除了 Node.js 以外的其他東西。
Corepack 有支援的 Package manager
Package manager Binary names Yarn yarn, yarnpkg pnpm pnpm, pnpx
使用方式#
package.json 建立 packageManager
執行 corepack enable
pnpm run script
< script setup lang = " tsx " >
const Foo = (props: { msg : string } ) => {
return < div > { props . msg} </ div >
在 ES6 之前,物件(Object)的屬性順序是沒有保證的。ES6 之後則有了明確規則,走訪屬性時會依照以下順序:
整數鍵(integer keys) :由小到大升序排列。
一般字串鍵 :依照「加入物件的先後順序」排列。
Symbol 鍵 :同樣依照插入順序,排在最後。
這裡有個容易踩到的細節:只有「標準的非負整數字串」才算整數鍵。像 "001"、"002" 這種有前導零 的字串,並不是標準寫法,所以會被當成一般字串鍵處理;而 "100" 才是真正的整數鍵。
[ Symbol ( " first " )] : " first " ,
[ Symbol ( " second " )] : " second " ,
console . log ( Reflect . ownKeys ( obj )) ;
// ["100", "002", "c", "b", "001", Symbol(first), Symbol(second)]
對照上面的規則來看這個結果:
"100" 是整數鍵,排最前面。
"002"、"c"、"b"、"001" 都是字串鍵,依插入順序排列。
兩個 Symbol 依插入順序排在最後。
取得相同父元素下,前一個子元素節點(Node)
document . getElementById ( " b1 " ) . previousSibling ; // #text
document . getElementById ( " b1 " ) . previousSibling . previousSibling ; // <img id="b0">
移除檔案
const shell = require ( ' shelljs ' )
shell . exec ( ' npx rimraf dist ' )
RFC 6750
Authorization: Bearer <token>
package.json
" test " : " echo \" Error: no test specified \" && exit 1 "
" @test/utils " : " workspace:^1.0.0 " ,
開閉原則OCP(開放/封閉原則)的描述是:
軟件實體(類,模塊,功能等)應打開以進行擴展,但應關閉以進行修改。
軟件實體(類,模塊,方法等等)必須對擴展開放,對修改關閉,即軟件實體必須在不修改的初步下擴展。
同一個節點並不會同時出現在兩個地方。所以當節點已經有父節點,它會先被移除,然後被插入在新的位置上。
parentNode . insertBefore ( newNode , referenceNode ) ;
const formatMoney = ( money ) => {
return money . replace ( new RegExp ( ` (?!^)(?=( \\ d{3})+ ${ money . includes ( ' . ' ) ? '\\ . ' : ' $ ' } ) ` , ' g ' ) , ' , ' )
formatMoney ( ' 123456789 ' ) // '123,456,789'
formatMoney ( ' 123456789.123 ' ) // '123,456,789.123'
formatMoney ( ' 123 ' ) // '123'
https://www.chromium.org/updates/same-site
Chrome 會將沒有對 Cookie SameSite 屬性的 Cookie 預設加入 SameSite=Lax 的屬性,這會致使不相同的網站,無法再透過 Get 以外的請求取得到 Cookie ,因此對外站進行 POST 請求;
如果要避免這問題,就必須將 SameSite 屬性設為 SameSite=None; secure,並且透過 Https 進行資料傳輸,就能避免 Cookie 在不同網站能被存取且又能安全傳遞資料。如果沒加上 secure ,一樣僅能透過 Https 運作。
fetch ( " http://192.168.1.100:3030 " , {
postRequest ( context : any , params : axiosParams ) {
url : ` ${ base }${ params . url } ` ,
https :
Origin
- Host
Origin
- Host
Origin
- Host
Origin
- Host
vue cli 顯示自訂訊息
new FriendlyErrorsWebpackPlugin ({
compilationSuccessInfo : {
messages : system . map ( sysName => ` ${ sysName } : \t` + chalk . cyan ( ` http://localhost: ${ port } / ${ sysName } ` ))
取得OpenSSL
Git已內建OpenSSL,預設路徑為C:\Program Files\Git\usr\bin\openssl.exe
建立Private Key與Certificate
新增一個 server.conf 檔案
subjectAltName = @alt_names
開啟命令列並執行以下指令以建立private key和certificate
Windows
$ openssl req -x509 -new -nodes -sha256 -utf8 -days 3650 -newkey rsa:2048 -keyout server.key -out cert.crt -config server.conf
Linux
$ openssl req -x509 -new -nodes -sha256 -utf8 -days 3650 -newkey rsa:2048 -keyout key.pem -out cert.pem -config server.conf
在 function 後面加上 * 就是一個 Generator(產生器)函式 。它最特別的地方是可以「暫停」與「恢復」執行:每呼叫一次 next(),就從上一個 yield 之後繼續跑,直到遇到下一個 yield 或 return。
function* doOperations () {
const operations = doOperations () ;
console . log ( " next : " , operations . next ()) ; // { done: false, value: 2 }
console . log ( " next : " , operations . next ()) ; // { done: false, value: 3 }
console . log ( " next : " , operations . next ()) ; // { done: true, value: 4 }
每次 next() 回傳一個物件:value 是這次 yield/return 的值,done 表示產生器是否已結束。
提前結束#
呼叫 return() 可以強制結束產生器。一旦結束,之後再呼叫 next() 也不會再執行任何程式碼或回傳新的值。
function* doOperations () {
const operations = doOperations () ;
console . log ( " next : " , operations . return ()) ; // { done: true, value: undefined }
console . log ( " next : " , operations . next ()) ; // { done: true, value: undefined }
“Once a generator is completed, either normally or early, it no longer processes any code or returns any values.”
— You Don’t Know JS: ES6 & Beyond , Kyle Simpson
用 yield* 委派給另一個產生器#
yield* 可以把另一個產生器(或可迭代物件)的值「攤平」接進來,方便組合出更長的序列。
function* generateCharSequence ( start , end ) {
for ( let i = start ; i <= end ; i ++ ) yield String . fromCharCode ( i ) ;
function* generateAPassword () {
yield * generateCharSequence ( 48 , 57 ) ;
for ( const code of generateAPassword ()) {
console . log ( " password : " , password ) ; // "0123456789"
Intl.NumberFormat 是瀏覽器內建的數字格式化工具,不用自己寫正則就能加上千分位、貨幣符號等。輸出格式會依「語系(locale)」而不同。
const number = 123456.789 ;
// 不指定語系,使用執行環境的預設語系(例如 en-US)
console . log ( new Intl . NumberFormat () . format ( number )) ;
console . log ( new Intl . NumberFormat ( " en-IN " ) . format ( number )) ;
new Intl . NumberFormat ( " zh-TW " , { style : " currency " , currency : " TWD " }) . format (
沒有指定語系時,結果取決於執行環境(瀏覽器或 Node 的 ICU 設定),所以正式產品建議明確帶入語系,避免不同裝置顯示不一致。
語法
void function saySomething ( msg ) {
saySomething ( ' Hi ' ) ; // Uncaught ReferenceError: saySomething is not defined
void vs undefined
undefined 並不是保留詞(reserved word)
樣板文字類型
type HeadingLevel = 1 | 2 | 3 | 4 | 5
type HeadingType = ` h ${ HeadingLevel } `
const heading1 : HeadingType = " h3 " // 🟢
const heading2 : HeadingType = " h6 " // Type '"h6"' is not assignable to type ...
type Power2 = 1 | 2 | 4 | 8 | 16 | 32 | 64
type FileUnit = " B " | " KB " | " MB " | " GB "
type FileSize1 = ` ${ number }${ FileUnit } `
type FileSize2 = ` ${ Power2 }${ FileUnit } `
const limitSize1_33MB : FileSize1 = " 33MB " // 🟢
const limitSize2_32MB : FileSize2 = " 32MB " // 🟢
const limitSize2_33MB : FileSize2 = " 33MB " // Type '"33MB"' is not assignable to type ...
node deploy/build.js --report --system=kch,ptch
可以解析node下,cmd 所帶入的參數
const argv = require ( ' minimist ' )( process . argv . slice ( 2 ))
console . log ( ' argv ' , argv ) // argv { _: [], report: true, system: 'kch,ptch' }
Taipei or Taibei or T’ai-pei
Zone Asia/Taipei 8:06:00 - LMT 1896 Jan 1
9:00 - JST 1945 Sep 21 1:00
nvm-Window , 下載nvm-setup.exe
基本操作:
nvm install <version> - 安裝指定版本 node
nvm ls - 顯示 nvm 已安裝清單
nvm use <version>- 在當前 shell(全域環境不影響) 使用指定版本node
取得最近距離的祖元素
< div id = " div-01 " > Here is div-01
< div id = " div-02 " > Here is div-02
< div id = " div-03 " > Here is div-03 </ div >
const el = document . getElementById ( ' div-03 ' ) ;
// the closest ancestor with the id of "div-02"
console . log ( el . closest ( ' #div-01 ' )) ; // <div id="div-01">
console . log ( Math . floor ( record . amount )) // amount 的型別會是 number
hidden 是一個布林屬性 :只要加在元素上,瀏覽器就不會顯示它,效果等同於 display: none——元素不佔版面、螢幕閱讀器也會略過。
< p hidden > 這段文字不會顯示在畫面上。 </ p >
title = " Strategic Homeland Intervention, Enforcement, and Logistics Division "
要再顯示出來,用 JavaScript 移除屬性即可:
element . removeAttribute ( " hidden " ) ;
注意:CSS 的 display 會蓋過 hidden。若元素被設定了 display: block,即使加上 hidden 也依然會顯示。
即使專案是純 JavaScript,也可以透過 JSDoc 的 @type 註解 讓 VS Code 推斷型別,得到自動補全與錯誤提示——不必改寫成 TypeScript。
以下用 Pinia store 為例,替 state 的每個欄位標註型別:
export const useTodos = defineStore ( " todos " , {
/** @type { { text: string, id: number, isFinished: boolean }[] } */
/** @type { 'all' | 'finished' | 'unfinished' } */
這樣一來,存取 todos 時 VS Code 就知道陣列元素有 text、id、isFinished;把 filter 指派成清單以外的字串時,也會即時標紅提示。
https://developer.chrome.com/docs/capabilities/web-apis/window-management?hl=zh-tw#demo
async function openWindowToMultipleScreen ( url ) {
' popup=1, ' + ' width= ' + screen . width + ' , height= ' + screen . height + ' , left= ' + screen . width + ' , top= ' + 0
if ( ! ( ' getScreenDetails ' in window )) return windowOpen ()
const screenDetail = await window . getScreenDetails ()
const currentScreen = screenDetail . currentScreen
const popup = windowOpen ()
if ( currentScreen . left === 0 ) {
const multiScreen = currentScreen . screens . find ( screen => screen . left !== 0 )
popup . moveTo ( multiScreen . width , 0 )
解決 key 無法型別判斷的問題 參考
const keys1 = Object . keys ( user ) as Array <keyof typeof user >
const keys2 = Object . keys ( user )
console . log ( user [ key as keyof typeof user ])
取得viewport x,y 軸所在位置的dom
npm 7.以上的版本才能使用
function calculationBMI ( bmi
} else if ( bmi >= 18.5 && bmi < 24 ) {
} else if ( bmi >= 24 && bmi < 27 ) {
} else if ( bmi >= 27 && bmi < 30 ) {
} else if ( bmi >= 30 && bmi < 35 ) {
calculationBMI ( 20 ) ; // '正常'
改為 FP
const lessThan = x => bmi => bmi < x ;
const calculationBMI = BMI =>
. on ( lessThan ( 0 ) , () => ' 資料錯誤 ' )
. on ( lessThan ( 18.5 ) , () => ' 體重過輕 ' )
. on ( lessThan ( 24 ) , () => ' 正常 ' )
. on ( lessThan ( 27 ) , () => ' 過重 ' )
. on ( lessThan ( 30 ) , () => ' 輕度肥胖 ' )
. on ( lessThan ( 35 ) , () => ' 中度肥胖 ' )
calculationBMI ( 20 ) ; // '正常'
在HTML結構的文字 抓取html content
htmlString.replace(/<[^>]*>/g, '').replace(/ /g, ' ')
當專案越來越大、模組越來越多,「程式碼要放在幾個 Git 倉庫」就成了一個需要取捨的問題。這篇整理三種常見策略,以及對應的建置工具。
三種策略#
Monolith:一個專案、一個 Git 倉庫。
優點:實作簡單,一庫到底。
缺點:複雜專案的程式碼複用性低,不利於團隊協作。
Multi-repo:拆成多個模組,一個模組一個 Git 倉庫。
優點:模組劃分清晰,每個模組都是獨立的 repo,利於團隊協作。
缺點:程式碼管理難度增加。例如某個模組出現 bug,相關模組都要各自編譯、上線,還要手動控制版本,非常繁瑣;issue 也散在各個 repo,管理麻煩。
Mono-repo:拆成多個模組,但所有模組放在同一個 Git 倉庫。
優點:程式碼結構清晰、利於協作,同時集中在一個倉庫也降低了專案管理、程式碼管理與除錯的難度。
缺點:專案本身會變得龐大,模組一多也會遇到一些新問題:
幽靈依賴(phantom dependency) :APP1 依賴了 [email protected] ,APP2 也想用 [email protected] 。此時在 APP2 裡直接 import X from 'A' 竟然跑得起來,看似沒問題,但其實 APP2 的 dependencies 裡根本沒宣告 A——它是「借用」了 APP1 裝下來的依賴。
peerDependencies 衝突 :APP1 依賴 [email protected] 、APP2 依賴 [email protected] ,若兩個版本完全不相容,專案就會直接壞掉。
因此 Mono-repo 需要更好的建置工具來管理,例如 Lerna、Nx。
建立一個新的 workspace:
npx create-nx-workspace@latest
Nx 與 Lerna 的差異 :
Lerna 專注於串連同一專案中的多個 package,並管理 npm 發佈,大致就這些。主要處理的是 package(而非完整應用),常搭配 gulp 或 rollup 輸出。
Nx 更專注於管理多個 package 的整體開發流程,適合完整的應用,常搭配 webpack。
上手範例#
需要 npm 7.0 以上版本(要用到 npm workspaces)。
git clone https://github.com/lerna/getting-started-example.git
cd getting-started-example
其他大型工具#
同類型的還有各大公司自家的建置系統:Bazel(Google)、Buck(Facebook)、Pants(Twitter)。
參考資料:
Git refresh the list of remote branches
git remote update origin --prune