Bir Web Worker'ı npm paketine nasıl gömdük

· 4 dk okuma

İşi ana iş parçacığının dışına taşıyan her kütüphanenin aynı kurulum sorunu vardır. Worker ayrı bir betiktir, tarayıcı onu bir URL'den yükler ve o URL kullanıcının hangi bundler'ı kullandığına bağlıdır. Sonuç: README'de bir bölüm büyür — Vite kullanıyorsanız şunu yapın. Next.js'te şöyle. Bu dosyayı public/ içine kopyalayın.

Bizde de o bölüm vardı. İnsanların en sık yanlış yaptığı tek şeydi.

@kanunilabs/[email protected] ile gitti. Kopyalanacak dosya, bundler reçetesi ve ayarlanacak workerUrl yok:

import { BasePivotGrid } from '@kanunilabs/pivotgrid-react';

<BasePivotGrid data={rows} initialFields={fields} />

Nasıl yaptığımız, neye mal olduğu ve nerede canınızı yakabileceği aşağıda.

Fikir

Bir Worker, Blob URL'inden oluşturulabilir:

const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
const worker = new Worker(url, { type: 'module' });

Yani worker'ın kaynak kodu paketin içinde bir dizeyse, çalışma anında çözülecek hiçbir şey kalmaz. URL yok, bundler'ın işi yok, kopyalama yok.

İşin püf noktası kaynak kelimesinde. O dize eksiksiz ve kendi kendine yeten bir program olmak zorunda. İçinde hâlâ import ifadesi bulunan bir worker sorunsuz oluşturulur, sonra çalıştığı anda patlar — çünkü Blob URL'inin o import'ları çözeceği bir modül grafiği yoktur.

Derleme

Üç adım, bu sırayla.

1. Worker'ları tek başlarına derleyin. İkinci bir bundler yapılandırması, her worker'ı kendi kendine yeten tek dosya olarak üretir — kod bölme kapalı, tüm workspace bağımlılıkları import olarak bırakılmak yerine içeri gömülü:

// tsup.worker.config.ts
export default defineConfig({
  entry: {
    'pivot.worker': 'src/worker/pivot.worker.ts',
    'export.worker': 'src/worker/export.worker.ts',
    'import.worker': 'src/worker/import.worker.ts',
  },
  splitting: false,
  noExternal: ['@kanunilabs/foundation'],
  clean: true,
});

2. Her derlenen dosyayı bir TypeScript modülüne çevirin. Küçük bir betik çıktıyı okur ve dize sabiti olarak geri yazar:

const source = readFileSync('dist/pivot.worker.js', 'utf8');
writeFileSync(
  'src/worker/workerSource.generated.ts',
  `export const EMBEDDED_PIVOT_WORKER_SOURCE = ${JSON.stringify(source)};\n`,
);

Burada JSON.stringify gerçek bir iş yapıyor: tırnakları, ters bölü işaretlerini ve satır sonlarını kaçırıyor; böylece minifier ne üretmiş olursa olsun sonuç geçerli bir dize sabiti oluyor.

Sessizce bozulduğu iki nokta

İkisi de sorunsuz kurulan, tip kontrolünden geçen ve yalnızca müşterinin makinesinde patlayan bir paket üretir. Açıkça korumaya değerler.

Ana derleme worker'ları siler. Çoğu bundler yapılandırması varsayılan olarak çıktı klasörünü temizler. Önce worker derlemesini, sonra temizleme açık halde ana derlemeyi çalıştırırsanız, üçüncü adım birinci adımın ürettiğini siler. Paket yine yayınlanır — sadece geri düşme yolu için worker dosyası kalmamıştır. clean yalnızca tek bir yapılandırmanın olmalı.

Gömülü kaynak kendi kendine yetmiyordur. Bir düzenleme, içeri gömülmüş bir bağımlılığı tekrar dış import'a çevirirse, Blob worker çalışma anında hata vermeye başlar ama bütün yerel testler geçmeye devam eder. Bu yüzden üretim betiği üç kontrolden geçmeyen dosyayı yazmayı reddediyor: asgari bayt boyutu, import/export ifadesi bulunmaması ve bir onmessage işleyicisinin varlığı.

Boyut eşiği worker başına, genel değil. Bizimkiler 16 KB, 10 KB ve 3 KB. Tek bir eşik ya bozuk 16 KB'lık worker'ı geçirir ya da sapasağlam 3 KB'lık olanı reddeder.

Bedeli ne

Üç worker'ı gömmek çekirdek paketi 30,1 KB — %17,4 büyüttü (173,2 KB → 203,3 KB; minified, gzip öncesi).

Bu bedava değil ve öyleymiş gibi davranmayacağız. Karşılığında, bütün iddiası yolunuzdan çekilmek olan bir kütüphanede koca bir kurulum adımı siliniyor. Tek worker için rakam çok daha küçüktü: DataGrid aynı değişiklik için %6,1 ödedi.

Bir kaçış kapısı bırakın

Blob worker'ı gerçekten çalıştıramayan tek bir ortam var: worker-src içinde blob: izni vermeyen katı bir Content-Security-Policy. Orayı çıkmaz sokak yapmayın.

export function startEmbeddedWorker(kind, resolveUrl) {
  try {
    return new Worker(embeddedWorkerUrl(kind), { type: 'module' });
  } catch {
    // CSP Blob'u engelledi. Worker'ı tamamen kaybetmek yerine gömme öncesi
    // davranışa geri dön.
    return new Worker(resolveUrl(), { type: 'module' });
  }
}

Worker dosyaları pakette durmaya devam ediyor, yani geri düşme yolunun yükleyeceği bir şey var. Açıkça verilen bir workerUrl ise hâlâ ikisini de yener — o yol, worker'ı kendi CDN'inden sunanlar için var ve gömme onu elinden almamalı.

Gerçekten çalıştığını doğrulamak

Buradaki hata sessiz olduğu için "iyi görünüyor" kanıt sayılmaz. İki kontrol otomatikleştirmeye değer:

  1. Worker yapıcısını araya girin ve gerçek bir sayfada aldığı URL'in blob: ile başladığını doğrulayın. Bizimki başlıyor.
  2. Yayınlanan tarball'da arayın. Derlenmiş her worker'dan uzun, tırnaksız bir parça alıp dist/index.js içinde arayın. Kaynak gerçekten gömülüyse birebir oradadır.

İkinci kontrolü, yerel derlemeyle değil, genel registry'den temiz bir klasöre yapılan npm install üzerinde çalıştırdık — üç worker'dan alınan dokuz parçanın dokuzu da bulundu. Bu iddianın söylenmeye değer tek biçimi bu.


Bu, PivotGrid 1.0.5 ile gelen değişikliklerden biri. Kaldırdığı kurulum adımı Kurulum sayfasında anlatılıyor.