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

Create modular build of jsPDF #839

Open
agilgur5 opened this issue Sep 17, 2016 · 22 comments
Open

Create modular build of jsPDF #839

agilgur5 opened this issue Sep 17, 2016 · 22 comments

Comments

@agilgur5
Copy link

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

@MrRio
Copy link
Member

MrRio commented Sep 25, 2016

Yeah this is an interesting problem. How do other libraries tackle it?

@agilgur5
Copy link
Author

agilgur5 commented Sep 27, 2016

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 html2canvas instead 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 as Object.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 Remove unnecessary libs from dist #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.

@simonbengtsson
Copy link
Contributor

simonbengtsson commented Dec 29, 2016

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.js Needed for all browsers to be able to save files with doc.save() (FileSystem api discontinued so can't be considered a polyfill anymore).
  • adler32cs/adler32cs.js and deflate.js Needed only for compress feature (when compress option is set to true).

Plugin dependencies

  • css_colors.js Used by context2d plugin
  • html2canvas/html2canvas.js Used by addHTML plugin
  • png_support/png.js and png_support/zlib.js Used by png support plugin

Polyfills

  • polyfill.js Needed 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.js Extraneous 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.

@agilgur5
Copy link
Author

agilgur5 commented Jan 3, 2017

@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.

@sgelb
Copy link

sgelb commented Feb 1, 2017

I'd really appreciate some work in this direction. Can you comment on this idea, @MrRio?

@simonbengtsson
Copy link
Contributor

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.

@agilgur5
Copy link
Author

agilgur5 commented Feb 4, 2017

@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.

@MrRio
Copy link
Member

MrRio commented Sep 26, 2017

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.

@MrRio
Copy link
Member

MrRio commented Sep 26, 2017

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.

@Uzlopak
Copy link
Collaborator

Uzlopak commented Sep 26, 2017

@MrRio
Can I help you somehow with this task?

@Uzlopak
Copy link
Collaborator

Uzlopak commented Feb 25, 2018

Something I coded for fun and PoC https://arasabbasi.github.io/jsPDF/builder/index.html

@alex2wong
Copy link

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.

@Uzlopak
Copy link
Collaborator

Uzlopak commented Mar 25, 2019

#2356

@github-actions
Copy link

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.

@simonbengtsson
Copy link
Contributor

That would be sad so will comment to keep it open.

@HackbrettXXX
Copy link
Collaborator

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.

@mahenk1
Copy link

mahenk1 commented Jul 23, 2020

This is an amazing idea !
Posting to keep it alive !

@simonbengtsson
Copy link
Contributor

Would a PR with the libs dist folder discussed above be considered for merge? If so I can make an attempt.

@HackbrettXXX
Copy link
Collaborator

@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/lib folder

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/lib folder? We should probably combine some of the modules in src/modules, e.g. utf-8 and ttfsupport. The libs in the src/libs folder should probably be hidden under an internal folder, 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 (modernize output files, refactor testing, add deployment tests for di… #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.

@HackbrettXXX
Copy link
Collaborator

#2804 is merged now.

@HackbrettXXX
Copy link
Collaborator

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.

@esaesa
Copy link

esaesa commented Aug 20, 2022

Good idea

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants