diff --git a/CHANGELOG.md b/CHANGELOG.md index 99e5eb47b..9e649266b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +:new: **New features** + +- Output source maps and use minified code in examples ([PR 1152](https://github.com/nhsuk/nhsuk-frontend/pull/1152)) + :wrench: **Fixes** We've configured our build tasks to use [Browserslist](https://browsersl.ist) for browser compatibility. This change was introduced in [pull request #1135: Configure Browserslist for build tooling](https://github.com/nhsuk/nhsuk-frontend/issues/1135) diff --git a/app/_templates/layout.njk b/app/_templates/layout.njk index 09fafc66f..603633a3d 100644 --- a/app/_templates/layout.njk +++ b/app/_templates/layout.njk @@ -17,9 +17,9 @@ - + - + diff --git a/docs/contributing/tooling.md b/docs/contributing/tooling.md index fb9e562cc..ee586d44b 100644 --- a/docs/contributing/tooling.md +++ b/docs/contributing/tooling.md @@ -28,11 +28,11 @@ To run a gulp task, run `npx gulp ` on the command line. | task | action | | ------------ | ---------------------------------------------------------------------- | | `default` | Serve the documentation on port 3000. Recompile when there are changes | -| `style` | Compiles CSS | -| `build` | Compiles CSS and JS | -| `bundle` | Creates distributable CSS and JS files in `dist/` | +| `style` | Compiles CSS only, including minified files in `dist/` | +| `script` | Compiles JS only, including minified files in `dist/` | +| `build` | Deletes `dist/` contents then runs `style` and `script` | | `zip` | Creates a distributable zip file in `dist/` | -| `watch` | Recompile distributables when there are changes | +| `watch` | Runs `style` and `script` when there are changes | | `docs:build` | Recompile documentation | | `docs:watch` | Recompile documentation when there are changes | | `docs:serve` | Serve documentation on port 3000 | diff --git a/gulpfile.js b/gulpfile.js index 1608b53a6..e0d808406 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,3 +1,8 @@ +const { join, relative } = require('path') +const { cwd } = require('process') +const { Transform } = require('stream') +const { fileURLToPath } = require('url') + const autoprefixer = require('autoprefixer') const cssnano = require('cssnano') const gulp = require('gulp') @@ -5,6 +10,7 @@ const postcss = require('gulp-postcss') const rename = require('gulp-rename') const gulpSass = require('gulp-sass') const terser = require('gulp-terser') +const PluginError = require('plugin-error') const dartSass = require('sass-embedded') const webpack = require('webpack-stream') @@ -35,26 +41,66 @@ const sass = gulpSass(dartSass) /* Build the CSS from source */ function compileCSS(done) { return gulp - .src(['packages/nhsuk.scss']) - .pipe(sass().on('error', done)) + .src(['packages/nhsuk.scss'], { + sourcemaps: true + }) + .pipe( + sass({ + sourceMap: true, + sourceMapIncludeSources: true + }).on('error', (error) => { + done( + new PluginError('compileCSS', error.messageFormatted, { + showProperties: false + }) + ) + }) + ) + .pipe( + new Transform({ + objectMode: true, + + // Make source file:// paths relative + transform(file, enc, cb) { + if (file.sourceMap?.sources) { + file.sourceMap.sources = file.sourceMap.sources.map((path) => + relative(join(cwd(), 'dist'), fileURLToPath(path)) + ) + } + + cb(null, file) + } + }) + ) .pipe(postcss([autoprefixer()])) - .pipe(gulp.dest('dist/')) + .pipe( + gulp.dest('dist/', { + sourcemaps: '.' + }) + ) } /* Minify CSS and add a min.css suffix */ function minifyCSS() { return gulp - .src([ - 'dist/*.css', - '!dist/*.min.css' // don't re-minify minified css - ]) + .src( + [ + 'dist/*.css', + '!dist/*.min.css' // don't re-minify minified css + ], + { sourcemaps: true } + ) .pipe(postcss([cssnano()])) .pipe( rename({ suffix: `-${version}.min` }) ) - .pipe(gulp.dest('dist/')) + .pipe( + gulp.dest('dist/', { + sourcemaps: '.' + }) + ) } /** @@ -62,11 +108,14 @@ function minifyCSS() { */ /* Use Webpack to build and minify the NHS.UK components JS. */ -function webpackJS() { +function webpackJS(done) { return gulp - .src('./packages/nhsuk.js') + .src('./packages/nhsuk.js', { + sourcemaps: true + }) .pipe( webpack({ + devtool: 'source-map', mode: 'production', module: { rules: [ @@ -84,24 +133,49 @@ function webpackJS() { minimize: false // minification is handled by terser }, output: { - filename: 'nhsuk.js' + filename: 'nhsuk.js', + + // Make source webpack:// paths relative + devtoolModuleFilenameTemplate(info) { + return relative(join(cwd(), 'dist'), info.absoluteResourcePath) + } + }, + stats: { + colors: true, + errors: false }, target: 'browserslist' + }).on('error', (error) => { + done( + new PluginError('webpackJS', error, { + showProperties: false + }) + ) + }) + ) + .pipe( + gulp.dest('./dist', { + sourcemaps: '.' }) ) - .pipe(gulp.dest('./dist')) } /* Minify the JS file for release */ function minifyJS() { return gulp - .src([ - 'dist/*.js', - '!dist/*.min.js' // don't re-minify minified javascript - ]) + .src( + [ + 'dist/*.js', + '!dist/*.min.js' // don't re-minify minified javascript + ], + { sourcemaps: true } + ) .pipe( terser({ format: { comments: false }, + sourceMap: { + includeSources: true + }, // Compatibility workarounds ecma: 5, @@ -110,23 +184,14 @@ function minifyJS() { ) .pipe( rename({ - suffix: '.min' + suffix: `-${version}.min` }) ) - .pipe(gulp.dest('dist/')) -} - -/* Version the JS file for release */ -function versionJS() { - return gulp - .src('dist/nhsuk.min.js') .pipe( - rename({ - basename: `nhsuk-${version}`, - extname: '.min.js' + gulp.dest('dist/', { + sourcemaps: '.' }) ) - .pipe(gulp.dest('dist/')) } /** @@ -148,25 +213,30 @@ function assets() { /* Copy JS files into their relevant folders */ function jsFolder() { - return gulp - .src('dist/*.min.js', { ignore: 'dist/nhsuk.min.js' }) - .pipe(gulp.dest('dist/js/')) + return gulp.src('dist/*.min.{js,js.map}').pipe(gulp.dest('dist/js/')) } /* Copy CSS files into their relevant folders */ function cssFolder() { - return gulp.src('dist/*.min.css').pipe(gulp.dest('dist/css/')) + return gulp.src('dist/*.min.{css,css.map}').pipe(gulp.dest('dist/css/')) } async function createZip() { const { default: zip } = await import('gulp-zip') return gulp - .src(['dist/css/*.min.css', 'dist/js/*.min.js', 'dist/assets/**'], { - base: 'dist', - encoding: false - }) + .src( + [ + 'dist/css/*.min.{css,css.map}', + 'dist/js/*.min.{js,js.map}', + 'dist/assets/**' + ], + { + base: 'dist', + encoding: false + } + ) .pipe(zip(`nhsuk-frontend-${version}.zip`)) .pipe(gulp.dest('dist')) } @@ -183,17 +253,10 @@ gulp.task('clean:zip', async () => { return clean(['dist/{assets,css,js}', 'dist/*.zip']) }) -gulp.task('style', compileCSS) +gulp.task('style', gulp.series([compileCSS, minifyCSS])) +gulp.task('script', gulp.series([webpackJS, minifyJS])) -gulp.task( - 'build', - gulp.series(['clean', gulp.parallel([compileCSS, webpackJS])]) -) - -gulp.task( - 'bundle', - gulp.series(['build', gulp.parallel([minifyCSS, minifyJS]), versionJS]) -) +gulp.task('build', gulp.series(['clean', gulp.parallel(['style', 'script'])])) gulp.task( 'zip', @@ -206,8 +269,8 @@ gulp.task( gulp.task('watch', () => Promise.all([ - gulp.watch(['packages/**/*.scss'], compileCSS), - gulp.watch(['packages/**/*.js'], webpackJS) + gulp.watch(['packages/**/*.scss'], gulp.series(['style'])), + gulp.watch(['packages/**/*.js'], gulp.series(['script'])) ]) ) diff --git a/package.json b/package.json index e63e2d3c5..1fd72e84e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "node": ">=20.0.0" }, "scripts": { - "build": "gulp bundle docs:build --color --series", + "build": "gulp build docs:build --color --series", "prestart": "npm run build", "start": "gulp --color", "lint": "npm run lint:js && npm run lint:css && npm run lint:prettier", diff --git a/tasks/docs.js b/tasks/docs.js index 815bb13de..3e24d3ecf 100644 --- a/tasks/docs.js +++ b/tasks/docs.js @@ -9,6 +9,7 @@ const nunjucks = require('nunjucks') const PluginError = require('plugin-error') const validatorConfig = require('../.htmlvalidate') +const { version } = require('../package.json') /** * Compile Nunjucks into HTML @@ -29,7 +30,8 @@ async function buildHTML() { const { name, dir } = parse(path) const html = env.render(path, { - baseUrl: '/nhsuk-frontend/' + baseUrl: '/nhsuk-frontend/', + version }) const destPath = join('dist/app', dir) @@ -71,8 +73,8 @@ async function validateHTML() { */ function copyCSS() { return gulp - .src('dist/*.css') - .pipe(gulp.dest('dist/app/assets')) + .src('dist/*.min.{css,css.map}') + .pipe(gulp.dest('dist/app/stylesheets')) .pipe(browserSync.stream()) } @@ -81,8 +83,8 @@ function copyCSS() { */ function copyJS() { return gulp - .src('dist/*.js') - .pipe(gulp.dest('dist/app/assets')) + .src('dist/*.min.{js,js.map}') + .pipe(gulp.dest('dist/app/javascripts')) .pipe(browserSync.stream()) } @@ -137,8 +139,8 @@ gulp.task('docs:watch', () => Promise.all([ gulp.watch(['app/**/*.njk'], buildHTML), gulp.watch(['dist/**/*.html']).on('change', browserSync.reload), - gulp.watch(['dist/*.css'], copyCSS), - gulp.watch(['dist/*.js'], copyJS), + gulp.watch(['dist/*.min.{css,css.map}'], copyCSS), + gulp.watch(['dist/*.min.{js,js.map}'], copyJS), gulp.watch(['packages/assets/**/*'], copyBinaryAssets) ]) )