Open joewestcott opened 1 year ago
This appears to be bugs (plural) in esbuild. The bwip-js ES6 modules are already properly structured for tree shaking and are verified to work with webpack.
The first bug I see is that esbuild is preferring the browser
entry in package.json
over the exports
map. Take a look at this module's package.json
:
...
"main": "./dist/bwip-js-node.js",
"browser": "./dist/bwip-js.js",
"exports": {
"browser": {
"import": "./dist/bwip-js.mjs",
"require": "./dist/bwip-js.js",
"script": "./dist/bwip-js-min.js"
},
...
When you look at the generated code, you will see esbuild pulled in the ./dist/bwip-js.js
code.
So I tried to trick esbuild into using the ES6 module by updating package.json
with "browser" : "./dist/bwip-js.mjs"
. That almost worked. It dead-code eliminated all of the symbol-specific export stubs except the ean13
export. Unfortunately, it kept the default export which prevented any substantive dead-code elimination. This is what I found in the generated code:
var bwip_js_default = {
// The public interface
toCanvas: ToCanvas,
render: Render,
raw: ToRaw,
fixupOptions: FixupOptions,
loadFont: FontLib.loadFont,
BWIPJS_VERSION: "3.4.4 (2023-07-27)",
BWIPP_VERSION,
// Internals
BWIPJS,
STBTT,
FontLib,
DrawingBuiltin,
DrawingCanvas
};
Because the default export is still present, the tree shaking can't happen.
Thanks for your insight. Are we hitting into this issue? https://github.com/evanw/esbuild/issues/1420
I don't believe that particular issue is the source of what we are seeing. We are not importing, then exporting namespaces. Similarly, we are not re-exporting the lower-level imports directly - our exports are new functions that reference the imports. Don't know if that is enough to trigger the behavior described.
What confuses me is why the default export is bundled. It is not referenced by the top-level code and should be eliminated.
Since esbuild seems to be an interesting bundler, I will play around with it some more to see if I can understand why it is including the default export.
But we are still facing the bug in esbuild where it is not prioritizing the exports
map in package.json
over the obsolete browser
field.
The default exports bug is now understood. If any of the exported values are unknown/untraceable, then esbuild gives up on the tree-shaking. Why an untraceable value in one module stops tree-shaking in another is beyond me - it should have no effect.
This is the default export in dist/bwip-js.mjs
:
export default {
// The public interface
toCanvas : ToCanvas, render : Render, raw : ToRaw,
fixupOptions : FixupOptions,
loadFont : FontLib.loadFont,
BWIPJS_VERSION : '3.4.4 (2023-07-27)',
BWIPP_VERSION : BWIPP_VERSION,
// Internals
BWIPJS, STBTT, FontLib, DrawingBuiltin, DrawingCanvas,
};
If the loadFont : FontLib.loadFont,
is commented out, the tree-shaking culls the result to approximately 150kb, a huge improvement over the previous 1.5mb.
So that bug is understood and can be worked around. But we need esbuild to properly resolve modules using the package.json
exports-map before it is worthwhile to implement any change in bwip-js. Using --log-level=verbose
gives a glimpse of what esbuild does to resolve the import statements, and it doesn't even look at the exports-map.
While looking over the esbuild source code, specifically internal/resolve.go
, I saw how to structure package.json
so it is compatible. Simply moving the main
and browser
fields below the exports
map makes the resolver logic happy. And this change should be 100% backward compatible with other package bundlers.
Release v3.4.5 also contains a wrapper around FontLib.loadFont()
for use in the default export. This makes esbuild's tree shaking happy as well. The end result:
$ cat esbuild.js
import { ean13 } from 'bwip-js';
console.log(ean13());
$ npx esbuild --bundle --outfile=bundle.js esbuild.js
bundle.js 164.9kb
⚡ Done in 335ms
Hi @metafloor, thanks for looking into this.
I'm quite surprised to see ESBuild working this way. Is this behaviour documented anywhere?
Happy to mark this issue as solved, although I would appreciate an answer regarding my PS to JS code question above. :)
Regarding the PS to JS, there is a develop
branch that I use to host the development tools. It is not well documented but basically the build process is:
$ ./psc
$ ./mkdist
$ node chkdist.js
$ ./runtests
$ ./imgtests
The psc
script builds src/bwipp.js
from barcode.ps
.
Here is the Vite work around.
export default defineConfig({
...
plugins: [react()],
resolve: {
alias: [
{
find: 'bwip-js',
replacement: resolve(
__dirname,
'node_modules/bwip-js/dist/bwip-js.mjs'
)
}
]
},
...
Enjoy!
I'd like to include this library in a front-end application, ideally without bundling the whole library. I don't think this is possible at the moment due to how it's structured. Have a look at the following example:
When bundling with ESBuild:
esbuild --bundle ./in.js --minify --outfile=out.js
The size of
out.js
is823.6kb
, which is the entire library, even though I only chose one symbology. It would also be nice if the symbol-specific exports didn't assume I require an output to a canvas.@metafloor Is it possible for others to contribute to this library? As far as I can tell, the code that does the postscript to js conversion hasn't been provided, it's only possible to see the output.