我出版了国家防范机制的套件 !!! 出版美人鱼增强能力 作为npm包
一. 导言导言 导言 导言 导言
终于鼓起勇气,让我的脚趾踏进Npm软件包的世界。 美人鱼 MSPK0js已经够模糊和奇怪了 我确实可以提供一些有用的东西
在为美人鱼建立一些非常有用的增强功能 MSMK0Qjs 图 *(\interactive pan}/zoom,全屏灯光框{, 导出到 PNGMS K5SVG,和自动主题切换@),我决定是时候适当包装它们并与社区分享了_.}这篇文章通过我是如何创建的 @mostlylucid/mermaid-enhancements 已经使用 npm 软件包@. @%
Onte: 还在研究发布@. 继续调制 @(}我的第一个 npm 套件,所以要稍微加点)
为何要套件 It? @
I' _BARBAR_在我的博客上使用这些增强功能有段时间了 @,}它们成为复杂的美人鱼图表工作的关键。
- 交互式的 Pan @ & @ @% 缩放 - 导航大图
- 全屏灯框 - 闪烁式浏览经验
- 导出到 PNG / @SVG Q-高 -质量图表下载
- 自动主题切换 -无缝灯光 @/dark 模式支持
- 响应设计 - 在移动和桌面上工作
既然我在各项目之间复制相同的代码 @,}创建合适的 npm 软件包是有道理的,任何人都可以使用@.}
项目结构
我用Script类型支持建立了一个专业的软件包结构 @:
mostlylucid-mermaid/
├── src/
│ ├── index.ts # Main entry point
│ ├── enhancements.ts # Pan/zoom/export functionality
│ ├── theme-switcher.ts # Theme switching logic
│ ├── types.ts # TypeScript type definitions
│ └── styles.css # Complete styling
├── examples/
│ └── demo.html # Full-featured demo
├── dist/ # Built output (generated)
├── package.json
├── tsconfig.json
├── README.md
├── QUICKSTART.md
├── PUBLISHING.md
└── LICENSE
软件包使用 TypeScript 来进行类型安全和更好的开发体验@, @} but 编译到 JavaScript 以取得最大兼容性@ .
建筑结构
这里#' @%s 组件是如何组合起来的 @:_@
这是它看起来像什么 它看起来像!
所以,你可以看到它“'”非常紧凑 并且有一些有用的功能 除了静态图表'." 它总是让我很烦恼 他们是如何在页面上
graph TD
A[User Initializes] --> B[init Function]
B --> C[initMermaid]
B --> D[enhanceMermaidDiagrams]
C --> E[Theme Detection]
C --> F[Event Listeners]
C --> G[Mermaid Rendering]
E --> E1[Global State]
E --> E2[LocalStorage]
E --> E3[DOM Class]
E --> E4[OS Preference]
F --> F1[Custom Events]
F --> F2[Media Query]
G --> H[Apply Enhancements]
D --> H
H --> I[Wrap Diagrams]
H --> J[Init Pan/Zoom]
H --> K[Add Controls]
I --> L[Interactive Diagram]
J --> L
K --> L
L --> M[User Interactions]
M --> M1[Zoom In/Out]
M --> M2[Pan]
M --> M3[Fullscreen]
M --> M4[Export PNG/SVG]
style A stroke:#059669,stroke-width:3px,color:#10b981
style L stroke:#2563eb,stroke-width:3px,color:#3b82f6
style M stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
核心执行
定义类型
第一次,I定义了全面的类型Script类型@:
// src/types.ts
export interface PanZoomInstance {
zoom(scale: number): void;
zoomIn(): void;
zoomOut(): void;
reset(): void;
fit(): void;
center(): void;
resize(): void;
destroy(): void;
isPanEnabled(): boolean;
enablePan(enabled: boolean): void;
}
export type ExportFormat = 'png' | 'svg';
export type Theme = 'dark' | 'default';
export type ControlAction = 'fullscreen' | 'zoomIn' | 'zoomOut' |
'reset' | 'pan' | 'exportPng' | 'exportSvg';
export interface EnhancementConfig {
icons?: IconConfig;
controls?: {
fullscreen?: boolean;
zoom?: boolean;
pan?: boolean;
export?: boolean;
};
}
主要入境点
主切入点是死亡的简单 :
// src/index.ts
export {
enhanceMermaidDiagrams,
cleanupMermaidEnhancements
} from './enhancements.js';
export {
initMermaid
} from './theme-switcher.js';
export async function init() {
await initMermaid();
}
export default {
init,
initMermaid,
enhanceMermaidDiagrams,
};
Panó/ 启用
增强逻辑将每个图表用控件包绑,并初始化 svg-}panMS K1çzoom:
// src/enhancements.ts
import svgPanZoom from 'svg-pan-zoom';
import { toPng, toSvg } from 'html-to-image';
const panZoomInstances = new Map();
function initPanZoom(svgElement: SVGElement, diagramId: string) {
// Clean up existing instance if present
if (panZoomInstances.has(diagramId)) {
try {
panZoomInstances.get(diagramId).destroy();
} catch (e) {
console.warn('Failed to destroy existing pan-zoom instance:', e);
}
panZoomInstances.delete(diagramId);
}
try {
const panZoomInstance = svgPanZoom(svgElement, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.1,
maxZoom: 10,
zoomScaleSensitivity: 0.3,
dblClickZoomEnabled: true,
mouseWheelZoomEnabled: true,
preventMouseEventsDefault: true,
contain: false
});
panZoomInstances.set(diagramId, panZoomInstance);
return panZoomInstance;
} catch (error) {
console.error('Failed to initialize pan-zoom:', error);
return null;
}
}
控件按钮是动态创建的@: @%
function createControlButtons(container: HTMLElement, diagramId: string) {
if (container.querySelector('.mermaid-controls')) {
return;
}
const controlsDiv = document.createElement('div');
controlsDiv.className = 'mermaid-controls';
const buttons = [
{ icon: 'bx-fullscreen', title: 'Fullscreen', action: 'fullscreen' },
{ icon: 'bx-zoom-in', title: 'Zoom In', action: 'zoomIn' },
{ icon: 'bx-zoom-out', title: 'Zoom Out', action: 'zoomOut' },
{ icon: 'bx-reset', title: 'Reset View', action: 'reset' },
{ icon: 'bx-move', title: 'Pan', action: 'pan' },
{ icon: 'bx-image', title: 'Export as PNG', action: 'exportPng' },
{ icon: 'bx-code-alt', title: 'Export as SVG', action: 'exportSvg' }
];
buttons.forEach(btn => {
const button = document.createElement('button');
button.className = `mermaid-control-btn bx ${btn.icon}`;
button.setAttribute('title', btn.title);
button.setAttribute('aria-label', btn.title);
button.setAttribute('data-action', btn.action);
button.setAttribute('data-diagram-id', diagramId);
controlsDiv.appendChild(button);
});
container.appendChild(controlsDiv);
}
出口功能
出口执行克隆SVG,保留了视图Box@,,并使用 html_-to}-imageMS K4
async function exportDiagram(
container: HTMLElement,
format: ExportFormat,
diagramId: string
) {
try {
const svgElement = container.querySelector('svg');
if (!svgElement) {
console.warn('No diagram found to export');
return;
}
// Clone to avoid modifying the original
const clonedSvg = svgElement.cloneNode(true) as SVGElement;
// Get or calculate viewBox
let viewBox = clonedSvg.getAttribute('viewBox');
if (!viewBox) {
const bbox = svgElement.getBBox();
viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
clonedSvg.setAttribute('viewBox', viewBox);
}
// Set explicit dimensions for proper export
const [, , vbWidth, vbHeight] = viewBox.split(' ').map(Number);
clonedSvg.setAttribute('width', vbWidth.toString());
clonedSvg.setAttribute('height', vbHeight.toString());
// Remove pan-zoom transforms
clonedSvg.removeAttribute('style');
clonedSvg.style.backgroundColor = 'transparent';
clonedSvg.style.maxWidth = 'none';
// Create temporary container
const tempDiv = document.createElement('div');
tempDiv.style.position = 'absolute';
tempDiv.style.left = '-9999px';
tempDiv.appendChild(clonedSvg);
document.body.appendChild(tempDiv);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `mermaid-diagram-${timestamp}`;
if (format === 'png') {
const dataUrl = await toPng(clonedSvg, {
backgroundColor: 'white',
pixelRatio: 2 // Higher quality
});
downloadFile(dataUrl, `${filename}.png`);
} else {
const dataUrl = await toSvg(clonedSvg, {
backgroundColor: 'transparent'
});
downloadFile(dataUrl, `${filename}.svg`);
}
document.body.removeChild(tempDiv);
console.log(`Diagram exported as ${format.toUpperCase()}`);
} catch (error) {
console.error('Failed to export diagram:', error);
}
}
主题切换
主题切换器处理多重探测方法@: @%
// src/theme-switcher.ts
export async function initMermaid() {
// Normalize code fences
normalizeMermaidCodeFences();
const mermaidElements = document.querySelectorAll(elementSelector);
if (mermaidElements.length === 0) return;
await saveOriginalData();
// Set up theme change handlers
const handleDarkThemeSet = async () => {
await resetProcessed();
await loadMermaid('dark');
};
const handleLightThemeSet = async () => {
await resetProcessed();
await loadMermaid('default');
};
// Listen for custom theme events
document.body.addEventListener('dark-theme-set', handleDarkThemeSet);
document.body.addEventListener('light-theme-set', handleLightThemeSet);
// OS theme change listener
if (typeof window.matchMedia === 'function') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', async (e) => {
await resetProcessed();
await loadMermaid(e.matches ? 'dark' : 'default');
});
}
// Detect current theme with fallbacks
let isDarkMode = false;
if (typeof window.__themeState !== 'undefined') {
isDarkMode = window.__themeState === 'dark';
} else if (localStorage.theme) {
isDarkMode = localStorage.theme === 'dark';
} else if (document.documentElement.classList.contains('dark')) {
isDarkMode = true;
} else if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
isDarkMode = true;
}
await loadMermaid(isDarkMode ? 'dark' : 'default');
}
软件包配置
宏包@.json
软件包@.json 定义了不同使用案例的多个切入点 @:
{
"name": "@mostlylucid/mermaid-enhancements",
"version": "1.0.0",
"description": "Enhance Mermaid.js diagrams with interactive pan/zoom, fullscreen lightbox, export to PNG/SVG, and automatic theme switching",
"main": "dist/index.js",
"module": "src/index.ts",
"types": "src/types.ts",
"exports": {
".": {
"types": "./src/types.ts",
"import": "./src/index.ts",
"require": "./dist/index.js"
},
"./min": {
"types": "./dist/index.d.ts",
"import": "./dist/index.min.js",
"require": "./dist/index.min.js"
},
"./styles.css": "./src/styles.css"
},
"unpkg": "dist/index.min.js",
"jsdelivr": "dist/index.min.js",
"scripts": {
"build": "tsc",
"minify": "node scripts/minify.js",
"build:all": "npm run build && npm run minify",
"prepublishOnly": "npm run build:all",
"dev": "cd examples && npx http-server -p 3000 -o"
},
"peerDependencies": {
"mermaid": "^10.0.0 || ^11.0.0"
},
"dependencies": {
"html-to-image": "^1.11.11",
"svg-pan-zoom": "^3.6.1"
}
}
TypeScript 配置
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "examples"]
}
使用率示例
基本用法
使用软件包的最简单方式@: @
import mermaid from 'mermaid';
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';
await init();
主题切换
使用光线的站点@/dark 模式 @:#
import { init } from '@mostlylucid/mermaid-enhancements';
// Initialize
await init();
// When theme changes
function toggleTheme() {
const isDark = document.body.classList.toggle('dark');
document.documentElement.classList.toggle('dark', isDark);
// Notify the enhancements
const event = new Event(isDark ? 'dark-theme-set' : 'light-theme-set');
document.body.dispatchEvent(event);
}
反应集成
import { useEffect } from 'react';
import { init, cleanupMermaidEnhancements } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';
function MermaidDiagram({ chart }: { chart: string }) {
useEffect(() => {
init();
return () => cleanupMermaidEnhancements();
}, [chart]);
return (
<div className="mermaid">
{chart}
</div>
);
}
Vue 融合
<template>
<div class="mermaid">{{ chart }}</div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue';
import { init, cleanupMermaidEnhancements } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';
const props = defineProps(['chart']);
onMounted(async () => {
await init();
});
onUnmounted(() => {
cleanupMermaidEnhancements();
});
</script>
演示页面
我创建了一个全面的演示页面, 显示所有特性@: @
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mermaid Enhancements Demo</title>
<!-- Boxicons for control button icons -->
<link href="https://unpkg.com/[email protected]/css/boxicons.min.css" rel="stylesheet">
<!-- Mermaid Enhancements CSS -->
<link rel="stylesheet" href="../src/styles.css">
</head>
<body>
<!-- Your diagrams -->
<div class="mermaid">
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Check setup]
C --> E[Zoom & Pan]
D --> F[Read docs]
E --> G[Export to PNG/SVG]
</div>
<!-- Load Mermaid -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
window.mermaid = mermaid;
</script>
<!-- Initialize enhancements -->
<script type="module">
import { init } from '../dist/index.js';
await init();
</script>
</body>
</html>
出版过程
这里的'+%s 出版流程@:}
sequenceDiagram
participant Dev as Developer
participant Git as Git Repo
participant NPM as npm Registry
participant CDN as unpkg/jsdelivr
participant User as End User
Dev->>Dev: Write code
Dev->>Dev: npm run build:all
Dev->>Dev: Test locally
Dev->>Git: git commit & push
Dev->>Git: Create version tag
Dev->>NPM: npm login
Dev->>NPM: npm publish --access public
NPM-->>CDN: Sync package
User->>NPM: npm install
User->>CDN: Import from CDN
NPM-->>User: Deliver package
CDN-->>User: Serve files
步骤#-BY-#Step
- 构建软件包@: @%
npm run build:all # Compiles TypeScript and minifies
- 本地端测试@: @
npm run dev # Opens demo at localhost:3000
- 版本bump:
npm version patch # or minor, or major
- 出版量=:
npm login
npm publish --access public
该软件包现在可以通过“:”提供。
- {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}我
npm install @mostlylucid/mermaid-enhancements - unpkg CDN:
https://unpkg.com/@mostlylucid/mermaid-enhancements - jsdelivr CDN:
https://cdn.jsdelivr.net/npm/@mostlylucid/mermaid-enhancements
分散化战略
我添加了一个简化脚本来减少捆绑大小@: @
// scripts/minify.js
const { minify } = require('terser');
const fs = require('fs');
const path = require('path');
async function minifyFile(inputPath, outputPath) {
const code = fs.readFileSync(inputPath, 'utf8');
const result = await minify(code, {
compress: {
dead_code: true,
drop_console: false,
drop_debugger: true,
keep_classnames: true,
keep_fnames: true,
},
mangle: {
keep_classnames: true,
keep_fnames: true,
},
format: {
comments: false,
},
});
fs.writeFileSync(outputPath, result.code);
const originalSize = fs.statSync(inputPath).size;
const minifiedSize = fs.statSync(outputPath).size;
const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
console.log(`✓ ${path.basename(outputPath)}: ${originalSize} → ${minifiedSize} bytes (${reduction}% smaller)`);
}
// Minify main bundle
minifyFile(
path.join(__dirname, '../dist/index.js'),
path.join(__dirname, '../dist/index.min.js')
);
结果*:
- 常规: @ 23.46KB
- 最小化@: @ @ 9.78_ KB @ I(58.3%} 减少 @ MPK3
文件文件文件文件文档文件文件的文件文件文件会议文件文件文件,文件文件文献文件文件/文件文件资料文件文件文文件文件归档文件文件:文件文件记录文件文件、文件文件提供文件文件文号文件文件(文件文件 Do文件文件存档文件文件 文件文件文件和文件文件证件文件文件文件中文件文件Do文件文件档案文件文件
我创建了全面的文档 :
- 读取@ . @ md @-完整的 API reference@,}示例_, 排除故障
- 快速裁剪 .# md @- @ @ 5- @ minute getting start guide
- 普林西辛米斯克 *-_Npm 出版说明
- 变化和变化 -版本历史
符号
CSS 完全响应和支持暗模式@: @%
/* Diagram wrapper */
.mermaid-wrapper {
position: relative;
border-radius: 0.5rem;
overflow: hidden;
width: 100%;
margin: 1rem 0;
}
/* Control buttons */
.mermaid-controls {
position: absolute;
top: 0.5rem;
right: 0.5rem;
display: flex;
gap: 0.25rem;
z-index: 10;
background: rgba(255, 255, 255, 0.9);
border-radius: 0.5rem;
padding: 0.25rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.dark .mermaid-controls {
background: rgba(31, 41, 55, 0.95);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
/* Individual buttons */
.mermaid-control-btn {
padding: 0.5rem;
border-radius: 0.25rem;
cursor: pointer;
transition: all 0.2s;
background: transparent;
border: none;
color: #4b5563;
font-size: 1.25rem;
}
.mermaid-control-btn:hover {
background: rgba(37, 99, 235, 0.1);
color: #2563eb;
transform: scale(1.1);
}
经验教训
1. 类型Script 是值得的
即使对于一个小型图书馆来说...,TypeScript在开发过程中也捕捉了几个错误,并且为用户提供了极好的 IDE 支持@.
2. 多入境点的事情
两者的支助 import 和 require@ ,_ + 提供简化版本@ MS K1} 让软件包更灵活@ MPK2\ {
"exports": {
".": {
"types": "./src/types.ts",
"import": "./src/index.ts",
"require": "./dist/index.js"
},
"./min": {
"import": "./dist/index.min.js"
}
}
3. Demo 是必需的
演示页帮助我抓了虫子,并充当活文献记录@. @%用户可以看到它是如何工作的#.}
4.清理很重要
总是为存储管理提供清理功能@: @%
export function cleanupMermaidEnhancements() {
panZoomInstances.forEach((instance, id) => {
try {
instance.destroy();
} catch (e) {
console.warn(`Failed to destroy pan-zoom instance ${id}:`, e);
}
});
panZoomInstances.clear();
}
5. 主题探测需求后退
不同的站点处理主题的方式不同 @,}所以我应用了多种探测方法@:
- 全球状态 (
window.__themeState) - 本地管理@( @%
localStorage.theme) - DOM 级 @ ( @%
document.documentElement.classList) - OS偏爱 @( @
prefers-color-scheme)
业绩考量
为性能优化软件包@ : @
- 代表团活动 - 所有控件按钮的单事件收听器
- 懒惰初始化 -Pan/Zoom 仅在需要时才初始化
- 清理事件 适当的记忆管理
- 最小值 Re- renders - 仅對主題變更的圖表
- DOMM 高效率的DOM业务 -用途
requestAnimationFrame用于平滑动画
浏览器支持
测试和研究“:”
- 希洛梅/ 爱奇 (latestMS K2
- 灭火器 @ ( @latest@)#
- {\pos(192,218)}翻譯 翻譯:
- 移动浏览器 @(iOSSafari_,Chrome Moved)
未来增强能力
未来版本的想法@: @%
- 可配置控制按钮图标 @(支持Font Oween,材料图标@)
- 键盘快捷键 @(e.gMS K3Ctrl+PLus 用于缩放@)}
- [ @]#触摸手势在移动@(\pinch to scorm_)}到缩放
- 复制图表源代码按钮
- 共享图表 URL 特性
- 图表说明
- 多导出格式 @(PDF,JEGMS K3
结论结论
将美人鱼强化包装为 npm 模块是一个伟大的学习体验@. @ the profession is now:}
- 使用类型Script 的安全性
- 记录下来了
- 易于使用
- 制作@-_ 已就绪
- 框架-不可知性
- 完全测试
如果您在您的工程中使用美人鱼 ,, 请给它一个试一下 !}互动的 pan/Zoom 和导出功能让复杂的图表工作得更好....