jsPDF
jsPDF copied to clipboard
Create modular build of jsPDF
Right now nearly all jsPDF plugins and polyfills are packaged together with the main build, resulting in the minified bundle being ~300KB in size and growing.
Developers should choose which plugins and polyfills they want, instead of jsPDF choosing for them. I only use images, text, and pages, and only need FileSaver as a polyfill. The rest of the functionality is entirely unnecessary to me and unnecessarily uses up the network bandwidth and CPU when a user's browser downloads + parses the giant file (not to mention it's just one of many other dependencies in a project). There's no need for me to have to install and package up all of html2canvas and downloadify when I don't even use them. Or all of requirejs (which causes compatibility problems very easily), Blob.js, and the canvas implementation, if I don't need them. etc. etc.
With a modular build I would do something like:
import jsPDF from 'jsPDF' // basic jsPDF functionality
import 'jsPDF-addImage' // add addImage functionality to jsPDF
import 'file-saver' // FileSaver polyfill
This would drastically reduce the size of the core jsPDF library, abstract out all the logic that is handled by plugins, make creating, updating, and managing plugins significantly easier, and entirely remove polyfills from the codebase. People would also be able to easily fork and create their own versions of existing plugins (like https://www.npmjs.com/package/jspdf-autotable)
As the plugins + polyfills are all in separate directories already and imported very similarly in https://github.com/MrRio/jsPDF/blob/master/main.js , this doesn't seem like it would be altogether difficult to implement
Yeah this is an interesting problem. How do other libraries tackle it?
There are several actionable steps
- [ ] Remove all polyfills from the codebase. Add them to the documentation and let developers know what they might need to use those polyfills for; what features use which polyfills.
- [ ] Move each plugin to a separate repository, perhaps under an umbrella "jsPDF" organization. If certain polyfills were only required for that plugin, move that documentation too. Potentially have separate maintainers for each of these plugins.
- [ ] Create packages for each of these plugins. These packages should also depend on existing packages instead of bundling the entire source code of a dependency (e.g.
npm i -S html2canvasinstead of having a subtree that contains a very old version of html2canvas) - [ ] Create a simple way for plugins to be used. With tiny changes to the current codebase (larger changes could make this system better), one could just add a
addPlugin(plugin)function to jsPDF-core, which could do something as simple asObject.assign(this, plugin)to attach any of the plugins exported methods to the prototype. A more complex way could be the one of the many ways in which one can use or build a modular version of lodash - [ ] Remove any extraneous/outdated code. Like #716 there seems to be a great deal of this in the codebase.
Further steps would involve automated testing and automated builds, such that a new version can be automatically released on NPM as soon as any change happens with very little hassle, among other steps.
I can get started on this perhaps by next week, as there is noticeable lag my users currently see due to the load time increase by this library. It would have to be done as several PRs and as the current maintainer it's up to you how you want each of the plugin repositories to be created (under an umbrella organization or not) and who the maintainers should be.
I have been thinking of this as well. What is the status of this now @agilgur5? Any progress?
Inspired by angular and bootstrap I suggest we export umd modules of jspdf core, plugins and third party code into a new dist folder called for example lib. These could then be used like @agilgur5 proposed above with es2015 modules or something like this with commonjs:
var jsPDF = require('jspdf/lib/core');
require('jspdf/lib/total-pages');
require('jspdf/lib/polyfills');
// Note that require('jspdf') would still import `jspdf.debug.js` and therefore preserve backwards compatibility and would be easier to use for developers
Since this only adds a folder with additional dist files it could be done with complete backwards compatibility. Would you accept a PR adding this folder @MrRio?
Discussion
After reading @agilgur5 comment above I looked up which and where third party code are actually in use by jspdf. Here is a categorized list of current (v1.3.2) included third party code:
Dependencies
filesaver.js/FileSaver.jsNeeded for all browsers to be able to save files withdoc.save()(FileSystem api discontinued so can't be considered a polyfill anymore).adler32cs/adler32cs.jsanddeflate.jsNeeded only for compress feature (when compress option is set to true).
Plugin dependencies
css_colors.jsUsed by context2d pluginhtml2canvas/html2canvas.jsUsed by addHTML pluginpng_support/png.jsandpng_support/zlib.jsUsed by png support plugin
Polyfills
polyfill.jsNeeded for IE10 support. Includes the following polyfills: btoa <IE10, atob <IE10, Array.isArray <IE9, [].forEach <IE9, [].map <IE9, Object.keys <IE9, "".trim <IE9. Also contains none standardized "".trimLeft, "".trimRight.cf-blob.js/Blob.jsExtraneous since every time Blob is used ArrayBuffer and Uint8Array is also used which is not polyfilled and has the same browser support as Blob (>IE10).
@agilgur5 also mentions other interesting things, but I think simply enabling importing parts of jspdf would be a good first step.
@simonbengtsson I have not started this. This requires a fundamental shift in how the library is maintained moving forward (otherwise the proposal is useless), as well as potentially the creation of an umbrella organization, and therefore all the maintainers should agree to this. As you can see, not a single one, including @MrRio, has even replied to my proposal. This is the way all modern libraries are organized, so the hold up is unfortunate.
Exporting to a dist or lib folder is also possible, though I think it's far easier for plugin maintainers to be able to have separate repositories for their plugins. A plugin should not need to be PR'ed and approved against the core codebase as it's entirely independent of the core codebase (aside from a peerDependency to it).
That being said, I like the idea of keeping "complete backwards compatibility" (jspdf for whole, jspdf/lib/core for just core) for some deprecation window.
Thanks for listing out all the dependencies @simonbengtsson! File-saving and compression I believe should also be opt-in and can also be made as plugins, which would move the first two to "Plugin dependencies". Good note that Blob.js is extraneous! I personally think all of the polyfills are now extraneous as Microsoft no longer supports < IE11 -- if a developer needs/wants to support them, they can import polyfills however they want (e.g. ES5 core-js) instead of using polyfill.js which may be duplicative of existing polyfills in their own codebase.
I'd really appreciate some work in this direction. Can you comment on this idea, @MrRio?
I agree with all your points @agilgur5. However I'm leaning towards that the complexity of having different repos would not be worth it. It might be easier to go for the monorepo approach for now. At least until an active maintainer wants to take responsibility of a certain plugin.
@simonbengtsson using lerna and managing a monorepo has it's own overhead as well that should not be ignored -- it's not altogether different from one group maintaining multiple repositories/plugins except that issues are all in one place (which may not be preferable for a package that gets so many issues already, many of which are low-quality/not descriptive). The monorepo approach namely works well for Babel because the codebase is split into lots of tiny packages + plugins that are meant to be composed together. Very few codebases are similar in structure, and many of the jsPDF plugins are quite large. Also, it's important to note that exporting to a dist or lib folder is different from a monorepo -- the former means when you npm install/yarn add jsPDF, you have to download all plugins and plugin dependencies in one go (which in some cases are quite large), the latter means you choose what you want to download. Both mean you choose what you use.
In any case, I don't see much value in discussing repository structure unless one of us will be an active maintainer, as all the current maintainers are inactive and don't follow any modern repository structure techniques yet.
I think we need to fix this soon. I agree that we need to modernise the way jsPDF can be imported. Thank you for your comments so far.
Our current plan is to try and get any currently working and mergeable PRs merged before the refactor starts. The main one is utf-8 support. We’ll then format using StandardJS or a popular ESLint config, then we’ll start modernising each file.
The way the plugins are currently make them very ‘pick and mix’. We should try slim down the core also, but we need to run some analysis on which plugins rely on which feature.
@MrRio Can I help you somehow with this task?
Something I coded for fun and PoC https://arasabbasi.github.io/jsPDF/builder/index.html
Any update on modular build of jsPDF ? currently the jspdf.min.js are still over 300Kb. I know this refactor work would involve much work, not sure how long it would take to modernize the structure of core and plugins.
#2356
This issue is stale because it has been open 90 days with no activity. It will be closed soon. Please comment/reopen if this issue is still relevant.
That would be sad so will comment to keep it open.
Yes, this is definitively a good idea but impossible without extensive help from the community. #2804 is a small step in the right direction (at least the polyfills are no longer bundled). Let's keep this open for the future.
This is an amazing idea ! Posting to keep it alive !
Would a PR with the libs dist folder discussed above be considered for merge? If so I can make an attempt.
@simonbengtsson a pull request would be very much appreciated :)
You can probably base on the changes I made in #2804 and add a couple more entry points to the rollup config for the files in the dist/lib folder.
Things to discuss:
- We should probably have such a lib folder for each of the three module formats: UMD/node/es6. I see three different options:
- Three lib folders:
dist/lib-es,dist/lib-umd,dist/lib-node - One lib folder with subfolders:
dist/lib/es,dist/lib/umd,dist/lib/node - Three versions for each file in the
dist/libfolder
- Three lib folders:
I think the first two options are better than the third one, because the folders won't be as cluttered in code completion, etc.
-
Which modules do we expose in in the
dist/libfolder? We should probably combine some of the modules insrc/modules, e.g.utf-8andttfsupport. The libs in thesrc/libsfolder should probably be hidden under aninternalfolder, etc. My suggestion here:- acroform as is
- maybe rename addimage to "image" or something
- annotations as is
- arabic as is (maybe combine with other font-related modules)
- autoprint as is
- rename bmp_support to "image-bmp"
- combine canvas and context2d to "canvas" module
- rename cell to "table"
- not sure if we should expose fileloading - seems more like internal API
- filters as is
- rename gif_support to "image-gif"
- html as is
- javascript as is
- rename jpeg_support to "image-jpeg"
- outline as is
- rename png_support to "image-png"
- rename setlanguage to language
- split_text_to_size and standard_fonts_metrics should be bundled with core
- svg as is
- maybe bundle total_pages with core because its so small? Doesn't feel like a real module to me...
- combine ttfsupport and utf8 to "unicode" (what's a good name here?)
- vfs as is (or bundle with core?)
- viewerpreferences as is
- rename webp_support to "image-webp"
- rename xmp_metadata to "metadata-xmp" (or even omit the "xmp")
-
How can we make this approach work with TypeScript and the .d.ts file?
-
How does this approach work with 3rd party plugins like AutoTable or svg2pdf? With this approach we introduce two different "worlds" that cannot be combined. Maybe we should not provide the pre-bundled dist files at all (or only the UMD version)? Would be a major breaking change, though (#2804 gives different names to the dist files anyways, so this is not too bad).
-
We should probably document which modules need to be loaded in the JSDoc of the respective methods.
What are your thoughts?
Before you start with the pull request, I think it is best if I merge #2804 first.
#2804 is merged now.
The getTextDimensions method from cell.js should probably be moved the core or split_text_to_size. With #2824 it will have a dependency on split_text_to_size.
Good idea