Skip to content

Commit c8090f3

Browse files
ryan-williamsclaude
andcommitted
add createPlotly() factory for tree-shakeable builds
New entry point `lib/factory.js` exports `createPlotly()` — consumers import only the traces and components they need: import { createPlotly } from 'plotly.js/factory'; import scatter from 'plotly.js/src/traces/scatter/index.js'; import legend from 'plotly.js/src/components/legend/index.js'; const Plotly = createPlotly({ traces: [scatter], components: [legend] }); Essential components (colorscale, fx, modebar) are always included. Optional components (shapes, errorbars, annotations, etc.) are only included if explicitly passed. Results (minified, scatter+bar+legend): - factory: 770 KB (-29% vs upstream basic 1,089 KB) - vs fork lite: -62 KB (shapes + errorbars tree-shaken away) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3277f58 commit c8090f3

File tree

6 files changed

+122
-9
lines changed

6 files changed

+122
-9
lines changed

lib/factory.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { createPlotly } from '../src/core-factory.js';

perf/bundle-compare/app-factory.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Factory-based app for bundle size comparison.
3+
* Uses createPlotly() with only scatter + bar + legend.
4+
*/
5+
import { createPlotly } from '../../src/core-factory.js';
6+
import scatter from '../../src/traces/scatter/index.js';
7+
import bar from '../../src/traces/bar/index.js';
8+
import legend from '../../src/components/legend/index.js';
9+
10+
var Plotly = createPlotly({
11+
traces: [scatter, bar],
12+
components: [legend],
13+
});
14+
15+
var el = document.createElement('div');
16+
document.body.appendChild(el);
17+
18+
Plotly.newPlot(el, [
19+
{ x: [1, 2, 3, 4], y: [10, 15, 13, 17], type: 'scatter', name: 'Line' },
20+
{ x: [1, 2, 3, 4], y: [16, 5, 11, 9], type: 'bar', name: 'Bar' },
21+
], {
22+
title: 'Bundle size test (factory)',
23+
width: 800,
24+
height: 400,
25+
});

perf/bundle-compare/compare.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ async function main() {
110110
const forkMinimal = await measureEntry('fork-minimal', 'lib/index-minimal.js');
111111
const forkLite = await measureEntry('fork-lite', 'lib/index-lite.js');
112112

113+
// Factory: tree-shaken scatter+bar+legend only
114+
const appFactory = await bundleSize('app-factory', {
115+
entryPoints: [join(__dirname, 'app-factory.js')],
116+
});
117+
113118
// App-level comparison (what consumers actually experience)
114119
const appUpBasic = await measureApp('app-upstream-basic', join(__dirname, 'upstream/plotly-basic.min.js'));
115120
const appForkLite = await measureApp('app-fork-lite', join(root, 'lib/index-lite.js'));
@@ -122,10 +127,12 @@ async function main() {
122127
['fork basic (min)', forkBasic, upBasic],
123128
['fork minimal (min)', forkMinimal, upBasic],
124129
['fork lite (min)', forkLite, upBasic],
130+
['fork factory (min)', appFactory, upBasic],
125131
['', 0, 0],
126132
['app + upstream basic', appUpBasic, appUpBasic],
127133
['app + fork basic', appForkBasic, appUpBasic],
128134
['app + fork lite', appForkLite, appUpBasic],
135+
['app + fork factory', appFactory, appUpBasic],
129136
];
130137

131138
for (const [label, size, baseline] of allResults) {
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"upstream_full__min_": 4831947,
33
"upstream_basic__min_": 1114816,
4-
"fork_full__min_": 4818460,
5-
"fork_basic__min_": 1104615,
6-
"fork_minimal__min_": 996905,
7-
"fork_lite__min_": 856900,
4+
"fork_full__min_": 4818593,
5+
"fork_basic__min_": 1104752,
6+
"fork_minimal__min_": 997042,
7+
"fork_lite__min_": 857037,
88
"app___upstream_basic": 1115573,
9-
"app___fork_basic": 1104763,
10-
"app___fork_lite": 856992
9+
"app___fork_basic": 1104900,
10+
"app___fork_lite": 857129
1111
}

perf/thresholds.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
{
22
"bundleSize": {
33
"lite": {
4-
"expected_bytes": 1825817
4+
"expected_bytes": 1830745
55
},
66
"lite-min": {
77
"expected_bytes": 916326
88
},
99
"minimal": {
10-
"expected_bytes": 2153474
10+
"expected_bytes": 2153511
1111
},
1212
"minimal-min": {
1313
"expected_bytes": 1076875
1414
},
1515
"basic": {
16-
"expected_bytes": 2458549
16+
"expected_bytes": 2458595
1717
},
1818
"basic-min": {
1919
"expected_bytes": 1185641

src/core-factory.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Tree-shakeable plotly.js factory.
3+
*
4+
* Instead of registering all components at import time (which forces
5+
* the bundler to include everything), consumers import only what they
6+
* need and pass it to createPlotly():
7+
*
8+
* import { createPlotly } from 'plotly.js/core';
9+
* import scatter from 'plotly.js/traces/scatter';
10+
* import bar from 'plotly.js/traces/bar';
11+
* import legend from 'plotly.js/components/legend';
12+
*
13+
* const Plotly = createPlotly({
14+
* traces: [scatter, bar],
15+
* components: [legend],
16+
* });
17+
* Plotly.newPlot(el, data, layout);
18+
*/
19+
import './lib/d3-compat.js';
20+
import 'd3-transition';
21+
import 'native-promise-only';
22+
import Registry from './registry.js';
23+
import plotApi from './plot_api/index.js';
24+
import FxModule from './components/fx/index.js';
25+
import PlotsModule from './plots/plots.js';
26+
import { version } from './version.js';
27+
import './plotcss.js';
28+
29+
// Essential components that every plot needs (always registered)
30+
import colorscale from './components/colorscale/index.js';
31+
import fx from './components/fx/index.js';
32+
import modebar from './components/modebar/index.js';
33+
34+
import localeEn from './locale-en.js';
35+
import localeEnUs from './locale-en-us.js';
36+
import Icons from './fonts/ploticon.js';
37+
import Snapshot from './snapshot/index.js';
38+
import PlotSchema from './plot_api/plot_schema.js';
39+
40+
export function createPlotly({ traces = [], components = [] } = {}) {
41+
var register = Registry.register;
42+
var Plotly = { version, register, Icons, Snapshot, PlotSchema };
43+
44+
// Register API methods
45+
var methodNames = Object.keys(plotApi);
46+
for(var i = 0; i < methodNames.length; i++) {
47+
var name = methodNames[i];
48+
if(name.charAt(0) !== '_') Plotly[name] = plotApi[name];
49+
register({ moduleType: 'apiMethod', name: name, fn: plotApi[name] });
50+
}
51+
52+
// Register essential components (always needed)
53+
register([colorscale, fx, modebar]);
54+
55+
// Register user-chosen traces and components
56+
for(var t = 0; t < traces.length; t++) register(traces[t]);
57+
for(var c = 0; c < components.length; c++) register(components[c]);
58+
59+
// Register locales
60+
register([localeEn, localeEnUs]);
61+
if(typeof window !== 'undefined' && window.PlotlyLocales && Array.isArray(window.PlotlyLocales)) {
62+
register(window.PlotlyLocales);
63+
delete window.PlotlyLocales;
64+
}
65+
66+
Plotly.Plots = {
67+
resize: PlotsModule.resize,
68+
graphJson: PlotsModule.graphJson,
69+
sendDataToCloud: PlotsModule.sendDataToCloud,
70+
};
71+
Plotly.Fx = {
72+
hover: FxModule.hover,
73+
unhover: FxModule.unhover,
74+
loneHover: FxModule.loneHover,
75+
loneUnhover: FxModule.loneUnhover,
76+
};
77+
78+
return Plotly;
79+
}
80+

0 commit comments

Comments
 (0)