Sandbox ile izolasyon
@tuvix.js/sandbox iki araç sunar. CssSandbox bir uygulamayı shadow root içine yerleştirir, böylece uygulamanın CSS'i ile sayfanın CSS'i birbirine karışmaz. JsSandbox kodu bir proxy window üzerinde çalıştırır, böylece kodun global yazmaları proxy'ye düşer. Orchestrator, sandbox: true ile kaydettiğiniz uygulamalar için CssSandbox kullanır; JsSandbox ise hiçbir zaman uygulamaz.
Uygulama başına CSS izolasyonu
import { createOrchestrator } from 'tuvix.js';
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'widget',
entry: {
scripts: ['https://cdn.example.com/widget/main.js'],
styles: ['https://cdn.example.com/widget/main.css'],
type: 'module',
},
container: '#widget',
activeWhen: '/',
sandbox: true, // { css: true } ile aynı
});Bu uygulama için orchestrator:
#widgetelemanına açık (open) bir shadow root ekler (ya da önceki bir mount'tan kalanı yeniden kullanır);- bunun içinde bir
<div data-tuvix-sandbox-root>oluşturur ve bu elemanımountfonksiyonunacontainerolarak verir; - entry'deki
stylesdosyalarınıdocument.headyerine shadow root içine<link>etiketleri olarak ekler vemountçağırmadan önce yüklenmeleri içinloader.timeoutsüresine kadar bekler.
Artık sayfanın CSS'i uygulamaya, uygulamanın CSS'i de sayfaya ulaşmaz. color ve font-family gibi kalıtılan özellikler host elemandan içeri akmaya devam eder; bunu durdurmak için bu özellikleri uygulamanın CSS'inde :host üzerinde tanımlayın.
Bir uygulama global seçiciler getiriyorsa (button { ... }, bir CSS reset) ya da sayfanın CSS'i uygulamayı bozuyorsa bunu kullanın.
Sınır durumlar
- Bir framework'ün çalışma zamanında eklediği stiller shadow root'un dışında,
document.headiçine gider. Vite'ın CSS enjeksiyonu, CSS-in-JS kütüphaneleri ve Angular'ın bileşen stilleri bunu yapar. Ayrı bir CSS dosyası üretipentry.stylesiçinde listeleyin ya da CSS'i shadow root'a kendiniz ekleyin. document.bodyiçine render eden portal'lar, modal'lar ve toast'lar shadow root'tan çıkar ve uygulamanın stillerini kaybeder.- Uygulamanın içinden çağrılan
document.querySelector, shadow root içindeki elemanları bulmaz. Sorguyucontainerüzerinden yapın. - Loader fonksiyonu olan entry'ler
styleslistelemez. Bu yüzden bundler'ınız onların CSS'inidocument.headiçine enjekte eder ve shadow root bu CSS'i dışarıda tutar. Sandbox kullanan uygulamaları{ scripts, styles, type }biçiminde bir entry ile kaydedin.
CssSandbox'ı elle kullanma
import { CssSandbox } from '@tuvix.js/sandbox';
declare const container: HTMLElement;
const css = new CssSandbox();
const shadow = css.wrap(container); // mevcut alt elemanları shadow root'a taşır
const adopted = css.adoptStyles(shadow, {
urls: ['/widget/main.css'],
cssText: ':host { display: block; font-family: system-ui; }',
});
await adopted.ready; // her <link> load ya da error tetikleyince resolve olur
const button = document.createElement('button');
button.textContent = 'Buy';
shadow.append(button);
// Daha sonra
adopted.remove();
css.unwrap(container);| Metot | Etkisi |
|---|---|
wrap(container) | Açık bir shadow root ekler ya da mevcut açık shadow root'u yeniden kullanır ve container'ın alt elemanlarını içine taşır. |
adoptStyles(shadow, { urls, cssText }) | Shadow root'un başına <link> ve <style> elemanları ekler. Orada zaten eklenmiş bir URL'i atlar. { elements, ready, remove } döndürür. |
addStyle(shadow, css) / removeStyle(shadow, el) | Tek bir <style> ekler ya da kaldırır. |
unwrap(container) | Stil dışındaki düğümleri light DOM'a geri taşır ve render edilmeleri için bir <slot> bırakır. <style> ve stil dosyası <link> düğümlerini (addStyle ve adoptStyles ile eklenenler dahil) DOM'dan çıkarır; o container için yapılan bir sonraki wrap çağrısı onları aynı sırayla geri koyar. |
Eleman shadow root barındıramıyorsa (örneğin <input>) ya da zaten kapalı (closed) bir shadow root'u varsa wrap hata fırlatır. ready hiçbir zaman reject olmaz. jsdom link'lerde hiçbir zaman load tetiklemez, bu yüzden jsdom testlerinde ready için await kullanmayın. Container unwrap edilmişken removeStyle ya da adopted.remove() çağırırsanız o stil dosyası bir sonraki wrap ile geri gelmez.
JsSandbox
import { JsSandbox } from '@tuvix.js/sandbox';
const js = new JsSandbox(['dataLayer'], /* strict */ true);
js.execScript('window.dataLayer = []'); // izin verilir, proxy'de saklanır
console.log('dataLayer' in window); // false, sayfa tanımlamadıysa
try {
js.execScript('counter = 1'); // izin verilmeyen bir global'e doğrudan atama
} catch (error) {
console.log(error instanceof TypeError); // true: strict mod yazmayı engelledi
}
const loose = new JsSandbox(); // strict değil
loose.execScript('var version = 2; build = 7;');
console.log(Reflect.get(loose.proxyWindow, 'version'), 'version' in window); // 2 false
js.reset(); // saklanan bütün global'leri unut- Proxy üzerinden yapılan yazmalar ve
Object.definePropertyçağrıları sandbox'taki bir depoya gider. Okumalar önce bu depoya, sonra gerçekwindownesnesine bakar. strict: trueile, yerleşik izin listesi veallowedGlobalsdışındaki bir global'e değer atamak, onu üst düzeydevarile tanımlamak ya da onaObject.definePropertyuygulamak birTypeErrorfırlatır ve hiçbir şey saklanmaz.execScript(code),codeiçeriğiniwith (scope)içinde çalıştırır;window,self,globalThisvethisproxy'ye bağlıdır. Doğrudan atamalar (x = 1), üst düzeyvar xvewindow.x = 1hepsi proxy'ye düşer;letveconstise script'e yerel kalır.execScriptiçinde tanımlanmamış bir identifier okumakReferenceErrorfırlatmak yerineundefineddöndürür ve"use strict"direktifinin hiçbir etkisi olmaz, çünküwithsloppy mod gerektirir.new Functionkullandığı için Content Security Policy'nizdeunsafe-evalgerekir.activate()vedeactivate(),activebayrağını ayarlar. Proxy her iki durumda da yazmaları saklar.
JsSandbox yalnızca global'lere proxyWindow üzerinden erişen kodu kapsar. <script> etiketiyle yüklenen bir script, bundle edilmiş bir modül, timer'lar ve event listener'lar gerçek window nesnesini kullanır. Bunu bir güvenlik sınırı olarak değil, çalıştırdığınız kod parçalarında yanlışlıkla yapılan global yazmalara karşı bir koruma olarak görün.
Birleşik sandbox
import { createSandbox } from '@tuvix.js/sandbox';
declare const container: HTMLElement;
const sandbox = createSandbox({ css: true, js: false });
const shadow = sandbox.activate(container); // ShadowRoot | null
if (shadow) sandbox.css.addStyle(shadow, '.box { padding: 8px; }');
sandbox.deactivate(container); // unwrap
sandbox.destroy(container); // deactivate + JS deposunu sıfırlacss değeri false olduğunda activate, null döndürür ve hiçbir şey eklemez.
Bir mikro uygulamanın içinde kullanma
import { defineMicroApp } from '@tuvix.js/core';
import { CssSandbox } from '@tuvix.js/sandbox';
const css = new CssSandbox();
export default defineMicroApp({
name: 'widget',
mount({ container }) {
const shadow = css.wrap(container);
css.addStyle(shadow, 'p { color: rebeccapurple; }');
const text = document.createElement('p');
text.textContent = 'Isolated';
shadow.append(text);
},
unmount({ container }) {
container.shadowRoot?.replaceChildren();
css.unwrap(container);
},
});Uygulamayı orchestrator mount ediyorsa register çağrısında sandbox: true kullanmayı tercih edin; aynı işi yapar ve yeniden mount'larda shadow root'u tekrar kullanır.
Ne zaman sandbox kullanılmaz
- Uygulama
document.bodyiçine portal kullanıyor ve stillerinin orada da geçerli olması gerekiyor. - Uygulamayı sayfanın tasarım sistemi stillendirmeli.
- Gerçek JavaScript izolasyonuna ihtiyacınız var. Bunun için bir
<iframe>kullanın.