diff --git a/README.md b/README.md index 9343c10..402084a 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ minify: - **verbose** - Verbose output. Defaults to `false`. - **exclude** - Exclude files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array. - **level** - Optimization level. Defaults to `2`. +- **sourceMap** - Source map options. Defaults to `false`. +- **mapIncludeSources** - Include sources in map, Defaults to `false`. - **globOptions** - See [globbing](#globbing) section. For more options, see [clean-css](https://github.com/jakubpawlowicz/clean-css). @@ -112,6 +114,8 @@ minify: - **mangle** - Mangle variable names. Defaults to `true`. Pass an object to specify [mangle options](https://github.com/terser-js/terser#mangle-options). - **output** - Output options. - To retain comments, `output: {comments: true}`. +- **sourceMap** - Source map options. Defaults to `false`. + - To include sources in map, `sourceMap: { includeSources: true }` - **globOptions** - See [globbing](#globbing) section. For more options, see [Terser](https://github.com/terser-js/terser). diff --git a/index.js b/index.js index c9235be..a646f36 100644 --- a/index.js +++ b/index.js @@ -32,6 +32,8 @@ hexo.config.minify.css = { verbose: false, exclude: ['*.min.css'], level: 2, + sourceMap: false, + mapIncludeSources: false, globOptions: { basename: true }, ...hexo.config.minify.css } @@ -44,6 +46,7 @@ hexo.config.minify.js = { compress: {}, mangle: true, output: {}, + sourceMap: false, globOptions: { basename: true }, ...hexo.config.minify.js } @@ -109,10 +112,20 @@ if (hexo.config.minify.enable === true && !(hexo.config.minify.previewServer === hexo.extend.filter.register('after_render:html', require('./lib/html').minifyHtml, hexo.config.minify.html.priority) } if (hexo.config.minify.css.enable === true) { - hexo.extend.filter.register('after_render:css', require('./lib/css').minifyCss, hexo.config.minify.css.priority) + if (hexo.config.minify.css.sourceMap) { + hexo.extend.filter.register('after_generate', require('./lib/css').minifyCssWithMap, hexo.config.minify.js.priority) + } + else { + hexo.extend.filter.register('after_render:css', require('./lib/css').minifyCss, hexo.config.minify.css.priority) + } } if (hexo.config.minify.js.enable === true) { - hexo.extend.filter.register('after_render:js', require('./lib/js').minifyJs, hexo.config.minify.js.priority) + if (hexo.config.minify.js.sourceMap) { + hexo.extend.filter.register('after_generate', require('./lib/js').minifyJsWithMap, hexo.config.minify.js.priority) + } + else { + hexo.extend.filter.register('after_render:js', require('./lib/js').minifyJs, hexo.config.minify.js.priority) + } } if (hexo.config.minify.svg.enable === true) { hexo.extend.filter.register('after_generate', require('./lib/svg').minifySvg, hexo.config.minify.svg.priority) diff --git a/lib/css.js b/lib/css.js index 5fe78e7..d7fe897 100644 --- a/lib/css.js +++ b/lib/css.js @@ -1,6 +1,6 @@ 'use strict' const CleanCSS = require('clean-css') -const { isMatch, logFn } = require('./tools') +const { isMatch, match, logFn } = require('./tools') /** * @param {string} str @@ -13,6 +13,7 @@ async function minifyCss(str, data) { const path = data.path const { exclude, globOptions, verbose } = options + options.returnPromise = true if (isMatch(path, exclude, globOptions)) return str @@ -25,6 +26,48 @@ async function minifyCss(str, data) { } } +function minifyCssWithMap() { + const hexo = this + const options = hexo.config.minify.css + const { parse } = require('path') + + const route = hexo.route + const routeList = route.list() + /** @type {{ exclude: string[] }} */ + const { exclude, globOptions, verbose } = options + const include = ['*.css', ...exclude.map(x => `!${x}`)] + options.returnPromise = true + const cleanCSS = new CleanCSS(options) + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', async () => { + if (assetTxt.length) { + try { + const { base, ext, name } = parse(path) + const { styles, sourceMap } = await cleanCSS.minify(assetTxt) + if (verbose) logFn.call(this, assetTxt, result, path, 'css') + route.set(path, `${styles}\n/*# sourceMappingURL=${base}.map */`) + const map = sourceMap.toJSON() + map.sources = [`${name}.source${ext}`] + if (options.mapIncludeSources === true) { + map.sourcesContent = [assetTxt] + } + route.set(`${path}.map`, JSON.stringify(map)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve() + }) + }) + })) +} + module.exports = { - minifyCss + minifyCss, + minifyCssWithMap } \ No newline at end of file diff --git a/lib/js.js b/lib/js.js index f32378c..50f5955 100644 --- a/lib/js.js +++ b/lib/js.js @@ -1,6 +1,6 @@ 'use strict' const { minify: terserMinify } = require('terser') -const { isMatch, logFn } = require('./tools') +const { isMatch, match, logFn } = require('./tools') /** * @param {string} str @@ -31,6 +31,54 @@ async function minifyJs(str, data) { } } +function minifyJsWithMap() { + const hexo = this + const options = hexo.config.minify.js + const { parse } = require('path') + + const route = hexo.route + const routeList = route.list() + /** @type {{ exclude: string[] }} */ + const { exclude, globOptions, verbose, ...jsOptions } = options + const include = ['*.js', ...exclude.map(x => `!${x}`)] + + // Terser doesn't like unsupported options + delete jsOptions.enable + delete jsOptions.priority + // Old option, retained to avoid crash when upgrading to v4 + delete jsOptions.logger + + return Promise.all((match(routeList, include, globOptions)).map(path => { + return new Promise((/** @type {(value: void) => void} */ resolve, reject) => { + const assetPath = route.get(path) + let assetTxt = '' + assetPath.on('data', chunk => (assetTxt += chunk)) + assetPath.on('end', async () => { + if (assetTxt.length) { + try { + const { base, ext, name } = parse(path) + jsOptions.sourceMap = { + ...jsOptions.sourceMap, + filename: base, + asObject: true, + url: `${base}.map` + } + const { code, map } = await terserMinify(assetTxt, { ...jsOptions }) + if (verbose) logFn.call(this, assetTxt, result, path, 'js') + route.set(path, code) + map.sources = [`${name}.source${ext}`] + route.set(`${path}.map`, JSON.stringify(map)) + } catch (err) { + reject(new Error(`Path: ${path}\n${err}`)) + } + } + resolve() + }) + }) + })) +} + module.exports = { - minifyJs + minifyJs, + minifyJsWithMap } \ No newline at end of file diff --git a/lib/zlib.js b/lib/zlib.js index cf1c5c2..5b3a8f5 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -10,7 +10,7 @@ function gzipFn() { const options = hexo.config.minify.gzip const route = hexo.route - /** @type {string} */ + /** @type {string[]} */ const routeList = route.list() const { globOptions, include, verbose } = options let level = options.level @@ -43,6 +43,7 @@ function brotliFn() { const options = hexo.config.minify.brotli const route = hexo.route + /** @type {string[]} */ const routeList = route.list() const { globOptions, include, verbose } = options let level = options.level