├── .npmignore
├── .prettierrc
├── tests
├── test-broken.ejs
├── test-broken.pug
├── test-broken.nunjucks
├── test-broken.handlebars
├── test.output.html
├── test.pug
├── test.output.raw.pug.html
├── test.ejs
├── test.handlebars
├── test.nunjucks
├── test.output.raw.html
├── test.output.raw.handlebars.html
└── index.js
├── examples
└── handlebars
│ ├── main.handlebars
│ ├── package.json
│ └── handlebars-example.js
├── LICENSE
├── package.json
├── .gitignore
├── minifier.js
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | examples
2 | tests
3 | package-lock.json
4 | .prettierrc
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "printWidth": 120,
4 | "singleQuote": true,
5 | "trailingComma": "all",
6 | "endOfLine": "auto",
7 | "useTabs": false,
8 | "tabWidth": 2
9 | }
10 |
--------------------------------------------------------------------------------
/tests/test-broken.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
10 |
--------------------------------------------------------------------------------
/tests/test-broken.pug:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
10 |
--------------------------------------------------------------------------------
/tests/test-broken.nunjucks:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
10 |
--------------------------------------------------------------------------------
/tests/test-broken.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
10 |
--------------------------------------------------------------------------------
/tests/test.output.html:
--------------------------------------------------------------------------------
1 | Express minify HTML testHello world
--------------------------------------------------------------------------------
/tests/test.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | // This unnecessary HTML comment should probably be removed!
4 | head.head
5 | meta(charset='UTF-8')
6 | title Express minify HTML test
7 | body#body
8 | | Hello #{hello}
9 | script(type='text/javascript').
10 | function reallyNiceFunction () {
11 | return "Hello!";
12 | };
13 |
14 |
--------------------------------------------------------------------------------
/tests/test.output.raw.pug.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Express minify HTML test
7 |
8 | Hello world
9 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/test.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Express minify HTML test
7 |
8 |
9 | Hello <%= hello %>
10 |
11 |
20 |
21 |
--------------------------------------------------------------------------------
/tests/test.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Express minify HTML test
7 |
8 |
9 | Hello {{hello}}
10 |
11 |
20 |
21 |
--------------------------------------------------------------------------------
/tests/test.nunjucks:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Express minify HTML test
7 |
8 |
9 | Hello {{ hello }}
10 |
11 |
20 |
21 |
--------------------------------------------------------------------------------
/tests/test.output.raw.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Express minify HTML test
7 |
8 |
9 | Hello world
10 |
11 |
20 |
21 |
--------------------------------------------------------------------------------
/tests/test.output.raw.handlebars.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Express minify HTML test
7 |
8 |
9 | Hello world
10 |
11 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/handlebars/main.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Express minify HTML handlebars example
7 |
8 |
9 | Hello {{hello}}!
10 |
11 |
20 |
--------------------------------------------------------------------------------
/examples/handlebars/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "express-minify-html-handlebars-example",
3 | "version": "0.0.1",
4 | "description": "Handlebars.js example for this middleware",
5 | "main": "handlebars-example.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node handlebars-example.js"
9 | },
10 | "author": "Matti Jokitulppo ",
11 | "license": "MIT",
12 | "dependencies": {
13 | "express": "^4.13.3",
14 | "express-handlebars": "^2.0.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/handlebars/handlebars-example.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var express = require('express');
4 | var minifyHTML = require('../../minifier.js');
5 | var exhbs = require('express-handlebars');
6 |
7 | var app = express();
8 |
9 | var hbs = exhbs.create({
10 | defaultLayout: 'main',
11 | layoutsDir: './'
12 | });
13 |
14 | app.engine('handlebars', hbs.engine);
15 | app.set('view engine', 'handlebars');
16 | app.set('views', './');
17 |
18 | app.use(minifyHTML({
19 | override: true,
20 | htmlMinifier: {
21 | removeComments: true,
22 | collapseWhitespace: true,
23 | collapseBooleanAttributes: true,
24 | removeAttributeQuotes: true,
25 | removeEmptyAttributes: true,
26 | minifyJS: true
27 | }
28 | }));
29 |
30 | app.get('/', function(req, res) {
31 | res.render('main', { hello: 'world' });
32 | });
33 |
34 | var server = app.listen(3000, function() {
35 | var host = server.address().address;
36 | var port = server.address().port;
37 |
38 | console.log('Handlebars example app listening at http://%s:%s', host, port);
39 | });
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Matti Jokitulppo (http://mattij.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "express-minify-html-2",
3 | "version": "2.0.0",
4 | "description": "Express.js middleware wrapper around html-minifier-terser",
5 | "main": "minifier.js",
6 | "scripts": {
7 | "test": "node tests"
8 | },
9 | "engines": {
10 | "npm": ">=6.4.0",
11 | "node": ">=14.0.0"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/meszaros-lajos-gyorgy/express-minify-html-2.git"
16 | },
17 | "keywords": [
18 | "express",
19 | "html",
20 | "minifier",
21 | "middleware",
22 | "minify"
23 | ],
24 | "author": "Matti Jokitulppo ",
25 | "maintainers": [
26 | "Lajos Mészáros "
27 | ],
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/meszaros-lajos-gyorgy/express-minify-html-2/issues"
31 | },
32 | "homepage": "https://github.com/meszaros-lajos-gyorgy/express-minify-html-2#readme",
33 | "dependencies": {
34 | "html-minifier-terser": "^7.2.0"
35 | },
36 | "devDependencies": {
37 | "ejs": "3.1.9",
38 | "express": "4.18.2",
39 | "express-handlebars": "7.0.7",
40 | "handlebars": "4.7.7",
41 | "nunjucks": "3.2.4",
42 | "pug": "3.0.2",
43 | "supertest": "6.3.3",
44 | "tape": "5.6.3"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # source: https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 | lerna-debug.log*
10 | .pnpm-debug.log*
11 |
12 | # Diagnostic reports (https://nodejs.org/api/report.html)
13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
14 |
15 | # Runtime data
16 | pids
17 | *.pid
18 | *.seed
19 | *.pid.lock
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 | *.lcov
27 |
28 | # nyc test coverage
29 | .nyc_output
30 |
31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
32 | .grunt
33 |
34 | # Bower dependency directory (https://bower.io/)
35 | bower_components
36 |
37 | # node-waf configuration
38 | .lock-wscript
39 |
40 | # Compiled binary addons (https://nodejs.org/api/addons.html)
41 | build/Release
42 |
43 | # Dependency directories
44 | node_modules/
45 | jspm_packages/
46 |
47 | # Snowpack dependency directory (https://snowpack.dev/)
48 | web_modules/
49 |
50 | # TypeScript cache
51 | *.tsbuildinfo
52 |
53 | # Optional npm cache directory
54 | .npm
55 |
56 | # Optional eslint cache
57 | .eslintcache
58 |
59 | # Optional stylelint cache
60 | .stylelintcache
61 |
62 | # Microbundle cache
63 | .rpt2_cache/
64 | .rts2_cache_cjs/
65 | .rts2_cache_es/
66 | .rts2_cache_umd/
67 |
68 | # Optional REPL history
69 | .node_repl_history
70 |
71 | # Output of 'npm pack'
72 | *.tgz
73 |
74 | # Yarn Integrity file
75 | .yarn-integrity
76 |
77 | # dotenv environment variable files
78 | .env
79 | .env.development.local
80 | .env.test.local
81 | .env.production.local
82 | .env.local
83 |
84 | # parcel-bundler cache (https://parceljs.org/)
85 | .cache
86 | .parcel-cache
87 |
88 | # Next.js build output
89 | .next
90 | out
91 |
92 | # Nuxt.js build / generate output
93 | .nuxt
94 | dist
95 |
96 | # Gatsby files
97 | .cache/
98 | # Comment in the public line in if your project uses Gatsby and not Next.js
99 | # https://nextjs.org/blog/next-9-1#public-directory-support
100 | # public
101 |
102 | # vuepress build output
103 | .vuepress/dist
104 |
105 | # vuepress v2.x temp and cache directory
106 | .temp
107 | .cache
108 |
109 | # Docusaurus cache and generated files
110 | .docusaurus
111 |
112 | # Serverless directories
113 | .serverless/
114 |
115 | # FuseBox cache
116 | .fusebox/
117 |
118 | # DynamoDB Local files
119 | .dynamodb/
120 |
121 | # TernJS port file
122 | .tern-port
123 |
124 | # Stores VSCode versions used for testing VSCode extensions
125 | .vscode-test
126 |
127 | # yarn v2
128 | .yarn/cache
129 | .yarn/unplugged
130 | .yarn/build-state.yml
131 | .yarn/install-state.gz
132 | .pnp.*
133 |
--------------------------------------------------------------------------------
/minifier.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { minify } = require('html-minifier-terser')
4 |
5 | const toArrayIfNotAlreadyAnArray = (value) => {
6 | return Array.isArray(value) ? value : [value]
7 | }
8 |
9 | const any = (fn, values) => {
10 | for (let value of values) {
11 | if (fn(value)) {
12 | return true
13 | }
14 | }
15 |
16 | return false
17 | }
18 |
19 | const none = (fn, values) => {
20 | return !any(fn, values)
21 | }
22 |
23 | const propOr = (key, fallbackValue, obj) => {
24 | if (typeof obj === 'object' && typeof obj[key] !== 'undefined') {
25 | return obj[key]
26 | }
27 |
28 | return fallbackValue
29 | }
30 |
31 | const parseOptions = (options) => ({
32 | override: propOr('override', false, options),
33 | exceptionUrls: toArrayIfNotAlreadyAnArray(propOr('exceptionUrls', propOr('exception_url', false, options), options)),
34 | htmlMinifier: Object.assign(
35 | {
36 | removeComments: true,
37 | collapseWhitespace: true,
38 | collapseBooleanAttributes: true,
39 | removeAttributeQuotes: true,
40 | removeEmptyAttributes: true,
41 | },
42 | propOr('htmlMinifier', {}, options),
43 | ),
44 | })
45 |
46 | const matchesException = (req, res) => {
47 | return (exception) => {
48 | if (exception instanceof RegExp) {
49 | return exception.test(req.url)
50 | }
51 |
52 | if (typeof exception === 'function') {
53 | return exception(req, res) || false
54 | }
55 |
56 | if (typeof exception === 'string') {
57 | return req.url.match(exception) !== null
58 | }
59 |
60 | return false
61 | }
62 | }
63 |
64 | const sendMinified = (res, next, htmlMinifierOptions, callback) => {
65 | if (typeof callback === 'undefined') {
66 | // No callback specified, just minify and send to client.
67 | return (err, html) => {
68 | if (err) {
69 | return next(err)
70 | }
71 |
72 | minify(html, htmlMinifierOptions)
73 | .then((html) => {
74 | res.send(html)
75 | })
76 | .catch((err) => {
77 | res.status(500).send(err)
78 | })
79 | }
80 | }
81 |
82 | // Custom callback specified by user, use that one
83 | return (err, html) => {
84 | if (!html) {
85 | callback(err, html)
86 | return
87 | }
88 |
89 | minify(html, htmlMinifierOptions)
90 | .then((html) => {
91 | callback(err, html)
92 | })
93 | .catch((err) => {
94 | callback(err)
95 | })
96 | }
97 | }
98 |
99 | module.exports = (options = {}) => {
100 | const { override, exceptionUrls, htmlMinifier } = parseOptions(options)
101 |
102 | return (req, res, next) => {
103 | res.renderMin = function (view, renderOpts, callback) {
104 | this.render(view, renderOpts, sendMinified(res, next, htmlMinifier, callback))
105 | }
106 |
107 | if (override && none(matchesException(req, res), exceptionUrls)) {
108 | res.oldRender = res.render
109 | res.render = function (view, renderOpts, callback) {
110 | this.oldRender(view, renderOpts, sendMinified(res, next, htmlMinifier, callback))
111 | }
112 | }
113 |
114 | return next()
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # express-minify-html-2
2 |
3 | Express middleware wrapper around [html-minifier-terser](https://github.com/terser/html-minifier-terser)
4 |
5 | ## Note that this is a fork of express-minify-html
6 |
7 | This fork is made in order to keep the [express-minify-html](https://github.com/melonmanchan/express-minify-html)
8 | project alive. All credits go to the contributors in the original repo.
9 |
10 | ## Description
11 |
12 | This express middleware simply enchances the regular 'render' method of the response object for minifying HTML.
13 |
14 | ## Usage
15 |
16 | ```sh
17 | npm install --save --production express-minify-html-2 express
18 | ```
19 |
20 | ```js
21 | var express = require('express')
22 | var minifyHTML = require('express-minify-html-2')
23 |
24 | var app = express()
25 |
26 | app.use(
27 | minifyHTML({
28 | override: true,
29 | /**
30 | * exceptionUrls can also be spelled as exception_url for backwards compatibility
31 | */
32 | exceptionUrls: false,
33 | htmlMinifier: {
34 | removeComments: true,
35 | collapseWhitespace: true,
36 | collapseBooleanAttributes: true,
37 | removeAttributeQuotes: true,
38 | removeEmptyAttributes: true,
39 | },
40 | }),
41 | )
42 |
43 | app.get('hello', function (req, res, next) {
44 | res.render('helloTemplate', { hello: 'world' }, function (err, html) {
45 | // The output is minified, huzzah!
46 | console.log(html)
47 | res.send(html)
48 | })
49 | })
50 | ```
51 |
52 | Set 'override' to false if you don't want to hijack the ordinary res.render function. This adds an additional
53 | res.renderMin function to the response object to render minimized HTML.
54 |
55 | The 'htmlMinifier' opts are simply passed on to the `html-minifier-terser` plugin. For all the available configuration
56 | options, see [the original repo](https://github.com/terser/html-minifier-terser#options-quick-reference)!
57 |
58 | If no callback is provided, res.render/res.renderMin sends the minified HTML to the client just as the regular
59 | express res.render does. Otherwise, the callback is called with the error object and the minified HTML content, as
60 | demonstrated above.
61 |
62 | the `exceptionUrls` (or it's alias `exception_url`) optional parameter is a single value, or an array of strings, regexes and functions
63 | that can be used to check whether minifying should be skipped entirely.
64 |
65 | ```js
66 | exceptionUrls: [
67 | 'url_to_avoid_minify_html', // String.
68 | /regex_to_analyze_req_to_avoid_minify/i, // Regex.
69 | function (req, res) {
70 | // Function.
71 | // Code to analyze req and decide if skips or not minify.
72 | // Needs to return a boolean value.
73 | return true
74 | },
75 | ]
76 | ```
77 |
78 | Full examples can naturally be found under the 'examples'-folder of this repository!
79 |
80 | ## Other infos
81 |
82 | The code inside minifier.js should be compatible with nodejs 6.0.0,
83 | but since `html-minifier-terser` requires "^14.13.1" the package also requires at least nodejs 14.0.0
84 |
85 | ## License
86 |
87 | MIT © [Matti Jokitulppo](http://mattij.com)
88 |
89 | [](https://badge.fury.io/js/express-minify-html-2)
90 | [](https://img.shields.io/npm/dm/express-minify-html-2.svg)
91 |
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs')
4 | const express = require('express')
5 | const expressHandlebars = require('express-handlebars')
6 | const minifyHTML = require('../minifier.js')
7 | const request = require('supertest')
8 | const test = require('tape')
9 | const nunjucks = require('nunjucks')
10 |
11 | const handlebars = expressHandlebars.create({
12 | defaultLayout: 'test',
13 | layoutsDir: __dirname,
14 | })
15 |
16 | let app
17 |
18 | nunjucks.configure(__dirname, {
19 | autoescape: true,
20 | express: app,
21 | })
22 |
23 | // -----------------------------
24 |
25 | app = express()
26 |
27 | app.engine('handlebars', handlebars.engine)
28 | app.engine('nunjucks', nunjucks.render)
29 |
30 | app.locals.pretty = true
31 |
32 | app.set('views', __dirname)
33 |
34 | app.use(
35 | minifyHTML({
36 | override: true,
37 | exception_url: ['skip-minify'],
38 | htmlMinifier: {
39 | removeComments: true,
40 | collapseWhitespace: true,
41 | collapseBooleanAttributes: true,
42 | removeAttributeQuotes: true,
43 | removeEmptyAttributes: true,
44 | minifyJS: true,
45 | },
46 | }),
47 | )
48 |
49 | app
50 | .get('/test', function (req, res, next) {
51 | res.render('test', { hello: 'world' })
52 | })
53 | .get('/test-broken', function (req, res, next) {
54 | res.render('test-broken', { hello: 'world' })
55 | })
56 | .get('/skip-minify', function (req, res, next) {
57 | res.render('test', { hello: 'world' })
58 | })
59 |
60 | // -----------------------------
61 |
62 | const expectedHTML = fs.readFileSync(__dirname + '/test.output.html', 'utf8')
63 | const expectedRawHTML = {
64 | default: fs.readFileSync(__dirname + '/test.output.raw.html', 'utf8'),
65 | pug: fs.readFileSync(__dirname + '/test.output.raw.pug.html', 'utf8'),
66 | handlebars: fs.readFileSync(__dirname + '/test.output.raw.handlebars.html', 'utf8'),
67 | }
68 |
69 | function checkMinified(t) {
70 | request(app)
71 | .get('/test')
72 | .expect(200)
73 | .end((err, res) => {
74 | t.equal(res.text, expectedHTML)
75 | t.end()
76 | })
77 | }
78 |
79 | function checkSkipMinified(t, engine) {
80 | request(app)
81 | .get('/skip-minify')
82 | .expect(200)
83 | .end((err, res) => {
84 | var raw = expectedRawHTML[engine] || expectedRawHTML.default
85 | t.equal(res.text.trim(), raw.trim())
86 | t.end()
87 | })
88 | }
89 |
90 | // -----------------------------
91 |
92 | test('Should minify EJS templates', (t) => {
93 | app.set('view engine', 'ejs')
94 |
95 | checkMinified(t)
96 | })
97 |
98 | test('Should minify Pug templates', (t) => {
99 | app.set('view engine', 'pug')
100 |
101 | checkMinified(t)
102 | })
103 |
104 | test('Should minify Handlebars templates', (t) => {
105 | app.set('view engine', 'handlebars')
106 |
107 | checkMinified(t)
108 | })
109 |
110 | test('Should minify Nunjucks templates', (t) => {
111 | app.set('view engine', 'nunjucks')
112 |
113 | checkMinified(t)
114 | })
115 |
116 | test('Should skip minify EJS templates', (t) => {
117 | app.set('view engine', 'ejs')
118 |
119 | checkSkipMinified(t, 'ejs')
120 | })
121 |
122 | test('Should skip minify Pug templates', (t) => {
123 | app.set('view engine', 'pug')
124 |
125 | checkSkipMinified(t, 'pug')
126 | })
127 |
128 | test('Should skip minify Handlebars templates', (t) => {
129 | app.set('view engine', 'handlebars')
130 |
131 | checkSkipMinified(t, 'handlebars')
132 | })
133 |
134 | test('Should skip minify Nunjucks templates', (t) => {
135 | app.set('view engine', 'nunjucks')
136 |
137 | checkSkipMinified(t)
138 | })
139 |
140 | test('Should pass error to express on broken html', (t) => {
141 | request(app)
142 | .get('/test-broken')
143 | .expect(500)
144 | .end(function (err, res) {
145 | t.end()
146 | })
147 | })
148 |
--------------------------------------------------------------------------------