Thanh at Pai

Trang Công Thành

Senior Bug Breeder

Bug - env variables và zod

2024-02-25-#code-suy#code-mistake

ladybug - Photo by Shannon Potter on Unsplash
ladybug - Photo by Shannon Potter on Unsplash

Trong những chuỗi ngày è cổ ra code dạo, chắc ai ít nhiều cũng có những sai lầm của tuổi "trẻ". Với mình, gần đây nhất là lọ mọ đi đọc code trên X/Twitter rồi quất luôn vào dự án đang làm. Và tất nhiên, toang!

The story 🔫

Một ngày đẹp trời mình dạo chơi trên X/Twitter và thấy mọi người chia sẻ quả snippet như này:

import * as z from 'zod'

const envScheme = z.object({
  NODE_ENV: z.enum(['development', 'production']),
  API_HOST: z.string(),
  // more keys
})

const env = envScheme.parse(process.env) // or scheme.parse(import.meta.env)

declare global {
  namespace NodeJS {
    interface ProcessEnv extends z.infer<typeof envScheme> {}
  }
}

console.log(process.env.NODE_ENV) // Yes! Typesafe
console.log(env.API_HOST) // Oh hell yeah!

Trông rất gì và này nọ. Typesafe và app crash nếu thiếu env bắt buộc. Thế là nếu deploy bản mới mà không chạy vì thiếu env thì đỡ phải chửi nhau với DevOps. Quá là ngon! Tạo PR thôi còn đợi gì nữa 🫣 Rồi code cũng lên production, chạy phà phà như dự kiến.

The bug 🐞

Nhưng trong một ngày đẹp trời, DevOps của công ty hì hục debug một lỗi khác thì phát hiện ra, tất cả các env của máy build được nhét hết vào trong output bundle. Từ Thế là tá hỏa đi tìm nguyên nhân tại sao. Cũng may là PR trước cũng không cách xa lắm nên chắc cũng tìm ra nhanh 🤣

Và nguyên nhân rất dở hơi cám lợn đó là do dòng envScheme.parse(process.env) này và do build tool của dự án đã tự resolve quả process.env object rồi bundle vào output. Máy build cũng không có thiết lập env gì dị giáo. Nhưng env khác của BE chung monorepo cũng bị bundle vào FE. Đáng sợ!

Mình có làm quả public repo cho anh chị em chưa mường tượng được lý do: trangcongthanh/parsing-env-with-zod.

Ace vào thư mục dist/apps, rồi vào fe1fe2 sẽ thấy gần cuối dòng cuối cùng (hoặc tìm từ khóa var th={) sẽ thấy tất cả các giá trị có trong import.meta.env object sẽ được đưa vào bundle js. Nhưng với fe3 thì nó không bị như vậy.

The fix 🔧

Ờ thì,

envScheme.parse({
  NODE_ENV: process.env.NODE_ENV,
  API_HOST: process.env.API_HOST,
})

Như này thì đống build tool sẽ replace giá trị cho những key được sử dụng thôi. Không có quất hết cả đống env vào nữa. Không những các tính năng trên vẫn y nguyên, an toàn hơn, mà bundle size còn được giảm tương đối 🤣

The "bài học" 📖

  • Luôn sử dụng thẳng env variable thay vì sử dụng cả object.
  • Sử dụng bundler "xịn".
  • Đừng copy code trên mạng để sử dụng ngay cho dự án hiện tại 😩

Happy coding 👨‍💻