diff --git a/app/tasks/scripts.mjs b/app/tasks/scripts.mjs index 073d144766..2b53adef4e 100644 --- a/app/tasks/scripts.mjs +++ b/app/tasks/scripts.mjs @@ -3,15 +3,13 @@ import { join } from 'path' import gulp from 'gulp' import { paths } from '../../config/index.js' -import { files, npm, scripts, task } from '../../tasks/index.mjs' +import { npm, scripts, task } from '../../tasks/index.mjs' /** * JavaScripts task (for watch) * Compilation, documentation */ export const compile = gulp.series( - files.updateFrontendVersionJavaScript, - task.name('compile:js', () => scripts.compile('all.mjs', { srcPath: join(paths.src, 'govuk'), diff --git a/app/tasks/styles.mjs b/app/tasks/styles.mjs index e1a9553159..a511923d88 100644 --- a/app/tasks/styles.mjs +++ b/app/tasks/styles.mjs @@ -3,15 +3,13 @@ import { join } from 'path' import gulp from 'gulp' import { paths } from '../../config/index.js' -import { files, npm, styles, task } from '../../tasks/index.mjs' +import { npm, styles, task } from '../../tasks/index.mjs' /** * Stylesheets task (for watch) * Compilation, documentation */ export const compile = gulp.series( - files.updateFrontendVersionSass, - task.name('compile:scss', () => styles.compile('**/[!_]*.scss', { srcPath: join(paths.app, 'src/stylesheets'), diff --git a/package-lock.json b/package-lock.json index 40d180a89a..fa1c86f4df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "postcss-unrgba": "^1.1.1", "puppeteer": "^19.8.2", "rollup": "0.59.4", + "rollup-plugin-replace": "^2.2.0", "sass-embedded": "^1.60.0", "sassdoc": "^2.7.4", "slash": "^5.0.0", @@ -9799,6 +9800,11 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -16478,6 +16484,14 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -20961,6 +20975,24 @@ "rollup": "bin/rollup" } }, + "node_modules/rollup-plugin-replace": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz", + "integrity": "sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==", + "deprecated": "This module has moved and is now available at @rollup/plugin-replace. Please update your dependencies. This version is no longer maintained.", + "dependencies": { + "magic-string": "^0.25.2", + "rollup-pluginutils": "^2.6.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, "node_modules/rollup/node_modules/@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -22358,6 +22390,12 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "deprecated": "See https://github.com/lydell/source-map-url#deprecated" }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead" + }, "node_modules/sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", @@ -32422,6 +32460,11 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -37726,6 +37769,14 @@ "yallist": "^4.0.0" } }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -41018,6 +41069,23 @@ } } }, + "rollup-plugin-replace": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz", + "integrity": "sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==", + "requires": { + "magic-string": "^0.25.2", + "rollup-pluginutils": "^2.6.0" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "requires": { + "estree-walker": "^0.6.1" + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -42099,6 +42167,11 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, "sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", diff --git a/package.json b/package.json index aeed3fe3e4..46deb980be 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "postcss-unrgba": "^1.1.1", "puppeteer": "^19.8.2", "rollup": "0.59.4", + "rollup-plugin-replace": "^2.2.0", "sass-embedded": "^1.60.0", "sassdoc": "^2.7.4", "slash": "^5.0.0", diff --git a/postcss.config.mjs b/postcss.config.mjs index d36d8a786e..bb217e370e 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -9,6 +9,8 @@ import unmq from 'postcss-unmq' import unopacity from 'postcss-unopacity' import unrgba from 'postcss-unrgba' +import { pkg } from './config/index.js' + /** * PostCSS config * @@ -58,6 +60,19 @@ export default ({ from = '', to = '', env = 'production' }) => { ) } + // Add GOV.UK Frontend release version + config.plugins.push({ + postcssPlugin: 'govuk-frontend-version', + Declaration: { + // Find CSS declaration for version, update value + // https://github.com/postcss/postcss/blob/main/docs/writing-a-plugin.md + // https://postcss.org/api/#declaration + '--govuk-frontend-version': (decl) => { + decl.value = `"${pkg.version}"` + } + } + }) + // Always minify CSS if (config.syntax !== scss) { config.plugins.push(cssnano({ diff --git a/postcss.config.unit.test.mjs b/postcss.config.unit.test.mjs index 8abdffba50..1ece6f31ad 100644 --- a/postcss.config.unit.test.mjs +++ b/postcss.config.unit.test.mjs @@ -80,6 +80,7 @@ describe('PostCSS config', () => { expect(getPluginNames(config)) .toEqual([ 'autoprefixer', + 'govuk-frontend-version', 'postcss-discard-comments', 'postcss-minify-gradients', 'postcss-reduce-initial', @@ -127,6 +128,7 @@ describe('PostCSS config', () => { 'postcss-unmq', 'postcss-unopacity', 'postcss-color-rgba-fallback', + 'govuk-frontend-version', 'postcss-discard-comments', 'postcss-minify-gradients', 'postcss-reduce-initial', @@ -170,7 +172,8 @@ describe('PostCSS config', () => { expect(getPluginNames(config)) .toEqual([ - 'autoprefixer' + 'autoprefixer', + 'govuk-frontend-version' ]) }) }) @@ -188,6 +191,7 @@ describe('PostCSS config', () => { .toEqual([ 'autoprefixer', 'postcss-pseudo-classes', + 'govuk-frontend-version', 'postcss-discard-comments', 'postcss-minify-gradients', 'postcss-reduce-initial', @@ -256,6 +260,7 @@ describe('PostCSS config', () => { 'postcss-unmq', 'postcss-unopacity', 'postcss-color-rgba-fallback', + 'govuk-frontend-version', 'postcss-discard-comments', 'postcss-minify-gradients', 'postcss-reduce-initial', diff --git a/src/govuk/all.mjs b/src/govuk/all.mjs index 87a0df48b8..86978772c3 100644 --- a/src/govuk/all.mjs +++ b/src/govuk/all.mjs @@ -89,6 +89,8 @@ function initAll (config) { export { initAll, version, + + // Components Accordion, Button, Details, diff --git a/tasks/build/dist.mjs b/tasks/build/dist.mjs index 9512d21ade..746c658975 100644 --- a/tasks/build/dist.mjs +++ b/tasks/build/dist.mjs @@ -24,8 +24,6 @@ export default gulp.series( }) ), - files.updateFrontendVersionJavaScript, - // Compile GOV.UK Frontend JavaScript task.name('compile:js', () => scripts.compile('all.mjs', { @@ -38,8 +36,6 @@ export default gulp.series( }) ), - files.updateFrontendVersionSass, - // Compile GOV.UK Frontend Sass task.name('compile:scss', () => styles.compile('**/[!_]*.scss', { diff --git a/tasks/build/dist.test.mjs b/tasks/build/dist.test.mjs index 10b0f0afce..cef98d9c7c 100644 --- a/tasks/build/dist.test.mjs +++ b/tasks/build/dist.test.mjs @@ -40,6 +40,10 @@ describe('dist/', () => { it('should contain source mapping URL', () => { expect(stylesheet).toMatch(new RegExp(`/\\*# sourceMappingURL=${filename}.map \\*/$`)) }) + + it('should contain version number custom property', () => { + expect(stylesheet).toContain(`--govuk-frontend-version:"${pkg.version}"`) + }) }) describe('govuk-frontend-ie8-[version].min.css', () => { @@ -73,6 +77,10 @@ describe('dist/', () => { expect(javascript).toBeTruthy() }) + it('should contain correct version number', () => { + expect(javascript).toContain(`.version="${pkg.version}",`) + }) + it('should contain source mapping URL', () => { expect(javascript).toMatch(new RegExp(`//# sourceMappingURL=${filename}.map$`)) }) diff --git a/tasks/build/package.test.mjs b/tasks/build/package.test.mjs index 930753110c..c4b9d37c6d 100644 --- a/tasks/build/package.test.mjs +++ b/tasks/build/package.test.mjs @@ -1,7 +1,7 @@ import { readFile } from 'fs/promises' import { join } from 'path' -import { paths } from '../../config/index.js' +import { paths, pkg } from '../../config/index.js' import { filterPath, getDirectories, getListing, mapPathTo } from '../../lib/file-helper.js' import { componentNameToClassName, componentPathToModuleName } from '../../lib/helper-functions.js' import { compileSassFile } from '../../lib/jest-helpers.js' @@ -112,6 +112,14 @@ describe('package/', () => { // Look for AMD module definition for 'GOVUKFrontend' expect(contents).toContain("typeof define === 'function' && define.amd ? define('GOVUKFrontend', ['exports'], factory)") }) + + it('should have correct version number', async () => { + const contents = await readFile(join(paths.package, 'govuk', 'all.js'), 'utf8') + + // Look for AMD module export 'GOVUKFrontend.version' + expect(contents).toContain(`var version = '${pkg.version}';`) + expect(contents).toContain('exports.version = version;') + }) }) describe('component', () => { diff --git a/tasks/files.mjs b/tasks/files.mjs index 82a1a8e4e1..b8d6e9d233 100644 --- a/tasks/files.mjs +++ b/tasks/files.mjs @@ -6,7 +6,7 @@ import cpy from 'cpy' import { deleteAsync } from 'del' import slash from 'slash' -import { pkg, paths } from '../config/index.js' +import { pkg } from '../config/index.js' /** * Delete path globs for a given destination @@ -28,22 +28,6 @@ export async function version (assetPath, { destPath }) { await writeFile(join(destPath, assetPath), pkg.version + EOL) } -/** - * Write govuk-frontend-version.mjs - * with `package/package.json` version - */ -export async function updateFrontendVersionJavaScript () { - return writeFile(join(`${paths.src}/govuk/common/`, 'govuk-frontend-version.mjs'), `export var version = '${pkg.version}'${EOL}`) -} - -/** - * Write _govuk-frontend-version.scss - * with `package/package.json` version - */ -export async function updateFrontendVersionSass () { - return writeFile(join(`${paths.src}/govuk/core/`, '_govuk-frontend-version.scss'), `:root {\n --govuk-frontend-version: "${pkg.version}";\n}${EOL}`) -} - /** * Copy files task * Copies files to destination diff --git a/tasks/scripts.mjs b/tasks/scripts.mjs index 2950cd8101..33f81f2d6a 100644 --- a/tasks/scripts.mjs +++ b/tasks/scripts.mjs @@ -2,8 +2,10 @@ import { join, parse } from 'path' import PluginError from 'plugin-error' import { rollup } from 'rollup' +import replace from 'rollup-plugin-replace' import { minify } from 'terser' +import { pkg } from '../config/index.js' import { getListing } from '../lib/file-helper.js' import { componentPathToModuleName } from '../lib/helper-functions.js' @@ -38,10 +40,20 @@ export async function compileJavaScript ([modulePath, { srcPath, destPath, fileP const moduleSrcPath = join(srcPath, modulePath) const moduleDestPath = join(destPath, filePath ? filePath(parse(modulePath)) : modulePath) + // Rollup plugins + const plugins = [ + // Add GOV.UK Frontend release version + // @ts-expect-error "This expression is not callable" due to incorrect types + replace({ + include: join(srcPath, 'common/govuk-frontend-version.mjs'), + values: { development: pkg.version } + }) + ] + // Option 1: Rollup bundle set (multiple files) // - Module imports are preserved, not concatenated if (moduleDestPath.endsWith('.mjs')) { - const bundle = await rollup({ input: [moduleSrcPath], experimentalPreserveModules: true }) + const bundle = await rollup({ input: [moduleSrcPath], plugins, experimentalPreserveModules: true }) // Compile JavaScript to ES modules await bundle.write({ @@ -55,7 +67,7 @@ export async function compileJavaScript ([modulePath, { srcPath, destPath, fileP // Option 1: Rollup bundle (single file) // - Universal Module Definition (UMD) bundle - const bundle = await rollup({ input: moduleSrcPath }) + const bundle = await rollup({ input: moduleSrcPath, plugins }) // Compile JavaScript to output format const bundled = await bundle[moduleDestPath.endsWith('.min.js') ? 'generate' : 'write']({