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的喔,不然會爆掉

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

react-router動畫切換

 我挺訝異vue-router原生支援的動畫換頁效果,在react-router6竟然沒有支援,官網老實說也寫得不是很清楚,畢竟這版本目前來說還很新,也沒有別人寫好的套件可以直接使用,只好上網爬文怎麼實現

基本上就是參考了這個網站(https://dev.to/fazliddin04/react-router-v6-animated-transitions-diy-3e6l)把最終效果實踐出來

正文開始

// src/style/route-animate.module.scss
.fadeIn {
animation: 0.4s fadeIn forwards;
}

.fadeOut {
animation: 0.2s fadeOut forwards;
}

@keyframes fadeIn {
from {
opacity: 0.6;
transform: translate(-3rem, 0px);
}
to {
opacity: 1;
transform: translate(0px, 0px);
}
}

@keyframes fadeOut {
from {
opacity: 1;
transform: translate(0px, 0px);
}
to {
opacity: 0;
transform: translate(-3rem, 0px);
}
}

// src/App.jsx
import route_styles from "./style/route-animate.module.scss"
......
const RoutesWithAnimate = () => {
const animateEnum = {
fadeIn: "fadeIn",
fadeOut: "fadeOut",
}
const location = useLocation()
const [displayLocation, setDisplayLocation] = useState(location)
const [transitionStage, setTransistionStage] = useState(animateEnum.fadeIn)
useEffect(() => {
    // 防止有變化query的頁面但route沒有變化
if (location.pathname !== displayLocation.pathname) {
setTransistionStage(animateEnum.fadeOut)
} else if (location !== displayLocation) {
setDisplayLocation(location)
}
}, [location, displayLocation])


return (
<div
className={classNames("min-h-full", route_styles[transitionStage])}
onAnimationEnd={() => {
if (transitionStage === animateEnum.fadeOut) {
setTransistionStage(animateEnum.fadeIn)
setDisplayLocation(location)
}
}}
>
<Routes location={displayLocation}>
<Route path={"/home"} element={<Home />} />
        <Route path={"/about"} element={<About />} />
<Route path="*" element={<Navigate to="/home" replace />} />
</Routes>
</div>
)
}

藉由把location暫存放入Routes裡面,利用時間差做到動畫效果,但終究沒辦法同時存在兩個location,所以沒辦法做到同時兩個route畫面出現在畫面上的效果,類似輪動這樣,這可能需要靠htmlToCanvas達成類似的效果或是等其他人出套件了,總之專案做到這裡就被打掉了....沒錯你沒看錯,被打掉了,因為有人覺得淡入淡出很醜,好吧就這樣吧,做個紀錄,紀錄自己曾經做過。

踩過的小坑
if (location !== displayLocation)

參考的網站判斷原來長這樣,但是這會造成一個問題,如果只是query切換沒有切換pathname,然後畫面中假設彈窗顯示,然後彈窗會去取query的值,這時因為Routes的location還沒有轉換,所以取到的query會是舊的。

例如
router變化:/home?id=1 => /home?id=2
希望達成效果:/home?id=1 => 開始淡出 => 彈窗抓取qeury: id = 2 => 開始淡入
實際執行狀況:/home?id=1 => 開始淡出 => 彈窗抓取qeury: id = 1 => 淡出跑完(setDisplayLocation) => /home?id=2 => 開始淡入 => 淡入跑完

先不要管為什麼系統這樣設計,總之~就是這樣拉~