@defra/docusaurus-theme-govuk 0.0.2-alpha → 0.0.4-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/index.js +84 -28
- package/package.json +6 -2
- package/src/lib/react-foundry-router-shim.js +2 -6
- package/src/theme/Layout/index.js +1 -0
package/README.md
CHANGED
|
@@ -13,12 +13,22 @@ A Docusaurus 3 theme that applies the [GOV.UK Design System](https://design-syst
|
|
|
13
13
|
- Bundled GDS Transport fonts and GOV.UK static assets
|
|
14
14
|
- Compatible with React 18 and React 19
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
## Installation
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
20
|
npm install docusaurus-theme-govuk
|
|
20
21
|
```
|
|
21
22
|
|
|
23
|
+
### Consumer responsibilities
|
|
24
|
+
|
|
25
|
+
- Install all required peer dependencies (see `peerDependencies` in the package).
|
|
26
|
+
- Use the theme in your Docusaurus config as shown below.
|
|
27
|
+
- Ensure your project uses compatible versions of Docusaurus and React.
|
|
28
|
+
- Configure navigation and sidebar via `themeConfig.govuk.navigation`.
|
|
29
|
+
|
|
30
|
+
No additional setup is required beyond standard Docusaurus theme usage.
|
|
31
|
+
|
|
22
32
|
## Configuration
|
|
23
33
|
|
|
24
34
|
Update your `docusaurus.config.js` (or `.cjs`):
|
package/index.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const
|
|
3
|
-
const CopyPlugin = require('copy-webpack-plugin');
|
|
2
|
+
const fs = require('fs');
|
|
4
3
|
|
|
5
4
|
module.exports = function themeGovuk(context, options) {
|
|
5
|
+
const siteDir = context.siteDir;
|
|
6
|
+
|
|
7
|
+
// Resolve webpack and plugins from the consumer's node_modules.
|
|
8
|
+
// Top-level require() would fail when installed via file: (symlink) because
|
|
9
|
+
// the theme directory has no local node_modules in that case.
|
|
10
|
+
const webpack = require(require.resolve('webpack', { paths: [siteDir] }));
|
|
11
|
+
const CopyPlugin = require(require.resolve('copy-webpack-plugin', { paths: [siteDir] }));
|
|
12
|
+
|
|
6
13
|
const themePath = path.resolve(__dirname, './src/theme');
|
|
7
14
|
const shimPath = path.resolve(__dirname, './src/lib/react-foundry-router-shim.js');
|
|
8
15
|
|
|
9
|
-
// Resolve govuk-frontend assets directory from
|
|
16
|
+
// Resolve govuk-frontend assets directory from the consumer's node_modules
|
|
10
17
|
const govukFrontendAssetsPath = path.dirname(
|
|
11
|
-
require.resolve('govuk-frontend/package.json')
|
|
18
|
+
require.resolve('govuk-frontend/package.json', { paths: [siteDir] })
|
|
12
19
|
);
|
|
13
20
|
|
|
14
21
|
// The base URL for this Docusaurus site (e.g. '/interactive-map/')
|
|
@@ -33,31 +40,71 @@ module.exports = function themeGovuk(context, options) {
|
|
|
33
40
|
},
|
|
34
41
|
|
|
35
42
|
configureWebpack(config, isServer, utils) {
|
|
36
|
-
// Resolve React from the consumer (siteDir) to avoid dual-instance issues.
|
|
37
|
-
// The theme ships its own node_modules/react via `file:` linking,
|
|
38
|
-
// so we must force all React imports to the consumer's single copy.
|
|
39
|
-
const siteDir = context.siteDir;
|
|
40
|
-
|
|
41
43
|
// Helper: resolve a package from the consumer's siteDir.
|
|
42
44
|
// Uses require.resolve with paths so it follows Node's resolution
|
|
43
45
|
// (handles hoisted AND nested node_modules like @docusaurus/core/node_modules/react-router-dom).
|
|
46
|
+
// Also handles ESM-only packages (e.g. @mdx-js/react) that don't export ./package.json.
|
|
47
|
+
// Find the directory of an installed package by walking node_modules up
|
|
48
|
+
// the filesystem from each search path. Uses only fs.existsSync — no
|
|
49
|
+
// require.resolve — so it works even for ESM-only packages (e.g.
|
|
50
|
+
// @mdx-js/react) where jiti's require.resolve shim fails at runtime.
|
|
51
|
+
// Falls back to __dirname so that packages installed in the theme's own
|
|
52
|
+
// node_modules are found when consuming via file: (local dev).
|
|
53
|
+
function findPkgDir(pkg, searchPaths) {
|
|
54
|
+
const allSearchPaths = [...searchPaths, __dirname];
|
|
55
|
+
for (const startDir of allSearchPaths) {
|
|
56
|
+
let dir = startDir;
|
|
57
|
+
while (true) {
|
|
58
|
+
const candidate = path.join(dir, 'node_modules', pkg);
|
|
59
|
+
if (fs.existsSync(path.join(candidate, 'package.json'))) {
|
|
60
|
+
return candidate;
|
|
61
|
+
}
|
|
62
|
+
const parent = path.dirname(dir);
|
|
63
|
+
if (parent === dir) break; // filesystem root
|
|
64
|
+
dir = parent;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Could not find package "${pkg}" from [${allSearchPaths.join(', ')}]`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
44
71
|
function resolveFromSite(pkg) {
|
|
45
72
|
try {
|
|
46
|
-
return
|
|
47
|
-
require.resolve(`${pkg}/package.json`, { paths: [siteDir] })
|
|
48
|
-
);
|
|
73
|
+
return findPkgDir(pkg, [siteDir]);
|
|
49
74
|
} catch {
|
|
50
|
-
// Fallback: try resolving from @docusaurus/core (where
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
54
|
-
return path.dirname(
|
|
55
|
-
require.resolve(`${pkg}/package.json`, { paths: [docuCorePath] })
|
|
56
|
-
);
|
|
75
|
+
// Fallback: try resolving from @docusaurus/core's location (where
|
|
76
|
+
// react-router may be nested in Docusaurus 3.9.x).
|
|
77
|
+
const docuCoreDir = findPkgDir('@docusaurus/core', [siteDir]);
|
|
78
|
+
return findPkgDir(pkg, [docuCoreDir]);
|
|
57
79
|
}
|
|
58
80
|
}
|
|
59
81
|
|
|
82
|
+
// Resolve the CJS entry point of a package without going through jiti.
|
|
83
|
+
// Reads the package's own package.json `main` field (CJS) rather than
|
|
84
|
+
// using require.resolve which jiti may fail on.
|
|
85
|
+
function findPkgEntry(pkg) {
|
|
86
|
+
const pkgDir = findPkgDir(pkg, [siteDir]);
|
|
87
|
+
const pkgJson = JSON.parse(
|
|
88
|
+
fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8')
|
|
89
|
+
);
|
|
90
|
+
const main = pkgJson.main || 'index.js';
|
|
91
|
+
return path.resolve(pkgDir, main);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Use MiniCssExtractPlugin for production/server builds, style-loader for dev/client
|
|
95
|
+
let MiniCssExtractPlugin;
|
|
96
|
+
if (!isServer && process.env.NODE_ENV === 'production') {
|
|
97
|
+
MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
98
|
+
if (!config.plugins) config.plugins = [];
|
|
99
|
+
config.plugins.push(new MiniCssExtractPlugin({ filename: 'assets/css/govuk-theme.[contenthash].css' }));
|
|
100
|
+
}
|
|
60
101
|
return {
|
|
102
|
+
// Also resolve webpack loaders from the theme's own node_modules.
|
|
103
|
+
// When consumed via file: (local dev), loaders like style-loader,
|
|
104
|
+
// css-loader etc. live in the theme dir, not the consumer's node_modules.
|
|
105
|
+
resolveLoader: {
|
|
106
|
+
modules: ['node_modules', path.resolve(__dirname, 'node_modules')],
|
|
107
|
+
},
|
|
61
108
|
resolve: {
|
|
62
109
|
extensions: ['.mjs', '.js', '.jsx', '.json', '.scss'],
|
|
63
110
|
fullySpecified: false,
|
|
@@ -102,15 +149,15 @@ module.exports = function themeGovuk(context, options) {
|
|
|
102
149
|
// which fail under webpack's fullySpecified enforcement for .mjs files.
|
|
103
150
|
new webpack.NormalModuleReplacementPlugin(
|
|
104
151
|
/^@react-foundry\/component-helpers$/,
|
|
105
|
-
|
|
152
|
+
findPkgEntry('@react-foundry/component-helpers')
|
|
106
153
|
),
|
|
107
154
|
new webpack.NormalModuleReplacementPlugin(
|
|
108
155
|
/^@react-foundry\/client-component-helpers$/,
|
|
109
|
-
|
|
156
|
+
findPkgEntry('@react-foundry/client-component-helpers')
|
|
110
157
|
),
|
|
111
158
|
new webpack.NormalModuleReplacementPlugin(
|
|
112
159
|
/^@react-foundry\/uri$/,
|
|
113
|
-
|
|
160
|
+
findPkgEntry('@react-foundry/uri')
|
|
114
161
|
),
|
|
115
162
|
// Resolve asset paths from @not-govuk packages to govuk-frontend
|
|
116
163
|
new webpack.NormalModuleReplacementPlugin(
|
|
@@ -159,10 +206,22 @@ module.exports = function themeGovuk(context, options) {
|
|
|
159
206
|
fullySpecified: false,
|
|
160
207
|
},
|
|
161
208
|
},
|
|
209
|
+
{
|
|
210
|
+
// Force .docusaurus generated files (registry.js, routes.js etc.)
|
|
211
|
+
// to javascript/auto so that webpack's require.resolveWeak() magic
|
|
212
|
+
// transforms work. Without this, a consumer with "type":"module"
|
|
213
|
+
// in their package.json causes webpack to treat these files as pure
|
|
214
|
+
// ESM, leaving require.resolveWeak() untransformed in the browser
|
|
215
|
+
// bundle, which throws "require is not defined" at runtime.
|
|
216
|
+
test: /\.docusaurus[/\\][^/\\]+\.js$/,
|
|
217
|
+
type: 'javascript/auto',
|
|
218
|
+
},
|
|
162
219
|
{
|
|
163
220
|
test: /\.scss$/,
|
|
164
221
|
use: [
|
|
165
|
-
'
|
|
222
|
+
(!isServer && process.env.NODE_ENV === 'production')
|
|
223
|
+
? require('mini-css-extract-plugin').loader
|
|
224
|
+
: 'style-loader',
|
|
166
225
|
{
|
|
167
226
|
loader: 'css-loader',
|
|
168
227
|
options: {
|
|
@@ -183,11 +242,7 @@ module.exports = function themeGovuk(context, options) {
|
|
|
183
242
|
{
|
|
184
243
|
loader: 'sass-loader',
|
|
185
244
|
options: {
|
|
186
|
-
implementation: require('sass'),
|
|
187
|
-
// Override GOV.UK asset path to include the Docusaurus baseUrl.
|
|
188
|
-
// The default '../../assets/' produces URLs without baseUrl,
|
|
189
|
-
// causing 404s when baseUrl is not '/'.
|
|
190
|
-
// Only prepend for SCSS/Sass files — plain CSS can't use Sass variables.
|
|
245
|
+
implementation: require(require.resolve('sass', { paths: [siteDir] })),
|
|
191
246
|
additionalData: (content, loaderContext) => {
|
|
192
247
|
if (/\.scss$|\.sass$/.test(loaderContext.resourcePath)) {
|
|
193
248
|
return `$govuk-assets-path: '${baseUrl}assets/';\n` + content;
|
|
@@ -196,6 +251,7 @@ module.exports = function themeGovuk(context, options) {
|
|
|
196
251
|
},
|
|
197
252
|
sassOptions: {
|
|
198
253
|
includePaths: [
|
|
254
|
+
path.join(siteDir, 'node_modules'),
|
|
199
255
|
path.resolve(__dirname, 'node_modules'),
|
|
200
256
|
],
|
|
201
257
|
quietDeps: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/docusaurus-theme-govuk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4-alpha",
|
|
4
4
|
"description": "A Docusaurus theme implementing the GOV.UK Design System for consistent, accessible documentation sites",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"@docusaurus/core": "^3.0.0",
|
|
25
25
|
"react": "^18.0.0 || ^19.0.0",
|
|
26
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
26
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
27
|
+
"webpack": "^5.0.0"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
30
|
"@mdx-js/react": "^3.0.0",
|
|
@@ -40,5 +41,8 @@
|
|
|
40
41
|
},
|
|
41
42
|
"engines": {
|
|
42
43
|
"node": ">=18.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"mini-css-extract-plugin": "^2.10.0"
|
|
43
47
|
}
|
|
44
48
|
}
|
|
@@ -43,13 +43,9 @@ export const useIsActive = () => {
|
|
|
43
43
|
|
|
44
44
|
return (href, exact = true) => {
|
|
45
45
|
const target = URI.parse(href, location.pathname);
|
|
46
|
-
const
|
|
47
|
-
// Root path '/' should only match exactly, not as a prefix for all paths
|
|
48
|
-
const pathStart = target.pathname === '' || (target.pathname !== '/' && location.pathname.startsWith(dir));
|
|
49
|
-
const pathMatch = target.pathname === '' || location.pathname === target.pathname;
|
|
46
|
+
const pathMatch = location.pathname === target.pathname;
|
|
50
47
|
const queryMatch = includes(location.query, target.query);
|
|
51
|
-
|
|
52
|
-
return exact ? activeExact : !!(activeExact || (pathStart && queryMatch));
|
|
48
|
+
return pathMatch && queryMatch;
|
|
53
49
|
};
|
|
54
50
|
};
|
|
55
51
|
|