-
-
Notifications
You must be signed in to change notification settings - Fork 11
feat: switch to htmlnano #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ec499ed
f998bb4
e7cc6ac
4ae2057
7c6d555
6c49747
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,3 +5,4 @@ tmp/ | |
| .idea/ | ||
| .nyc_output/ | ||
| coverage/ | ||
| package-lock.json | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,25 +17,25 @@ You can set options of HTMLMinifier in the main `_config.yml` file: | |
|
|
||
| ``` yaml | ||
| html_minifier: | ||
| exclude: | ||
| exclude: | ||
| ``` | ||
|
|
||
| - **exclude**: Exclude files from being minified. Support [globbing patterns](https://github.com/micromatch/micromatch#extended-globbing). | ||
|
|
||
| Default options: | ||
|
|
||
| ``` yaml | ||
| html_minifier: | ||
| html_minifier: | ||
| collapseBooleanAttributes: true | ||
| collapseWhitespace: true | ||
| # Ignore '<!-- more -->' https://hexo.io/docs/tag-plugins#Post-Excerpt | ||
| ignoreCustomComments: [ !!js/regexp /^\s*more/] | ||
| removeComments: true | ||
| removeEmptyAttributes: true | ||
| removeScriptTypeAttributes: true | ||
| removeStyleLinkTypeAttributes: true | ||
| minifyJS: true | ||
| minifyCSS: true | ||
| removeRedundantAttributes: true | ||
| removeAttributeQuotes: true | ||
| minifyJs: true | ||
| minifyCss: true | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should provide a migration guide since the configuration has changed. |
||
| ``` | ||
|
|
||
| - **ignoreCustomComments**: Array of regex'es that allow to ignore certain comments, when matched. Need to prepend [`!!js/regexp`](https://github.com/nodeca/js-yaml#supported-yaml-types) to support regex. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,36 @@ | ||
| 'use strict'; | ||
| const minify = require('html-minifier').minify; | ||
| const htmlnano = require('htmlnano'); | ||
| const micromatch = require('micromatch'); | ||
|
|
||
| module.exports = function(str, data) { | ||
| const options = this.config.html_minifier; | ||
| module.exports = async function(str, data) { | ||
| const options = { ...this.config.html_minifier }; | ||
| const path = data.path; | ||
| const exclude = options.exclude; | ||
| const { exclude, ignoreCustomComments, removeComments } = options; | ||
|
|
||
| if (path && exclude && exclude.length) { | ||
| if (micromatch.isMatch(path, exclude)) return str; | ||
| } | ||
|
|
||
| delete options.exclude; | ||
| delete options.ignoreCustomComments; | ||
| delete options.removeComments; | ||
|
Comment on lines
+14
to
+16
|
||
| if (removeComments) { | ||
| if (Array.isArray(ignoreCustomComments) && ignoreCustomComments.length) { | ||
| options.removeComments = comment => { | ||
| comment = comment.replace(/^<!--|-->$/g, ''); | ||
| for (const regex of ignoreCustomComments) { | ||
| if (regex.test(comment)) return false; | ||
| } | ||
| return true; | ||
| }; | ||
| } else { | ||
| options.removeComments = 'safe'; | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| return minify(str, options); | ||
| const result = await htmlnano.process(str, options); | ||
| return result.html; | ||
| } catch (err) { | ||
| throw new Error(`Path: ${path}\n${err}`); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,65 +1,61 @@ | ||
| 'use strict'; | ||
|
|
||
| const should = require('chai').should(); // eslint-disable-line | ||
| const { minify } = require('html-minifier'); | ||
|
|
||
| describe('hexo-html-minifier', () => { | ||
| const ctx = { | ||
| config: { | ||
| html_minifier: { | ||
| exclude: [], | ||
| collapseBooleanAttributes: true, | ||
| collapseWhitespace: true, | ||
| ignoreCustomComments: [/^\s*more/], | ||
| removeComments: true, | ||
| removeEmptyAttributes: true, | ||
| removeScriptTypeAttributes: true, | ||
| removeStyleLinkTypeAttributes: true, | ||
| minifyJS: true, | ||
| minifyCSS: true | ||
| removeRedundantAttributes: true, | ||
| removeAttributeQuotes: true, | ||
| minifyJs: true, | ||
| minifyCss: true | ||
| } | ||
| } | ||
| }; | ||
| const h = require('../lib/filter').bind(ctx); | ||
| const defaultCfg = JSON.parse(JSON.stringify(ctx.config)); | ||
| const defaultCfg = {...ctx.config.html_minifier}; | ||
stevenjoezhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const input = '<p id="">foo</p>'; | ||
| const path = 'index.html'; | ||
|
|
||
| beforeEach(() => { | ||
| ctx.config = JSON.parse(JSON.stringify(defaultCfg)); | ||
| ctx.config.html_minifier = {...defaultCfg}; | ||
stevenjoezhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
|
|
||
| it('default', () => { | ||
| const result = h(input, { path }); | ||
| it('default', async () => { | ||
| const result = await h(input, { path }); | ||
| result.should.eql('<p>foo</p>'); | ||
| }); | ||
|
|
||
| it('option', () => { | ||
| it('option', async () => { | ||
| ctx.config.html_minifier.removeEmptyAttributes = false; | ||
| const result = h(input, { path }); | ||
| result.should.eql(input); | ||
| const result = await h(input, { path }); | ||
| // htmlnano still removes the empty attribute value, leaving just the attribute name | ||
| result.should.eql('<p id>foo</p>'); | ||
| }); | ||
|
|
||
| it('exclude', () => { | ||
| it('exclude', async () => { | ||
| ctx.config.html_minifier.exclude = '**/*.min.html'; | ||
| const result = h(input, { path: 'foo/bar.min.html' }); | ||
| const result = await h(input, { path: 'foo/bar.min.html' }); | ||
| result.should.eql(input); | ||
| }); | ||
|
|
||
| it('invalid input', () => { | ||
| it('invalid input', async () => { | ||
| // htmlnano handles malformed HTML gracefully without throwing errors | ||
| const invalid = '<html><>?:"{}|_+</html>'; | ||
| let expected; | ||
|
|
||
| try { | ||
| minify(invalid); | ||
| } catch (err) { | ||
| expected = err; | ||
| } | ||
| const result = await h(invalid, { path }); | ||
| // htmlnano processes the content as-is | ||
| result.should.eql(invalid); | ||
| }); | ||
|
|
||
| try { | ||
| h(invalid, { path }); | ||
| should.fail(); | ||
| } catch (err) { | ||
| err.message.should.eql(`Path: ${path}\n${expected}`); | ||
| } | ||
| it('ignoreCustomComments', async () => { | ||
| const content = '<!-- more -->\n<p>Content</p>'; | ||
| const result = await h(content, { path }); | ||
| result.should.include('<!-- more -->'); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO:
We should provide a migration guide since the configuration has changed.