- 数据可视化
- 前端
【免费下载链接】billboard.js
📊 Re-usable, easy interface JavaScript chart library based on D3.js, with SVG and Canvas rendering support
导读:billboard.js 从 v4 起以模块化方式发布,ESM 构建中每个图表类型、交互能力和可选 API 都必须显式导入并调用对应的 resolver 函数,才能被打包器正确 tree-shake。本篇以官方 MODULE_IMPORTS.md 为主体,结合仓库源码,系统讲解模块注册原理、两种推荐使用模式、SPA/多路由下的最佳实践、完整模块目录与错误信息对照表,帮助你在 ESM 工程中零报错接入,同时保证 UMD/CDN 用户无需任何改动。
为什么 ESM 需要显式注册模块
billboard.js 是一个模块化库。在ESM 构建下,每一个图表类型、交互能力和可选 API 都不会被自动启用,必须由使用者显式导入并调用;这样打包器才能把未用到的代码从产物中剔除(tree-shaking)。而在UMD 构建(billboard.js、billboard.pkgd.js及 CDN 版本)中,所有模块在加载时被自动注册,UMD 用户可以直接跳过本文档。
从入口源码可以清晰看到这一差异:
- UMD 入口 src/index.ts 在文件顶层就遍历执行了全部 shape 与 interaction 模块,并显式调用
exportApi()、flow()、grid()、regions()、category()、canvas(),因此 UMD 包内一切开箱即用; - ESM 入口 src/index.esm.ts 则只做两件事:以副作用导入方式安装可选 API 的“桩”(stub),并以命名导出方式暴露所有 resolver,把“是否调用”的决定权完全交给使用者。
import bb, {bar, grid} from "billboard.js"; bb.generate({ ...bar(), // enable "bar" chart type ...grid(), // enable chart.xgrids() / chart.ygrids() + grid renderer data: { columns: [...] } });如果你是在控制台看到类似Please, make sure if 'xxx' module has been imported and specified correctly.的错误,那么只需对照下文各表格,找到对应模块并导入即可解决。
模块注册的底层原理
每个模块都以命名导出的形式从billboard.js暴露,导出的是一个resolver 函数(如bar()、grid())。调用该函数会执行三个动作:
- 将模块的内部实现挂载到
Chart/ChartInternal原型上; - 注册该模块自带的默认选项;
- 返回一个可直接展开(spread)进
bb.generate({...})的值,因此同一次调用既启用了功能,又可选地完成了配置。
以图表类型bar为例,src/config/resolver/shape/bar.ts 的实现是:
export let bar = (): string => ( extendAxis([shapeBar, shapePointCommon], [optBar, optPoint]), (bar = () => TYPE.BAR)() );它把 bar 形状渲染器与公共点渲染器挂到原型上,注册 bar/point 的默认选项,之后返回字符串"bar"(即 src/config/const.ts 中的TYPE.BAR)——这解释了为什么data: { type: bar() }和data: { type: "bar" }等价。
可选 API 模块grid同理,src/config/resolver/grid.ts 会:
- 用
extend(ChartInternal.prototype, internalGrid)挂载内部 grid 渲染逻辑; - 直接赋值覆盖桩方法,安装
chart.xgrids()/chart.ygrids(); - 注册 grid 的默认选项,返回空对象(
{}),所以...grid()展开是安全的。
幂等性:只需调用一次
每个 resolver每个应用只需运行一次。首次调用把模块注册到原型上,之后的所有调用都是 no-op(no-operation,空操作)。这也是两种使用模式可以自由混用的前提。
两种使用模式
Pattern A —— 内联展开(单图表场景)
把...bar()/...grid()直接 spread 进bb.generate(...),既启用模块,又应用模块自带的默认选项。适合一个文件只创建一个图表的场景。
import bb, {bar, grid} from "billboard.js"; bb.generate({ ...bar(), ...grid(), data: { columns: [...] } });Pattern B —— 启动时初始化一次,全局复用(多图表场景)
如果某个文件或应用启动代码会创建多个图表,只需在模块顶部依次调用各 resolver 一次。此后所有bb.generate({...})都可以继续使用 v3 及更早版本的普通配置写法——data.type: "bar"、grid: {...}、regions: [...]等。
import bb, {bar, line, grid, regions} from "billboard.js"; // Run once per app — prototype registration is global and idempotent. bar(); line(); grid(); regions(); // From here on, write config the familiar way. const chartA = bb.generate({ bindto: "#chartA", data: { type: "bar", columns: [["data1", 30, 200, 100, 400]] }, grid: { x: { lines: [{ value: 2, text: "Mark" }] } } }); const chartB = bb.generate({ bindto: "#chartB", data: { types: { revenue: "bar", trend: "line" }, columns: [["revenue", 300, 350, 300], ["trend", 130, 100, 140]] }, regions: [{ axis: "x", start: 1, end: 2, class: "highlight" }] });两个图表共享同一份已注册模块,无需再次 spread...bar()或...grid()。resolver 也可以集中放在独立的引导文件(如src/chart-setup.js)中,从入口导入一次即可。
跨页面 / 多路由使用(SPA、Next.js 等)
Chart.prototype是同一个 JavaScript 运行时内所有 billboard.js 副本共享的单一对象。一旦某个模块的 resolver 被调用,其注册结果对应用内所有后续创建的图表都可见——包括不同路由、不同组件、懒加载页面等。同时 resolver 是幂等的:调用两次bar()是安全的,第二次为空操作。
这带来三个实际结论。
1. 在应用启动时统一注册(推荐)
创建单一 setup 文件,导入并调用应用用到的所有模块,然后从根入口导入它。路由级代码之后只需写普通的bb.generate({...}),无需关心具体需要哪个模块。
// src/chart-setup.js — imported once from the app entry import {bar, line, pie, grid, regions, category, zoom} from "billboard.js"; bar(); line(); pie(); grid(); regions(); category(); zoom();// src/main.js (or pages/_app.tsx, app/layout.tsx, etc.) import "./chart-setup"; // side-effect import — registers once import bb from "billboard.js"; // …mount app…// src/pages/dashboard.js import bb from "billboard.js"; // Nothing else to import. Write config the v3 way. bb.generate({ bindto: "#a", data: { type: "bar", columns: [...] }, grid: { x: { lines: [...] } } });// src/pages/report.js — a totally different route import bb from "billboard.js"; bb.generate({ bindto: "#b", data: { type: "pie", columns: [...] } }); // chart.regions([...]) also works here — registered by chart-setup.js2. 或按功能就近注册
如果偏好就近(colocated)导入,每个模块文件可以在顶层调用自己的 resolver。由于幂等性,多个文件调用同一个 resolver 是无害的——第一个生效,其余全部成为 no-op。
// src/widgets/SalesBar.js import bb, {bar, grid} from "billboard.js"; bar(); grid(); // safe even if another module also ran them export function renderSalesBar(el) { return bb.generate({ bindto: el, data: { type: "bar", columns: [...] }, grid: { y: { lines: [...] } } }); }3. 警惕代码分割顺序
原型只在 resolver 被调用时才会被扩展——仅 import 模块并不会注册。在按 chunk 懒加载的打包场景(React.lazy、动态import()、Next.jsdynamic)中,如果页面 A 的图表需要grid,但grid()调用位于页面 B 的 chunk 里,那么 A 先加载时会因为 B 尚未加载而抛出 “please import grid” 错误。
最安全的模式:把模块注册放到共享的引导 chunk(即模式 1)中,让每个路由都能及时拿到。只有当某个功能确实只被某个 chunk 自身使用时,才把 resolver 放进懒加载 chunk。
// ❌ Broken — grid registration only happens when /report loads // src/routes/report.lazy.js import {grid} from "billboard.js"; grid(); // If the user hits /dashboard first and it uses chart.xgrids(), error. // ✅ Safe — registered eagerly in shared bootstrap // src/chart-setup.js import {grid} from "billboard.js"; grid();这一设计在构建配置层面也有印证:ESM 构建配置 config/rolldown/esm.js 中,treeshake.moduleSideEffects被设为hasSideEffects,只对 src/Chart/api/stubs.ts 这类需要保留副作用的文件放行,其余模块代码严格按使用情况摇树。
独立 bundle / 微前端
如果页面中两部分以独立 bundle 形式存在(如宿主应用 + MFE 微前端组件,通过不同<script>标签或不同 ESM 依赖图加载),那么每个 bundle 都拥有自己独立的 billboard.js 副本与独立的Chart.prototype。在 bundle A 中完成的注册不会传递到 bundle B,每个 bundle 都必须各自调用所需的 resolver。
如果某个图表方法在一个页面正常、在另一个页面却抛出
Please, make sure if '…' module has been imported,原因几乎总是:(a) 引导代码被懒加载,而报错页面先被加载;或 (b) 独立 bundle 需要各自注册。
模块目录
图表类型
导入你使用的类型,并把返回值传给data.type/data.types:
| Module | data.typevalue |
|---|---|
area | "area" |
areaLineRange | "area-line-range" |
areaSpline | "area-spline" |
areaSplineRange | "area-spline-range" |
areaStep | "area-step" |
areaStepRange | "area-step-range" |
bar | "bar" |
bubble | "bubble" |
candlestick | "candlestick" |
donut | "donut" |
funnel | "funnel" |
gauge | "gauge" |
line | "line" |
pie | "pie" |
polar | "polar" |
radar | "radar" |
scatter | "scatter" |
spline | "spline" |
step | "step" |
treemap | "treemap" |
这些导出统一来自 src/config/resolver/shape/index.ts,每个类型都对应 src/config/const.ts 中TYPE_METHOD_NEEDED声明的初始化方法(如BAR → initBar、PIE → initArc),初始化时正是通过检查这些方法是否存在于ChartInternal上来判定模块是否已注册。
import bb, {bar, line} from "billboard.js"; bb.generate({ data: { type: bar(), // single type types: { data2: line() }, columns: [["data1", 30, 200], ["data2", 100, 50]] } });交互模块
| Module | Enables |
|---|---|
selection | data.selection.enabled+chart.select/unselect/selected |
subchart | subchart.show+chart.subchart.* |
zoom | zoom.enabled+chart.zoom.* |
import bb, {bar, zoom} from "billboard.js"; bb.generate({ zoom: { enabled: zoom() }, data: { type: bar(), columns: [...] } });可选 API 模块
| Module | Chart method(s) | Internal effect |
|---|---|---|
exportApi | chart.export() | — |
flow | chart.flow() | flowtransition renderer |
grid | chart.xgrids()/chart.ygrids() | Grid lines & grid-focus renderer |
regions | chart.regions() | Region renderer |
category | chart.category()/chart.categories() | — |
import bb, {bar, grid, regions, category, exportApi, flow} from "billboard.js"; const chart = bb.generate({ ...grid(), ...regions(), ...category(), ...exportApi(), ...flow(), data: { type: bar(), columns: [...] }, grid: { x: { lines: [...] } }, regions: [{ start: 1, end: 2 }] }); chart.xgrids([...]); chart.regions([...]); chart.category(0); chart.export(); chart.flow({ columns: [...] });错误信息参考
当可选 API 方法在未导入对应模块的情况下被调用时,会抛出如下错误:
❌ [billboard.js] Please, make sure if '<module>' module has been imported and specified correctly.这个错误并非普通的is not a function。ESM 入口 src/index.esm.ts 会副作用导入 src/Chart/api/stubs.ts,在Chart.prototype上安装export、flow、xgrids、ygrids、regions、category、categories的自安装桩方法;调用桩方法时触发 src/module/error.ts 的checkApiModuleImport(),根据 src/config/const.ts 的API_MODULE_NEEDED映射表给出精确的导入指引,并在控制台附带指向本文档的说明。
方法 → 所需模块对照:
| If you call … | Import and invoke … |
|---|---|
chart.export() | exportApi() |
chart.flow(...) | flow() |
chart.xgrids(...)/chart.ygrids(...) | grid() |
chart.regions(...) | regions() |
chart.category(...)/chart.categories(...) | category() |
当data.type指向一个未导入 resolver 的图表类型时,图表初始化阶段会抛出类似错误:
❌ [billboard.js] Please, make sure if '<typeName>' module has been imported and specified correctly.该场景由 src/module/error.ts 的checkModuleImport()负责:它会遍历TYPE_METHOD_NEEDED,检查ChartInternal上是否存在对应的初始化方法(initBar、initArc、initLine等),缺失即报错。值得一提的是,若data.type与data.types均为空,默认按"line"类型检查——也就是说,完全不带类型配置就调用bb.generate()仍需要导入line模块。
UMD / CDN 与 React:无需任何改动
UMD 入口在加载时会自动调用每个 resolver,因此chart.xgrids()、chart.export()等开箱即用:
<script src="https://cdn.jsdelivr.net/npm/billboard.js/dist/billboard.pkgd.min.js"></script> <script> const chart = bb.generate({ data: { type: "bar", columns: [["data1", 30, 200, 100]] } }); chart.xgrids([{ value: 1, text: "L1" }]); // works without extra setup </script>React 组件同样如此:dist/billboard.react.js是暴露BillboardReact全局变量的 UMD 构建,与之配套的 packaged 入口已注册所有模块,无需调用任何 resolver。
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/billboard.js/dist/billboard.pkgd.min.js"></script> <script src="$YOUR_PATH/billboard.react.js"></script> <script> ReactDOM.createRoot(document.getElementById("root")).render( React.createElement(BillboardReact.Chart, { bb, options: { data: { type: "bar", columns: [["data1", 30, 200, 100]] } } }) ); </script>modulesprop 只对 ESM 入口有意义——UMD 下已无模块可注册。注意这里的 React 是外部依赖,页面必须加载 UMD 版 React:React 18 及以下提供 UMD 构建,React 19 不再提供。
相关文档
- 各版本变更说明:CHANGELOG-v4.md、CHANGELOG-v2.md
- 逐版本变更记录:CHANGELOG.md
如果希望进一步理解模块化的工程细节,可继续阅读仓库内的 DEVELOPMENT.md(构建与开发流程)、PERFORMANCE.md(性能基准与优化),以及 ESM 构建配置 config/rolldown/esm.js。
- 数据可视化
- 前端
【免费下载链接】billboard.js
📊 Re-usable, easy interface JavaScript chart library based on D3.js, with SVG and Canvas rendering support
相关推荐
如何通过ES模块设计优化Sonner的Tree-shaking与按需导入性能
如何通过ES模块设计优化Sonner的Tree shaking与按需导入性能 Sonner是一个轻量级且功能强大的React通知组件库,它通过精心设计的ES模块
Babylon.js ES6/npm 模块化使用指南:按需导入与 Tree Shaking 实战
Babylon.js ES6/npm 模块化使用指南:按需导入与 Tree Shaking 实战 导读 本指南围绕 Babylon.js 官方 ES6 支持文档
图形学游戏开发3D渲染Naive UI 按需引入完全指南:Tree Shaking、自动导入与全局按需安装
Naive UI 按需引入完全指南:Tree Shaking、自动导入与全局按需安装 Naive UI(当前仓库版本 2.45.2)是一套基于 Vue 3、使用
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考