diff --git a/src/__tests__/cli.js b/src/__tests__/cli.js index a6750ae..5723267 100644 --- a/src/__tests__/cli.js +++ b/src/__tests__/cli.js @@ -181,6 +181,22 @@ test("cli", function(t) { ) planned += 1 + exec( + cssnextBin + " --extract src/__tests__/fixtures/extract.css " + + "--config src/__tests__/fixtures/extract.json", + function(err, stdout) { + if (err) { + throw err + } + t.deepEqual( + JSON.parse(stdout), + JSON.parse(utils.readFixture("extract.expected", ".json")), + "should extract custom properties and custom medias on --extract" + ) + } + ) + planned += 1 + exec( cssnextBin + " --compress src/__tests__/fixtures/compress.css", function(err, stdout) { diff --git a/src/__tests__/fixtures/extract.css b/src/__tests__/fixtures/extract.css new file mode 100644 index 0000000..c0c1abe --- /dev/null +++ b/src/__tests__/fixtures/extract.css @@ -0,0 +1,6 @@ +:root { + --width: 1px; + --width: calc(1px + 1px); + --width2: var(--width); +} +@custom-media --mq (max-width: 30em); diff --git a/src/__tests__/fixtures/extract.expected.json b/src/__tests__/fixtures/extract.expected.json new file mode 100644 index 0000000..79f5cc6 --- /dev/null +++ b/src/__tests__/fixtures/extract.expected.json @@ -0,0 +1,12 @@ +{ + "customProperties":{ + "width": "2px", + "width2": "2px", + "color": "#e00", + "color2": "#e00" + }, + "customMedias":{ + "mq": "(max-width: 30em)", + "mq2": "screen" + } +} diff --git a/src/__tests__/fixtures/extract.json b/src/__tests__/fixtures/extract.json new file mode 100644 index 0000000..59e9dda --- /dev/null +++ b/src/__tests__/fixtures/extract.json @@ -0,0 +1,15 @@ +{ + "features": { + "customProperties": { + "variables": { + "color": "#e00", + "color2": "var(--color)" + } + }, + "customMedia": { + "extensions": { + "mq2": "screen" + } + } + } +} diff --git a/src/bin.js b/src/bin.js index 1b33303..67f9b88 100755 --- a/src/bin.js +++ b/src/bin.js @@ -22,6 +22,7 @@ program .option("-b, --browsers ", "browsers list (comma separated)") .option("-I, --no-import", "do not inline @import") .option("-U, --no-url", "do not adjust url()") + .option("-e, --extract", "output values of custom properties") .option("-c, --compress", "compress output") .option("-s, --sourcemap", "add sourcemap") .option("-w, --watch", "watch the input file for changes") @@ -87,6 +88,7 @@ if ("compress" in program) { if ("watch" in program) { config.watch = program.watch } +config.extract = program.extract const input = program.args[0] ? path.resolve(program.args[0]) : null const output = program.args[1] ? path.resolve(program.args[1]) : null diff --git a/src/index.js b/src/index.js index 9673e52..1c48477 100644 --- a/src/index.js +++ b/src/index.js @@ -112,11 +112,28 @@ function cssnext(string, options) { ) ) ) { - const plugin = cssnext.features[key]( - typeof features[key] === "object" - ? {...features[key]} - : undefined - ) + let pluginOpts = typeof features[key] === "object" + ? {...features[key]} + : undefined + if (options.extract) { + switch (key) { + case "customProperties": + if (!pluginOpts) { + pluginOpts = {} + } + pluginOpts.preserve = "computed" + pluginOpts.appendVariables = true + break + case "customMedia": + if (!pluginOpts) { + pluginOpts = {} + } + pluginOpts.preserve = true + pluginOpts.appendExtensions = true + break + } + } + const plugin = cssnext.features[key](pluginOpts) plugin.postcssPlugin = "cssnext" postcss.use(plugin) } @@ -159,6 +176,44 @@ function cssnext(string, options) { if (typeof string === "string") { const result = postcss.process(string, options) + // extract customProperties and customMedias + if (options.extract) { + const map = { + customProperties: {}, + customMedias: {}, + } + result.root.eachRule(function(rule) { + if ( + rule.selectors.length !== 1 || + rule.selectors[0] !== ":root" || + rule.parent.type !== "root" + ) { + return + } + rule.each(function(decl) { + let name = decl.prop + if (name.slice(0, 2) !== "--") { + return + } + name = name.slice(2) + map.customProperties[name] = decl.value + }) + }) + result.root.eachAtRule(function(atRule) { + if (atRule.name !== "custom-media") { + return + } + const params = atRule.params.split(" ") + let name = params.shift() + if (name.slice(0, 2) !== "--") { + return + } + name = name.slice(2) + map.customMedias[name] = params.join(" ") + }) + return JSON.stringify(map) + "\n" + } + // default behavior, cssnext returns a css string if no or inline sourcemap if (options.map === null || (options.map === true || options.map.inline)) { return result.css