Agent SkillsNanmiCoder/dsh-agent-teams › plugin-heavy-dep

plugin-heavy-dep

GitHub

指导在轻量级Web插件中安全集成重型浏览器依赖(如Mermaid)。涵盖按需懒加载、打包为单文件、通过主机路由安全提供服务以及失败降级处理,确保小体积与高可用性。

.dsh/skills/plugin-heavy-dep/SKILL.md NanmiCoder/dsh-agent-teams

Trigger Scenarios

添加大型图表或编辑器库到轻量插件 配置动态导入和懒加载逻辑 设置主机路由以安全托管资源文件 处理资源加载失败的降级方案

Install

npx skills add NanmiCoder/dsh-agent-teams --skill plugin-heavy-dep -g -y
More Options

Non-standard path

npx skills add https://github.com/NanmiCoder/dsh-agent-teams/tree/main/.dsh/skills/plugin-heavy-dep -g -y

Use without installing

npx skills use NanmiCoder/dsh-agent-teams@plugin-heavy-dep

指定 Agent (Claude Code)

npx skills add NanmiCoder/dsh-agent-teams --skill plugin-heavy-dep -a claude-code -g -y

安装 repo 全部 skill

npx skills add NanmiCoder/dsh-agent-teams --all -g -y

预览 repo 内 skill

npx skills add NanmiCoder/dsh-agent-teams --list

SKILL.md

Frontmatter
{
    "name": "plugin-heavy-dep",
    "description": "Use when adding a heavyweight browser dependency (diagram\/chart renderers like mermaid, code editors, big wasm-adjacent libs) to a lightweight DSH Web plugin that must stay small, when wiring a lazy-loaded chunk through a host route, when the lazy import intermittently fails or falls back, or when rendering untrusted markup (SVG\/HTML) produced by such a dependency."
}

Add Heavy Dependencies to a Lightweight DSH Web Plugin

A lightweight Web plugin (small client bundle, no build farm) can still ship a multi-megabyte renderer — if the heavy code never loads until it is needed and degrades gracefully when it cannot load. This skill is the integration checklist; every item below was earned from a real mermaid integration.

1. Decide lazy vs inline

If the dependency would multiply the client bundle several-fold and only one feature needs it (a fence renderer, an editor opened on demand), do NOT inline it into the client bundle. Split it into a separate chunk file the client imports dynamically only when the feature actually renders.

2. Bundle the chunk as ONE file

Bundle the dependency into a single self-contained ESM file with code-splitting disabled. A general-purpose bundler left on default settings splits the library's internal dynamic imports into sibling chunk files with content-hashed names — the browser then resolves them as relative imports against the chunk's URL, and every sibling must also be served, named exactly, and MIME-correct. One file, one import, no relative-resolution class of bugs.

3. Serve it from a host route scoped to your own lib

Register a prefix route on webServer that serves files from the plugin's own lib directory only:

  • Resolve the lib directory from the host bundle itself (import.meta.url), never from process.cwd().
  • Restrict to a whitelist of extensions (your chunk is .js/.mjs — nothing else should ever be served).
  • Containment guard: verify the requested path stays inside the lib dir. Two hard-won rules:
    • Compare with path.relative, not startsWith: a prefix compare is wrong the moment the filesystem normalizes differently than your base string.
    • Windows drive letters change case: realpathSync may return e:\… where your base says E:\…; a case-sensitive compare then misjudges a perfectly contained path as an escape and answers 403. path.relative (plus an isAbsolute check on the result for cross-drive) is robust.
  • Serve with a JavaScript MIME (application/javascript) — a wrong MIME makes the browser reject the dynamic import.

Remember: the host route only exists after a dsh restart; a hard refresh alone does not register new host code. A 404 on a freshly added route almost always means "not restarted yet" or "the installed copy predates the route".

4. Import lazily, cache verdicts, fall back

  • import() the chunk URL on first render of the feature; cache the successful module so repeated fences do not re-import.
  • Do not cache failures the same way — or a transient failure sticks for the page lifetime; let the next attempt retry, but throttle (a failing import in a loop is its own console spam).
  • Always render a fallback (e.g. the original code block) when the import or the render throws; reading must never break because a diagram could not load. Tag the fallback with a state attribute and log the failure reason to the console — "it fell back" without a reason is undebuggable.

5. Untrusted markup: sanitize before innerHTML

A renderer fed untrusted text (markdown, model output) emits markup you must treat as hostile before dangerouslySetInnerHTML:

  • Configure the renderer to its strict mode and to emit real SVG text rather than HTML labels (HTML labels ride inside <foreignObject> — the one channel that carries raw HTML inside an SVG).
  • Suppress the library's global error side effects (some renderers dump a giant error SVG into document.body before rejecting).
  • Then re-sanitize the emitted SVG yourself with a zero-dependency whitelist pass: parse as XML (image/svg+xml; a parse failure rejects the whole string), accept only an <svg> root, strip foreignObject/script and foreign-HTML elements case-insensitively, strip on*/@* attributes, and strip all href/xlink:href (static diagrams gain nothing from links; a hostile href can navigate the GUI). Defense in depth, not defense instead.

6. Interaction ownership under a modal

A fullscreen zoom/pan modal over the plugin's panels must own ALL wheel events while open — including Ctrl+wheel, if the underlying pane already binds Ctrl+wheel (font sizing). Guard the pane-level handler with a modal presence check; otherwise both behaviors fire on one gesture. Modal zoom: wheel anchored at the cursor, drag to pan, keyboard shortcuts, Esc/overlay click to close.

7. Ship it

  • Commit the built chunk to the package (the plugin ships lib/); declare it in files so published payloads stay closed.
  • Document the restart requirement (host route) vs refresh-only (client) in the release notes — users hit 404s otherwise.
  • A regression test per pitfall: the fallback path (chunk import fails → original rendering), the sanitizer (strips the hostile channels), and — for the host route — a containment test that a case-differing but contained path is served, not refused.

Version History

  • 1caff61 Current 2026-09-08 19:18

Same Skill Collection

.dsh/skills/dsh-plugin-development/SKILL.md
.dsh/skills/plugin-runtime-debug/SKILL.md
.dsh/skills/plugin-test/SKILL.md
.dsh/skills/plugin-upgrade/SKILL.md
skills/dsh-plugin-development/SKILL.md
skills/plugin-heavy-dep/SKILL.md
skills/plugin-runtime-debug/SKILL.md
skills/plugin-test/SKILL.md
skills/plugin-upgrade/SKILL.md
.dsh/skills/dsh-benchmark-case/SKILL.md
.dsh/skills/dsh-upgrade-audit/SKILL.md
.dsh/skills/plugin-release/SKILL.md
.dsh/skills/plugin-workflow/SKILL.md
.dsh/skills/plugin-write/SKILL.md
skills/dsh-benchmark-case/SKILL.md
skills/dsh-upgrade-audit/SKILL.md
skills/plugin-release/SKILL.md
skills/plugin-workflow/SKILL.md
skills/plugin-write/SKILL.md

Metadata

Files
0
Version
1caff61
Hash
5a2630a1
Indexed
2026-09-08 19:18

ホーム - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-10 02:14
浙ICP备14020137号-1 $お客様$