2023年8月31日 星期四

next.js初體驗小記

 在某些機緣巧合下開啟了第一次接觸Next.js,照官方指令創建專案第一感覺是:好慢!

說好的比vite快兩百倍呢==,我看他底層還是用webpack在打包,可能之前在講的打包工具還沒上線吧。

我用過Nuxt.js,一開始想說兩者屬於同樣性質的框架,使用上應該不會差太多吧,結果還真的差不多。因為網路上已經有很多文章說明Next.js的使用這邊就不再贅述。

這裡只想小小吐槽兩件事

第一點,只要有使用到事件的,基本上無法再server site被render,所以必須在專案最上方寫上“use client“,讓框架知道這個檔案只適用於client端渲染,但.....這是for檔案,如果我畫面上只有一個tag有事件觸發,我就必須把這個tag單獨用component包起來,並讓他在client端渲染,何其不方便,你看看人家Astro.js,當然這又是另外一個故事了。照這邏輯看起來,幾乎沒有頁面可以use server啊....這樣的設計到底為了什麼==


第二點,在Next.js框架是不能ajsx出去要資料的,會發生CORS,處理方式有兩個:

第一就是使用rewrites進行網址改寫達到proxy的效果

第二就是打自己的路由,例如我在/app/api/user/routes.ts寫入

export async function GET() {
return NextResponse.json(await useApi("contact").getList())
}
export async function POST(req: NextRequest) {
const { contact } = await req.json()
return NextResponse.json(await useApi("contact").create(contact))
}
export async function PATCH(req: NextRequest) {
const { id, info } = await req.json()
return NextResponse.json(await useApi("contact").update({ id, ...info }))
}
export async function DELETE(req: NextRequest) {
return NextResponse.json(await useApi("contact").delete({ id: req.nextUrl.searchParams.get("id")! }))
}

但這有個問題,如果api接收的params寫在網址上,例如/api/user/1,1在server這邊是讀不到的,真不知道是他NextRequest沒寫好還是怎樣,我知道有人會說要開個[變數]資料夾去接收變數,xxxx這我也試過了,router取params的變數這功能只存在client端。或是知道怎麼做的可以留言給我,總之我試了很久是找不到方法。

因為上述問題,所以client打的網址必須放在參數裡讓server讀到,變成/api/user?id=1,然後再由server放進params裡去打api,這也導致client跟server端打的網址可能會不一樣,但打自己,server在打出去,還有兩層邏輯....選這方法的人大概率不是蠢就是壞,這樣邏輯會四散各地,還要為client跟server寫兩次打api方法。

而且方法二執行起來,意思跟proxy差不多,所以理所當然我選擇了方法一。

兩天用下來我發現,或可真的可以好好學習一下Astro.js,MPA VS SPA之爭,可惜台灣學習資源很少。

Astro.js是最近才聽到的框架,好像也行之有年,也是ssr框架,然後也支援其他框架寫出來的component,所以可以進行整合。與以往SPA只有單一入口不同,會把不同pages最後分別導出成各自的index.html,撰寫邏輯跟api也都不難,有機會真的想要更深入研究一下

以上圖槽完Next.js心情舒服多了,來捆


html快取

快取大概是前端諸多夢魘之一吧,通常我們能做的有限,在現在打包軟體的加持下,js,css等檔案已經幾乎不可能有快取,因為後面會有hash,那唯一還是有可能被快取的檔案就是入口的index.html

今天又被客戶反應有快取,原本理智線已經快斷掉的我,突然被提醒meta能否設定一下讓html不要被快取,於是參考一下別人的網站列出下面四行

<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache" content="no-cache" />

就加一下,靜待日後觀察吧~

2023年7月24日 星期一

history.back的小坑

原來原生在window.history.back(),回到上一頁後,在“很多環境”是預設不會重新載入的,加速瀏覽,所以在上一頁就必須加上以下內容,幫助畫面重新載入

window.onpageshow = function(event) { if (event.persisted) { window.location.reload(); } };

因為小編spa寫久了,從來沒遇過這個問題,今天踩到做一下紀錄


2023年7月14日 星期五

webpack轉換vite使用心得

 最近把公司專案從webpack4搬到vite上,所以研究了一下,只能說我非常喜歡vite的風格,不像webpack有一大堆看不懂的設定跟一堆plugin,設定起來非常快速跟方便。

不過兩之間還是有一定的差距,不可能只搬打包工具而程式碼不動,這裡就列出幾個會有影響的程式碼

require
vite是不支援require的,就算裝了套件vite-plutin-require,require進來的也只會有路徑而已,但svg可以透過vite-plugin-svgr把require進來的東西轉成component,但如果要做到dynamic import也只能透過異步加載(loadable)的方式來處理

process.env
在webpack可以用這種寫法將環境變數注入到程式碼中,vite當然也可以,但寫法不同,是用import.meta.env,如果不想改寫法,可以在vite.config.js裡面設定define把process.env.API_HOST: 127.0.0.1之類的,讓vite專案也看得懂process.env.XXXX
ps. 如果使用import.meta.env記得要將環境變數以VITE_開頭才讀取得到喔

static public資料夾
webpack好像不特別設定是不會打包靜態資料夾的,可以進行另外處理,但在vite有個publicDir,預設為public,在打包時就會把public裡面的東西打包進目標資料夾的根目錄,例如/public/image => /dist/image
如果不想打包可以設為false

image
在專案中如果是css設定background-image: url(...),vite會自動判斷最終這張圖片是否是存在靜態資料夾,如果不是則是會被打包成圖片檔+hash的檔名,等於把路徑寫在css裡,路徑跟圖片是會被強迫編譯的,這點跟webpack就不一樣,少了一點彈性,沒辦法拿來做文章。
如果在html裡,<img src="/image/logo.png"/>則不會被編譯,當然如果你網址是指到你的根目錄這完全不會有任何問題,但如果你的網址有前綴,例如https://test.com/web/xxxxx,這樣/image/logo.png就會找不到圖片,因為圖片在/web/image/logo.png,所以解決方式是使用import logo from "/public/image/logo.png"
<img src={logo} />
這樣打包完src就會被編譯成/web/image/logo.png

如果像上述情況有前綴,在vite.config.js裡面有個base: "/web",這樣所有的引入都會加上前綴/web

我有試過官方提供new URL("image path").href,在固定路徑的情況下可以,但如果是dynamic path就會變成undefined,試了好幾次也沒辦法,只好放棄

曾經我有試過require("image path")的方式把引入變成路徑,一開始有成功,但重開專案之後又失敗了,也是很神奇的一次經驗。

大致上遇到要調整的就這些,只能說vite真的是很神,設定簡單,也不用裝一堆外掛,編譯速度又快

之後有空也可以pwa的部分

100%與100vh的差異

除了網路上大家常說有沒有內容,會不會被撐開的差異外,今天在實際操作上還真的遇到一點差異,而平常不會注意到的

這張就是我把高度設為100vh的狀況,container是用flex完全置中,但上面卻會有一塊空白,導致確定按鈕會有點超出介面

這張就是我高度設定100%,在完全置中情況下,上下空白幾乎是一樣的

為什麼會有這樣些微的差距是因為vh是表示螢幕的高度,而非扣除掉原生軟體介面,剩餘的高度,所以第一張圖來看,如果把網址列及控制列算進去,他確實是完全置中沒錯,但這不是我們要的效果,所以乖乖設成100%吧



2023年6月12日 星期一

AdonisJs上傳base64圖片

 小編在開發專案時遇到ReactNative套件相容問題無法取得File,因此無法正常post FormData的狀況,無奈之下只好跟前端協議改送base64編碼過來後端進行decode,在此做一下紀錄

export class AvatarUploadImageValidator {
@validate(schema.string(), {
required: File7000.IMAGE_ERROR.toString(),
string: File7000.IMAGE_ERROR.toString(),
})
public image: string
}

因為Adonisjs沒有特別驗證base64編碼,就簡單收一段文字

export default class FileService {
async uploadBase64({ base64, path, trx }) {
const [_full, contentType, image] = base64.match(/data:(image\/\w+);base64,(.*)/)
const ext = contentType.split('/')[1]
if (!ext || !contentType || !image) {
throw new ApiException(CommonCodes.FORMAT_ERROR, 'avatar image format error')
}
const decodedImage = Buffer.from(image, 'base64')

const filePath = `public/${path}/${DateTime.now().toFormat('yyyyMMddHHmmss')}_${generateCode(3)}.${ext}`
await Drive.put(filePath, decodedImage, {
contentType,
})
const fileUrl = await Drive.getUrl(filePath)
return File.create(
{
url: fileUrl,
},
{ client: trx }
)
}
}


先跟前端溝通好傳完整的編碼進來(不要只有圖片的部分),然後使用正則把需要的資訊分別提取出來,之後使用原生Buffer.from進行decode base64,剩下就是設定路徑及檔案名稱,最後再使用Adonisjs所提供的Drive進行上傳到s3,並取得網址,再來就是資料庫操作,如此打完收工。

2023年5月22日 星期一

AdonisJs時間戳怎麼老是早八小時

在AdonisJs裏面時間物件預設是使用luxon的DateTime,但使用上你會發現時間會跟現實差時區的八小時(小編人在台灣GM+8),解決辦法就是在環境變數上加上TZ變數(注意不是TimeZone),

// .env
TZ=Asia/Taipei

只需要這樣,程式碼跟設定都無需做任何改動,你就會發現created_at跟updated_at都回到正常時間了。
但是!沒錯別這麼快走,上面說到只有created_at跟updated_at,可能底層有針對這兩個變數做特別處理,所以會特別順利。
如果你有自行設置其他datetime欄位或timestamp欄位,例如小編昨天新增了一個sent_at欄位,結果就不會如預期,還是差八小時,那這問題到底在哪呢?
經過思考後我想起,在db的設置上似乎也有timezone的設置,雖然不知道有沒有關係但就姑且一試吧。

mysql: {
client: "mysql",
connection: {
host: Env.get("MYSQL_HOST"),
port: Env.get("MYSQL_PORT"),
user: Env.get("MYSQL_USER"),
password: Env.get("MYSQL_PASSWORD", ""),
database: Env.get("MYSQL_DB_NAME"),
        // 加入 timezone,取自.env TZ
timezone: Env.get("TZ"),
},
// ...略
},

再來測試一下,竟然過了!太棒了下班!這幾天遇到的雷都還不算很大啊~