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

Rename exported JavaScript modules to include component name #2426

Merged
merged 5 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,29 @@ If you see unexpected behaviour, make sure the `id` for the textarea is unique

This change was introduced in [pull request #2408: Prevent issues with character count when textarea `id` includes CSS syntax characters](https://github.com/alphagov/govuk-frontend/pull/2408).

#### Make sure individually imported JavaScript modules work as expected

You do not need to do anything if you have either:

- followed our [Getting Started guide](https://frontend.design-system.service.gov.uk/get-started/#5-get-the-javascript-working) and are importing all of the GOV.UK Frontend JavaScript in one go via `all.js`
- installed GOV.UK Frontend using precompiled files

We've changed the naming of our components' JavaScript modules so that individual imports are now attached to
`window.GOVUKFrontend.[ComponentName]` instead of `window.GOVUKFrontend`.

You can now import multiple modules without overwriting the previous one, for example:

```
//= require govuk/components/accordion/accordion.js
//= require govuk/components/button/button.js

# These modules are available under window.GOVUKFrontend.Accordion and window.GOVUKFrontend.Button respectively
```

If you're importing JavaScript modules individually, you should check any references to `window.GOVUKFrontend` in your code and update them to point to the correct component, `window.GOVUKFrontend.[ComponentName]`. You can now remove any workarounds you may have implemented.

This change was introduced in [pull request #1836: Rename exported JavaScript modules to include component name](https://github.com/alphagov/govuk-frontend/issues/1836)].

#### Remove calls to deprecated `iff` Sass function

We've removed the `iff` function which we deprecated in [GOV.UK Frontend version 3.6.0](https://github.com/alphagov/govuk-frontend/releases/tag/v3.6.0).
Expand Down Expand Up @@ -149,6 +172,7 @@ We’ve made fixes to GOV.UK Frontend in the following pull requests:
- [#2370: Prevent issues with conditionally revealed content when content `id` includes CSS syntax characters](https://github.com/alphagov/govuk-frontend/pull/2370)
- [#2408: Prevent issues with character count when textarea `id` includes CSS syntax characters](https://github.com/alphagov/govuk-frontend/pull/2408)
- [#2434: Add brand colour for Department for Levelling Up, Housing and Communities (DLUHC)](https://github.com/alphagov/govuk-frontend/pull/2434)
- [#1836: Rename exported JavaScript modules to include component name](https://github.com/alphagov/govuk-frontend/issues/1836))

## 3.14.0 (Feature release)

Expand Down
20 changes: 20 additions & 0 deletions lib/helper-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,23 @@ const componentNameToMacroName = componentName => {
return `govuk${macroName}`
}
exports.componentNameToMacroName = componentNameToMacroName

// Convert component name to JavaScript UMD module name
//
// This helper function takes a component name and returns the corresponding
// module name, which is used by rollup to set the `window` global and UMD/AMD export name
//
// Component names are lowercase, dash-separated strings (button, date-input),
// whilst module names have a `GOVUKFrontend.` prefix and are pascal cased (GOVUKFrontend.Button,
// GOVUKFrontend.CharacterCount).
const componentNameToJavaScriptModuleName = componentName => {
const macroName = componentName
.toLowerCase()
.split('-')
// capitalize each 'word'
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join('')

return `GOVUKFrontend.${macroName}`
}
exports.componentNameToJavaScriptModuleName = componentNameToJavaScriptModuleName
29 changes: 29 additions & 0 deletions tasks/gulp/__tests__/after-build-package.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ var glob = require('glob')

const configPaths = require('../../../config/paths.json')
const lib = require('../../../lib/file-helper')
const { componentNameToJavaScriptModuleName } = require('../../../lib/helper-functions')

const { renderSass } = require('../../../lib/jest-helpers')

const readFile = util.promisify(fs.readFile)
const componentNames = lib.allComponents.slice()
const componentsWithJavaScript = glob.sync(configPaths.package + 'govuk/components/' + '**/!(*.test).js')

describe('package/', () => {
it('should contain the expected files', () => {
Expand Down Expand Up @@ -101,6 +103,19 @@ describe('package/', () => {
})
})

describe('all.js', () => {
it('should have correct module name', async () => {
const allJsFile = path.join(configPaths.package, 'govuk', 'all.js')
return readFile(allJsFile, 'utf8')
.then((data) => {
expect(data).toContain("typeof define === 'function' && define.amd ? define('GOVUKFrontend', ['exports'], factory)")
})
.catch(error => {
throw error
})
})
})

describe('component', () => {
it.each(componentNames)('\'%s\' should have macro-options.json that contains JSON', (name) => {
const filePath = path.join(configPaths.package, 'govuk', 'components', name, 'macro-options.json')
Expand All @@ -127,6 +142,20 @@ describe('package/', () => {
})
})

describe('components with JavaScript', () => {
it.each(componentsWithJavaScript)('\'%s\' should have component JavaScript file with correct module name', (javaScriptFile) => {
const moduleName = componentNameToJavaScriptModuleName(path.parse(javaScriptFile).name)

return readFile(javaScriptFile, 'utf8')
.then((data) => {
expect(data).toContain("typeof define === 'function' && define.amd ? define('" + moduleName + "', factory)")
})
.catch(error => {
throw error
})
})
})

describe('fixtures', () => {
it.each(componentNames)('\'%s\' should have fixtures.json that contains JSON', (name) => {
const filePath = path.join(configPaths.package, 'govuk', 'components', name, 'fixtures.json')
Expand Down
71 changes: 44 additions & 27 deletions tasks/gulp/compile-assets.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'

const { componentNameToJavaScriptModuleName } = require('../../lib/helper-functions')

const path = require('path')

const gulp = require('gulp')
Expand All @@ -13,6 +15,7 @@ const taskArguments = require('./task-arguments')
const gulpif = require('gulp-if')
const uglify = require('gulp-uglify')
const eol = require('gulp-eol')
const glob = require('glob')
const rename = require('gulp-rename')
const cssnano = require('cssnano')
const postcsspseudoclasses = require('postcss-pseudo-classes')({
Expand Down Expand Up @@ -185,31 +188,45 @@ gulp.task('scss:compile', function (done) {

// Compile js task for preview ----------
// --------------------------------------
gulp.task('js:compile', () => {
// for dist/ folder we only want compiled 'all.js' file
const srcFiles = isDist ? configPaths.src + 'all.js' : configPaths.src + '**/*.js'

return gulp.src([
srcFiles,
'!' + configPaths.src + '**/*.test.js'
])
.pipe(rollup({
// Used to set the `window` global and UMD/AMD export name.
name: 'GOVUKFrontend',
// Legacy mode is required for IE8 support
legacy: true,
// UMD allows the published bundle to work in CommonJS and in the browser.
format: 'umd'
}))
.pipe(gulpif(isDist, uglify({
ie8: true
})))
.pipe(gulpif(isDist,
rename({
basename: 'govuk-frontend',
extname: '.min.js'
})
))
.pipe(eol())
.pipe(gulp.dest(destinationPath))
gulp.task('js:compile', (done) => {
// For dist/ folder we only want compiled 'all.js'
const fileLookup = isDist ? configPaths.src + 'all.js' : configPaths.src + '**/!(*.test).js'

// Perform a synchronous search and return an array of matching file names
const srcFiles = glob.sync(fileLookup)

srcFiles.forEach(function (file) {
vanitabarrett marked this conversation as resolved.
Show resolved Hide resolved
// This is combined with desinationPath in gulp.dest()
// so the files are output to the correct folders
const newDirectoryPath = path.dirname(file).replace('src/govuk', '')

// We only want to give component JavaScript a unique module name
let moduleName = 'GOVUKFrontend'
if (path.dirname(file).includes('/components/')) {
moduleName = componentNameToJavaScriptModuleName(path.parse(file).name)
}

return gulp.src(file)
.pipe(rollup({
// Used to set the `window` global and UMD/AMD export name
// Component JavaScript is given a unique name to aid individual imports, e.g GOVUKFrontend.Accordion
name: moduleName,
// Legacy mode is required for IE8 support
legacy: true,
// UMD allows the published bundle to work in CommonJS and in the browser.
format: 'umd'
}))
.pipe(gulpif(isDist, uglify({
ie8: true
})))
.pipe(gulpif(isDist,
rename({
basename: 'govuk-frontend',
extname: '.min.js'
})
))
.pipe(eol())
.pipe(gulp.dest(destinationPath() + newDirectoryPath))
})
done()
})