From fd93e21598ee092160019cc8ec9a45ff8bcbc5d7 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Tue, 6 Jun 2023 12:57:02 +0100 Subject: [PATCH] Resolve `govuk-frontend` at project level by default Node.js `require.resolve()` paths are optionally provided, for example: 1. Review app uses local `node_modules` before project level 2. Package stats uses local `node_modules` before project level This is necessary for workspace `govuk-frontend@4` installs to be found --- shared/lib/names.js | 23 ++++++++++++++++------- shared/lib/names.unit.test.mjs | 32 ++++++++++++++++++++++++++++++++ shared/stats/rollup.config.mjs | 2 +- shared/stats/src/index.mjs | 6 +++++- shared/tasks/styles.mjs | 12 +++++++++--- 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/shared/lib/names.js b/shared/lib/names.js index 6d412bc192..d7f2f8c9c6 100644 --- a/shared/lib/names.js +++ b/shared/lib/names.js @@ -1,5 +1,6 @@ const { dirname, join, parse } = require('path') +const { paths } = require('govuk-frontend-config') const { minimatch } = require('minimatch') /** @@ -68,8 +69,12 @@ function componentPathToModuleName (componentPath) { * @param {PackageOptions} [options] - Package resolution options * @returns {string} Path to installed npm package entry */ -function packageEntryToPath (packageEntry, { modulePath } = {}) { - const packagePath = require.resolve(packageEntry) +function packageEntryToPath (packageEntry, { modulePath, requirePaths } = {}) { + const packagePath = require.resolve(packageEntry, { + paths: requirePaths ?? [ + join(paths.root, 'node_modules') + ] + }) // Append optional module path return modulePath !== undefined ? join(dirname(packagePath), modulePath) : packagePath @@ -82,16 +87,17 @@ function packageEntryToPath (packageEntry, { modulePath } = {}) { * @param {PackageOptions} [options] - Package resolution options * @returns {string} Path to installed npm package field */ -function packageFieldToPath (packageName, { modulePath, field = 'main' } = {}) { +function packageFieldToPath (packageName, { modulePath, requirePaths, field = 'main' } = {}) { const packageEntry = `${packageName}/package.json` // Package field as child path - const entryPath = require(packageEntryToPath(packageEntry))[field] + const entryPath = require(packageEntryToPath(packageEntry, { requirePaths }))[field] const childPath = modulePath !== undefined ? join(dirname(entryPath), modulePath) : entryPath // Append optional module path return packageEntryToPath(packageEntry, { - modulePath: childPath + modulePath: childPath, + requirePaths }) } @@ -103,11 +109,13 @@ function packageFieldToPath (packageName, { modulePath, field = 'main' } = {}) { * * @param {string} packageName - Installed npm package name * @param {string} [childPath] - Child directory path (optional, relative to package.json), for example `src/govuk/all.mjs` + * @param {PackageOptions} [options] - Package resolution options * @returns {string} Path to installed npm package */ -function packageNameToPath (packageName, childPath = '') { +function packageNameToPath (packageName, childPath = '', { requirePaths } = {}) { return packageEntryToPath(`${packageName}/package.json`, { - modulePath: childPath + modulePath: childPath, + requirePaths }) } @@ -124,4 +132,5 @@ module.exports = { * @typedef {object} PackageOptions * @property {string} [field] - Package field name from package.json, for example `module` * @property {string} [modulePath] - Module path (optional, relative to package entry), for example `all.mjs` + * @property {string[]} [requirePaths] - Node.js require 'node_modules` lookup paths */ diff --git a/shared/lib/names.unit.test.mjs b/shared/lib/names.unit.test.mjs index 092eef9143..2b6b4bf38c 100644 --- a/shared/lib/names.unit.test.mjs +++ b/shared/lib/names.unit.test.mjs @@ -201,3 +201,35 @@ describe('packageNameToPath', () => { .toBe(resolvedPath) }) }) + +describe("packageNameToPath (with custom 'node_module' paths)", () => { + const packages = [ + { + packageName: 'govuk-frontend', + childPath: 'src/govuk/all.mjs', + options: { requirePaths: [join(paths.root, 'node_modules')] }, + resolvedPath: join(paths.package, 'src/govuk/all.mjs') + }, + { + packageName: 'govuk-frontend-review', + childPath: 'src/app.mjs', + options: { requirePaths: [join(paths.root, 'node_modules')] }, + resolvedPath: join(paths.app, 'src/app.mjs') + }, + { + packageName: 'autoprefixer', + options: { requirePaths: [join(paths.package, 'node_modules')] }, + resolvedPath: join(paths.root, 'node_modules/autoprefixer') + }, + { + packageName: 'postcss', + options: { requirePaths: [join(paths.app, 'node_modules')] }, + resolvedPath: join(paths.root, 'node_modules/postcss') + } + ] + + it.each(packages)("locates path for npm package '$packageName'", ({ packageName, childPath, options = {}, resolvedPath }) => { + expect(packageNameToPath(packageName, childPath, options)) + .toBe(resolvedPath) + }) +}) diff --git a/shared/stats/rollup.config.mjs b/shared/stats/rollup.config.mjs index f128a420d8..46180299a7 100644 --- a/shared/stats/rollup.config.mjs +++ b/shared/stats/rollup.config.mjs @@ -30,7 +30,7 @@ export default defineConfig(modulePaths * Input plugins */ plugins: [ - resolve({ modulePaths: [paths.root] }), + resolve({ modulePaths: packageOptions.requirePaths }), // Stats: File size visualizer({ diff --git a/shared/stats/src/index.mjs b/shared/stats/src/index.mjs index 41e71e9eda..6ba9494609 100644 --- a/shared/stats/src/index.mjs +++ b/shared/stats/src/index.mjs @@ -16,7 +16,11 @@ const componentNamesWithJavaScript = await getComponentNames((componentName, com */ export const packageOptions = { field: 'module', - modulePath: 'all.mjs' + modulePath: 'all.mjs', + requirePaths: [ + join(paths.stats, 'node_modules'), + join(paths.root, 'node_modules') + ] } /** diff --git a/shared/tasks/styles.mjs b/shared/tasks/styles.mjs index 86f8d77d9c..f7d047056d 100644 --- a/shared/tasks/styles.mjs +++ b/shared/tasks/styles.mjs @@ -39,10 +39,16 @@ export async function compile (pattern, options) { * * @param {AssetEntry} assetEntry - Asset entry */ -export async function compileStylesheet ([modulePath, { configPath, srcPath, destPath, filePath }]) { +export async function compileStylesheet ([modulePath, { configPath, srcPath, destPath, filePath, workspace }]) { const moduleSrcPath = join(srcPath, modulePath) const moduleDestPath = join(destPath, filePath ? filePath(parse(modulePath)) : modulePath) + // Default paths to resolve @imports + const requirePaths = [ + join(workspace, 'node_modules'), + join(paths.root, 'node_modules') + ] + let css let map @@ -81,8 +87,8 @@ export async function compileStylesheet ([modulePath, { configPath, srcPath, des // Resolve @imports via loadPaths: [ - packageNameToPath('govuk-frontend', 'dist'), - join(paths.root, 'node_modules') + packageNameToPath('govuk-frontend', 'dist', { requirePaths }), + ...requirePaths ], // Sass custom logger