diff --git a/lib/index.js b/lib/index.js index 79d43f5..300bfc6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -12,6 +12,7 @@ const basePlugin = require('./plugins/base') const logger = require('./logger') const serveStatic = require('./utils/serveStatic') const renderHtml = require('./utils/renderHtml') +const minifyHtml = require('./utils/minifyHtml') const emoji = require('./emoji') const inspect = require('./utils/inspect') const validateConfig = require('./validateConfig') @@ -76,7 +77,8 @@ class Ream extends Event { extract: !this.options.dev }, pwa: false, - minimize: !this.options.dev + minimize: !this.options.dev, + minifyHtml: false } // config from constructor can override user config @@ -209,7 +211,7 @@ class Ream extends Event { routes.map(async route => { // Fake req const context = { req: { url: route } } - const html = await renderHtml(this.renderer, context) + const html = await this.renderHtml(context) const targetPath = this.resolveOutDir( `generated/${route.replace(/\/?$/, '/index.html')}` ) @@ -335,7 +337,7 @@ class Ream extends Event { } const context = { req, res } - const html = await renderHtml(this.renderer, context) + const html = await this.renderHtml(context) res.setHeader('content-type', 'text/html') res.end(html) @@ -403,6 +405,14 @@ class Ream extends Event { this.emit('renderer-ready', serverType) } + async renderHtml(context) { + let html = await renderHtml(this.renderer, context) + if (this.config.minifyHtml) { + html = minifyHtml(html, this.config.minifyHtml) + } + return html + } + resolveOutDir(...args) { return this.resolveBaseDir(this.config.outDir, ...args) } diff --git a/lib/utils/minifyHtml.js b/lib/utils/minifyHtml.js new file mode 100644 index 0000000..0c1578e --- /dev/null +++ b/lib/utils/minifyHtml.js @@ -0,0 +1,13 @@ +const { minify } = require('html-minifier') + +const defaultOptions = { + collapseWhitespace: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true, + useShortDoctype: true, + minifyCSS: true +} + +module.exports = (html, options) => + minify(html, options === true ? defaultOptions : options) diff --git a/lib/validateConfig.js b/lib/validateConfig.js index e7c453c..ee9a2ce 100644 --- a/lib/validateConfig.js +++ b/lib/validateConfig.js @@ -36,7 +36,8 @@ const schema = joi.object().keys({ // To add more controls, e.g. "notifyOnUpdate" will show a notifier when a new update is available pwa: joi.boolean(), minimize: joi.boolean(), - defaultBabelPreset: joi.any().valid(['minimal', false]) + defaultBabelPreset: joi.any().valid(['minimal', false]), + minifyHtml: joi.alternatives().try(joi.object(), joi.boolean()) }) module.exports = config => { diff --git a/package.json b/package.json index 0d3af1e..961fa0a 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "file-loader": "^1.1.6", "fs-extra": "^6.0.0", "hash-sum": "^1.0.2", + "html-minifier": "^4.0.0", "internal-ip": "^3.0.1", "joi": "^13.6.0", "joycon": "^1.0.4", diff --git a/tap-snapshots/test-projects-html-minifier-index.test.js-TAP.test.js b/tap-snapshots/test-projects-html-minifier-index.test.js-TAP.test.js new file mode 100644 index 0000000..63c449e --- /dev/null +++ b/tap-snapshots/test-projects-html-minifier-index.test.js-TAP.test.js @@ -0,0 +1,58 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/projects/html-minifier/index.test.js TAP test/projects/html-minifier > page 1`] = ` + + + + + + + + + + + + + +
+ Test +
+ + + +` + +exports[`test/projects/html-minifier/index.test.js TAP test/projects/html-minifier > page 2`] = ` +
Test
+` + +exports[`test/projects/html-minifier/index.test.js TAP test/projects/html-minifier > page 3`] = ` + + + + + + + + + + + + + +
+ Test +
+ + + +` diff --git a/test/lib/testProject.js b/test/lib/testProject.js index 7bdce0f..cf05636 100644 --- a/test/lib/testProject.js +++ b/test/lib/testProject.js @@ -36,7 +36,7 @@ class Client { } } -module.exports = function(baseDir, fn) { +module.exports = function(baseDir, fn, config) { // Calculate relative base directory for consistent snapshots. const relativeBaseDir = path.relative('.', baseDir) return tap.test(relativeBaseDir, async t => { @@ -49,7 +49,8 @@ module.exports = function(baseDir, fn) { css: { extract: false }, - minimize: false + minimize: false, + ...config } ) app.chainWebpack(config => { diff --git a/test/projects/html-minifier/index.test.js b/test/projects/html-minifier/index.test.js new file mode 100644 index 0000000..162aa20 --- /dev/null +++ b/test/projects/html-minifier/index.test.js @@ -0,0 +1,25 @@ +const testProject = require('../../lib/testProject') + +const configs = [ + { + minifyHtml: false + }, + { + minifyHtml: true + }, + { + minifyHtml: { + minifyCSS: true + } + } +] + +for (const config of configs) { + testProject( + __dirname, + async (t, c) => { + t.matchSnapshot((await c.axios.get('/')).data, 'page') + }, + config + ) +} diff --git a/test/projects/html-minifier/pages/index.vue b/test/projects/html-minifier/pages/index.vue new file mode 100644 index 0000000..2a1dcfd --- /dev/null +++ b/test/projects/html-minifier/pages/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/test/projects/html-minifier/ream.config.js b/test/projects/html-minifier/ream.config.js new file mode 100644 index 0000000..b3f4081 --- /dev/null +++ b/test/projects/html-minifier/ream.config.js @@ -0,0 +1,3 @@ +module.exports = { + fsRoutes: true +} diff --git a/yarn.lock b/yarn.lock index 8d925ae..8352387 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2346,6 +2346,14 @@ callsites@^2.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= +camel-case@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -2575,6 +2583,13 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +clean-css@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" + integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== + dependencies: + source-map "~0.6.0" + clean-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" @@ -2793,7 +2808,7 @@ commander@^2.14.1, commander@^2.9.0, commander@~2.17.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@~2.20.0: +commander@^2.19.0, commander@~2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== @@ -5416,6 +5431,11 @@ he@^1.1.0: resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -5483,6 +5503,19 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= +html-minifier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56" + integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig== + dependencies: + camel-case "^3.0.0" + clean-css "^4.2.1" + commander "^2.19.0" + he "^1.2.0" + param-case "^2.1.1" + relateurl "^0.2.7" + uglify-js "^3.5.1" + http-cache-semantics@^3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" @@ -7139,6 +7172,11 @@ loud-rejection@^1.0.0, loud-rejection@^1.6.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= + lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -7689,6 +7727,13 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" integrity sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA== +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + node-emoji@^1.4.1: version "1.8.1" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" @@ -8520,6 +8565,13 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" +param-case@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= + dependencies: + no-case "^2.2.0" + parse-asn1@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" @@ -9779,6 +9831,11 @@ regjsparser@^0.3.0: dependencies: jsesc "~0.5.0" +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + release-zalgo@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" @@ -10484,7 +10541,7 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, sour resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -11359,7 +11416,7 @@ uglify-js@^2.6: optionalDependencies: uglify-to-browserify "~1.0.0" -uglify-js@^3.1.4: +uglify-js@^3.1.4, uglify-js@^3.5.1: version "3.5.11" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.11.tgz#833442c0aa29b3a7d34344c7c63adaa3f3504f6a" integrity sha512-izPJg8RsSyqxbdnqX36ExpbH3K7tDBsAU/VfNv89VkMFy3z39zFjunQGsSHOlGlyIfGLGprGeosgQno3bo2/Kg== @@ -11569,6 +11626,11 @@ update-notifier@^2.3.0, update-notifier@^2.5.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" +upper-case@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= + uri-js@^4.2.1: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"