Skip to content

Commit 7cb7b62

Browse files
Refactor GOVUKFrontendComponent root typing
- Moves the type check of `root` into `GOVUKFrontendComponent` base class which components in the Design System extend - Moves assignment of `root` into the `GOVUKFrontendComponent` base class which components in the Design System extend - Adds `elementType` which specifies `type` of `root` to be checked and can be overloaded by child classes - Add `getGOVUKFrontendExportsNames` to `rollup.release.config.mjs` which ensures that we don't run into an error in which `window.*` is undefined during build - Update `Skeleton` section of js documentation to reflect new changes
1 parent de6b95b commit 7cb7b62

File tree

3 files changed

+50
-21
lines changed

3 files changed

+50
-21
lines changed

docs/contributing/coding-standards/js.md

-12
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,6 @@ export class Example extends GOVUKFrontendComponent {
2727
constructor($root){
2828
super($root)
2929

30-
if (!($root instanceof HTMLElement)) {
31-
if (!($root instanceof HTMLElement)) {
32-
throw new ElementError({
33-
componentName: 'Example',
34-
element: $root,
35-
identifier: 'Root element (`$root`)'
36-
})
37-
}
38-
}
39-
40-
this.$root = $root
41-
4230
// Code goes here
4331
this.$root.addEventListener('click', () => {
4432
// ...

packages/govuk-frontend/rollup.release.config.mjs

+22-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,35 @@ import config from '@govuk-frontend/config'
22
import { babel } from '@rollup/plugin-babel'
33
import replace from '@rollup/plugin-replace'
44
import terser from '@rollup/plugin-terser'
5-
import * as GOVUKFrontend from 'govuk-frontend/src/govuk/all.mjs'
65
import { defineConfig } from 'rollup'
76

7+
// GOV.UK Frontend uses browser APIs at `import` time
8+
// because of static properties. These APIs are not available
9+
// in Node.js.
10+
// We mock them the time of the `import` so we can read
11+
// the name of GOV.UK Frontend's exports without errors
12+
async function getGOVUKFrontendExportsNames() {
13+
try {
14+
global.HTMLElement = /** @type {any} */ (function () {})
15+
global.HTMLAnchorElement = /** @type {any} */ (function () {})
16+
return Object.keys(await import('govuk-frontend/src/govuk/all.mjs'))
17+
} finally {
18+
delete global.HTMLElement
19+
delete global.HTMLAnchorElement
20+
}
21+
}
22+
823
/**
924
* Rollup config for GitHub release
1025
*
1126
* ECMAScript (ES) module bundles for browser <script type="module">
1227
* or using `import` for modern browsers and Node.js scripts
28+
*
29+
* @param {import('rollup').RollupOptions} input
30+
* @returns {Promise<import('rollup').RollupOptions|import('rollup').RollupOptions[]>} rollup config
1331
*/
14-
export default defineConfig(({ i: input }) => ({
32+
33+
export default defineConfig(async ({ i: input }) => ({
1534
input,
1635

1736
/**
@@ -37,7 +56,7 @@ export default defineConfig(({ i: input }) => ({
3756
keep_fnames: true,
3857
// Ensure all top-level exports skip mangling, for example
3958
// non-function string constants like `export { version }`
40-
reserved: Object.keys(GOVUKFrontend)
59+
reserved: await getGOVUKFrontendExportsNames()
4160
},
4261

4362
// Include sources content from source maps to inspect

packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs

+28-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import { isInitialised, isSupported } from './common/index.mjs'
2-
import { InitError, SupportError } from './errors/index.mjs'
2+
import { ElementError, InitError, SupportError } from './errors/index.mjs'
33

44
/**
55
* Base Component class
66
*
77
* Centralises the behaviours shared by our components
88
*
99
* @virtual
10+
* @template {Element} [RootElementType=HTMLElement]
1011
*/
1112
export class GOVUKFrontendComponent {
13+
/**
14+
* @type {typeof Element}
15+
*/
16+
static elementType = HTMLElement
17+
18+
/**
19+
* @protected
20+
* @type {RootElementType}
21+
*/
22+
$root
23+
1224
/**
1325
* Constructs a new component, validating that GOV.UK Frontend is supported
1426
*
@@ -31,27 +43,37 @@ export class GOVUKFrontendComponent {
3143
throw new InitError(`\`moduleName\` not defined in component`)
3244
}
3345

46+
if (!($root instanceof childConstructor.elementType)) {
47+
throw new ElementError({
48+
element: $root,
49+
component: childConstructor,
50+
identifier: 'Root element (`$root`)',
51+
expectedType: childConstructor.elementType.name
52+
})
53+
} else {
54+
this.$root = /** @type {RootElementType} */ ($root)
55+
}
56+
3457
childConstructor.checkSupport()
3558

36-
this.checkInitialised($root)
59+
this.checkInitialised()
3760

3861
const moduleName = childConstructor.moduleName
3962

40-
$root?.setAttribute(`data-${moduleName}-init`, '')
63+
this.$root.setAttribute(`data-${moduleName}-init`, '')
4164
}
4265

4366
/**
4467
* Validates whether component is already initialised
4568
*
4669
* @private
47-
* @param {Element | null} [$root] - HTML element to be checked
4870
* @throws {InitError} when component is already initialised
4971
*/
50-
checkInitialised($root) {
72+
checkInitialised() {
5173
const constructor = /** @type {ChildClassConstructor} */ (this.constructor)
5274
const moduleName = constructor.moduleName
5375

54-
if ($root && moduleName && isInitialised($root, moduleName)) {
76+
if (moduleName && isInitialised(this.$root, moduleName)) {
5577
throw new InitError(constructor)
5678
}
5779
}

0 commit comments

Comments
 (0)