forked from axa-ch/metalsmith-postcss
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
129 lines (113 loc) · 4.63 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import postcssLib from 'postcss'
import path from 'path'
import mod, { createRequire } from 'module'
// support for dynamic imports landed in Node 13.2.0, and was available with --experimental-modules flag in 12.0.0
// ideally all the loaders should be refactored to be async, and loaded only when the plugin runs
const req = mod.require || createRequire(import.meta.url)
const defaultOptions = {
pattern: '**/*.css',
map: false,
plugins: []
}
/**
* @param {import('postcss').ProcessOptions['map']} map
* @param {boolean} development
* @returns {import('postcss').SourceMapOptions}
*/
function normalizeMapOptions(map, development) {
// no source maps in prod by default unless overridden with options
if (!map && !development) return false
// legacy compat sets inline: true when map: true. false would be better
return {
inline: map === true ? true : typeof map.inline === 'boolean' ? map.inline : development,
sourcesContent: true
}
}
/**
* @typedef {Object} SourceMapOptions
* @property {boolean} [inline]
*/
/**
* A metalsmith plugin that sends your CSS through any [PostCSS](https://github.com/postcss/postcss) plugins
* @param {Object} options
* @param {string|string[]} [options.pattern] Pattern(s) of CSS files to match relative to `Metalsmith.source()`. Default is `**\/*.css`
* @param {boolean|SourceMapOptions} [options.map] Pass `true` for inline sourcemaps, or `{ inline: false }` for external source maps
* @param {string|{'postcss-plugin': Object}|Array<{'postcss-plugin': Object}|string>} options.plugins
* An object with PostCSS plugin names as keys and their options as values, or an array of PostCSS plugins as names, eg `'postcss-plugin'`
* or objects in the format `{ 'postcss-plugin': {...options}}`
* @returns {import('metalsmith').Plugin}
*/
function initPostcss(options) {
options = Object.assign({}, defaultOptions, options || {})
const pluginsConfig = Array.isArray(options.plugins) ? options.plugins : [options.plugins]
const plugins = []
// Require each plugin, pass its options
// and add it to the plugins array.
pluginsConfig.forEach(function (pluginsObject) {
if (typeof pluginsObject === 'string') {
plugins.push(req(pluginsObject)({}))
} else {
Object.keys(pluginsObject).forEach(function (pluginName) {
const value = pluginsObject[pluginName]
if (value === false) return
const pluginOptions = value === true ? {} : value
plugins.push(req(pluginName)(pluginOptions))
})
}
})
const processor = postcssLib(plugins)
return function postcss(files, metalsmith, done) {
const map = normalizeMapOptions(options.map, metalsmith.env('NODE_ENV') === 'development')
const styles = metalsmith.match(options.pattern, Object.keys(files))
const debug = metalsmith.debug('@metalsmith/postcss')
debug('Running with options %O', { ...options, map })
const promises = []
styles.forEach(function (file) {
const contents = files[file].contents.toString()
const absolutePath = path.resolve(metalsmith.source(), file)
// if a previous source map has been generated for this file (eg through sass),
// pass its contents onto postcss
const prevMap = files[`${file}.map`]
const mapOpts = map && prevMap ? { prev: prevMap.contents.toString(), ...map } : map
debug.info('Processing file "%s"', file)
const promise = processor
.process(contents, {
from: absolutePath,
to: absolutePath,
map: mapOpts
})
.then(function (result) {
files[file].contents = Buffer.from(result.css)
debug.info('Updated CSS at "%s"', file)
if (map.inline) {
if (prevMap) {
debug.info('Moving contents of previous source map file "%s" inline', file)
delete files[`${file}.map`]
}
} else if (result.map) {
debug.info('%s source map at "%s"', prevMap ? 'Updating' : 'Adding', file)
files[`${file}.map`] = {
contents: Buffer.from(result.map.toString()),
mode: files[file].mode,
stats: files[file].stats
}
}
})
promises.push(promise)
})
Promise.all(promises)
.then(() => {
debug('Finished processing %s CSS file(s)', styles.length)
done()
})
.catch((error) => {
// JSON.stringify on an actual error object yields 0 key/values
if (error instanceof Error) {
return done(error)
}
// istanbul ignore next
done(new Error('Error during postcss processing: ' + JSON.stringify(error)))
})
}
}
export default initPostcss