我出版了国家防范机制的套件 !!! 出版美人鱼增强能力 作为npm包 (中文 (Chinese Simplified))

我出版了国家防范机制的套件 !!! 出版美人鱼增强能力 作为npm包

Friday, 07 November 2025

//

12 minute read

一. 导言导言 导言 导言 导言

终于鼓起勇气,让我的脚趾踏进Npm软件包的世界。 美人鱼 MSPK0js已经够模糊和奇怪了 我确实可以提供一些有用的东西

在为美人鱼建立一些非常有用的增强功能 MSMK0Qjs 图 *(\interactive pan}/zoom,全屏灯光框{, 导出到 PNGMS K5SVG,和自动主题切换@),我决定是时候适当包装它们并与社区分享了_.}这篇文章通过我是如何创建的 @mostlylucid/mermaid-enhancements 已经使用 npm 软件包@. @%

Onte: 还在研究发布@. 继续调制 @(}我的第一个 npm 套件,所以要稍微加点)

npm 版本版本 npm 下载 驾照@: @unlisense 类型( T) 套装大小

为何要套件 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

  1. 构建软件包@: @%
npm run build:all  # Compiles TypeScript and minifies
  1. 本地端测试@: @
npm run dev  # Opens demo at localhost:3000
  1. 版本bump:
npm version patch  # or minor, or major
  1. 出版量=:
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. 主题探测需求后退

不同的站点处理主题的方式不同 @,}所以我应用了多种探测方法@:

  1. 全球状态 (window.__themeState)
  2. 本地管理@( @%localStorage.theme)
  3. DOM 级 @ ( @%document.documentElement.classList)
  4. 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 和导出功能让复杂的图表工作得更好....

资源资源资源 资源和资源资源资源

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.