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

Compile ES modules using Rollup #3386

Merged
merged 2 commits into from
Apr 3, 2023
Merged

Compile ES modules using Rollup #3386

merged 2 commits into from
Apr 3, 2023

Conversation

colinrotherham
Copy link
Contributor

This PR uses Rollup to compile (not copy) ES modules into ./package/govuk-esm

It adds on to recent "Build tasks" work to unblock:

@colinrotherham colinrotherham added feature request User requests a new feature javascript labels Mar 15, 2023
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 15, 2023 14:55 Inactive
// Copy GOV.UK Frontend JavaScript (ES modules)
await files.copy('**/!(*.test).mjs', {
// Compile GOV.UK Frontend JavaScript (ES modules)
await scripts.compile('!(*.test).mjs', {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Notice the glob pattern has the suffix **/ removed

Rollup will automatically find child import modules for us from top-level entry scripts like:

./src/govuk/all.mjs
./src/govuk/common.mjs
./src/govuk/i18n.mjs

input: moduleSrcPath,

// Preserve JavaScript ESM import statements
experimentalPreserveModules: format === 'es'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Regarding the "experimental" option name, I've taken a closer look and we're good to go.

Since we're currently pinned on Rollup v0.59.4 (for IE8 { legacy: true }) we get stable module support. But until the Rollup v1.0.0 release there were few missing features to be aware of:

  1. No dynamic import()
  2. No multiple input named entries in watch mode
  3. No chunk optimisation
  4. No tree shaking

We don't actually need these (yet) as we currently only copy our *.mjs files between directories.

I've added some background info for context:

Rollup v0.57.0

The option { experimentalPreserveModules } was added in Rollup v0.57.0

This takes us from 15 March 2018 to the last IE8 release in 28 May 2018:

rollup@0.57.0
rollup@0.57.1
rollup@0.58.0
rollup@0.58.1
rollup@0.58.2
rollup@0.59.0
rollup@0.59.1
rollup@0.59.2
rollup@0.59.3
rollup@0.59.4

Rollup v0.58.0

It was stabilised in Rollup v0.58.0 with some notable PRs:

Hoisting of deep imports for performance
(e.g. Polyfills moved into all.mjs)
rollup/rollup#2073

Fixes for "single file" builds like ours
rollup/rollup#2123

Warnings removed for tree-shaken imports (that were exported)
rollup/rollup#2124

Rollup v0.59.0

With another fix in Rollup v0.59.0

Fixes for file paths in source maps
rollup/rollup#2161

Rollup v1.0.0

The option was renamed to { preserveModules } in Rollup v1.0.0

@romaricpascal
Copy link
Member

romaricpascal commented Mar 16, 2023

To track what was mentioned in the dev catch-up, the hoisting of deep imports may have impacts on tree-shaking, which need looking into.

In our current package, import {Accordion} from 'govuk-frontend' will only bring the polyfills necessary for the Accordion.

However with the polyfills of all components being hoisted when building all.mjs, it's likely polyfills that the Accordion doesn't need will end up in the final bundle (especially as our polyfills are not pure), so we'll need to check what happens there.

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 16, 2023 11:42 Inactive
@colinrotherham
Copy link
Contributor Author

colinrotherham commented Mar 16, 2023

@romaricpascal That's a great point

Thankfully the hoisted imports in all.mjs (from this PR) continue to be tree shaken away 🎉

Below is some testing from our webpack example using only { Button } via import/require

Bundling via import from copied files

File sizes for main.min.js with webpack resolving package/govuk-esm/* copied ES modules

14KB — Button polyfills only

import { Button } from 'govuk-frontend'
import { Button } from 'govuk-frontend/govuk-esm/all.mjs'
import Button from 'govuk-frontend/govuk-esm/components/button/button.mjs'

Bundling via import from Rollup es output

File sizes for main.min.js with webpack resolving package/govuk-esm/* compiled ES modules

14KB — Button polyfills only

import { Button } from 'govuk-frontend'
import { Button } from 'govuk-frontend/govuk-esm/all.mjs'
import Button from 'govuk-frontend/govuk-esm/components/button/button.mjs'

Bundling via require() from Rollup umd output

File sizes for main.min.js with webpack resolving package/govuk/* compiled UMD modules

50KB — All polyfills included

const { Button } = require('govuk-frontend')
const { Button } = require('govuk-frontend/govuk/all.js')

12KB — Button polyfills only

const Button = require('govuk-frontend/govuk/components/button/button.js')

@colinrotherham colinrotherham force-pushed the build-tasks-gulpfile-app branch 2 times, most recently from c74785a to 28db545 Compare March 20, 2023 14:22
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 20, 2023 14:25 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 20, 2023 16:59 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 21, 2023 16:26 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 21, 2023 16:51 Inactive
@colinrotherham colinrotherham force-pushed the build-tasks-gulpfile-app branch 2 times, most recently from 20c939d to 09453b3 Compare March 22, 2023 11:15
@colinrotherham colinrotherham force-pushed the build-tasks-gulpfile-app branch 2 times, most recently from ebc21bf to d761c8c Compare March 29, 2023 09:36
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 29, 2023 09:37 Inactive
@colinrotherham colinrotherham added this to the [NEXT] milestone Mar 29, 2023
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 29, 2023 16:33 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 29, 2023 16:52 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 29, 2023 22:00 Inactive
@colinrotherham colinrotherham force-pushed the build-tasks-gulpfile-app branch 3 times, most recently from 7887cab to 69b3f08 Compare March 30, 2023 19:07
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3386 March 30, 2023 19:09 Inactive
Base automatically changed from build-tasks-gulpfile-app to main March 30, 2023 19:13
@colinrotherham
Copy link
Contributor Author

colinrotherham commented Mar 31, 2023

I've completed testing in Rollup with npm link on a local build of GOV.UK Frontend

Good news, JavaScript output is identical

Source map content appears to be the Rollup-transformed ES module code (with semicolons inserted etc) but this is likely GOV.UK Design System @rollup/* plugins needing an "input source map" config to find the original sources

})
// Option 1: Rollup bundle set (multiple files)
// - Module imports are preserved, not concatenated
if (moduleDestPath.endsWith('.mjs')) {
Copy link
Member

Choose a reason for hiding this comment

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

This function seems to be doing 2 different jobs in two separate code paths – would it make sense to split it into two functions (one for generating a bundle and one that preserves modules) and decide which one to call in the compile function instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah could do. Only reason I didn't is that we'll just use the new code path in rollup@3 and delete the old

In newer versions of Rollup the { preserveModules } option can also output CJS/AMD

Is it a blocker?

Copy link
Member

Choose a reason for hiding this comment

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

Gotcha, so once we've upgraded to v3 we'll be able to simplify this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exactly. In this PR we process:

  1. Entry point *.mjs files for ESM, top level only
  2. Every non-test *.mjs file for UMD, recursively with **
task.name('compile:ms', () => scripts.compile('!(*.test).mjs') // Entries all.mjs, common.mjs etc
task.name('compile:js', () => scripts.compile('**/!(*.test).mjs') // Every non-test *.mjs file

But in future we'll use entry points for both and let Rollup discover all child modules.

Copy link
Member

Choose a reason for hiding this comment

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

OK, that makes sense 👍🏻 Happy to leave this for now then.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We tried this in #3462 but we'd have to output CommonJS rather than the hybrid Universal Module Definition (UMD) bundles for browsers (see comment)

@36degrees 36degrees mentioned this pull request Apr 3, 2023
1 task
@colinrotherham colinrotherham merged commit 09d8051 into main Apr 3, 2023
19 checks passed
@colinrotherham colinrotherham deleted the rollup-esm branch April 3, 2023 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request User requests a new feature javascript
Projects
Development

Successfully merging this pull request may close these issues.

None yet

4 participants