☰
billboard.js 模块化导入完全指南:ESM 按需注册、Tree-shaking 与常见错误排查
2026/10/10 5:08:23 网站建设 项目流程
  • 数据可视化
  • 前端

【免费下载链接】billboard.js

📊 Re-usable, easy interface JavaScript chart library based on D3.js, with SVG and Canvas rendering support

项目地址:https://gitcode.com/gh_mirrors/bi/billboard.js
点击查看免费下载

导读: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())。调用该函数会执行三个动作:

  1. 将模块的内部实现挂载到Chart/ChartInternal原型上;
  2. 注册该模块自带的默认选项;
  3. 返回一个可直接展开(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.js

2. 或按功能就近注册

如果偏好就近(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:

Moduledata.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]] } });

交互模块

ModuleEnables
selectiondata.selection.enabled+chart.select/unselect/selected
subchartsubchart.show+chart.subchart.*
zoomzoom.enabled+chart.zoom.*
import bb, {bar, zoom} from "billboard.js"; bb.generate({ zoom: { enabled: zoom() }, data: { type: bar(), columns: [...] } });

可选 API 模块

ModuleChart method(s)Internal effect
exportApichart.export()—
flowchart.flow()flowtransition renderer
gridchart.xgrids()/chart.ygrids()Grid lines & grid-focus renderer
regionschart.regions()Region renderer
categorychart.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

项目地址:https://gitcode.com/gh_mirrors/bi/billboard.js
点击查看免费下载
上一篇:DuiLib_Ultimate多语言支持实现教程:轻松构建国际化应用
下一篇:nice-color-palettes 项目使用教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询