@defra/docusaurus-theme-govuk 0.0.1-alpha → 0.0.3-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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/index.js +78 -29
  3. package/package.json +3 -2
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 webpack = require('webpack');
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 this package's dependencies
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,64 @@ 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 path.dirname(
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 react-router is nested in 3.9.x)
51
- const docuCorePath = path.dirname(
52
- require.resolve('@docusaurus/core/package.json', { paths: [siteDir] })
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
+
60
94
  return {
95
+ // Also resolve webpack loaders from the theme's own node_modules.
96
+ // When consumed via file: (local dev), loaders like style-loader,
97
+ // css-loader etc. live in the theme dir, not the consumer's node_modules.
98
+ resolveLoader: {
99
+ modules: ['node_modules', path.resolve(__dirname, 'node_modules')],
100
+ },
61
101
  resolve: {
62
102
  extensions: ['.mjs', '.js', '.jsx', '.json', '.scss'],
63
103
  fullySpecified: false,
@@ -72,12 +112,10 @@ module.exports = function themeGovuk(context, options) {
72
112
  'react-router': resolveFromSite('react-router'),
73
113
  'react-router-dom': resolveFromSite('react-router-dom'),
74
114
  'react-router-config': resolveFromSite('react-router-config'),
75
- // Ensure @mdx-js/react resolves from the theme's node_modules,
76
- // not the consumer's (which may not have it installed).
77
- '@mdx-js/react': path.resolve(
78
- __dirname,
79
- 'node_modules/@mdx-js/react'
80
- ),
115
+ // Ensure @mdx-js/react resolves from the consumer's node_modules.
116
+ // When installed from npm the theme ships no node_modules of its own,
117
+ // so we must point webpack at the copy already present in the site.
118
+ '@mdx-js/react': resolveFromSite('@mdx-js/react'),
81
119
  },
82
120
  },
83
121
  plugins: [
@@ -104,15 +142,15 @@ module.exports = function themeGovuk(context, options) {
104
142
  // which fail under webpack's fullySpecified enforcement for .mjs files.
105
143
  new webpack.NormalModuleReplacementPlugin(
106
144
  /^@react-foundry\/component-helpers$/,
107
- require.resolve('@react-foundry/component-helpers')
145
+ findPkgEntry('@react-foundry/component-helpers')
108
146
  ),
109
147
  new webpack.NormalModuleReplacementPlugin(
110
148
  /^@react-foundry\/client-component-helpers$/,
111
- require.resolve('@react-foundry/client-component-helpers')
149
+ findPkgEntry('@react-foundry/client-component-helpers')
112
150
  ),
113
151
  new webpack.NormalModuleReplacementPlugin(
114
152
  /^@react-foundry\/uri$/,
115
- require.resolve('@react-foundry/uri')
153
+ findPkgEntry('@react-foundry/uri')
116
154
  ),
117
155
  // Resolve asset paths from @not-govuk packages to govuk-frontend
118
156
  new webpack.NormalModuleReplacementPlugin(
@@ -161,6 +199,16 @@ module.exports = function themeGovuk(context, options) {
161
199
  fullySpecified: false,
162
200
  },
163
201
  },
202
+ {
203
+ // Force .docusaurus generated files (registry.js, routes.js etc.)
204
+ // to javascript/auto so that webpack's require.resolveWeak() magic
205
+ // transforms work. Without this, a consumer with "type":"module"
206
+ // in their package.json causes webpack to treat these files as pure
207
+ // ESM, leaving require.resolveWeak() untransformed in the browser
208
+ // bundle, which throws "require is not defined" at runtime.
209
+ test: /\.docusaurus[/\\][^/\\]+\.js$/,
210
+ type: 'javascript/auto',
211
+ },
164
212
  {
165
213
  test: /\.scss$/,
166
214
  use: [
@@ -185,7 +233,7 @@ module.exports = function themeGovuk(context, options) {
185
233
  {
186
234
  loader: 'sass-loader',
187
235
  options: {
188
- implementation: require('sass'),
236
+ implementation: require(require.resolve('sass', { paths: [siteDir] })),
189
237
  // Override GOV.UK asset path to include the Docusaurus baseUrl.
190
238
  // The default '../../assets/' produces URLs without baseUrl,
191
239
  // causing 404s when baseUrl is not '/'.
@@ -198,6 +246,7 @@ module.exports = function themeGovuk(context, options) {
198
246
  },
199
247
  sassOptions: {
200
248
  includePaths: [
249
+ path.join(siteDir, 'node_modules'),
201
250
  path.resolve(__dirname, 'node_modules'),
202
251
  ],
203
252
  quietDeps: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/docusaurus-theme-govuk",
3
- "version": "0.0.1-alpha",
3
+ "version": "0.0.3-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",