feat: add source map supports

This commit is contained in:
wherewhere 2024-12-04 16:41:23 +08:00
parent 83f62ea518
commit 2528fcc394
5 changed files with 116 additions and 7 deletions

View File

@ -90,6 +90,8 @@ minify:
- **verbose** - Verbose output. Defaults to `false`. - **verbose** - Verbose output. Defaults to `false`.
- **exclude** - Exclude files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array. - **exclude** - Exclude files. Support [wildcard](http://www.globtester.com/) pattern(s) in a string or array.
- **level** - Optimization level. Defaults to `2`. - **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. - **globOptions** - See [globbing](#globbing) section.
For more options, see [clean-css](https://github.com/jakubpawlowicz/clean-css). 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). - **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. - **output** - Output options.
- To retain comments, `output: {comments: true}`. - 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. - **globOptions** - See [globbing](#globbing) section.
For more options, see [Terser](https://github.com/terser-js/terser). For more options, see [Terser](https://github.com/terser-js/terser).

View File

@ -32,6 +32,8 @@ hexo.config.minify.css = {
verbose: false, verbose: false,
exclude: ['*.min.css'], exclude: ['*.min.css'],
level: 2, level: 2,
sourceMap: false,
mapIncludeSources: false,
globOptions: { basename: true }, globOptions: { basename: true },
...hexo.config.minify.css ...hexo.config.minify.css
} }
@ -44,6 +46,7 @@ hexo.config.minify.js = {
compress: {}, compress: {},
mangle: true, mangle: true,
output: {}, output: {},
sourceMap: false,
globOptions: { basename: true }, globOptions: { basename: true },
...hexo.config.minify.js ...hexo.config.minify.js
} }
@ -109,11 +112,21 @@ 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) hexo.extend.filter.register('after_render:html', require('./lib/html').minifyHtml, hexo.config.minify.html.priority)
} }
if (hexo.config.minify.css.enable === true) { if (hexo.config.minify.css.enable === true) {
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) hexo.extend.filter.register('after_render:css', require('./lib/css').minifyCss, hexo.config.minify.css.priority)
} }
}
if (hexo.config.minify.js.enable === true) { if (hexo.config.minify.js.enable === true) {
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) hexo.extend.filter.register('after_render:js', require('./lib/js').minifyJs, hexo.config.minify.js.priority)
} }
}
if (hexo.config.minify.svg.enable === true) { if (hexo.config.minify.svg.enable === true) {
hexo.extend.filter.register('after_generate', require('./lib/svg').minifySvg, hexo.config.minify.svg.priority) hexo.extend.filter.register('after_generate', require('./lib/svg').minifySvg, hexo.config.minify.svg.priority)
} }

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
const CleanCSS = require('clean-css') const CleanCSS = require('clean-css')
const { isMatch, logFn } = require('./tools') const { isMatch, match, logFn } = require('./tools')
/** /**
* @param {string} str * @param {string} str
@ -13,6 +13,7 @@ async function minifyCss(str, data) {
const path = data.path const path = data.path
const { exclude, globOptions, verbose } = options const { exclude, globOptions, verbose } = options
options.returnPromise = true
if (isMatch(path, exclude, globOptions)) return str if (isMatch(path, exclude, globOptions)) return str
@ -25,6 +26,48 @@ async function minifyCss(str, data) {
} }
} }
module.exports = { function minifyCssWithMap() {
minifyCss 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,
minifyCssWithMap
} }

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
const { minify: terserMinify } = require('terser') const { minify: terserMinify } = require('terser')
const { isMatch, logFn } = require('./tools') const { isMatch, match, logFn } = require('./tools')
/** /**
* @param {string} str * @param {string} str
@ -31,6 +31,54 @@ async function minifyJs(str, data) {
} }
} }
module.exports = { function minifyJsWithMap() {
minifyJs 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,
minifyJsWithMap
} }

View File

@ -10,7 +10,7 @@ function gzipFn() {
const options = hexo.config.minify.gzip const options = hexo.config.minify.gzip
const route = hexo.route const route = hexo.route
/** @type {string} */ /** @type {string[]} */
const routeList = route.list() const routeList = route.list()
const { globOptions, include, verbose } = options const { globOptions, include, verbose } = options
let level = options.level let level = options.level
@ -43,6 +43,7 @@ function brotliFn() {
const options = hexo.config.minify.brotli const options = hexo.config.minify.brotli
const route = hexo.route const route = hexo.route
/** @type {string[]} */
const routeList = route.list() const routeList = route.list()
const { globOptions, include, verbose } = options const { globOptions, include, verbose } = options
let level = options.level let level = options.level