Photo by Malin K. on Unsplash

Vite.JS

React Projemi CRA (Create React App)’den Vite.JS Dönüştürdüm.

Bu konuda daha önce üzerinde durduğum bir konuydu. Bu konuda bir kaç blog yazısı yazmıştım. Fakat bu sefer biraz pratik çalışmalarımın onurdayibasi.dev Vite altyapına geçirme sürecini anlatacağım.

Frontend Development With JS
8 min readJan 22, 2024

--

Bu yazıda onurdayibasi.dev sitesini nasıl Vite.js dönüştürdüğümden bahsedeceğim. Öncelikle basitçe hangi kütüphaneleri ve araçları kullanıyordu, bunları nasıl değiştirdiğimi kabaca anlatayım.

Zaman içerisinde ilk önce

  • Redux ve Class Component → Hooks ve ReactQuery ve Zustand
  • ReactQuery ve ReactRouter versiyonlarını güncelledim
  • Node versiyonu ve CRA → ViteJS geçirdim.

Eksta: Yukarıda belirtmesemde UI Kütüphanesi olarak AntD, Test için Vitest , ve E2E test için Playwright kod altyapısını geçirdim.

Tabi tüm bu işlemlerde olduğu gibi 200'ün üzerinde örnek barındıran ve birçok farklı kütüphane kullanılan bu ortamda bu işlemler o kadar kolay olmuyor ve birçok sorun çıkabiliyor.

Aşağıda konun detaylarına geçmeden önce bu işlem ile ilgili daha önceden yazdığım 2 blog yazısından bahsetmek istiyorum.

Her ne kadar Server Side Framework olarak React ekosisteminde Next.js ve Remix baskınlığı olsada SPA dediğimiz Client Side Rendering kısmında Vite tüm UI kütüphaneleri için bir defacto haline geldi.

Node.JS ve React Versiyonlarını Yükseltme

Site üzerinde birçok örnek uygulama olduğu için kullanılan kütüphanelerin versiyonları birbirini desteklemiyor olabilir. Örneğin *

  • ReactQuery5 → React16–18
  • Custom Hook -> React Experimental ()

kullanıyor olabilir. Aynı şekilde bazı araçlar Node14, 16 ile düzgün çalışırken bazıları Node18 — Node20 çalışabilir

Tüm bu durumlarda npm install yaptığımızda şu şekilde — force veya— legacy-peer-deps ile -lock.json oluşturmamızı ister. Her ne kadar bu durumu localinizde yapsanız oluşturanız dahi deploy ettiğiniz platformlarda Netlify ve Vercel bu sorun devam edecek.

Bunun için projenin içerisine .npmrc oluşturup bunun içerisinde ilgili tanımı yapınca artık bu birden fazla kütüphanenin versiyonu Netlify gibi ortamlarada rahatlıkla kuruluyor. Konu detayına GitHub NPM discussion erişebilirsiniz.

legacy-peer-deps=true

Vite Boş Bir Proje Oluşturalım

Vite kısmına geçmeden önce Vite ile ile boş bir proje oluşturdum.

npm create vite@latest 

Buradan proje ismini, TypeScript/JavaScript vb seçenekleri kullanarak boş bir proje oluşturdum. Bu projeyi çalıştırdığınızda basit bir Vite projesinin çalıştığını görebilirsiniz.

Kendi CRA Projemin , Vite ile Oluşturduğum Projeye Taşıdım.

Bundan sonraki adım olarak CRA projedeki tüm src klasörünü alıp, Vite projesinin içerisine aktarıyorum. Bu kısımda tek dikkat edeceğimiz kısım bizim index.jsx dosyası bu kısımda main.jsx dosyası haline gelecek.

import React from 'react';
import ReactDOM from 'react-dom/client';
import {Provider} from 'react-redux';
import {BrowserRouter as Router} from 'react-router-dom';

import { worker } from './knowledges/network/mocks/browser'
worker.start({ onUnhandledRequest: 'bypass' })

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Router>
<App />
</Router>
</PersistGate>
</Provider>
</React.StrictMode>,
);

Bu kısımda zorlu bir konu daha bulunuyor. MSW (MockServiceWorker) kullandığımız kısımda require bulunuyor.

const {worker} = require('./knowledges/network/mocks/browser');
worker.start({ onUnhandledRequest: 'bypass' })

Vite.js require sevmediği için bunu import dönüştürmemiz ve async hale getirmemiz gerekiyor.

import { handlers } from '@od/knowledges/network/mocks/handlers'

const worker = setupWorker(...handlers)

async function prepare() {
return worker.start({ onUnhandledRequest: 'bypass' })
}

prepare().then(() => {
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Router>
<App />
</Router>
</PersistGate>
</Provider>
</React.StrictMode>
)
})

Absolute Path yerine Alias Oluşturma

CRA projesinde Webpack ile çalışırken jsconfig.json veya tsconfig.json ile baseURL tanımlamamız sonucunda

{
"compilerOptions": {
"baseUrl": "./src"
}
}

src altında bulunan folder direk ulabiliyorduk.

import { worker } from 'knowledges/network/mocks/browser'
import { APage } from 'pages/network/mocks/browser'

Ama vite kısmında bunun için bir alias tanımlaması yapmamız gerekiyor. Aşağıda vite.config.js dosyasına alias olarak @od veriyorum

/* eslint-disable no-undef */
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
define: { global: {} },
plugins: [react()],
resolve: { alias: { '@od': `${path.resolve('./src/')}` } },
})

Artık projemde @od/pages @od/knowledges gibi pathler vererek ilerleyebilirim.

import { worker } from '@od/knowledges/network/mocks/browser'
import { APage } from '@od/pages/network/mocks/browser'

DefineConfig de Window Tanımı Yapılmamış Olması

Vite içerisinde global objesi tanımlanmamış durumda. Özetle tarayıcı ile sunucu üzerinde çalıştırdığımızda çıkan bu farklılık webpack içerisinde window olarak tanımlandığı ve bunu baz alan bazı kütüphaneler bu globalden faydalanarak çalıştığı için bazı fonksiyonlarda problemler olabiliyor.

https://stackoverflow.com/questions/72114775/vite-global-is-not-defined

Ben bu duruma react-dnd drag-drop işlemlerindeki örneklerde karşılaştım. Bu durumu düzeltmek için vite.config.js içerisinde global tanımlamasını yapmanız gerekiyor.

export default defineConfig({
define: {global: 'window'},
});

Package.json Dosyasından React-Script Silinmesi

  • Package.json içerisinde CRA(Create React App) gelen react-script vb kütüphaneleri kaldırdım.
  • package.lock.json dosyasını sildim.
  • nvm use ile istediğim node paketine geçtim
  • npm install ile node_modules paketlerini tekrardan yükledim.

Ana Dizindeki Config Dosyalarını Düzenledim.

Ana dizin altında bulunan aşağıdaki dosyalarını direk Vite projesine taşıdım.

  • .prettierignore
  • .prettierrc
  • .eslintrc.cjs

netlify.toml dosyasında biraz değişiklik yapmam gerekiyordu . Bunu Vite çıktısını alacağı yere göre düzenledim.

[build]
command = "npm run build"
publish = "dist"

Proje içerisinde Require kullanımı Olan Bir Kısmı Dynamic Import Dönüştürdüm

onurdayibasi.dev sitesini gidip herhangi bir örnekte veya sayfada ⌘K tıkladığınızda aşağıdaki ekran açılır. Bu ekranda soldaki liste örnekleri, sağdaki listede knowledge-map isimlerini verir.

Hangi örneğin hangi KnowledgeMap içerisinde olduğunu bulmak için proje içerisinde dah önceden yer alan knowledge-map verilerinin içerisinden sample listesini çıkaran bir kod yazmıştım

const knowledgeSamples = folderItems.map((item) => ({
path: `/${item.name}-knowledge-map`,
title: item.title,
samples: Object.values(require(`knowledges/${item.name}/data/knowledgeData`).samples),
}));

const knowledgeSampleMap = {};
knowledgeSamples.forEach((el) => {
el.samples.forEach((sample) => {
....
});

Not: vite require plugini var ama ben tamamen vite tabanlı bir yapıya geçtiğim için require yapmamaya özen gösteriyorum

Tabiki vite.js require ile çalışmak istemiyor, Bende bu durumda require import olacak şekilde bir güncelleme yaptım.

const knowledgeSamples = []

const knowledgeSamples = await Promise.all(
folderItems.map(async (item) => {
const module = await import(
`./knowledges/${item.name}/data/knowledgeData.js`
)
return {
path: `/${item.name}-knowledge-map`,
title: item.title,
samples: Object.values(module.samples),
}
})
)

...

Bu kısım local’de sorunsuz çalıştı. Fakat Netlify üzerinde build alırken şu şekilde bir hata ile karşılaştım.

Top-level await is not available in the configured target environment (“chrome87”, “edge88”, “es2020”, “firefox78”, “safari14”

9:26:09 AM: [vite:esbuild-transpile] Transform failed with 1 error:
9:26:09 AM: assets/SampleList-!~{05E}~.js:876:25: ERROR: Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14" + 2 overrides)
9:26:09 AM:
9:26:09 AM: Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14" + 2 overrides)
9:26:09 AM: 874| //const knowledgeSamples = []
9:26:09 AM: 875|
9:26:09 AM: 876| const knowledgeSamples = await Promise.all(
9:26:09 AM: | ^
9:26:09 AM: 877| folderItems.map(async (item) => {

Buna göre aynı index sayfasındaki gibi bunu bir async methodun içerisine alıp bunu kullanan yerden fonksiyonu çağırmamız durumunda vite.js dynamic import kısmında hata aldık.

Bundan dolayı bu kısımları modülün üzerinde statik olarak yükleterek bu sorunu aşabildim.

import {samples as cardsSamples} from '@od/knowledges/cards/data/knowledgeData'

.js veya .jsx uzantılı dosyaların düzgün ayalarlanması gerekiyor.

CRA ile oluşturduğum projede dosyanın js veya jsx olmasını eğer bir modul içerisinde React bileşeni ve return gibi durumlarda JSX dönerse dosya ismini .jsx yapıyor. Tamamen util js dosyalarını .js olarak kullanıyordum.

Gözümden kaçan birkaç yer için Vite.js build hatası verdi. Bu durum çok fazla yerde yoktu ama Vite.js bu konuda beni uyarması ve hata vermesi hoşuma gitti.

Netlify Build Alırken Bellek Yetersizliği Problem

Netlify üzerinde build alırken Heap out of Memory hatası verdi.

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
2:09:15 AM: 1: 0xb090e0 node::Abort() [node]
2:09:15 AM: 2: 0xa1b70e [node]
2:09:15 AM: 3: 0xce1a20 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
2:09:15 AM: 4: 0xce1dc7 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
2:09:15 AM: 5: 0xe99435 [node]
2:09:15 AM: 6: 0xea90fd v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]

Bunun için Env Variable içerisine Node belleğini arttıracak parametre ekledim ve sorun çözüldü.

NODE_OPTIONS 
--max-old-space-size=4096

Netlify’de Build Alırken rollup-linux-x64-gnu Opsiyonel Dependency Olmaması

Vite altyapısında rollup kullanıyor ve npm paketleri arasında rollup-linux-x64-gnu olmaması Netlify tarafında sorun çıkardı.

2:03:57 AM: Error: Cannot find module @rollup/rollup-linux-x64-gnu. npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). Please try `npm i` again after removing both package-lock.json and node_modules directory.
2:03:57 AM: at requireWithFriendlyError (/opt/build/repo/node_modules/rollup/dist/native.js:87:9)
2:03:57 AM: at Object.<anonymous> (/opt/build/repo/node_modules/rollup/dist/native.js:96:48)

Bunun için package.json dosyası içerisine optionalDependencies tanımlamak gerekiyor.

  "optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.6.1"
},

Dynamic Import ve Routing Problemi

Kendi projelerimde kullanmak için geliştirdiğim basit bir Routing altyapısı bulunuyor temelde React-Router üzerine çalışsa da bir takım ekstra yeteneklere sahip

  • dinamik yükleme, module spliting,
  • redirection
  • error handling
  • not found page handling
  • nested pages and subpages
  • etc..

Projede birçok örnek olması ve bunların bağlantılı kütüphanelerinin çok olması nedeniyle Dynamic Loading onurdayibasi.dev için çok önemli bir yere sahip Fakat Vite.js getirdiği one level depth. Webpack göre benim projemde büyük kısıtlamalara neden oldu.

React.lazy() → Dynamic Variable Load

Bir diğer gördüğüm noktada webpack tarafında React.lazy ile bir bileşen dynamic olarak yüklemeye çalıştığımızda

const PageComp = React.lazy(() => import(`pages/${el.modulePath}`));

webpack sadece ön kısmında pages/ gibi bir sabitin olmasını diğer kısmın aa/bb/cc olabilmesine imkan tanıyor. Halbuki vite.js

const PageComp = React.lazy(() => import(`pages/${el.modulePath}/index.jsx`));

başında ve sonunda bir sabitin olmasını (pages/…./index.jsx) gibi ve el.modulePath “/” içermemesini variable tanımlamarına bu dinamiklik kısıtlaması oldukça fazla yani

Önemli Not:

Bu kısımda Vite.js getirdiği bir kısıtlamadan bahsetmek istiyorum. Birden fazla folder path desteklemiyor. Webpack n level depth dosyaları import edebilirken Vite için

  • ./ ile başlamalı ve
  • direk bir seviye değişken variable dosya ismi verilebiliyor)

https://vitejs.dev/guide/features.html#dynamic-import

Bu kısımda 2 çözümden birisine uygulamam gerekiyordu.

  1. Tüm sayfaları tek bir proje altına taşımak. Ve AliasPage üzerinden ilgili gerçek sayfaları çağırmak
  2. Benim durumda folder isimlerini kodda stati kswitch edip fName farklı tutmaya karar verdim .
    const folderName = modulePaths[0];
const fName = modulePaths[1];
switch (folderName) {
case 'animation':
return React.lazy(() => import(`./pages/animation/${fName}.jsx`));
case 'authorization':
return React.lazy(() => import(`./pages/authorization/${fName}.jsx`));
case 'cards':
return React.lazy(() => import(`./pages/cards/${fName}.jsx`));

React Virtualized Kütüphanesi Vite.js Build Alırken Çıkan problem

Anladığım kadarı ile problem React-virtualized kısmından kaynaklı fakat bir şekilde webpack bunu problemsiz çalıştırdığı için Vite.js de bu kısım sorun çıkarıyordu .

Kodu analiz ettiğimde projede sadece lazy-log kütüphanesinin react-virtualized kütüphanesine bağımlığı olduğunu gördüm. Şimdilik bu kütüphaneyi package.json çıkararak ilgili örneği comment-out ettim.

Sonuç

Sonuç olarak uzun uğraşlardan sonra Vite.js geçiş yapmış bulunuyorum. Eksik olan bir çok kısım var ama eskiden olduğu gibi birçok kütüphaneyi sisteme eklemek yerine artık bunun üzerine çok güçlü bir proje altyapısı geliştirmeyi planlıyorum.

Bu da zaman içerisinde bazı kütüphanelerde karar kılmam, bazı kütüphaneleri ve örnekleri projemden çıkarmam anlamına geliyor. Oluşturmak istediğim altyapı ile ilgili detayları aşağıdaki blog yazılarından görebilirsiniz.

Okumaya Devam Et 😃

Bu yazının devamı veya yazı grubundaki diğer yazılara erişmek için bu linke tıklayabilirsiniz.

Yazıyı beğendiyseniz 👏 👏 👏 alkışlamayı ve çevremizle paylaşmayı unutmayalım. Teşekkürler.

--

--