isolet-widget-isolation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

isolet Widget Isolation

isolet 小部件隔离方案

Skill by ara.so — Daily 2026 Skills collection.
isolet-js
packages any component (React, Solid, Svelte, vanilla JS, etc.) into a self-contained, isolated widget. Widgets render inside shadow DOM by default, so styles are fully scoped. Output formats include IIFE (script tag), ESM, and CommonJS.
ara.so提供的技能——2026每日技能合集。
isolet-js
可将任意组件(React、Solid、Svelte、原生JS等)打包为一个自包含、隔离的小部件。小部件默认在Shadow DOM内渲染,因此样式完全具备作用域。输出格式包括IIFE(脚本标签)、ESM和CommonJS。

Install

安装

sh
npm install isolet-js
sh
npm install isolet-js

Core API:
createIsolet

核心API:
createIsolet

ts
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;     // boolean
ts
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 build
sh
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:
  • styles
    in config
    → CSS is read,
    url()
    references (fonts, images) inlined as data URIs, result available as
    __ISOLET_CSS__
    in your entry
  • .css
    imports
    → converted to JS string exports (shadow DOM safe)
  • Asset imports (
    .png
    ,
    .woff2
    ,
    .mp3
    , etc.) → inlined as data URIs
  • styles: "./path.css"
    in
    createIsolet
    → resolved and inlined at build time
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),
});
构建流水线会自动处理所有内容:
  • 配置中的**
    styles
    ** → 读取CSS,将url()引用的资源(字体、图片)转为Data URI内联,结果在入口文件中以
    __ISOLET_CSS__
    变量提供
  • .css
    导入
    → 转为JS字符串导出(兼容Shadow DOM)
  • 资源导入
    .png
    .woff2
    .mp3
    等) → 转为Data URI内联
  • 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 = `<p>Hello, ${props.name ?? "World"}!</p>`;
    },
    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 = `<p>Hello, ${props.name ?? "World"}!</p>`;
    },
    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
isolation: "shadow-dom"
(default). Verify your
css
option or
styles
path is correctly set — without CSS in the shadow root, the host page styles will not apply inside.
__ISOLET_CSS__
is undefined
This variable is only injected by the
isolet build
CLI when
styles
is set in config. For manual Vite builds, add
autoStylesPlugin()
to your Vite config.
Component not rendering Ensure the
mount
function appends to
container
, not to
document.body
. In shadow DOM mode, the container is inside the shadow root.
Cleanup not running Return a cleanup function from your
mount
callback. Without it,
widget.unmount()
cannot tear down framework internals (timers, subscriptions, etc.).
IIFE global not found Check
globalName
in config matches what you reference in HTML. The runtime core exposes
globalThis.__ISOLET__
when no
globalName
is set.
External deps not found at runtime If you set
external: ["react"]
, the host page must provide
React
globally or via module federation before your widget script loads.
样式向内/向外泄漏 使用默认的
isolation: "shadow-dom"
。检查
css
选项或
styles
路径是否设置正确——如果Shadow根中没有CSS,宿主页面的样式将不会在小部件内生效。
__ISOLET_CSS__
未定义
该变量仅在配置中设置
styles
时,由
isolet build
CLI注入。如果是手动Vite构建,请在Vite配置中添加
autoStylesPlugin()
组件未渲染 确保
mount
函数将内容添加到
container
中,而非
document.body
。在Shadow DOM模式下,容器位于Shadow根内部。
清理函数未执行
mount
回调中返回一个清理函数。如果没有该函数,
widget.unmount()
无法销毁框架内部资源(定时器、订阅等)。
IIFE全局变量未找到 检查配置中的
globalName
与HTML中引用的名称是否一致。如果未设置
globalName
,运行时核心会暴露
globalThis.__ISOLET__
运行时未找到外部依赖 如果设置了
external: ["react"]
,宿主页面必须在小部件脚本加载前,通过全局变量或模块联邦提供
React