isolet-widget-isolation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseisolet Widget Isolation
isolet 小部件隔离方案
Skill by ara.so — Daily 2026 Skills collection.
isolet-js由ara.so提供的技能——2026每日技能合集。
isolet-jsInstall
安装
sh
npm install isolet-jssh
npm install isolet-jsCore API: createIsolet
createIsolet核心API:createIsolet
createIsoletts
import { createIsolet } from "isolet-js";
const widget = createIsolet({
name: "my-widget", // required: unique identifier
mount: myMountFn, // required: (container, props) => cleanup | void
css: `h1 { color: red; }`, // optional: scoped CSS
isolation: "shadow-dom", // "shadow-dom" | "scoped" | "none"
shadowMode: "open", // "open" | "closed"
hostAttributes: { "data-widget": "true" },
zIndex: 9999,
});
widget.mount(document.body, { title: "Hello" }); // mount into target
widget.update({ title: "Updated" }); // update props
widget.unmount(); // tear down
// Instance properties
widget.container; // HTMLElement — the render container
widget.shadowRoot; // ShadowRoot | null
widget.mounted; // booleants
import { createIsolet } from "isolet-js";
const widget = createIsolet({
name: "my-widget", // 必填:唯一标识符
mount: myMountFn, // 必填:(container, props) => cleanup | void
css: `h1 { color: red; }`, // 可选:作用域CSS
isolation: "shadow-dom", // "shadow-dom" | "scoped" | "none"
shadowMode: "open", // "open" | "closed"
hostAttributes: { "data-widget": "true" },
zIndex: 9999,
});
widget.mount(document.body, { title: "Hello" }); // 挂载到目标元素
widget.update({ title: "Updated" }); // 更新属性
widget.unmount(); // 卸载
// 实例属性
widget.container; // HTMLElement — 渲染容器
widget.shadowRoot; // ShadowRoot | null
widget.mounted; // 布尔值Framework Adapters
框架适配器
React
React
tsx
import { createIsolet } from "isolet-js";
import { react } from "isolet-js/react";
function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>;
}
const widget = createIsolet({
name: "greeting",
mount: react(Greeting),
css: `h1 { color: tomato; font-family: sans-serif; }`,
});
widget.mount(document.body, { name: "World" });
widget.update({ name: "Isolet" });
widget.unmount();tsx
import { createIsolet } from "isolet-js";
import { react } from "isolet-js/react";
function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>;
}
const widget = createIsolet({
name: "greeting",
mount: react(Greeting),
css: `h1 { color: tomato; font-family: sans-serif; }`,
});
widget.mount(document.body, { name: "World" });
widget.update({ name: "Isolet" });
widget.unmount();Vanilla JS
原生JS
ts
import { createIsolet } from "isolet-js";
import { vanilla } from "isolet-js/vanilla";
const widget = createIsolet({
name: "counter",
mount: vanilla((container, props) => {
let count = props.initial ?? 0;
const btn = document.createElement("button");
btn.textContent = `Count: ${count}`;
btn.onclick = () => {
btn.textContent = `Count: ${++count}`;
};
container.appendChild(btn);
// Return cleanup function
return () => container.removeChild(btn);
}),
});
widget.mount(document.getElementById("app"), { initial: 5 });ts
import { createIsolet } from "isolet-js";
import { vanilla } from "isolet-js/vanilla";
const widget = createIsolet({
name: "counter",
mount: vanilla((container, props) => {
let count = props.initial ?? 0;
const btn = document.createElement("button");
btn.textContent = `Count: ${count}`;
btn.onclick = () => {
btn.textContent = `Count: ${++count}`;
};
container.appendChild(btn);
// 返回清理函数
return () => container.removeChild(btn);
}),
});
widget.mount(document.getElementById("app"), { initial: 5 });Solid
Solid
tsx
import { createIsolet } from "isolet-js";
import { render } from "solid-js/web";
import App from "./App";
const widget = createIsolet({
name: "solid-widget",
mount(container, props) {
const dispose = render(() => <App {...props} />, container);
return dispose; // dispose is the cleanup function
},
});tsx
import { createIsolet } from "isolet-js";
import { render } from "solid-js/web";
import App from "./App";
const widget = createIsolet({
name: "solid-widget",
mount(container, props) {
const dispose = render(() => <App {...props} />, container);
return dispose; // dispose 是清理函数
},
});Svelte
Svelte
ts
import { createIsolet } from "isolet-js";
import App from "./App.svelte";
const widget = createIsolet({
name: "svelte-widget",
mount(container, props) {
const app = new App({ target: container, props });
return () => app.$destroy();
},
});ts
import { createIsolet } from "isolet-js";
import App from "./App.svelte";
const widget = createIsolet({
name: "svelte-widget",
mount(container, props) {
const app = new App({ target: container, props });
return () => app.$destroy();
},
});Isolation Modes
隔离模式
ts
// Full CSS isolation — shadow DOM (default)
createIsolet({ name: "w", mount: fn, isolation: "shadow-dom" });
// Scoped — plain div wrapper, styles injected globally
createIsolet({ name: "w", mount: fn, isolation: "scoped" });
// No isolation — mounts directly into target element
createIsolet({ name: "w", mount: fn, isolation: "none" });ts
// 完整CSS隔离 — Shadow DOM(默认)
createIsolet({ name: "w", mount: fn, isolation: "shadow-dom" });
// 作用域模式 — 普通div包裹,样式全局注入
createIsolet({ name: "w", mount: fn, isolation: "scoped" });
// 无隔离 — 直接挂载到目标元素
createIsolet({ name: "w", mount: fn, isolation: "none" });CLI
CLI 命令
sh
npx isolet-js init # scaffold isolet.config.ts
npx isolet-js build # bundle widget(s) from config
npx isolet-js build --watch # rebuild on file changes
npx isolet-js build --minify # minified production buildsh
npx isolet-js init # 生成 isolet.config.ts 脚手架
npx isolet-js build # 根据配置打包小部件
npx isolet-js build --watch # 文件变更时自动重建
npx isolet-js build --minify # 生成压缩后的生产构建包Config File
配置文件
ts
// isolet.config.ts
import { defineConfig } from "isolet-js";
export default defineConfig({
name: "my-widget",
entry: "./src/index.ts",
styles: "./src/widget.css", // CSS to inline; url() assets become data URIs
format: ["iife", "esm"], // output formats
outDir: "./dist", // default: "dist"
globalName: "MyWidget", // global name for IIFE builds
external: ["react"], // don't bundle these deps
dts: true, // emit .d.ts files
minify: true, // minify output
platform: "browser", // target platform
});ts
// isolet.config.ts
import { defineConfig } from "isolet-js";
export default defineConfig({
name: "my-widget",
entry: "./src/index.ts",
styles: "./src/widget.css", // 要内联的CSS;url()引用的资源会转为Data URI
format: ["iife", "esm"], // 输出格式
outDir: "./dist", // 默认值:"dist"
globalName: "MyWidget", // IIFE构建的全局变量名
external: ["react"], // 不打包这些依赖
dts: true, // 生成 .d.ts 文件
minify: true, // 压缩输出
platform: "browser", // 目标平台
});Multiple Widgets
多小部件配置
ts
export default defineConfig([
{ name: "widget-a", entry: "./src/a.ts", styles: "./src/a.css" },
{ name: "widget-b", entry: "./src/b.ts", format: ["esm"] },
]);ts
export default defineConfig([
{ name: "widget-a", entry: "./src/a.ts", styles: "./src/a.css" },
{ name: "widget-b", entry: "./src/b.ts", format: ["esm"] },
]);CSS & Asset Handling
CSS与资源处理
The build pipeline handles everything automatically:
- in config → CSS is read,
stylesreferences (fonts, images) inlined as data URIs, result available asurl()in your entry__ISOLET_CSS__ - imports → converted to JS string exports (shadow DOM safe)
.css - Asset imports (,
.png,.woff2, etc.) → inlined as data URIs.mp3 - in
styles: "./path.css"→ resolved and inlined at build timecreateIsolet
ts
// Entry file using __ISOLET_CSS__ injected by CLI
import { createIsolet } from "isolet-js";
import { react } from "isolet-js/react";
import MyComponent from "./MyComponent";
declare const __ISOLET_CSS__: string;
export const widget = createIsolet({
name: "my-widget",
css: __ISOLET_CSS__, // populated from config.styles at build time
mount: react(MyComponent),
});Or reference the CSS path directly (auto-resolved at build time):
ts
createIsolet({
name: "my-widget",
styles: "./widget.css", // isolet build resolves this
mount: react(MyComponent),
});构建流水线会自动处理所有内容:
- 配置中的**** → 读取CSS,将url()引用的资源(字体、图片)转为Data URI内联,结果在入口文件中以
styles变量提供__ISOLET_CSS__ - 导入 → 转为JS字符串导出(兼容Shadow DOM)
.css - 资源导入(、
.png、.woff2等) → 转为Data URI内联.mp3 - 中的**
createIsolet** → 构建时自动解析并内联styles: "./path.css"
ts
// 使用CLI注入的__ISOLET_CSS__变量的入口文件
import { createIsolet } from "isolet-js";
import { react } from "isolet-js/react";
import MyComponent from "./MyComponent";
declare const __ISOLET_CSS__: string;
export const widget = createIsolet({
name: "my-widget",
css: __ISOLET_CSS__, // 构建时由config.styles填充
mount: react(MyComponent),
});或者直接引用CSS路径(构建时自动解析):
ts
createIsolet({
name: "my-widget",
styles: "./widget.css", // isolet构建会自动解析
mount: react(MyComponent),
});Manual Vite Plugin Setup
手动配置Vite插件
If using Vite directly instead of the CLI:
ts
// vite.config.ts
import { defineConfig } from "vite";
import {
cssTextPlugin,
inlineAssetsPlugin,
autoStylesPlugin,
} from "isolet-js/plugins";
export default defineConfig({
plugins: [cssTextPlugin(), inlineAssetsPlugin(), autoStylesPlugin()],
});如果直接使用Vite而非CLI:
ts
// vite.config.ts
import { defineConfig } from "vite";
import {
cssTextPlugin,
inlineAssetsPlugin,
autoStylesPlugin,
} from "isolet-js/plugins";
export default defineConfig({
plugins: [cssTextPlugin(), inlineAssetsPlugin(), autoStylesPlugin()],
});Script Tag (IIFE) Usage
脚本标签(IIFE)使用方式
html
<script src="https://unpkg.com/isolet-js/dist/index.iife.js"></script>
<script>
const { createIsolet } = __ISOLET__;
const widget = createIsolet({
name: "inline-widget",
mount(container, props) {
container.innerHTML = ``;
},
css: `p { font-family: sans-serif; color: navy; }`,
});
widget.mount(document.body, { name: "Visitor" });
</script>For a bundled custom widget via IIFE:
ts
// isolet.config.ts
export default defineConfig({
name: "my-widget",
entry: "./src/index.ts",
format: ["iife"],
globalName: "MyWidget",
minify: true,
});html
<!-- Resulting script tag distribution -->
<script src="./dist/my-widget.iife.js"></script>
<script>
MyWidget.widget.mount(document.getElementById("root"), { title: "Hi" });
</script>html
<script src="https://unpkg.com/isolet-js/dist/index.iife.js"></script>
<script>
const { createIsolet } = __ISOLET__;
const widget = createIsolet({
name: "inline-widget",
mount(container, props) {
container.innerHTML = ``;
},
css: `p { font-family: sans-serif; color: navy; }`,
});
widget.mount(document.body, { name: "Visitor" });
</script>通过IIFE打包自定义小部件:
ts
// isolet.config.ts
export default defineConfig({
name: "my-widget",
entry: "./src/index.ts",
format: ["iife"],
globalName: "MyWidget",
minify: true,
});html
<!-- 最终的脚本标签分发方式 -->
<script src="./dist/my-widget.iife.js"></script>
<script>
MyWidget.widget.mount(document.getElementById("root"), { title: "Hi" });
</script>Common Patterns
常见模式
Lazy-mount on demand
按需延迟挂载
ts
const widget = createIsolet({ name: "chat", mount: react(ChatApp), css: styles });
document.getElementById("open-chat").addEventListener("click", () => {
if (!widget.mounted) {
widget.mount(document.body, { userId: currentUserId });
}
});
document.getElementById("close-chat").addEventListener("click", () => {
widget.unmount();
});ts
const widget = createIsolet({ name: "chat", mount: react(ChatApp), css: styles });
document.getElementById("open-chat").addEventListener("click", () => {
if (!widget.mounted) {
widget.mount(document.body, { userId: currentUserId });
}
});
document.getElementById("close-chat").addEventListener("click", () => {
widget.unmount();
});z-index overlay widget
z-index 覆盖层小部件
ts
const modal = createIsolet({
name: "modal",
mount: react(ModalComponent),
css: modalStyles,
zIndex: 10000,
hostAttributes: { role: "dialog", "aria-modal": "true" },
});ts
const modal = createIsolet({
name: "modal",
mount: react(ModalComponent),
css: modalStyles,
zIndex: 10000,
hostAttributes: { role: "dialog", "aria-modal": "true" },
});Reactive props updates
响应式属性更新
ts
const widget = createIsolet({ name: "status", mount: react(StatusBar), css });
widget.mount(document.body, { status: "idle" });
// Later, update without remounting:
widget.update({ status: "loading" });
widget.update({ status: "done" });ts
const widget = createIsolet({ name: "status", mount: react(StatusBar), css });
widget.mount(document.body, { status: "idle" });
// 后续无需重新挂载即可更新:
widget.update({ status: "loading" });
widget.update({ status: "done" });Troubleshooting
故障排查
Styles leaking in or out
Use (default). Verify your option or path is correctly set — without CSS in the shadow root, the host page styles will not apply inside.
isolation: "shadow-dom"cssstyles__ISOLET_CSS__isolet buildstylesautoStylesPlugin()Component not rendering
Ensure the function appends to , not to . In shadow DOM mode, the container is inside the shadow root.
mountcontainerdocument.bodyCleanup not running
Return a cleanup function from your callback. Without it, cannot tear down framework internals (timers, subscriptions, etc.).
mountwidget.unmount()IIFE global not found
Check in config matches what you reference in HTML. The runtime core exposes when no is set.
globalNameglobalThis.__ISOLET__globalNameExternal deps not found at runtime
If you set , the host page must provide globally or via module federation before your widget script loads.
external: ["react"]React样式向内/向外泄漏
使用默认的。检查选项或路径是否设置正确——如果Shadow根中没有CSS,宿主页面的样式将不会在小部件内生效。
isolation: "shadow-dom"cssstyles__ISOLET_CSS__stylesisolet buildautoStylesPlugin()组件未渲染
确保函数将内容添加到中,而非。在Shadow DOM模式下,容器位于Shadow根内部。
mountcontainerdocument.body清理函数未执行
从回调中返回一个清理函数。如果没有该函数,无法销毁框架内部资源(定时器、订阅等)。
mountwidget.unmount()IIFE全局变量未找到
检查配置中的与HTML中引用的名称是否一致。如果未设置,运行时核心会暴露。
globalNameglobalNameglobalThis.__ISOLET__运行时未找到外部依赖
如果设置了,宿主页面必须在小部件脚本加载前,通过全局变量或模块联邦提供。
external: ["react"]React