From e38d075fc270a2b2b57f9164ee654d5860bd0f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Wed, 3 Nov 2021 15:58:46 +0100 Subject: [PATCH] Dynamically import Vega-Lite JS when needed (#673) --- assets/js/vega_lite/index.js | 48 +++++++++++++++++++++++------------- assets/js/vega_lite/vega.js | 4 +++ assets/webpack.config.js | 10 ++++++++ 3 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 assets/js/vega_lite/vega.js diff --git a/assets/js/vega_lite/index.js b/assets/js/vega_lite/index.js index f304e8294..727c00719 100644 --- a/assets/js/vega_lite/index.js +++ b/assets/js/vega_lite/index.js @@ -1,8 +1,16 @@ -import * as vega from "vega"; -import vegaEmbed from "vega-embed"; import { getAttributeOrThrow } from "../lib/attribute"; import { throttle } from "../lib/utils"; +/** + * Dynamically imports the vega-related modules. + */ +function importVega() { + return import( + /* webpackChunkName: "vega" */ + "./vega" + ); +} + // See https://github.com/vega/vega-lite/blob/b61b13c2cbd4ecde0448544aff6cdaea721fd22a/src/compile/data/assemble.ts#L228-L231 const DEFAULT_DATASET_NAME = "source_0"; @@ -37,7 +45,10 @@ const VegaLite = { spec.data = { values: [] }; } - this.state.viewPromise = vegaEmbed(this.state.container, spec, {}) + this.state.viewPromise = importVega() + .then(({ vegaEmbed }) => { + return vegaEmbed(this.state.container, spec, {}); + }) .then((result) => result.view) .catch((error) => { const message = `Failed to render the given Vega-Lite specification, got the following error:\n\n ${error.message}\n\nMake sure to check for typos.`; @@ -57,10 +68,11 @@ const VegaLite = { this.state.viewPromise.then((view) => { const currentData = view.data(dataset); - const changeset = buildChangeset(currentData, data, window); - // Schedule resize after the run finishes - throttledResize(view); - view.change(dataset, changeset).run(); + buildChangeset(currentData, data, window).then((changeset) => { + // Schedule resize after the run finishes + throttledResize(view); + view.change(dataset, changeset).run(); + }); }); } ); @@ -84,17 +96,19 @@ function getProps(hook) { } function buildChangeset(currentData, newData, window) { - if (window === 0) { - return vega.changeset().remove(currentData); - } else if (window) { - const toInsert = newData.slice(-window); - const freeSpace = Math.max(window - toInsert.length, 0); - const toRemove = currentData.slice(0, -freeSpace); + return importVega().then(({ vega }) => { + if (window === 0) { + return vega.changeset().remove(currentData); + } else if (window) { + const toInsert = newData.slice(-window); + const freeSpace = Math.max(window - toInsert.length, 0); + const toRemove = currentData.slice(0, -freeSpace); - return vega.changeset().remove(toRemove).insert(toInsert); - } else { - return vega.changeset().insert(newData); - } + return vega.changeset().remove(toRemove).insert(toInsert); + } else { + return vega.changeset().insert(newData); + } + }); } export default VegaLite; diff --git a/assets/js/vega_lite/vega.js b/assets/js/vega_lite/vega.js new file mode 100644 index 000000000..d37712ca4 --- /dev/null +++ b/assets/js/vega_lite/vega.js @@ -0,0 +1,4 @@ +import * as vega from "vega"; +import vegaEmbed from "vega-embed"; + +export { vega, vegaEmbed }; diff --git a/assets/webpack.config.js b/assets/webpack.config.js index 06ce93511..813e94ba6 100644 --- a/assets/webpack.config.js +++ b/assets/webpack.config.js @@ -56,6 +56,16 @@ module.exports = (env, options) => { ], optimization: { minimizer: ["...", new CssMinimizerPlugin()], + splitChunks: { + cacheGroups: { + // Chunk splitting is by default enabled for all async chunks, + // so for the dynamically loaded js/vega_lite/vega.js Webpack + // would produce one almost empty chunk and then a vendor chunk + // with "vega" and "vega-embed". We want to dynamically load all + // of it at once, so we disable the default vendors chunk + defaultVendors: false + } + } }, resolve: { fallback: {