UNPKG

get-sass-vars

Version:

Get Sass variables as JSON object.

259 lines (220 loc) 7.59 kB
'use strict'; var stripOuter = require('strip-outer'); var camelcaseKeys = require('camelcase-keys'); var postcss = require('postcss'); var postcssScss = require('postcss-scss'); var sass = require('sass'); var jsonFns = require('node-sass-json-functions'); var fromEntries = require('@ungap/from-entries'); var replaceAll = require('string-replace-all-ponyfill'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var stripOuter__default = /*#__PURE__*/_interopDefaultLegacy(stripOuter); var camelcaseKeys__default = /*#__PURE__*/_interopDefaultLegacy(camelcaseKeys); var postcss__default = /*#__PURE__*/_interopDefaultLegacy(postcss); var postcssScss__default = /*#__PURE__*/_interopDefaultLegacy(postcssScss); var sass__namespace = /*#__PURE__*/_interopNamespace(sass); var jsonFns__default = /*#__PURE__*/_interopDefaultLegacy(jsonFns); var fromEntries__default = /*#__PURE__*/_interopDefaultLegacy(fromEntries); var replaceAll__default = /*#__PURE__*/_interopDefaultLegacy(replaceAll); /** * @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue * @typedef {JsonValue[]} JsonArray * @typedef {string | number | boolean | null} JsonPrimitive * @typedef {{[Key in string]?: JsonValue}} JsonObject */ /** * @typedef {object} Options * @property {boolean=} camelize Camelize first-level JSON object keys and strip inital `$` (e.g. `$foo-bar` will become `fooBar`). * @property {import("sass").Options<"async">=} sassOptions Options for Sass renderer. */ /** * @typedef {object} SyncOptions * @property {boolean=} camelize Camelize first-level JSON object keys and strip inital `$` (e.g. `$foo-bar` will become `fooBar`). * @property {import("sass").Options<"sync">=} sassOptions Options for Sass renderer. */ const sassVariableSelector = '.__sassVars__'; const sassVariableDeclarationRegex = /^\$/; const noop = () => ({ postcssPlugin: '_noop' }); /** * @param {import('postcss').Declaration} decl */ function getEncodedValueNode(decl) { return { prop: 'content', /* * Decl.prop as property is wrapped inside quotes so it doesn’t get transformed with Sass * decl.prop as value will be transformed with Sass */ value: `"${decl.prop}" ":" json-encode(${decl.prop}, $quotes: false)` }; } /** * @param {string} value */ function sanitizeValue(value) { value = stripOuter__default["default"](value, '"'); value = stripOuter__default["default"](value, "'"); value = replaceAll__default["default"](value, '\\\\', '\\'); value = value.replace(/^\\"(.+)\\"$/, '"$1"'); value = replaceAll__default["default"](value, '\\\\', '\\'); return value; } /** * Gets Sass variables from Sass string. * * Only top-level variables will be considered, anything inside selector or at-rule is ignored. * * @param {string} input Sass input string. * @param {Options=} options */ async function main(input, options) { const { camelize = false, sassOptions = /** @type {import("sass").Options<"async">} */ ({}) } = options || {}; const cssProcessor = postcss__default["default"]([noop()]); /* eslint-disable no-undefined */ const initialResponse = await cssProcessor.process(input, { syntax: postcssScss__default["default"], from: undefined }); const initialRoot = initialResponse.root; const node = postcss__default["default"].rule({ selector: sassVariableSelector }); initialRoot.walkDecls(sassVariableDeclarationRegex, (decl) => { if (decl.parent === initialRoot) { node.append(getEncodedValueNode(decl)); } }); initialRoot.append(node); const { functions, silenceDeprecations, ...otherSassOptions } = sassOptions; let sassResponse = await sass__namespace.compileStringAsync(initialRoot.toString(), { functions: { ...jsonFns__default["default"], ...functions }, ...otherSassOptions, // eslint-disable-next-line unicorn/prefer-spread silenceDeprecations: Array.from( new Set([...(silenceDeprecations || []), 'color-4-api']) ) }); let sassResponseString = typeof sassResponse === 'undefined' ? '' : sassResponse.css.toString(); const finalResponse = await cssProcessor.process(sassResponseString, { from: undefined }); const finalRoot = finalResponse.root; /** @type {JsonObject} */ const data = {}; finalRoot.walkRules(sassVariableSelector, (rule) => { rule.walkDecls('content', (decl) => { let [property, value] = decl.value.split(' ":" '); property = stripOuter__default["default"](property, '"'); value = sanitizeValue(value); data[property] = JSON.parse(value); }); }); if (camelize) { return /** @type {JsonObject} */ ( camelcaseKeys__default["default"]( fromEntries__default["default"]( Object.entries(data).map(([key, value]) => [ stripOuter__default["default"](key, '$'), value ]) ) ) ); } return data; } /** * Gets Sass variables from Sass string. Sync version. * * Only top-level variables will be considered, anything inside selector or at-rule is ignored. * * @param {string} input Sass input string. * @param {SyncOptions=} options */ function mainSync(input, options) { const { camelize = false, sassOptions = /** @type {import("sass").Options<"sync">} */ ({}) } = options || {}; const cssProcessor = postcss__default["default"]([noop()]); /* eslint-disable no-undefined */ const initialResponse = cssProcessor.process(input, { syntax: postcssScss__default["default"], from: undefined }); const initialRoot = initialResponse.root; const node = postcss__default["default"].rule({ selector: sassVariableSelector }); initialRoot.walkDecls(sassVariableDeclarationRegex, (decl) => { if (decl.parent === initialRoot) { node.append(getEncodedValueNode(decl)); } }); initialRoot.append(node); const { functions, silenceDeprecations, ...otherSassOptions } = sassOptions; let sassResponse = sass__namespace.compileString(initialRoot.toString(), { // @ts-ignore functions: { ...jsonFns__default["default"], ...functions }, ...otherSassOptions, // eslint-disable-next-line unicorn/prefer-spread silenceDeprecations: Array.from( new Set([...(silenceDeprecations || []), 'color-4-api']) ) }); let sassResponseString = typeof sassResponse === 'undefined' ? '' : sassResponse.css.toString(); const finalResponse = cssProcessor.process(sassResponseString, { from: undefined }); const finalRoot = finalResponse.root; /** @type {JsonObject} */ const data = {}; finalRoot.walkRules(sassVariableSelector, (rule) => { rule.walkDecls('content', (decl) => { let [property, value] = decl.value.split(' ":" '); property = stripOuter__default["default"](property, '"'); value = sanitizeValue(value); data[property] = JSON.parse(value); }); }); if (camelize) { return /** @type {JsonObject} */ ( camelcaseKeys__default["default"]( fromEntries__default["default"]( Object.entries(data).map(([key, value]) => [ stripOuter__default["default"](key, '$'), value ]) ) ) ); } return data; } main.sync = mainSync; module.exports = main; //# sourceMappingURL=index.js.map