npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

marked-echarts

v0.2.0

Published

Safe, streaming-friendly ECharts fenced code blocks for Marked, browsers, Vue 3, and React.

Readme

marked-echarts

Safe, streaming-friendly ECharts fenced code blocks for Marked, browser applications, Vue 3, and React.

marked-echarts recognizes fully closed echarts fences containing strict JSON. Its core entry produces typed tokens and inert HTML placeholders—never scripts. Optional browser, Vue, and React entry points turn validated placeholders or source into charts.

简体中文

Why this package?

  • Strict JSON only: no JavaScript object literals, eval, Function, or inline scripts.
  • Safe defaults for AI-generated and user-provided Markdown.
  • Streaming-aware: an incomplete fence stays an ordinary Marked code token.
  • Isolated Marked instances: no import-time or global marked mutation.
  • Framework-independent browser hydration with resize and disposal handling.
  • Vue 3 integration built on the lifecycle behavior of vue-echarts.
  • React integration with SSR and Strict Mode-safe lifecycle handling.
  • Separate entry points so core consumers do not load ECharts or framework code.

Install

Core Marked integration:

npm install marked marked-echarts

Add ECharts for browser hydration:

npm install marked marked-echarts echarts

Add the Vue adapter:

npm install marked marked-echarts echarts vue vue-echarts

Add the React adapter:

npm install marked marked-echarts echarts react react-dom

Markdown syntax

Only a closed fenced code block whose first info-string word is echarts is recognized:

```echarts
{
  "xAxis": {
    "type": "category",
    "data": ["Mon", "Tue", "Wed"]
  },
  "yAxis": { "type": "value" },
  "series": [
    { "type": "bar", "data": [120, 200, 150] }
  ]
}
```

Backtick and tilde fences, up to three leading spaces, and longer matching closing fences are supported. Configure additional language identifiers with aliases.

Marked usage

Create an isolated parser and register the returned standard MarkedExtension explicitly:

import { Marked } from 'marked'
import markedECharts from 'marked-echarts'

const parser = new Marked(markedECharts())
const html = parser.parse(markdown, { async: false })

The renderer returns a safe structure containing a hidden chart container and a visible, escaped <pre> fallback. It does not parse or execute code and does not output <script> or inline styles.

Marked itself does not sanitize arbitrary Markdown HTML. If the entire Markdown input is untrusted, sanitize the complete marked.parse() result with a maintained HTML sanitizer before inserting it into the DOM. The ECharts placeholder generated by this package is inert, but that does not make unrelated Markdown HTML safe.

Custom aliases are opt-in:

const parser = new Marked(
  markedECharts({ aliases: ['chart-json', 'echarts-json'] }),
)

Browser runtime

Import the external stylesheet, insert the Marked output, and hydrate within a scoped root:

import { Marked } from 'marked'
import markedECharts from 'marked-echarts'
import {
  hydrateMarkedECharts,
  disposeMarkedECharts,
} from 'marked-echarts/browser'
import 'marked-echarts/style.css'

const parser = new Marked(markedECharts())
const root = document.querySelector<HTMLElement>('#content')!
root.innerHTML = parser.parse(markdown, { async: false })

const hydration = await hydrateMarkedECharts({ root })

// Dispose only charts inside root.
disposeMarkedECharts(root)

// Or dispose the charts returned by this hydration call.
hydration.dispose()

By default, hydrateMarkedECharts() dynamically imports the echarts peer only after it finds a valid placeholder. A host can avoid that import or provide a custom ECharts build:

import * as echarts from 'echarts'

const hydration = await hydrateMarkedECharts({
  root,
  echarts,
  renderer: 'svg',
  theme: 'dark',
  onError(error) {
    console.error('Chart fallback remains visible', error)
  },
})

The runtime:

  • validates every chart independently;
  • avoids duplicate initialization;
  • calls setOption(..., { notMerge: true }) when source on an existing placeholder changes;
  • uses ResizeObserver, with a window resize listener fallback;
  • disconnects observers/listeners and disposes ECharts instances;
  • restores the source fallback after parse, load, initialization, or update failure;
  • returns an empty handle instead of crashing when called without a DOM.

Vue 3

The Vue entry imports Vue and vue-echarts; the core and browser entries do not. Register the ECharts modules your application needs (or import the full ECharts build), then use the component:

import 'echarts'
import { createApp, h } from 'vue'
import { MarkedECharts } from 'marked-echarts/vue'
import 'marked-echarts/style.css'

const app = createApp({
  render: () =>
    h(MarkedECharts, {
      source: '{"series":[{"type":"bar","data":[1,2,3]}]}',
      renderer: 'canvas',
      theme: 'dark',
      autoresize: true,
      onReady: (chart) => console.log('ready', chart),
      onError: (error) => console.error(error),
    }),
})

app.mount('#app')

Template example with streaming and slots:

<MarkedECharts :source="source" :streaming="streaming" :loading="loading">
  <template #loading>Loading chart…</template>
  <template #fallback="{ source }"><pre>{{ source }}</pre></template>
  <template #error="{ error }">Could not render: {{ error }}</template>
</MarkedECharts>

While streaming is true, the component never mounts vue-echarts; it renders the fallback slot or escaped source. When streaming ends, it validates source and mounts the chart. A supplied option takes precedence over source and is treated as already parsed/trusted JSON data.

autoresize defaults to true. Changing theme or renderer is passed through to vue-echarts, which owns ECharts initialization, smart updates, automatic resize, theme application, and unmount disposal.

React

The React entry imports React only when marked-echarts/react is imported. ECharts is initialized in a client effect, so server rendering produces an escaped source fallback without accessing window or document:

import { MarkedECharts } from 'marked-echarts/react'
import 'marked-echarts/style.css'

export function Chart({ source, streaming }) {
  return (
    <MarkedECharts
      source={source}
      streaming={streaming}
      renderer="canvas"
      autoresize
      fallback={({ source }) => <pre>{source}</pre>}
      errorFallback={({ error }) => <p>Could not render: {error.kind}</p>}
      onReady={(chart) => console.log('ready', chart)}
      onError={(error) => console.error(error)}
    />
  )
}

The adapter dynamically imports the full echarts peer by default. Pass echarts or loadECharts to use an application-managed or tree-shaken runtime:

The chart layout contains a dedicated empty ECharts host. ECharts exclusively owns descendants of that host, while React fallback and loading overlays remain sibling nodes owned by React. This prevents imperative renderer DOM updates from conflicting with React reconciliation.

import * as echarts from 'echarts/core'
import { BarChart } from 'echarts/charts'
import { GridComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import { MarkedECharts } from 'marked-echarts/react'

echarts.use([BarChart, GridComponent, CanvasRenderer])

<MarkedECharts source={source} echarts={echarts} />

While streaming is true the chart is not initialized. A supplied option takes precedence over source and is treated as already parsed/trusted. Source or option updates use replacement semantics without recreating the instance; changing theme, renderer, or runtime recreates it. autoresize defaults to true and may also receive { throttle, onResize }.

Custom VNode integration

A Marked lexer → custom token → VNode renderer can consume the core API directly:

import { Marked } from 'marked'
import markedECharts, {
  isEChartsToken,
  parseEChartsOption,
} from 'marked-echarts'
import { h } from 'vue'
import { MarkedECharts } from 'marked-echarts/vue'

const parser = new Marked(markedECharts())
const tokens = parser.lexer(markdown)

const nodes = tokens.map((token) => {
  if (isEChartsToken(token)) {
    const parsed = parseEChartsOption(token.text)
    return parsed.ok
      ? h(MarkedECharts, { option: parsed.value })
      : h('pre', token.text)
  }
  return renderOtherToken(token)
})

An unclosed echarts fence is returned by Marked as an ordinary code token, so an AI stream can keep displaying source until the complete closing fence arrives.

Public API

marked-echarts

import markedECharts, {
  markedECharts as namedMarkedECharts,
  isEChartsToken,
  parseEChartsOption,
  DEFAULT_ECHARTS_SECURITY_LIMITS,
  type EChartsToken,
  type MarkedEChartsOptions,
  type EChartsSecurityOptions,
  type ParseEChartsOptionResult,
  type SafeEChartsOption,
} from 'marked-echarts'
  • markedECharts(options?): returns a standard, side-effect-free MarkedExtension.
  • isEChartsToken(value): reliable runtime/type guard.
  • parseEChartsOption(source, security?): returns { ok: true, value, statistics } or { ok: false, error }; it does not throw for source validation failures.
  • EChartsToken: { type: 'echarts'; raw; text; language }.

marked-echarts/browser

  • hydrateMarkedECharts(options?): asynchronously hydrates all placeholders under root and returns chart handles plus per-chart errors.
  • disposeMarkedECharts(root?): disposes scoped charts, or all active charts when no root is passed, and returns the count.
  • HydrateMarkedEChartsOptions: accepts root, echarts, loadECharts, renderer, theme, security, and onError.

marked-echarts/vue

  • MarkedECharts: Vue 3 component.
  • Props: source, option, streaming, autoresize, theme, renderer, loading, and security.
  • Events: ready and error.
  • Slots: loading, fallback, and error.

marked-echarts/react

  • MarkedECharts: React component with a forwarded resize/dispose handle.
  • Props: source, option, streaming, autoresize, theme, renderer, loading, security, echarts, loadECharts, className, and style.
  • Callbacks: onReady and onError.
  • Render surfaces: fallback, loadingFallback, and errorFallback.

Security boundaries

Default limits:

| Limit | Default | | ---------------------------------------- | ------: | | UTF-8 source bytes | 100,000 | | Object/array depth | 20 | | Object keys | 2,000 | | Any single array | 10,000 | | Total series | 100 | | Aggregate data/source rows or points | 50,000 |

Override only what your application can safely afford:

const result = parseEChartsOption(source, {
  strict: true,
  limits: {
    maxSourceBytes: 50_000,
    maxDataPoints: 10_000,
  },
})

Always enforced:

  • strict JSON.parse and a JSON-object root;
  • recursive rejection of __proto__, prototype, and constructor;
  • all configured resource limits;
  • structured failures with stable codes and paths; no silent deletion.

Strict mode is on by default and additionally rejects HTTP(S), data, blob, file, protocol-relative, image://, and javascript: resource-shaped strings; string-backed image fields; HTML formatter markup; and tooltip.extraCssText. Setting strict: false relaxes only those resource/HTML/style checks. It never enables JavaScript syntax or dangerous keys.

The parser validates data, not the full semantic ECharts schema. Keep ECharts and its extensions updated, restrict which chart modules you register when possible, and apply your application's usual DOM sanitization to the complete Markdown output. See SECURITY.md.

Error fallback

Do not discard parse errors. Keep source visible and use the error code for UI messaging:

const result = parseEChartsOption(source)
if (!result.ok) {
  showCodeFallback(source, result.error)
}

The browser runtime does this automatically. The Vue and React components render their error fallback, or source by default, and emit the same structured parse error.

CSP

The package requires no unsafe-eval and generates no executable or inline script. The core renderer also emits no inline style. Import marked-echarts/style.css from your normal CSS pipeline.

vue-echarts 8 can work with strict CSP; consult its current CSP guidance if you target browsers without constructable stylesheet support. Your ECharts features and surrounding application may have additional img-src, font-src, style-src, or network requirements that are outside this package.

Compatibility

| Package/runtime | Supported | Locally verified | | --------------- | ------------------ | ------------------------- | | Marked | 16, 17, 18 | 18.0.10 | | ECharts | 6.x | 6.1.0 | | React | 18.2+, 19.x | 19.2.8 | | Vue | 3.3+ | 3.5.41 | | vue-echarts | 8.1+ | 8.1.0 | | Node.js | 20+ | 22.17 | | Browser output | Modern ESM, ES2022 | jsdom + build smoke tests |

CI runs the tokenizer suite against Marked 16, 17, and 18 and the React client/SSR suites against React 18 and 19. Browser and React resizing use ResizeObserver when available and a window resize fallback otherwise.

Compared with marked-extension-echart

| | marked-echarts | marked-extension-echart | | --------------- | -------------------------------- | ------------------------------------ | | Input | Strict JSON | JSON or JavaScript object literal | | Renderer output | Inert placeholder/source | Inline initialization script | | CSP | No inline script/eval required | Script execution required | | Streaming | Requires a complete closer | Not designed as a streaming contract | | Runtime | Browser, Vue, and React entries | Renderer-managed script | | Safety limits | Depth/size/count/resource policy | Not provided |

This project independently implements the behavior described here. No third-party source code was copied.

Development

npm install
npm run quality

See CONTRIBUTING.md for focused commands and release checks.

License and acknowledgements

MIT © 2026 marked-echarts contributors.

Acknowledgements: