Skip to content

Commit

Permalink
Refactor GOVUKFrontendComponent root typing
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
patrickpatrickpatrick authored and romaricpascal committed Oct 8, 2024
1 parent de6b95b commit 7cb7b62
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 21 deletions.
12 changes: 0 additions & 12 deletions docs/contributing/coding-standards/js.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,6 @@ export class Example extends GOVUKFrontendComponent {
constructor($root){
super($root)

if (!($root instanceof HTMLElement)) {
if (!($root instanceof HTMLElement)) {
throw new ElementError({
componentName: 'Example',
element: $root,
identifier: 'Root element (`$root`)'
})
}
}

this.$root = $root

// Code goes here
this.$root.addEventListener('click', () => {
// ...
Expand Down
25 changes: 22 additions & 3 deletions packages/govuk-frontend/rollup.release.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,35 @@ import config from '@govuk-frontend/config'
import { babel } from '@rollup/plugin-babel'
import replace from '@rollup/plugin-replace'
import terser from '@rollup/plugin-terser'
import * as GOVUKFrontend from 'govuk-frontend/src/govuk/all.mjs'
import { defineConfig } from 'rollup'

// GOV.UK Frontend uses browser APIs at `import` time
// because of static properties. These APIs are not available
// in Node.js.
// We mock them the time of the `import` so we can read
// the name of GOV.UK Frontend's exports without errors
async function getGOVUKFrontendExportsNames() {
try {
global.HTMLElement = /** @type {any} */ (function () {})
global.HTMLAnchorElement = /** @type {any} */ (function () {})
return Object.keys(await import('govuk-frontend/src/govuk/all.mjs'))
} finally {
delete global.HTMLElement
delete global.HTMLAnchorElement
}
}

/**
* Rollup config for GitHub release
*
* ECMAScript (ES) module bundles for browser <script type="module">
* or using `import` for modern browsers and Node.js scripts
*
* @param {import('rollup').RollupOptions} input
* @returns {Promise<import('rollup').RollupOptions|import('rollup').RollupOptions[]>} rollup config
*/
export default defineConfig(({ i: input }) => ({

export default defineConfig(async ({ i: input }) => ({
input,

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

// Include sources content from source maps to inspect
Expand Down
34 changes: 28 additions & 6 deletions packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { isInitialised, isSupported } from './common/index.mjs'
import { InitError, SupportError } from './errors/index.mjs'
import { ElementError, InitError, SupportError } from './errors/index.mjs'

/**
* Base Component class
*
* Centralises the behaviours shared by our components
*
* @virtual
* @template {Element} [RootElementType=HTMLElement]
*/
export class GOVUKFrontendComponent {
/**
* @type {typeof Element}
*/
static elementType = HTMLElement

/**
* @protected
* @type {RootElementType}
*/
$root

/**
* Constructs a new component, validating that GOV.UK Frontend is supported
*
Expand All @@ -31,27 +43,37 @@ export class GOVUKFrontendComponent {
throw new InitError(`\`moduleName\` not defined in component`)
}

if (!($root instanceof childConstructor.elementType)) {
throw new ElementError({
element: $root,
component: childConstructor,
identifier: 'Root element (`$root`)',
expectedType: childConstructor.elementType.name
})
} else {
this.$root = /** @type {RootElementType} */ ($root)
}

childConstructor.checkSupport()

this.checkInitialised($root)
this.checkInitialised()

const moduleName = childConstructor.moduleName

$root?.setAttribute(`data-${moduleName}-init`, '')
this.$root.setAttribute(`data-${moduleName}-init`, '')
}

/**
* Validates whether component is already initialised
*
* @private
* @param {Element | null} [$root] - HTML element to be checked
* @throws {InitError} when component is already initialised
*/
checkInitialised($root) {
checkInitialised() {
const constructor = /** @type {ChildClassConstructor} */ (this.constructor)
const moduleName = constructor.moduleName

if ($root && moduleName && isInitialised($root, moduleName)) {
if (moduleName && isInitialised(this.$root, moduleName)) {
throw new InitError(constructor)
}
}
Expand Down

0 comments on commit 7cb7b62

Please sign in to comment.