Skip to content

Commit 0121c06

Browse files
Merge pull request #1261 from nhsuk/package-exports
Add package entry points
2 parents 9f5d719 + e848ab0 commit 0121c06

File tree

12 files changed

+301
-111
lines changed

12 files changed

+301
-111
lines changed

.github/workflows/pull-request.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,49 @@ jobs:
7777
name: Javascript unit tests coverage
7878
path: coverage
7979

80+
exports:
81+
name: Package ${{ matrix.conditions }}
82+
runs-on: ubuntu-latest
83+
needs: [build]
84+
85+
strategy:
86+
fail-fast: false
87+
88+
matrix:
89+
conditions:
90+
- require
91+
- import
92+
93+
steps:
94+
- name: Checkout
95+
uses: actions/checkout@v4
96+
97+
- name: Restore dependencies (from cache)
98+
uses: actions/cache/restore@v4
99+
with:
100+
key: npm-install-${{ hashFiles('package-lock.json') }}
101+
path: node_modules
102+
103+
- name: Restore build (from cache)
104+
uses: actions/cache/restore@v4
105+
with:
106+
path: dist/
107+
key: build-${{ github.sha }}
108+
109+
- name: Setup Node
110+
uses: actions/setup-node@v4
111+
with:
112+
node-version-file: .nvmrc
113+
114+
- name: Resolve entry path
115+
run: |
116+
node --eval "console.log(require.resolve('nhsuk-frontend'))" --conditions ${{ matrix.conditions }}
117+
node --eval "console.log(require.resolve('nhsuk-frontend/package.json'))" --conditions ${{ matrix.conditions }}
118+
node --eval "console.log(require.resolve('nhsuk-frontend/packages/nhsuk'))" --conditions ${{ matrix.conditions }}
119+
node --eval "console.log(require.resolve('nhsuk-frontend/packages/nhsuk.js'))" --conditions ${{ matrix.conditions }}
120+
node --eval "console.log(require.resolve('nhsuk-frontend/packages/components/button/button'))" --conditions ${{ matrix.conditions }}
121+
node --eval "console.log(require.resolve('nhsuk-frontend/packages/components/button/button.js'))" --conditions ${{ matrix.conditions }}
122+
80123
regression:
81124
name: Visual regression tests
82125
runs-on: macos-latest

.github/workflows/sass.yml

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ jobs:
77
name: Dart Sass ${{ matrix.package-version }}
88
runs-on: ubuntu-latest
99

10+
env:
11+
RULE_1: '@import "nhsuk";'
12+
RULE_2: '@use "nhsuk" as *;'
13+
RULE_3: '@forward "nhsuk";'
14+
RULE_4: '@forward "node_modules/nhsuk-frontend/packages/nhsuk";'
15+
RULE_5: '@forward "node_modules/nhsuk-frontend/packages/core/all";'
16+
RULE_6: '@forward "pkg:nhsuk-frontend";'
17+
1018
strategy:
1119
fail-fast: false
1220

@@ -35,20 +43,35 @@ jobs:
3543

3644
- name: Install package
3745
run: |
46+
npm install --omit=dev
3847
npm install -g sass@${{ matrix.package-version }}
3948
sass --version
4049
41-
- name: Check compilation
50+
- name: Create test files
4251
run: |
4352
mkdir -p .tmp
44-
time sass packages/nhsuk.scss > .tmp/check.css
53+
echo '${{ env.RULE_1 }}' > .tmp/input1.scss
54+
echo '${{ env.RULE_2 }}' > .tmp/input2.scss
55+
echo '${{ env.RULE_3 }}' > .tmp/input3.scss
56+
echo '${{ env.RULE_4 }}' > .tmp/input4.scss
57+
echo '${{ env.RULE_5 }}' > .tmp/input5.scss
58+
echo '${{ env.RULE_6 }}' > .tmp/input6.scss
59+
60+
- name: Check compilation
61+
run: |
62+
time sass packages/nhsuk.scss > .tmp/check.css --load-path .
4563
4664
- name: Check load paths
4765
run: |
48-
mkdir -p .tmp
49-
time sh -c 'echo "@import "\""nhsuk"\"";" | sass --stdin --load-path packages > .tmp/check1.css'
50-
time sh -c 'echo "@forward "\""nhsuk"\"";" | sass --stdin --load-path packages > .tmp/check2.css'
51-
time sh -c 'echo "@use "\""nhsuk"\"" as *;" | sass --stdin --load-path packages > .tmp/check3.css'
66+
time sass .tmp/input1.scss > .tmp/check1.css --load-path packages
67+
time sass .tmp/input2.scss > .tmp/check2.css --load-path packages
68+
time sass .tmp/input3.scss > .tmp/check3.css --load-path packages
69+
time sass .tmp/input4.scss > .tmp/check3.css --load-path .
70+
time sass .tmp/input5.scss > .tmp/check3.css --load-path .
71+
72+
- name: Check package importer
73+
run: |
74+
time sass .tmp/input6.scss > .tmp/check6.css --pkg-importer node
5275
5376
# Check output for uncompiled Sass
5477
- name: Check output
@@ -57,3 +80,6 @@ jobs:
5780
! grep "\$nhsuk-" .tmp/check1.css
5881
! grep "\$nhsuk-" .tmp/check2.css
5982
! grep "\$nhsuk-" .tmp/check3.css
83+
! grep "\$nhsuk-" .tmp/check4.css
84+
! grep "\$nhsuk-" .tmp/check5.css
85+
! grep "\$nhsuk-" .tmp/check6.css

docs/installation/installing-with-npm.md

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ To build the stylesheet you will need a pipeline set up to compile [Sass](https:
3939
You need to import the NHS.UK frontend styles into the main Sass file in your project. You should place the below code before your own Sass rules (or Sass imports).
4040

4141
```scss
42-
@import "node_modules/nhsuk-frontend/packages/nhsuk";
42+
@import "node_modules/nhsuk-frontend";
4343
```
4444

4545
Alternatively you can import each of the individual components separately, meaning you can import just the components that you are using.
@@ -82,30 +82,32 @@ You might wish to copy the file into your project or reference it straight from
8282
</head>
8383
```
8484

85-
### Option 2: Import JavaScript using modules
85+
### Option 2: Import JavaScript using a bundler
8686

87-
If you're using a transpiler or bundler such as [Babel](https://babeljs.io/) or [Webpack](https://webpack.js.org/), you can use the ES6 import syntax to import components via modules into your main Javascript file.
87+
We encourage the use of ECMAScript (ES) modules, but you should check your bundler does not unnecessarily downgrade modern JavaScript for unsupported browsers.
88+
89+
If you decide to import using a bundler like [Rollup](https://rollupjs.org/) or [webpack](https://webpack.js.org/), import and run the `initAll` function to initialise NHS.UK frontend:
90+
91+
```js
92+
import { initAll } from 'nhsuk-frontend'
93+
initAll()
94+
```
95+
96+
#### Initialise individual components
97+
98+
Rather than using `initAll`, you can initialise individual components used by your service. For example:
8899

89100
```js
90-
// Components
91-
import Checkboxes from 'nhsuk-frontend/packages/components/checkboxes/checkboxes.js';
92-
import Details from 'nhsuk-frontend/packages/components/details/details.js';
93-
import ErrorSummary from 'nhsuk-frontend/packages/components/error-summary/error-summary.js';
94-
import Header from 'nhsuk-frontend/packages/components/header/header.js';
95-
import Radios from 'nhsuk-frontend/packages/components/radios/radios.js';
96-
import SkipLink from 'nhsuk-frontend/packages/components/skip-link/skip-link.js';
101+
import initRadios from 'nhsuk-frontend/packages/components/radios/radios.js';
102+
import initSkipLink from 'nhsuk-frontend/packages/components/skip-link/skip-link.js';
97103

98104
// Polyfills
99105
import 'nhsuk-frontend/packages/polyfills.js';
100106

101-
// Initialize components
107+
// Initialise components
102108
document.addEventListener('DOMContentLoaded', () => {
103-
Checkboxes();
104-
Details();
105-
ErrorSummary();
106-
Header();
107-
Radios();
108-
SkipLink();
109+
initRadios();
110+
initSkipLink();
109111
});
110112
```
111113

jest.config.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,20 @@ module.exports = {
3434
projects: [
3535
{
3636
...config,
37-
displayName: 'JSDom',
37+
displayName: 'JavaScript unit tests',
38+
testEnvironment: 'node',
39+
testMatch: ['<rootDir>/**/*.unit.test.{js,mjs}']
40+
},
41+
{
42+
...config,
43+
displayName: 'JavaScript behaviour tests',
3844
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
3945
testEnvironment: 'jsdom',
4046
testMatch: ['<rootDir>/**/*.jsdom.test.{js,mjs}']
4147
},
4248
{
4349
...config,
44-
displayName: 'Pupppeteer',
50+
displayName: 'JavaScript component tests',
4551
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
4652
testEnvironment: 'jest-environment-puppeteer',
4753
testMatch: ['<rootDir>/**/*.puppeteer.test.{js,mjs}'],

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55
"engines": {
66
"node": "^20.9.0 || ^22.11.0"
77
},
8+
"workspaces": [
9+
"."
10+
],
11+
"exports": {
12+
".": {
13+
"sass": "./packages/index.scss",
14+
"default": "./packages/index.js"
15+
},
16+
"./*": "./*",
17+
"./packages/*": {
18+
"sass": "./packages/*.scss",
19+
"default": "./packages/*.js"
20+
},
21+
"./packages/*.js": "./packages/*.js",
22+
"./packages/*.scss": "./packages/*.scss",
23+
"./package.json": "./package.json"
24+
},
25+
"main": "packages/index.js",
26+
"sass": "packages/index.scss",
827
"scripts": {
928
"install:playwright": "playwright install chromium --with-deps --only-shell",
1029
"install:puppeteer": "puppeteer browsers install",
@@ -68,6 +87,7 @@
6887
"html-validate": "^9.5.3",
6988
"jest": "^29.7.0",
7089
"jest-environment-jsdom": "^29.7.0",
90+
"jest-environment-node": "^29.7.0",
7191
"jest-environment-puppeteer": "^11.0.0",
7292
"jest-puppeteer": "^11.0.0",
7393
"nunjucks": "^3.2.4",

packages/components/character-count/character-count.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class CharacterCount {
77
this.lastInputTimestamp = null
88
}
99

10-
// Initialize component
10+
// Initialise component
1111
init() {
1212
// Check that required elements are present
1313
if (!this.$textarea) {

packages/index.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* eslint-disable import/prefer-default-export */
2+
3+
// Components
4+
const initButton = require('./components/button/button')
5+
const initCharacterCount = require('./components/character-count/character-count')
6+
const initCheckboxes = require('./components/checkboxes/checkboxes')
7+
const initDetails = require('./components/details/details')
8+
const initErrorSummary = require('./components/error-summary/error-summary')
9+
const initHeader = require('./components/header/header')
10+
const initRadios = require('./components/radios/radios')
11+
const initSkipLink = require('./components/skip-link/skip-link')
12+
const initTabs = require('./components/tabs/tabs')
13+
14+
require('./polyfills.js')
15+
16+
/**
17+
* Use this function to initialise nhsuk-frontend components within a
18+
* given scope. This function is called by default with the document
19+
* element, but you can call it again later with a new DOM element
20+
* containing nhsuk-frontend components which you wish to initialise.
21+
*
22+
* @param {HTMLElement} scope
23+
*/
24+
function initAll(scope) {
25+
initHeader()
26+
initSkipLink()
27+
initButton({ scope })
28+
initCharacterCount({ scope })
29+
initCheckboxes({ scope })
30+
initDetails({ scope })
31+
initErrorSummary({ scope })
32+
initRadios({ scope })
33+
initTabs({ scope })
34+
}
35+
36+
module.exports = {
37+
initButton,
38+
initCharacterCount,
39+
initCheckboxes,
40+
initDetails,
41+
initErrorSummary,
42+
initHeader,
43+
initRadios,
44+
initSkipLink,
45+
initTabs,
46+
initAll
47+
}

packages/index.jsdom.test.mjs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {
2+
initAll,
3+
initButton,
4+
initCharacterCount,
5+
initCheckboxes,
6+
initDetails,
7+
initErrorSummary,
8+
initHeader,
9+
initRadios,
10+
initSkipLink,
11+
initTabs
12+
} from './index.js'
13+
import * as NHSUKFrontend from './index.js'
14+
15+
jest.mock('./components/button/button.js')
16+
jest.mock('./components/character-count/character-count.js')
17+
jest.mock('./components/checkboxes/checkboxes.js')
18+
jest.mock('./components/details/details.js')
19+
jest.mock('./components/error-summary/error-summary.js')
20+
jest.mock('./components/header/header.js')
21+
jest.mock('./components/radios/radios.js')
22+
jest.mock('./components/skip-link/skip-link.js')
23+
jest.mock('./components/tabs/tabs.js')
24+
25+
describe('NHS.UK frontend', () => {
26+
describe('Exports', () => {
27+
it('should export init all function', () => {
28+
expect(NHSUKFrontend).toHaveProperty('initAll')
29+
})
30+
31+
it('should export init component functions', () => {
32+
expect(NHSUKFrontend).toHaveProperty('initButton')
33+
expect(NHSUKFrontend).toHaveProperty('initCharacterCount')
34+
expect(NHSUKFrontend).toHaveProperty('initCheckboxes')
35+
expect(NHSUKFrontend).toHaveProperty('initDetails')
36+
expect(NHSUKFrontend).toHaveProperty('initErrorSummary')
37+
expect(NHSUKFrontend).toHaveProperty('initHeader')
38+
expect(NHSUKFrontend).toHaveProperty('initRadios')
39+
expect(NHSUKFrontend).toHaveProperty('initSkipLink')
40+
expect(NHSUKFrontend).toHaveProperty('initTabs')
41+
})
42+
})
43+
44+
describe('initAll', () => {
45+
it('should init components', () => {
46+
initAll()
47+
48+
expect(initButton).toHaveBeenCalled()
49+
expect(initCharacterCount).toHaveBeenCalled()
50+
expect(initCheckboxes).toHaveBeenCalled()
51+
expect(initDetails).toHaveBeenCalled()
52+
expect(initErrorSummary).toHaveBeenCalled()
53+
expect(initHeader).toHaveBeenCalled()
54+
expect(initRadios).toHaveBeenCalled()
55+
expect(initSkipLink).toHaveBeenCalled()
56+
expect(initTabs).toHaveBeenCalled()
57+
})
58+
59+
it('should init components (with scope)', () => {
60+
const scope = document
61+
62+
initAll(scope)
63+
64+
expect(initHeader).toHaveBeenCalled()
65+
expect(initSkipLink).toHaveBeenCalled()
66+
67+
expect(initButton).toHaveBeenCalledWith({ scope })
68+
expect(initCharacterCount).toHaveBeenCalledWith({ scope })
69+
expect(initCheckboxes).toHaveBeenCalledWith({ scope })
70+
expect(initDetails).toHaveBeenCalledWith({ scope })
71+
expect(initErrorSummary).toHaveBeenCalledWith({ scope })
72+
expect(initRadios).toHaveBeenCalledWith({ scope })
73+
expect(initTabs).toHaveBeenCalledWith({ scope })
74+
})
75+
})
76+
})

0 commit comments

Comments
 (0)