Skip to content

Better modularization of our plugins - #22888

Closed
Johann-S wants to merge 9 commits into
v4-devfrom
webpack
Closed

Johann-S wants to merge 9 commits into
v4-devfrom
webpack

Conversation

@Johann-S

@Johann-S Johann-S commented Jun 22, 2017 •

Copy link
Copy Markdown
Member

This PR will allow folks to import only modules they want to use for example :

import { Util, Modal } from 'bootstrap'

This PR introduce the use of rollup and add two new dist files :

  • bootstrap.bundle.js
  • bootstrap.bundle.min.js

This two files include Popper.js so when you include those files in your HTML pages you don't have to include Popper.js before Bootstrap.

/CC @mdo @petetnt @bardiharborow @hnrch02
Close: #19017, #22783

Edit :
/CC @IdanCo if they are something to change in our webpack get started example
I know the branch of this PR is webpack but finally rollup allow us to have smalls dist files

@Johann-S Johann-S added js JavaScript or TypeScript sources and plugins meta The repository itself: templates, guidelines, config v4 v4, the frozen docs on gh-pages labels Jun 22, 2017
@IdanCo

IdanCo commented Jun 22, 2017

Copy link
Copy Markdown
Contributor

What a blessed PR! Finally bootstrap is aligning with modern module systems! Can't wait to dig in, probably this weekend

Comment thread js/tests/visual/alert.html Outdated
</div>

<script src="../../../assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../assets/js/vendor/popper.min.js"></script>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to remove 😆

@mdo
mdo requested review from bardiharborow, cvrebert and fat June 22, 2017 15:30
@petetnt

petetnt commented Jun 22, 2017

Copy link
Copy Markdown
Contributor

Cool stuff! I'll be pretty offline for the finnish midsummer time, but I'd love to review this early next week!

@FezVrasta

Copy link
Copy Markdown
Contributor

I'd suggest to go with Rollup instead of Webpack since it's best suited for libraries bundling (the webpack maintainers think the same).

This would also reduce the bundle size

@Johann-S

Johann-S commented Jun 23, 2017 •

Copy link
Copy Markdown
Member Author

I'll give another try to Rollup in another branch and if I have something something better I'll incorporate that here thanks @FezVrasta 👍

Edit :
First try with Rollup for our dist/js/bootstrap.js :

  • With Webpack : 132 kb,
  • With Rollup : 106 kb

Switch to rollup done 👍

Comment thread package.json Outdated
"style": "dist/css/bootstrap.css",
"sass": "scss/bootstrap.scss",
"main": "dist/js/bootstrap",
"main": "src/js/index",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you were aiming for, but no need to change the main file

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because with that entry point we can load only required plugins it allow folks to make small bundle

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't point to the source but to a es5 transpiled version

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @FezVrasta (and in any case i think you meant js/src and not src/js)

Nevertheless, there is still some issue with importing specific modules (it happens with either main file) . I havn't figured out the root cause, but the symptom is that both import "bootstrap" and import { module } from "bootstrap" ends up loading ALL modules.

To test it i've created a new branch of an empty scaffold project, and added @Johann-S latest commit as a dependency. Feel free to clone and do npm install. Then npm run serve will run the app with the new dependency. notice that you should manually fix "package.json" in the bootstrap directory to point to the right file (dist/js/bootstrap) before serving.

And on a side note - great job @Johann-S ! With this new build, importing bootstrap with webpack (or any other bundler for that matter) becomes a simple one liner. Sadly it will render the entire installation guide i wrote obsolete, but i'll cope ;-)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I forgot to mention that the Es5 + es modules version should be defined in the "module" property, while the es5 w/o es modules (umd usually) should point to "main"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: ends up loading ALL modules issue is documented in my original issue #19017. Re-exported modules will call all the IIFEs when the index file is evaluated, leading into namespace pollution. See https://codesandbox.io/s/vLXO14lg for an example.

In my branch I opted in for having module users to initialize their own components with ModuleNameHere.init(), while the browser bundles (including single modules, like tooltip.js) auto-initialize themselves with a secret variable ([1] [2]) which solves the problem but creates some cognitive load for the end user.

@petetnt petetnt Jun 25, 2017 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way would be lazily evaluate the modules with import() call, but not sure if that would work either.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lazy load is overkill, better use named experts for each plugin/module so that people can import just what they need

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's only possible by importing the modules with import Tooltip from 'bootstrap/js/some-module-name'. Importing the modules anywhere (like in index.js) evaluates the files as evident in https://codesandbox.io/s/Mjgm6qzAm, rendering import { SomeModule } from 'bootstrap' pretty moot as it's always equivalent of import Bootstrap from 'bootstrap'.

Userland could adapt some bundler that's smart enough to tree-shake the other modules but even that might be a scretch. Do correct me if I am wrong though.

@petetnt

petetnt commented Jun 26, 2017

Copy link
Copy Markdown
Contributor

I was thinking about the modularization part last night and my conclusions were something like this (if my idea of #22888 (comment) is correct):

  • Without any escape hatch for preventing auto-init importing all the modules essentially boils down to import 'bootstrap' as the modules don't really have that many static methods available that would be useful outside of the modules themselves.
  • Importing import { Tooltip, Modal } from 'bootstrap' most likely is not going to happen without any side-effects...
  • however, releasing the modules as namespaced versions (for example @bootstrap/tooltip, @bootstrap/modal on npm would provide modularity for those who want to include only parts of the modules themselves (which is actually pretty nice, as proven for example by https://lodash.com/ and its namespaced modules (https://www.npmjs.com/browse/keyword/lodash-modularized).
  • one could also import the modules from the folders with import 'bootstrap/js/tooltip' or import 'bootstrap/js/modal'.

@Johann-S

Johann-S commented Jun 26, 2017 •

Copy link
Copy Markdown
Member Author

I have some tests to do with all your comments thanks @petetnt, @FezVrasta and @IdanCo

I'm not sure if it's the best way but something like this works very well to import only needed plugins :
import Modal from 'bootstrap/js/dist/modal'

@FezVrasta

Copy link
Copy Markdown
Contributor

This is the same as import { Modal} from 'bootstrap', but the latter works better and follows the conventions

@Johann-S

Johann-S commented Jun 26, 2017 •

Copy link
Copy Markdown
Member Author

Unfortunately when I did import { Modal } from 'bootstrap' @FezVrasta every other plugins were imported and not only Modal plugin

Edit :
@petetnt that's right what did lodash here : https://github.com/lodash/lodash/tree/npm-packages allow to export plugins separately, but it seems pretty hard to maintain a separate branch only for our js plugins

@FezVrasta

FezVrasta commented Jun 26, 2017 •

Copy link
Copy Markdown
Contributor

This means there's something wrong with your code. I'd say you may want to merge this PR just to provide the needed tooling. Then we may work together on future PRs to improve the code splitting.

I may even work on this personally once this PR is merged

@IdanCo

IdanCo commented Jun 26, 2017

Copy link
Copy Markdown
Contributor

I think you're in the right direction. IMO, loading the entire library should remain as simple as possible. importing individual modules is usually the doing of more advanced users, in which case bootstrap can allow itself a bit more complex implementation.

In any case i'll do some testings later today to see if it's possible to hold the stick on both ends by fixing the "all or nothing" issue.

@petetnt

petetnt commented Jun 26, 2017 •

Copy link
Copy Markdown
Contributor

@FezVrasta There's nothing wrong with the code, as I stated above, import { Modal } from 'bootstrap' is not equivalent of import Modal from 'bootstrap/js/dist/modal'. It's just that the index.js file is evaluated when import * from 'bootstrap' is called like I explained before.

I think lodash is now built with lodash-cli/ nowadays but building 9 individual files with RollUp and creating a simple release script would not be too time consuming, it can be done later too though.

@Johann-S

Copy link
Copy Markdown
Member Author

I wont merge this PR untily Beta 1 is shipped, but you can submit a PR which target this branch @FezVrasta 👍

Thanks @IdanCo, do not hesitate to let us know what result you had

@petetnt with this PR rollup build seperately each of our files with build/build-plugins.js script but yes it's a good idea to create a release script for that 👍

With all your feedbacks a good beginning should be what we have currently, allow user to import only needed plugins with import { <plugin name> } from 'bootstrap/js/dist/<plugin name> and for folks who needs the entire libs allow us to do import { <plugin name> } from 'bootstrap' or simply import 'bootstrap'

@FezVrasta

FezVrasta commented Jun 26, 2017 •

Copy link
Copy Markdown
Contributor

@petetnt the whole idea about named exports is that you can import the library and have three shake remove unused code super easily.

Doing import Modal from 'bootstrap/js/dist/modal' is just a way to import a single file, but it doesn't take advantage of the ES Modules architecture and relies on internal structure of the library, which may change in the future and break others code.

To test that the library properly uses static imports and ES Modules you should import it in a dummy project with wepkack or Rollup, both of them support three shake, and the outputted bundle should include only what you actually imported.

@petetnt

petetnt commented Jun 26, 2017

Copy link
Copy Markdown
Contributor

@FezVrasta I already mentioned userland treeshaking in #22888 (comment). The issue is that will tree shakers eliminate the IIFEs that pollute global namespace, especially when it's done against the already-once built files (when things like Webpack 2 have had problems with treeshaking and re-exports in general: webpack/webpack#2867).

If it works, then fine, but the side-effect is still unwanted and unexpected in general.

@FezVrasta

Copy link
Copy Markdown
Contributor

@petetnt I think we should look forward and comply with the modern standards (in this case, named exports + three shaking) and don't stagnate on old habits.

In this case, IIFE and UMD aren't bundle formats that ever considered three shaking as part of them, and nobody is trying to make them work with it.
We should provide ability to three shake just to who actually uses a tooling capable to handle it properly.

Comment thread js/src/index.js Outdated

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I export our plugins with export default but I'm not sure if this is the good way to have allow folks to :
import { Util, Modal } from 'bootstrap' ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, if you export default { stuff } you are exporting an object with inside the modules.

If you want to allow users to import single modules, you have to do export { stuff1, stuff2, etc }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @FezVrasta 👍

@FezVrasta

Copy link
Copy Markdown
Contributor

Related to this:
#22888 (comment)

Nothing prevent us from providing both the solutions, if people use modern toolings, they will want to use the ES5 + ESM bundle to take advantage of three shaking.

If they use old tooling, they'll want to use the UMD/IIFE bundle, and, in this case, we may provide sub-bundles with the specific modules inside.

@Johann-S

Copy link
Copy Markdown
Member Author

I agree with @FezVrasta, if I try to allow folks to only import specific plugins it's to allow them to have small bundle when they import bootstrap in their projects so tree-shaking is an important thing to concider here

@Johann-S
Johann-S force-pushed the webpack branch 2 times, most recently from ccbd3c8 to 2ad42e6 Compare June 26, 2017 12:47
@Johann-S

Johann-S commented Jul 8, 2017

Copy link
Copy Markdown
Member Author

If they are folks who want to test that in Local this my test folder :
testBsModules.zip

To use that you have to pull this branch in the root of your bootstrap folder run : npm link
After that you have to go to the root of my test folder and run : npm link bootstrap

everything is good 👍 you just have to install deps (npm i) and run npm run build

@XhmikosR

Copy link
Copy Markdown
Member

@addyosmani: do you have any idea how to solve this? Thanks in advance!

To sum up if someone trys to build a project with Bootstrap and just want to include our Util.js with import { Util } from 'bootstrap' currently the other source files are added too... They isn't any tree shaking done.

@mdo

mdo commented Aug 11, 2017

Copy link
Copy Markdown
Member

We, uh, have some conflicts here lol.

@Johann-S

Johann-S commented Aug 11, 2017 •

Copy link
Copy Markdown
Member Author

Yep I'll take care of that 😆
About that PR I can't go further here, everything is done, if someone have other feedbacks don't hesitate 👍

Currently it seems it's a bit hard for modules bundler to tree shake huge library, I hope in a near futur it'll be the case 🙏

@addyosmani

Copy link
Copy Markdown
Contributor

@XhmikosR Just saw this. Sorry for the delayed response.

@Johann-S Do you know to what extent your latest commits using RollUp are failing to tree-shake as expected? Do you still see the problem of other modules (e.g beyond the 'Util' example) getting pulled in?

@Johann-S

Copy link
Copy Markdown
Member Author

@addyosmani yep I still have this issue, when we import a module every others modules are bundled too...
Maybe that's because Bootstrap depends a lot of jQuery.
I read a few days the Troubleshooting section of Rollupjs (see : https://github.com/rollup/rollup/wiki/Troubleshooting#tree-shaking-doesnt-seem-to-be-working) and it indicate that if a library is too complex Rollup won't tree shake because they don't want to break everything.

If you have any idea about what I did wrong ? Or any other ideas it would be great, thank you 👍

Comment thread build/rollup.config.js
external: external,
globals: {
jquery: '$',
'popper.js': 'Popper'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dependencies should be defined as external, but not global. Bootstrap should actively try to import them (mostly to fix #23381)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I remove globals I have those warnings :
image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's because you don't have any import $ from 'jquery' and import Popper from 'popper.js' in the code?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's wrong in this PR we import Popper.js and jQuery (for example see : https://github.com/twbs/bootstrap/blob/webpack/js/src/tooltip.js)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, probably you still need those for the UMD and IIFE bundles, but you must make sure no never reference to them in the source, but always require them with import

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone want to use Bootstrap in a UMD module a rollupjs config like this would work perfectly without : window.Popper = Popper :

image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this we can do require(['jquery', 'popper.js', 'bootstrap'], ($, Popper, Bootstrap) => {})?

@Johann-S Johann-S Aug 18, 2017 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I have this issue :
⚠️ 'jquery' is imported by ..\bootstrap\js\dist\esm\scrollspy.js, but could not be resolved – treating it as an external dependency

So I'm not sure in an UMD build everything works too...

EDIT :
My ESM files import jQuery and that's why I have this issue 😩

RE EDIT :
jQuery is difficult to import... For now I cannot make it work without something like this :
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
But for Popper.js it works perfectly

Do you have any ideas @FezVrasta ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope sorry, It's been a while since last time I used jQuery :/

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got to be sure but it seems that's because jQuery do not have an ES6 release 😟

@prograhammer

prograhammer commented Aug 30, 2017 •

Copy link
Copy Markdown

Has anyone seen this from Webpack's docs here.

To get most out of tree shaking with external packages, you have to use babel-plugin-transform-imports to rewrite imports so that they work with webpack's tree shaking logic. See webpack issue #2867 for more information.

It seems tree shaking doesn't work on external packages yet. You would have to explicitly do import Bar from 'foo/bar'.

@FezVrasta

Copy link
Copy Markdown
Contributor

Here they are using Rollup, not webpack

@Johann-S

Johann-S commented Oct 6, 2017

Copy link
Copy Markdown
Member Author

Closing for now, I'll come back with a better PR, thanks to all you helped me a lot

@Johann-S Johann-S closed this Oct 6, 2017
@mdo
mdo deleted the webpack branch May 1, 2018 03:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

js JavaScript or TypeScript sources and plugins meta The repository itself: templates, guidelines, config v4 v4, the frozen docs on gh-pages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants