Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Reduction of Bundle Size #7

Closed
MuslemRahimi opened this issue Nov 29, 2023 · 29 comments
Closed

Support Reduction of Bundle Size #7

MuslemRahimi opened this issue Nov 29, 2023 · 29 comments
Labels
enhancement New feature or request

Comments

@MuslemRahimi
Copy link

Hi,

I only use specific components of the Chart.js libary.
Is it possible in your svelte wrapper library to support the reduction/selection of specific components of Chart.js.

The library is quite big and any form of reduction would be awesome

@bherbruck
Copy link
Owner

I think that's on echarts, which is a peer dependency of this wrapper.
They have a way to package a subset of the library: https://echarts.apache.org/en/builder.html

Here you can see echarts package size compared to the svelte-echarts https://pkg-size.dev/svelte-echarts

$ du -hs ./node_modules/*echarts
44M     ./node_modules/echarts
36K     ./node_modules/svelte-echarts

HOWEVER:

Svelte appears to already be pruning the packages or something when you build the svelte project. Clone this repo and build it, the total final package size is 1.2M:

du -hs ./build
1.2M    ./build

It looks like that equates to 384kB gzipped:
image

@MuslemRahimi
Copy link
Author

Thanks for your reply.
I also found this link https://echarts.apache.org/en/builder.html however i didn't know how to apply this to sveltekit and your svelte wrapper for echarts. Is it possible to show a example how it is done?

It looks like that equates to 384kB gzipped:

Same for me. But 384kB is waayyy to big for a bar chart graph

@bherbruck
Copy link
Owner

bherbruck commented Nov 30, 2023

You make a good point.

I think you'd have to manually embed them in your project, or create a separate repo/npm package for each exported build from that. We could make something like @echarts-contrib/line with just the prepackaged output of that site. Maybe even automate it to watch the builds of echarts and generate each one.

I'd also have to remove echarts as a peer dependency of svelte-echarts, then it'd be installed like npm install svelte-echarts @echarts-contrib/line

@bherbruck
Copy link
Owner

I found this too, I'll look into it

https://apache.github.io/echarts-handbook/en/basics/import/

@MuslemRahimi
Copy link
Author

I'd also have to remove echarts as a peer dependency of svelte-echarts, then it'd be installed like npm install svelte-echarts @echarts-contrib/line

That would be really cool.
Reducing the bundle size would be a huge game-changer for me!

@MuslemRahimi
Copy link
Author

Any new updates regarding the bundle size

@bherbruck
Copy link
Owner

No, I've tried but have been unsuccessful getting this to tree shake effectively. I might need help on this one

@MuslemRahimi
Copy link
Author

Thank you for your effort. Im not sure if I am helpful. But if you need another hand let me know

@bherbruck
Copy link
Owner

@MuslemRahimi that would be very helpful if you could take a look at it

@bherbruck
Copy link
Owner

UPDATE

Bad news, looks like echarts tree-shaking API is an issue right now when using echarts with vite.

Maybe there is a way to get vite/svelte build options to treat echarts/core as ESM?

The issue appears to be related to the way echarts/core handles imports/exports. It is an ES-module but the package.json still assumes commonjs. See this issue: apache/echarts#16709

echarts seems to have had this issue with vite for a while https://github.com/apache/echarts/issues/15138 and

Looks like it is working in vue-echarts with webpack/rollup (no vite)

When using the recommended import * as echarts from 'echarts/core';, I get syntax errors due to echarts being commonjs but echarts/core is uses explicit import/export syntax.

Here's the work I've done in a new branch for testing:

import * as echarts from 'echarts/core.js'

It produces this error:

/workspaces/svelte-echarts/node_modules/echarts/core.js:20
export * from './lib/export/core.js';
^^^^^^

SyntaxError: Unexpected token 'export'
    at internalCompileFunction (node:internal/vm:77:18)
    at wrapSafe (node:internal/modules/cjs/loader:1288:20)
    at Module._compile (node:internal/modules/cjs/loader:1340:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at cjsLoader (node:internal/modules/esm/translators:345:17)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:294:7)
    at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24

@rumihumphrey
Copy link

Not sure if this helps, I was able to get the reduced bundle size working with Echarts in Sveltekit with the subset of the library: https://echarts.apache.org/en/builder.html by adding it to my app.html like this:
<script src="/echarts.min.js"></script>
and then something like this in my component:

const makeChart = () => {
	destroyChart();
	chart = echarts.init(chartContainer, theme);
	setOption();
};

onMount(() => {
	makeChart();
});

While this works it is not ideal for me as I only want to load echarts it in my charts component, not for the whole app. Still I'm using this for now. It might be more useful for someone that doesn't have this requirement of only wanting to load it dynamically.

All my attempts to load it dynamically resulted in File '/etc/echarts.js' is not a module errors not sure if there is a way to wrap the file so that it doesn't complain about this, or some other technique for loading the file.

@MuslemRahimi
Copy link
Author

const makeChart = () => {
destroyChart();
chart = echarts.init(chartContainer, theme);
setOption();
};

onMount(() => {
makeChart();
});

Can you share your full code. I couldn't reproduce it yet!

@rumihumphrey
Copy link

rumihumphrey commented Jan 24, 2024

here is my src/app.html:

<!doctype html>
<html lang="en" class="dark">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		%sveltekit.head%
	</head>
	<body data-theme="skeleton" data-sveltekit-preload-data="hover">
		<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
		<script src="/echarts.min.js"></script>
	</body>
</html>

my component, I'm new to SvelteKit so not sure I'm doing everything correctly but it works for me:

<script lang="ts">
	import { onMount, onDestroy } from 'svelte';
	export let id;
	export let theme = 'dark';
	export let option = {};
	export let notMerge = false;
	export let replaceMerge = false;
	export let lazyUpdate = false;
	let chart: any;
	let chartContainer: HTMLElement;
	let resizeObserver: any;

	const setOption = () => {
		if (chart && !chart.isDisposed()) {
			chart.setOption(option, notMerge, replaceMerge, lazyUpdate);
		}
	};

	const destroyChart = () => {
		if (chart && !chart.isDisposed()) {
			chart.dispose();
		}
	};

	const makeChart = () => {
		destroyChart();
		// echarts is loaded in app.html using a reduced bundle size
		// @ts-ignore
		chart = echarts.init(chartContainer, theme);
		setOption();
	};

	onMount(() => {
		makeChart();
		resizeObserver = new ResizeObserver((entries) => {
			for (let entry of entries) {
				if (entry.target === chartContainer) {
					if (chart && !chart.isDisposed()) {
						chart.resize();
					}
				}
			}
		});
		resizeObserver.observe(chartContainer);
	});

	onDestroy(() => {
		destroyChart();
		if (resizeObserver) {
			resizeObserver.disconnect();
		}
	});

	$: option && setOption();
	$: chart && theme && makeChart();
</script>

<div bind:this={chartContainer} class=" h-full w-full"></div>

@rumihumphrey
Copy link

This is interesting it looks like a fix for this is in v 5.5.rc1, not sure when 5.5 will get released but good news when it does I think it may solve the issue, granted once/if the implementation details are worked out. Would be nice if this could work in Sveltekit with svelte-echarts at some point and I wouldn't have to use the hacky solution I posted, I like the library here but didn't know how to get around this issue with it, maybe that could change with 5.5.

@MuslemRahimi
Copy link
Author

This is interesting it looks like a fix for this is in v 5.5.rc1, not sure when 5.5 will get released but good news when it does I think it may solve the issue, granted once/if the implementation details are worked out. Would be nice if this could work in Sveltekit with svelte-echarts at some point and I wouldn't have to use the hacky solution I posted, I like the library here but didn't know how to get around this issue with it, maybe that could change with 5.5.

I did not know about this pull request. Really nice. @bherbruck can we add this to your library?

@bherbruck
Copy link
Owner

@vdiff looks like that will fix it as ESM errors are exactly the problem!

@MuslemRahimi do you want to switch to a release candidate now? Maybe we can make our own rc version denoting the inclusion of the echarts rc? I think the branch is public if you want to see if the build succeeds on the echarts 5.5 rc1 version from NPM.

@MuslemRahimi
Copy link
Author

@vdiff looks like that will fix it as ESM errors are exactly the problem!

@MuslemRahimi do you want to switch to a release candidate now? Maybe we can make our own rc version denoting the inclusion of the echarts rc? I think the branch is public if you want to see if the build succeeds on the echarts 5.5 rc1 version from NPM.

sounds good. I think adding our own rc version would be better and quicker before the actual stable release of echarts.

@bherbruck
Copy link
Owner

bherbruck commented Jan 31, 2024

Looks like that works! The only thing to think about now is how we handle whether to make tree shaking optional or required.
The API is way different for the tree-shakable mode and more verbose, that's why this exists: https://vue-echarts.dev/#codegen (not ideal).

Here's the working version of the demo code from the repo:

<script lang="ts">
  import * as echarts from 'echarts/core'
  import { BarChart } from 'echarts/charts'
  import { GridComponent } from 'echarts/components'
  import { CanvasRenderer } from 'echarts/renderers'
  echarts.use([BarChart, GridComponent, CanvasRenderer])

  import { Chart, type EChartsOptions } from '$lib'

  const options: EChartsOptions = {
    xAxis: {
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
      type: 'category',
    },
    yAxis: {
      type: 'value',
    },
    series: [
      {
        data: [820, 932, 901, 934, 1290, 1330, 1320],
        type: 'bar',
      },
    ],
  }
</script>

<div class="app">
  <Chart {options} />
</div>

<style>
  .app {
    width: 100vw;
    height: 100vh;
  }
</style>

Bundle size is almost 4x smaller!

image

@bherbruck
Copy link
Owner

If anyone wants to update the tests, feel free

@rumihumphrey
Copy link

Nice one, that's awesome, look forward to using it!

That codegen is cool, I hadn't seen that. I have used this library echarts-for-react with React and it's pretty nice. They don't have a codegen like that though, and honestly it has tripped me up a couple times trying to figure out what all I'm using, like I didn't think mark lines etc would need to import a component but they do.

Anyway they provide a nice list of options, not sure if it makes sense to do something like that, although that codegen is pretty sweet.

import React from 'react';
// import the core library.
import ReactEChartsCore from 'echarts-for-react/lib/core';
// Import the echarts core module, which provides the necessary interfaces for using echarts.
import * as echarts from 'echarts/core';
// Import charts, all with Chart suffix
import {
  // LineChart,
  BarChart,
  // PieChart,
  // ScatterChart,
  // RadarChart,
  // MapChart,
  // TreeChart,
  // TreemapChart,
  // GraphChart,
  // GaugeChart,
  // FunnelChart,
  // ParallelChart,
  // SankeyChart,
  // BoxplotChart,
  // CandlestickChart,
  // EffectScatterChart,
  // LinesChart,
  // HeatmapChart,
  // PictorialBarChart,
  // ThemeRiverChart,
  // SunburstChart,
  // CustomChart,
} from 'echarts/charts';
// import components, all suffixed with Component
import {
  // GridSimpleComponent,
  GridComponent,
  // PolarComponent,
  // RadarComponent,
  // GeoComponent,
  // SingleAxisComponent,
  // ParallelComponent,
  // CalendarComponent,
  // GraphicComponent,
  // ToolboxComponent,
  TooltipComponent,
  // AxisPointerComponent,
  // BrushComponent,
  TitleComponent,
  // TimelineComponent,
  // MarkPointComponent,
  // MarkLineComponent,
  // MarkAreaComponent,
  // LegendComponent,
  // LegendScrollComponent,
  // LegendPlainComponent,
  // DataZoomComponent,
  // DataZoomInsideComponent,
  // DataZoomSliderComponent,
  // VisualMapComponent,
  // VisualMapContinuousComponent,
  // VisualMapPiecewiseComponent,
  // AriaComponent,
  // TransformComponent,
  DatasetComponent,
} from 'echarts/components';
// Import renderer, note that introducing the CanvasRenderer or SVGRenderer is a required step
import {
  CanvasRenderer,
  // SVGRenderer,
} from 'echarts/renderers';

// Register the required components
echarts.use(
  [TitleComponent, TooltipComponent, GridComponent, BarChart, CanvasRenderer]
);

// The usage of ReactEChartsCore are same with above.
<ReactEChartsCore
  echarts={echarts}
  option={this.getOption()}
  notMerge={true}
  lazyUpdate={true}
  theme={"theme_name"}
  onChartReady={this.onChartReadyCallback}
  onEvents={EventsDict}
  opts={}
/>

@rumihumphrey
Copy link

I am happy to update the tests or at least take a stab at it, but with work stuff I may not have time for a few days, so kinda depends on how fast you want it.

@bherbruck
Copy link
Owner

<ReactEChartsCore echarts={echarts} />

"injecting" the echarts instance and having this wrapper as essentially type/api-only may be a good way to do it! I really like that. All the tests could probably pass in that scenario too, they're super simple though. The only thing we might lose is some actions/reactivity stuff I've been working on, maybe not though.

@rumihumphrey
Copy link

Glad to hear that you might have been able to glean some techniques from the react echarts library. Hope that works out. I wasn't really sure if you still wanted help with writing tests or anything like that, but let me know if there is anything I can contribute.

@bherbruck
Copy link
Owner

Glad to hear that you might have been able to glean some techniques from the react echarts library. Hope that works out. I wasn't really sure if you still wanted help with writing tests or anything like that, but let me know if there is anything I can contribute.

Time is tight right now, I could use the help

@rumihumphrey
Copy link

Time is tight right now, I could use the help

I have been trying to help with this a bit this week, time is tight for me too with work but I can work on it a bit in free time, I tried RC2 for 5.5 thinking it might help with some of the typing, release notes seemed like it might. So far haven't had much luck. I'm going to keep messing with it, will keep you posted if I have any luck, so far mostly just trying to get the tests to run.

@MuslemRahimi
Copy link
Author

Any new updates regarding the bundle size?

@rumihumphrey
Copy link

Any new updates regarding the bundle size?

Echarts 5.5.0 was officially released last week. I've been using it. Trying to get the tests to run with the new tree shaking branch, so far only got the first test to run. Finding it hard so far to get the other tests to run with the same echarts instance. Haven't had much time, but will continue to try when I can.

@MuslemRahimi
Copy link
Author

Any new updates regarding the bundle size?

Echarts 5.5.0 was officially released last week. I've been using it. Trying to get the tests to run with the new tree shaking branch, so far only got the first test to run. Finding it hard so far to get the other tests to run with the same echarts instance. Haven't had much time, but will continue to try when I can.

Thanks for the heads up :)

@bherbruck
Copy link
Owner

This is implemented in v1.0.0-rc1

~50% reduction in bundle size

<script>
  import { Chart } from 'svelte-echarts'

  import { init, use } from 'echarts/core'
  import type { EChartsOption } from 'echarts'
  import { BarChart } from 'echarts/charts'
  import { GridComponent, TitleComponent } from 'echarts/components'
  import { CanvasRenderer } from 'echarts/renderers'

  // now with tree-shaking
  use([BarChart, GridComponent, CanvasRenderer, TitleComponent])
  
  let options = {
    // some options...
  }

</script>

<div class="app">
  <Chart {init} {options} />
</div>

@bherbruck bherbruck added the enhancement New feature or request label May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants