govuk-frontend: Cannot import standard ES6 modules from npm
What
At the moment if you try to import using our documentation using a standard ES6 bundler such as Rollup it will fail.
This is because we ship our bundles as UMD, which is great for browser and CommonJs support but not ES6 Modules (better for build pipelines).
This means our current guidance around rollup is misleading and only works when compiling from source.
We could consider adding more real world examples that import the package directly to ensure what we recommend is tested.
I’m not sure if we should be publishing these as .mjs
files, which seems to be what Node.js is going towards.
- Webpack example: https://github.com/alphagov/govuk-frontend/tree/master/examples/webpack
- Rollup advice on modules https://github.com/rollup/rollup/wiki/pkg.module
- Microbundle https://github.com/developit/microbundle
Why
Some users rely on getting modules directly from /src
, which we don’t recommend and don’t want to encourage. Many of these users say they’re running outdated versions. We should make it easier for them to import as standard ES6 modules.
Done when
- Test including modules directly from
/src
- Research into
type: module
in the package.json - Test in some dummy apps/services
- Automated testing
- Implement decided approach
- Do a pre-release and share with the community to make sure this solves the problems (try and reach out to those we know are going directly to
/src
) - Documentation
- Raised a PR for using this in the Design System
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 3
- Comments: 26 (24 by maintainers)
Commits related to this issue
- Add Rollup plugins Add Rollup plugins, includes commonjs plugins since GOV.UK Frontend is currently shipped as UMD. See https://github.com/alphagov/govuk-frontend/issues/711 for more details. — committed to alphagov/govuk-design-system by NickColley 6 years ago
- Add Rollup plugins Add Rollup plugins, includes commonjs plugins since GOV.UK Frontend is currently shipped as UMD. See https://github.com/alphagov/govuk-frontend/issues/711 for more details. — committed to alphagov/govuk-design-system by NickColley 6 years ago
- WIP Playing around with esm modules in govuk-frontend. Based on original work by @domoscargin who did the initial investigation into different approaches for this. This commit explores option 3 more... — committed to alphagov/govuk-frontend by deleted user 2 years ago
- Copy ESM modules into separate govuk-esm folder in package Playing around with esm modules in govuk-frontend. Based on original work by @domoscargin who did the initial investigation into different ... — committed to alphagov/govuk-frontend by deleted user 2 years ago
- Copy ESM modules into separate govuk-esm folder in package Playing around with esm modules in govuk-frontend. Based on original work by @domoscargin who did the initial investigation into different ... — committed to alphagov/govuk-frontend by deleted user 2 years ago
- Copy ESM modules into separate govuk-esm folder in package Playing around with esm modules in govuk-frontend. Based on original work by @domoscargin who did the initial investigation into different ... — committed to alphagov/govuk-frontend by deleted user 2 years ago
- Copy ESM modules into separate govuk-esm folder in package Playing around with esm modules in govuk-frontend. Based on original work by @domoscargin who did the initial investigation into different ... — committed to alphagov/govuk-frontend by deleted user 2 years ago
- Copy ESM modules into separate govuk-esm folder in package Playing around with esm modules in govuk-frontend. Based on original work by @domoscargin who did the initial investigation into different ... — committed to alphagov/govuk-frontend by deleted user 2 years ago
In our project we use very few govuk-frontend js modules:
Button
,Radios
,Checkboxes
,ErrorSummary
, that’s about it. So to save on the bundle size I made a customall.js
file that only imports the components I need. The other reason we do this is that we have some custom components which use the same polyfills (and one that reuses Button) which can also be added to the sameall.js
file. The tree shaking will avoid bundling the same polyfills or components twice, resulting in a single small js bundle.Unforuntatley the only way to achieve this is to add this Github repo as a npm depency, as the unbundled source is not included in the npm package and using the individually bundled components does not take advantage of tree shaking, bloating out the total bundle size.
This also presents us with some problems:
So in an ideal world the unbundled component js would be included in the govuk-frontend npm package in a way in which it could be imported as an ES6 module. This could also allow serving the ES6 modules directly over HTTP2 in the future, falling back to the bundle.
I’d be happy to help on the implementation of this if an approach could be agreed.
Thanks!
@domoscargin and I have been testing this approach with the following bundlers: Webpack; Parcel; Rollup; esbuild.
Setting
sideEffects
The biggest change we’ve made since my last comment is setting
sideEffects
in the govuk-frontendpackage/package.json
. I originally set this tofalse
when first prototyping approaches to this card, for unknown reasons (most likely copied from an example without giving it much thought).However, @domoscargin noticed when he was testing that polyfills were being ignored and weren’t being bundled. Having read https://sgom.es/posts/2020-06-15-everything-you-never-wanted-to-know-about-side-effects/, I came to the conclusion that we needed to set
sideEffects
totrue
because of the way we import polyfills - we import them and rely on them being there without having to ‘call’ them, which means our code has ‘side effects’. A bundler might otherwise exclude these because it thinks the imported code isn’t being referenced and therefore isn’t needed. I’ve gone one step further and actually setsideEffects
to"govuk-esm/vendor/**”
because I think these are the only JavaScript files that we use in this way.I’ve updated the pre-release with this change and tested with all the bundlers listed above. All output JavaScript now seems to correctly include polyfills and only includes them once even if they’re imported by multiple components (tree-shaking).
common.mjs
As discussed on Slack, we’ve decided not to export the methods included in
common.mjs
in ourall.mjs
file. These are internal methods which were never intended to be made ‘public’, despite people importing them at the moment in the way we currently ship our JS as UMD probably because it’s convenient and there’s nothing to stop them from doing that. As this is a new feature, we’re not going to include the functions in that file (nodeListForEach
andgenerateUniqueID
) as an export in our ES modules.Stats
Original UMD bundle in
package/govuk
: 96.01 kB Rollup: 55.98 kB Webpack: 76.05 kB Parcel: 36.14 kB (minified) esbuild: 42.76 kBNote: variations in file size should be down to different config setups, there might be config tweaks/changes our users can make to bring that file size down even further.
Hey! Thanks so much for this work, once released this should allow me install the govuk-frontend module properly again rather than include a tagged point in the repo to get the original source. I’m gonna make a branch on our frontend project to test it out and see if these changes will fit neatly into my build. Thanks
@36degrees Good shout on
nodeListForEach
- I’d noted down our decision, but forgotten to update that branch. Should be updated now in this commit.For the other point, importing from
govuk-frontend
is how I’d expect someone to import. We’re not defining anything new called ‘govuk-frontend-esm’. Instead, the fact that we’re using an import statement should be enough for thegovuk-frontend
package.json to “point us” to the govuk-esm directory because we’ve made these changes to thepackage/package.json
file.Does that make sense?
ok thanks @36degrees I’ll try that first and see what happens. (Probably this weekend)
ESM Write-Up
I picked this work up last week and carried on where @domoscargin left off - looking into the 3 options detailed in the comment above in more detail to see which is most feasible.
Option 1: using rollup
I’ve almost immediately discarded this option because of the downsides written up by @domoscargin.
Option 2: copying src files into /package alongside UMD
It seems difficult to get this working properly (i.e: separating out .mjs and .js and making sure you’re only importing either ESM or UMD). I changed the
package/package.json
to give 2 entry points for UMD or ESM, e.g:However, even though the initial entry point works correctly, the JavaScript that
all.mjs
imports seems to still target .js (UMD) files and there’s no way of telling it to import.mjs
instead.I didn’t dig into setting
”type”: “module”
in thepackage/package.json
which might change that default behaviour - I’m not sure. That might become a breaking change, where ESM becomes the default, which I think we want to avoid.Option 3: copying src files into a new folder for ESM
This is the option which I’ve tested the most as it seems the most feasible option.
➡️ See WIP govuk-frontend branch
I’ve added a new
copysrc
task which copies the JavaScript files in src, renames them to have the .mjs file extension and saves them into a newpackage/govuk-esm
folder.Note: This only copies JavaScript into the
package/govuk-esm
folder. Thepackage/govuk-esm
folder is not a complete copy of what’s currently inpackage/govuk
. I think our JavaScript is separate enough from our sass, nunjucks etc that we can pull it out into a separate folder like this, but happy to be corrected on that.➡️ See WIP govuk-design-system branch
I tested this by editing the
govuk-frontend.js
in the Design System repo and replacing it with:The only thing which needed adjusting to get this to work were the imports of our common helpers.
Next steps
Things I was hoping to do, but unfortunately didn’t get enough time before annual leave:
direction most packages are moving to is to specify
type: module
in package.json Sindre Sorhus’s FAQ. for more details alternatively ship additionalgovuk-frontend-esm
npm packageYeah I believe that’s the only way currently to achieve what you’d get for free with tree shaking.