The way Semiotic is bundled and shipped right now it includes all its dependencies as a part of the bundle which increases the size of the package and affects consumers bundles. It becomes harder to update dependencies versions and potentially introduce duplicated code. UMD format does not allow tree-shaking which also forces users to import from direct files. This PR aims at modernizing the build configs to ensure smaller bundle, better dependencies management, better compatibility with JS tooling and CDNs.
Breaking changes
Some changes directly affected how the package is distributed and consumed by developers. Here's a brief description.
The dist folder only includes bundled (via rollup) commonjs version semiotic.js and ES modules version semiotic.module.js. The package.json has been updated to work with both files (see fields main and module). Rollup now bundles semiotic.d.ts file with all necessary TypeScript declarations for users. semiotic.module.d.ts is created automatically for the sake of proper TS compatibility, but is not really different from the original file.
No more direct file imports or default Semiotic import
Given the new files structure, there is no longer a way to import some components directly, e.g. import XYFrame from 'semiotic/lib/XYFrame'. The only way to import things is by using named imports like import { XYFrame } from 'semiotic'. This improves developer experience, ensures proper TS types evaluation and helps build systems to eliminate unused code. sideEffects: false has been added to package.json to ensure it.
Since the docs suggest using syntax like import XYFrame from 'semiotic/lib/XYFrame', this change becomes a breaking change. The docs need to be updated and changelog needs to mention what can possibly break after upgrading the library.
Dependencies are not bundled
Before, several dependencies (e.g. D3 related libs, labella, etc) have been added to the resulting bundle and shipped to NPM as a part of Semiotic source code. This made it harder to updated these dependencies to fix bugs and this definitely adds unnecessary weight to users bundles if they have Semiotic dependencies used for something else (e.g. D3 libs).
Now, Rollup skips bundling Semiotic dependencies so they will be installed as a part of users code base. This makes the resulting bundle significantly smaller, also eliminating the need to minify the code. The code will still be minified on the users side (modern build system has it enabled by default) but during development process users will be able to dig into semiotic code if necessary.
Running npm publish --dry-run here is what stats I see on this branch:
When minification and gzip are applied to dist/semiotic.js, it becomes just 78.85KB in size.
UMD format is no longer available
UMD format is slowly dying and it is better to switch to ES Modules to fully support modern bundlers and other tooling. In fact, given browsers support of ES Modules, it is still very easy to use Semiotic in browser environment. README was already suggesting using unpkg.com, so I updated that example with the new approach:
<script type="module">
import { XYFrame } from "https://unpkg.com/semiotic?module"
</script>
Modern JavaScript CDNs like unpkg.com and esm.sh work great with ES Modules, properly resolving their dependencies and making the use of libraries a pure joy.
UMD format has been replaced with CommonJS + ES Modules bundles. ESM variant is the one that can be used directly in the browser (at least via CDNs like unpkg.com and esm.sh) and modern build systems can pick up ESM variant and tree-shake it. So CJS is just a fallback that will be gone in the future.
I've used rollup-plugin-auto-external to ensure that dependencies (e.g. d3-*, labella, etc) are not added to the resulting bundle. Bundling necessary dependencies together is a user concern: they either gonna use a build system that does it correctly, honoring correct versions, removing dead code, etc, or CDNs can resolve necessary dependencies on demand (unpkg.com and esm.sh doing it flawlessly). Fewer things to ship via NPM, and the browser use case is preserved.
Given previous point, I was able to remove plenty of plugins that no longer provide anything for the bundle
I've added rollup-plugin-bundle-size which shows the size of bundles after the build is finished. Useful to track unexpected changes
I've replaced rollup-plugin-typescript with rollup-plugin-ts which is able to bundle TS declaration as a single file (thus semiotic.d.ts was created), just like Rollup bundles the actual code. I needed to use v1.x of the plugin though, but I'll update it to latest as a part of upgrading the rest of dependencies
https://github.com/nteract/semiotic/commit/74f473f2fa00ef542d3851bdf4564755374d4c00 Updated some internal modules to use named exports so the resulting d.ts file has a proper structure and avoids issues. As an idea, we can completely get rid of default exports for internal stuff, to ensure everything has a name that can be referenced
Rationale
The way Semiotic is bundled and shipped right now it includes all its dependencies as a part of the bundle which increases the size of the package and affects consumers bundles. It becomes harder to update dependencies versions and potentially introduce duplicated code. UMD format does not allow tree-shaking which also forces users to import from direct files. This PR aims at modernizing the build configs to ensure smaller bundle, better dependencies management, better compatibility with JS tooling and CDNs.
Breaking changes
Some changes directly affected how the package is distributed and consumed by developers. Here's a brief description.
Improved files structure of Semiotic NPM package
The
dist
folder only includes bundled (via rollup) commonjs versionsemiotic.js
and ES modules versionsemiotic.module.js
. Thepackage.json
has been updated to work with both files (see fieldsmain
andmodule
). Rollup now bundlessemiotic.d.ts
file with all necessary TypeScript declarations for users.semiotic.module.d.ts
is created automatically for the sake of proper TS compatibility, but is not really different from the original file.No more direct file imports or default Semiotic import
Given the new files structure, there is no longer a way to import some components directly, e.g.
import XYFrame from 'semiotic/lib/XYFrame'
. The only way to import things is by using named imports likeimport { XYFrame } from 'semiotic'
. This improves developer experience, ensures proper TS types evaluation and helps build systems to eliminate unused code.sideEffects: false
has been added topackage.json
to ensure it.Since the docs suggest using syntax like
import XYFrame from 'semiotic/lib/XYFrame'
, this change becomes a breaking change. The docs need to be updated and changelog needs to mention what can possibly break after upgrading the library.Dependencies are not bundled
Before, several dependencies (e.g. D3 related libs,
labella
, etc) have been added to the resulting bundle and shipped to NPM as a part of Semiotic source code. This made it harder to updated these dependencies to fix bugs and this definitely adds unnecessary weight to users bundles if they have Semiotic dependencies used for something else (e.g. D3 libs).Now, Rollup skips bundling Semiotic dependencies so they will be installed as a part of users code base. This makes the resulting bundle significantly smaller, also eliminating the need to minify the code. The code will still be minified on the users side (modern build system has it enabled by default) but during development process users will be able to dig into semiotic code if necessary.
Running
npm publish --dry-run
here is what stats I see on this branch:When minification and gzip are applied to
dist/semiotic.js
, it becomes just78.85KB
in size.UMD format is no longer available
UMD format is slowly dying and it is better to switch to ES Modules to fully support modern bundlers and other tooling. In fact, given browsers support of ES Modules, it is still very easy to use Semiotic in browser environment. README was already suggesting using unpkg.com, so I updated that example with the new approach:
Modern JavaScript CDNs like unpkg.com and esm.sh work great with ES Modules, properly resolving their dependencies and making the use of libraries a pure joy.
Details
rollup-plugin-auto-external
to ensure that dependencies (e.g.d3-*
,labella
, etc) are not added to the resulting bundle. Bundling necessary dependencies together is a user concern: they either gonna use a build system that does it correctly, honoring correct versions, removing dead code, etc, or CDNs can resolve necessary dependencies on demand (unpkg.com and esm.sh doing it flawlessly). Fewer things to ship via NPM, and the browser use case is preserved.rollup-plugin-bundle-size
which shows the size of bundles after the build is finished. Useful to track unexpected changesrollup-plugin-typescript
withrollup-plugin-ts
which is able to bundle TS declaration as a single file (thussemiotic.d.ts
was created), just like Rollup bundles the actual code. I needed to use v1.x of the plugin though, but I'll update it to latest as a part of upgrading the rest of dependenciesprettier
as an explicit dev dependency to make sure everyone who works on Semiotic has the same version and the editor of choice can pick it uplabella
itself does not have named imports so it is better keep importing it as a single object to ensure proper compatibility for ESM version of the bundlesideEffect: false
topackage.json
ensures that users' bundler of choice can tree-shake the ESM version of Semiotic