2023年11月20日 星期一

vue2 event hook destroyed

在官方建議寫法一般是這樣

mounted() {
this.$root.$on("getSelf", this.getSelf)
},
destroyed() {
this.$root.$off("getSelf")
},

但你也可以這樣寫

mounted() {
this.$root.$on("getSelf", this.getSelf)
this.$on("hook:destroyed", () => this.$root.$off("getSelf"))
},

這樣寫有什麼好處呢,主要防止邏輯四散,每個人寫作習慣不同,mounted跟destroyed不一定就在旁邊,有人mounted會寫一大串,最造成閱讀上沒這麼直覺。你可以在宣告偵聽時就確定他會在component destroyed時被取消偵聽減少資源浪費。這寫法就跟golang的defer類似。

其實早在很久前就有這樣寫了,只是沒記下來真的會忘記,到最近又碰到vue想要寫才把這招翻出來,所以就做個紀錄

2023年10月26日 星期四

docker,或者說docker-compoes踩的小坑,nginx host name指向問題

先附上我寫的docker-compose

情境說明
我在構思前後端架構時,後端用nodejs,然後前端用nginx,這沒什麼問題,然後因為要走ssl,我懶得把ssl key也copy到後端專案,打算直接在nginx用proxy的方式做掉,OK事情就是這樣發生了,先附上docker-compose跟nginx設定

# docker-compose.yml
version: "3.6"

services:
node-api:
build:
context: .
dockerfile: Dockerfile
container_name: stemi_node_api
volumes:
- ./tmp:/app/tmp
working_dir: /app
command: sh -c "yarn dev"
ports:
- 8000:3333
depends_on:
- python-api
environment:
- PYTHON_API_HOST=python-api

nginx:
build:
context: web
dockerfile: Dockerfile
ports:
- "443:443"
- "3333:3333"
container_name: nginx
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./nginx:/etc/nginx/conf.d
- ../ssl/:/ssl
depends_on:
- node-api

# nginx/default.conf
# ...前端設定省略,因為這不是今天踩坑的主題
server {
listen 3333 ssl;
server_name localhost;
ssl_certificate /ssl/certificate.crt;
ssl_certificate_key /ssl/private.key;

# 根目錄
location / {
proxy_pass http://node-api:8000;
}
}

這一切乍看之下都很完美,但實際在打https://127.0.0.1:3333卻會報502,意思就是打不到東西,我一開始是想是不是node-api讀不到的關係,查了很多關於links, depends_on, networks的文件,最後進到nginx container裡面實際下指令nslookup node-api也確實有,ping也ping得到

/app # nslookup node-api
Server:         127.0.0.11
Address:        127.0.0.11:53
Non-authoritative answer:
Non-authoritative answer:
Name:   node-api
Address: 172.23.0.2

/app # ping node-api
PING node-api (172.23.0.2): 56 data bytes
64 bytes from 172.23.0.2: seq=0 ttl=64 time=0.465 ms
64 bytes from 172.23.0.2: seq=1 ttl=64 time=0.387 ms
^C

後來我發現,我啟動的container也只有兩個,但ip卻有三個
172.23.0.1
172.23.0.2 <= node-api
172.23.0.3 <= nginx
ok問題來了,照上面指令2應該就是node-api,3應該是nginx,那1是什麼呢?我繼續下指令curl -G http://172.23.0.1:8000

/app # curl -G http://172.23.0.1:8000 {"code":0,"data":"ok"}/app #

矮油這下問題嚴重了,我一度以為docker出問題,把host name指向錯誤的主機,想上網找但關鍵字也不知道怎麼下

後來在苦無對策之際,最後試著下一個指令

/app # curl -G http://node-api:3333
{"code":0,"data":"ok"}/app #

一切都真相大白了,我一直以為container是在各自的容器(我不知道這專有名詞是啥,只是講container會搞混,在我想像中他形式比較像沙箱跟區網的概念,將箱子也不好聽,所以就叫中文的容器吧),藉由expose把port開出來讓外界使用,事實上如果用docker-compose那大家就會在同一個容器內,所以網內互打是打原本的port不是export的port,所以1應該就是最後所有container的集合....吧,所以最終只要這樣修改就好。

# nginx/default.conf
  location / {
proxy_pass http://node-api:3333; # 把port改掉
}

因此最終node-api的ports設定也可以拿掉。


我也不是專業的devpos,就是專案需要所以簡單地摸了一下,踩了坑就記錄一下,歡迎不大家吝嗇指教,只是希望可以噴得小力一點!

2023年9月25日 星期一

url retry

 需求是某些地區只能打domain,有些地區只能打ip,所以pm要求我寫一個retry的功能,當一個不通在打另外一個

export const orderPostRetryHandler = async (callApi) => {
return retryHandler(callApi, [
{ baseUrl: "url1", maxTimes: 2 },
{ baseUrl: "url2", maxTimes: 2 },
])
}

export const retryHandler = (callApi, retrySettings) => {
let tryTimes = 1
let index = 0
const tryFunc = async () => {
const { baseUrl, maxTimes } = retrySettings[index]
try {
const res = await callApi(baseUrl)
if (res.status == -1) {
throw res
}
return res
} catch (e) {
tryTimes++
if (tryTimes > maxTimes) {
if (++index >= retrySettings.length) {
throw e
}
tryTimes = 1
}
return tryFunc()
}
}
return tryFunc()
}

但這有個問題,就是等打了兩遍都打不通時間都過去十秒了,真的有人願意等這麼久嗎?所以我突發奇想,為什麼不兩個網址都打,哪個通就走哪個,所以我又改成以下

export const urlChecker = (urls) => {
let passUrl = ""
return async () => {
if (passUrl) return passUrl
const res = await Promise.any(urls.map((url) => fetch(url)))
passUrl = res.url
return passUrl
}
}
const getActivatedUrl = await urlChecker("url1", "url2")
fetch(`${await getActivatedUrl()}/api/`)

這樣體驗好多了

1/19調整一下,如果使用any回來的是無效網址那就會壞掉
所以須改用Promise.all(urls.map(url => axios.get(url, {timeout: 3000})).then(urls => urls.filter(x =>x)[0])



2023年9月20日 星期三

fetch timeout

 前言:上篇提到我加了@vite/plugin-lagacy讓畫面成功渲染了,但打某支api卻會報錯promise rejection.....

我一開始想說是不是沒有支援Promise,我一直嘗試在polyfills塞入各種套件,但始終沒有解決。一直到我後來思考,不對啊,首頁就打了數隻api都沒事,為什麼到該頁打該api才會報!應該是該api有問題,但看來看去底層是一樣的,為什麼只有這隻會報.....

最終找不到辦法,因為我手上也頂多只有6s手機,無法進行除錯,只好到這個網站:https://vikyd.github.io/download-chromium-history-version/#/,下載舊版的chromium看能否模擬事件發生。好在在46版時發生了,然後我在報錯訊息中多看到幾個字“AbortController”,原來是我為了讓fetch有timeout,所以使用了這個物件,但這個物件載舊版瀏覽器上沒有,所以報錯,並非是沒有promise polyfill,所以我把AbortController加上一些判斷如下

if (typeof AbortController == "function") {
const controller = new AbortController()
const timeout = 5000
setTimeout(() => {
controller.abort()
}, timeout)
obj = { ...obj, signal: controller.signal }
}

fetch(url, obj)
.then((res) => {
......
return res.json()
})

變成有就5秒timeout沒有就預設30秒,呵呵,事情就解決了!!撒花~~



vite支援老舊瀏覽器

 前言:公司客服反映有八年前android(chrome)跟十年前5s(safari),打開網頁看不到畫面,換台3000塊以上的手機很難嗎==這要求跟要我支援ie差不多吧,好在客戶群沒很大,老闆就沒逼很急,所以花了點時間研究,做一下紀錄。

簡單說就是vite有出一個plugin:@vite/plugin-lagacy,看名字應該看得出來是為了骨灰級瀏覽器而有的,新的瀏覽器都支援ESM,然而不支援ESM的瀏覽器只好使用pollyfill去讓他被支援,詳細配置如下

// vite.config.js
plugins: [
......
legacy({
targets: ["defaults", "ie 11"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
}),
],

基本上配置這樣就可以了,我都支援到ie11了總可以放過我了吧。確實在同事口中傳回來捷報說有畫面了,但是!人生最可怕的就是這個但是,但是在打某支api會出現,promise rejection.....

只好下一篇待續

windows iis支援webp

 前言:在公司前輩的要求提醒之下,把png檔都轉為webp檔,說這樣除了檔案較小,也可以增加載入的速度(據說是協議有特別優化過)。在開發期間都相當順利,沒想到上正式機卻發生圖出不來,原因是因為測試機是linux,正式機是window(別問我為什麼,每間公司都有本難念的經),然而在iis上預設是不支援webp的,之後的是可想而知,所以我做一下紀錄。

點開MIME類型


點擊右上角增加

輸入相關資訊


直接送出即可。

或是直接寫進web.config
<configuration> 
    <system.webServer> 
        <staticContent> 
            <mimeMap fileExtension=".webp" mimeType="image/webp" /> 
        </staticContent> 
    </system.webServer> 
</configuration>

小記:後續主管還提到精靈圖,就是把所有圖片壓在同一張圖片,用css去切割,減少request次數,只是真的有需要做到這樣嗎==,好在webp效果不明顯,後續就沒有繼續做下去了

參考網站:https://www.itnota.com/serving-webp-image-iis/

2023年9月13日 星期三

react-router一般加載與懶加載

 在原先規劃系統設計是去讀src/pages/裡面有index.jsx?的頁面,搞得跟next.js類似

事實上這樣也不難,在vite.config.js這樣寫就好

import { defineConfig } from "vite"
import { GlobSync } from "glob"

export default defineConfig(({ mode }) => {
return {
......
define: {
__PAGES_ROUTES__: GlobSync("src/pages/**/index.{js,jsx}").found.map((x) => x.replace(/src\/pages\/(.*)\/index.jsx?/, "$1")),
},
}
})


 import loadable from "@loadable/component"
使用loadable進行懶加載
 ......
        __PAGES_ROUTES__.map((route, i) => {
const Component = loadable(() => import(`./pages/${route}`), {
     fallback: <LayoutPage center={() => ""} />,
})
return <Route path={route} key={i} element={<Component />} />
})

但今天收到一個難題需求,老闆希望把懶加載拔掉,減短切換頁的等待時間,因為不可能把Component在後端讀好傳到前端來,所以上面那方法就作廢,但總不會要我把上百個Component import進來吧,後來想到vite有附glob給我,稍微研究一下改成以下

const modules = import.meta.glob("./pages/**/index.{js,jsx}", {
import: "default",
eager: true,
})
......
    Object.keys(modules).map((route, i) => {
const path = route.replace(/\.\/pages\/(.*)\/index.jsx?/, "$1")
const Component = modules[route]
return <Route path={path} key={i} element={<Component />} />
})}

這樣改唯一的問題是,yarn dev第一次進入頁面等待時間會比較久一點,之後改動要等編譯時間也會久一點。當然在build的客戶端是沒有差的,是開發上的爽度差別而已
補充:用判斷讓import.meta.glob不要執行也沒用,他是屬於編譯層級的,所以一定會跑,要快只能註解掉
然後不能有在./pages/...../index.js是沒有export default的喔,不然會爆掉

總之就是這樣拉,打完收工