Skip to content

Commit

Permalink
Enable Rollup dual compile for ES and CommonJS modules
Browse files Browse the repository at this point in the history
All modules are now compiled to `dist/govuk` with UMD bundles removed for now (e.g. `all.js` is now a CommonJS module)
  • Loading branch information
colinrotherham committed Jun 1, 2023
1 parent 3dcefb3 commit 27eb30c
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 111 deletions.
4 changes: 2 additions & 2 deletions docs/contributing/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ npm scripts are defined in `package.json`. These trigger a number of Gulp tasks.
- copy Sass files, applying Autoprefixer via PostCSS
- copy Nunjucks component template/macro files, including JSON configs
- copy GOV.UK Prototype Kit config files
- compile JavaScript to CommonJS modules
- compile JavaScript to ECMAScript (ES) modules
- compile JavaScript to Universal Module Definition (UMD)
- runs `npm run postbuild:package` (which will test the output is correct)

**`npm run build:release` will do the following:**
Expand Down Expand Up @@ -91,7 +91,7 @@ This task will:
This task will:

- check JavaScript code quality via ESLint (`npm run lint:js`) (using JavaScript Standard Style)
- compile JavaScript to Universal Module Definition (UMD) into `./packages/govuk-frontend-review/dist/javascripts`
- compile JavaScript to Immediately Invoked Function Expression (IIFE) into `./packages/govuk-frontend-review/dist/javascripts`
- compile JavaScript documentation into `./packages/govuk-frontend-review/dist/docs/jsdoc`

## Review app only
Expand Down
2 changes: 1 addition & 1 deletion packages/govuk-frontend-review/tasks/scripts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import gulp from 'gulp'
* @type {import('govuk-frontend-tasks').TaskFunction}
*/
export const compile = (options) => gulp.series(
task.name('compile:js', () =>
task.name("compile:js 'bundle'", () =>
scripts.compile('all.mjs', {
...options,

Expand Down
4 changes: 2 additions & 2 deletions packages/govuk-frontend-review/tasks/watch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ export const watch = (options) => gulp.parallel(
/**
* JavaScripts build watcher
*/
task.name('compile:js watch', () =>
task.name("compile:js 'bundle' watch", () =>
gulp.watch([
`${slash(paths.root)}/typedoc.config.js`,
`${slash(paths.app)}/src/javascripts/**/*.mjs`,
`${slash(paths.package)}/dist/govuk-esm/**/*.mjs`
`${slash(paths.package)}/dist/govuk/**/*.mjs`
], scripts(options))
)
)
4 changes: 2 additions & 2 deletions packages/govuk-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "GOV.UK Frontend contains the code you need to start building a user interface for government platforms and services.",
"version": "4.6.0",
"main": "dist/govuk/all.js",
"module": "dist/govuk-esm/all.mjs",
"module": "dist/govuk/all.mjs",
"files": [
"dist",
"govuk-prototype-kit.config.json",
Expand All @@ -13,7 +13,7 @@
"exports": {
".": {
"sass": "./dist/govuk/all.scss",
"import": "./dist/govuk-esm/all.mjs",
"import": "./dist/govuk/all.mjs",
"require": "./dist/govuk/all.js"
},
"./*": "./*"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,30 @@ import { defineConfig } from 'rollup'
/**
* Rollup config for npm publish
*
* ECMAScript (ES) modules for browser <script type="module">
* or using `import` for modern browsers and Node.js scripts
* 1. CommonJS modules for Node.js `require()`
*
* 2. ECMAScript (ES) modules for browser <script type="module">
* or using `import` for modern browsers and Node.js scripts
*/
export default defineConfig(({ i: input }) => ({
input,

/**
* Output options
*/
output: {
entryFileNames: '[name].mjs',
format: 'es',
preserveModules: true
},
output: [
{
exports: 'named',
format: 'cjs',
interop: 'esModule',
preserveModules: true
},
{
entryFileNames: '[name].mjs',
format: 'es',
preserveModules: true
}
],

/**
* Input plugins
Expand Down
39 changes: 0 additions & 39 deletions packages/govuk-frontend/rollup.umd.config.mjs

This file was deleted.

106 changes: 65 additions & 41 deletions packages/govuk-frontend/tasks/build/package.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { join } from 'path'
import { paths, pkg } from 'govuk-frontend-config'
import { compileSassFile } from 'govuk-frontend-helpers/tests'
import { filterPath, getComponentNames, getListing, mapPathTo } from 'govuk-frontend-lib/files'
import { componentNameToClassName, componentPathToModuleName } from 'govuk-frontend-lib/names'
import { componentNameToClassName } from 'govuk-frontend-lib/names'
import { outdent } from 'outdent'

describe('packages/govuk-frontend/dist/', () => {
let listingPackage
Expand All @@ -13,7 +14,6 @@ describe('packages/govuk-frontend/dist/', () => {

let componentsFilesSource
let componentsFilesDist
let componentsFilesDistESM

let componentNames

Expand All @@ -24,7 +24,6 @@ describe('packages/govuk-frontend/dist/', () => {

componentsFilesSource = await getListing(join(paths.package, 'src/govuk/components'))
componentsFilesDist = await getListing(join(paths.package, 'dist/govuk/components'))
componentsFilesDistESM = await getListing(join(paths.package, 'dist/govuk-esm/components'))

// Components list
componentNames = await getComponentNames()
Expand All @@ -47,28 +46,20 @@ describe('packages/govuk-frontend/dist/', () => {
// Removes GOV.UK Prototype kit config (moved to package top level)
.flatMap(mapPathTo(['**/govuk-prototype-kit.config.mjs'], () => []))

// Replaces all source '*.mjs' files
.flatMap(mapPathTo(['**/*.mjs'], ({ dir: requirePath, name }) => {
const importFilter = /^govuk(?!-)/
// All source `**/*.mjs` files compiled to CommonJS modules
.flatMap(mapPathTo(['**/*.mjs'], ({ dir: requirePath, name }) => [
join(requirePath, `${name}.mjs`),

// All source `**/*.mjs` files compiled to `**/*.js`
const output = [
join(requirePath, `${name}.js`),
join(requirePath, `${name}.js.map`) // with source map
]

// Only source `./govuk/**/*.mjs` files compiled to `./govuk-esm/**/*.mjs`
if (importFilter.test(requirePath)) {
const importPath = requirePath.replace(importFilter, 'govuk-esm')

output.push(...[
join(importPath, `${name}.mjs`),
join(importPath, `${name}.mjs.map`) // with source map
])
}
// CommonJS modules
join(requirePath, `${name}.js`),
join(requirePath, `${name}.js.map`) // with source map
]))

return output
}))
// Only source `./govuk/**/*.mjs` files compiled to ES modules
.flatMap(mapPathTo(['**/govuk/**/*.mjs'], ({ dir: requirePath, name }) => [
join(requirePath, `${name}.mjs`),
join(requirePath, `${name}.mjs.map`) // with source map
]))

// Add Autoprefixer prefixes to all source '*.scss' files
.flatMap(mapPathTo(['**/*.scss'], ({ dir: requirePath, name }) => [
Expand Down Expand Up @@ -96,9 +87,8 @@ describe('packages/govuk-frontend/dist/', () => {
'package.json',
'postcss.config.mjs',
'postcss.config.unit.test.mjs',
'rollup.esm.config.mjs',
'rollup.modules.config.mjs',
'rollup.release.config.mjs',
'rollup.umd.config.mjs',
'tsconfig.build.json',
'tsconfig.json'
])
Expand All @@ -111,16 +101,53 @@ describe('packages/govuk-frontend/dist/', () => {
})
})

describe('all.mjs', () => {
it('should export each module', async () => {
const contents = await readFile(join(paths.package, 'dist/govuk/all.mjs'), 'utf8')

// Look for ES modules named exports
expect(contents).toContain('export { Accordion, Button, CharacterCount, Checkboxes, Details, ErrorSummary, Header, NotificationBanner, Radios, SkipLink, Tabs, initAll };')
})
})

describe('all.js', () => {
it('should have correct module name', async () => {
it('should export each module', async () => {
const contents = await readFile(join(paths.package, 'dist/govuk/all.js'), 'utf8')

// Look for AMD module definition for 'GOVUKFrontend'
expect(contents).toContain("typeof define === 'function' && define.amd ? define('GOVUKFrontend', ['exports'], factory)")
// Look for CommonJS named exports for components
expect(contents).toContain(outdent`
exports.Accordion = accordion.default;
exports.Button = button.default;
exports.CharacterCount = characterCount.default;
exports.Checkboxes = checkboxes.default;
exports.Details = details.default;
exports.ErrorSummary = errorSummary.default;
exports.Header = header.default;
exports.NotificationBanner = notificationBanner.default;
exports.Radios = radios.default;
exports.SkipLink = skipLink.default;
exports.Tabs = tabs.default;
`)

// Look for CommonJS named exports for utilities
expect(contents).toContain('exports.initAll = initAll;')
expect(contents).toContain('exports.version = govukFrontendVersion.version;')
})
})

describe('common/govuk-frontend-version.mjs', () => {
it('should have correct version number', async () => {
const contents = await readFile(join(paths.package, 'dist/govuk/all.js'), 'utf8')
const contents = await readFile(join(paths.package, 'dist/govuk/common/govuk-frontend-version.mjs'), 'utf8')

// Look for ES modules `version` named export
expect(contents).toContain(`var version = '${pkg.version}';`)
expect(contents).toContain('export { version };')
})
})

describe('common/govuk-frontend-version.js', () => {
it('should have correct version number', async () => {
const contents = await readFile(join(paths.package, 'dist/govuk/common/govuk-frontend-version.js'), 'utf8')

// Look for CommonJS `version` named export
expect(contents).toContain(`var version = '${pkg.version}';`)
Expand Down Expand Up @@ -185,31 +212,28 @@ describe('packages/govuk-frontend/dist/', () => {

const componentSource = componentsFilesSource.filter(componentFilter)
const componentDist = componentsFilesDist.filter(componentFilter)
const componentDistESM = componentsFilesDistESM.filter(componentFilter)

// UMD bundle not found at source
// CommonJS module not found at source
expect(componentSource)
.toEqual(expect.not.arrayContaining([join(componentName, `${componentName}.js`)]))

// UMD bundle generated in dist
// CommonJS and ES modules generated in dist
expect(componentDist)
.toEqual(expect.arrayContaining([join(componentName, `${componentName}.js`)]))

// ES module generated in dist
expect(componentsFilesDistESM)
.toEqual(expect.arrayContaining([join(componentName, `${componentName}.mjs`)]))
.toEqual(expect.arrayContaining([
join(componentName, `${componentName}.js`),
join(componentName, `${componentName}.mjs`)
]))

const [modulePath] = componentDist
.filter(filterPath([`**/${componentName}.js`]))

const [modulePathESM] = componentDistESM
const [modulePathESM] = componentDist
.filter(filterPath([`**/${componentName}.mjs`]))

const moduleName = componentPathToModuleName(join(paths.package, 'src/govuk/components', modulePath))
const moduleText = await readFile(join(paths.package, 'dist/govuk/components', modulePath), 'utf8')
const moduleTextESM = await readFile(join(paths.package, 'dist/govuk-esm/components', modulePathESM), 'utf8')
const moduleTextESM = await readFile(join(paths.package, 'dist/govuk/components', modulePathESM), 'utf8')

expect(moduleText).toContain(`typeof define === 'function' && define.amd ? define('${moduleName}', factory)`)
expect(moduleText).toContain(`exports.default = ${componentNameToClassName(componentName)};`)
expect(moduleTextESM).toContain(`export { ${componentNameToClassName(componentName)} as default }`)
})

Expand Down
2 changes: 1 addition & 1 deletion packages/govuk-frontend/tasks/build/release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default (options) => gulp.series(
),

// Compile GOV.UK Frontend JavaScript
task.name('compile:js', () =>
task.name("compile:js 'bundle'", () =>
scripts.compile('all.mjs', {
...options,

Expand Down
19 changes: 3 additions & 16 deletions packages/govuk-frontend/tasks/scripts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,15 @@ import gulp from 'gulp'
*/
export const compile = (options) => gulp.series(
/**
* Compile GOV.UK Frontend JavaScript (ES modules)
* Compile GOV.UK Frontend JavaScript (CommonJS and ES modules)
*/
task.name('compile:mjs', () =>
task.name("compile:js 'modules'", () =>
scripts.compile('!(*.test).mjs', {
...options,

srcPath: join(options.srcPath, 'govuk'),
destPath: join(options.destPath, 'govuk-esm'),
configPath: join(options.basePath, 'rollup.esm.config.mjs')
})
),

/**
* Compile GOV.UK Frontend JavaScript (UMD bundles)
*/
task.name('compile:js', () =>
scripts.compile('**/!(*.test).mjs', {
...options,

srcPath: join(options.srcPath, 'govuk'),
destPath: join(options.destPath, 'govuk'),
configPath: join(options.basePath, 'rollup.umd.config.mjs')
configPath: join(options.basePath, 'rollup.modules.config.mjs')
})
),

Expand Down

0 comments on commit 27eb30c

Please sign in to comment.