postcss-modules-scope
Advanced tools
Comparing version 2.0.1 to 2.1.0
{ | ||
"name": "postcss-modules-scope", | ||
"version": "2.0.1", | ||
"version": "2.1.0", | ||
"description": "A CSS Modules transform to extract export statements from local-scope classes", | ||
@@ -37,5 +37,10 @@ "main": "src/index.js", | ||
"homepage": "https://github.com/css-modules/postcss-modules-scope", | ||
"prettier": { | ||
"semi": true, | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
}, | ||
"dependencies": { | ||
"css-selector-tokenizer": "^0.7.0", | ||
"postcss": "^7.0.6" | ||
"postcss": "^7.0.6", | ||
"postcss-selector-parser": "^6.0.0" | ||
}, | ||
@@ -46,7 +51,6 @@ "devDependencies": { | ||
"coveralls": "^3.0.2", | ||
"css-selector-parser": "^1.0.4", | ||
"eslint": "^5.9.0", | ||
"nyc": "^13.1.0", | ||
"mocha": "^5.2.0" | ||
"mocha": "^6.0.2", | ||
"nyc": "^13.1.0" | ||
} | ||
} |
178
src/index.js
'use strict'; | ||
const postcss = require('postcss'); | ||
const Tokenizer = require('css-selector-tokenizer'); | ||
const selectorParser = require('postcss-selector-parser'); | ||
const hasOwnProperty = Object.prototype.hasOwnProperty; | ||
function getSingleLocalNamesForComposes(selectors) { | ||
return selectors.nodes.map(node => { | ||
function getSingleLocalNamesForComposes(root) { | ||
return root.nodes.map(node => { | ||
if (node.type !== 'selector' || node.nodes.length !== 1) { | ||
throw new Error( | ||
'composition is only allowed when selector is single :local class name not in "' + | ||
Tokenizer.stringify(selectors) + | ||
'"' | ||
`composition is only allowed when selector is single :local class name not in "${root}"` | ||
); | ||
} | ||
node = node.nodes[0]; | ||
if ( | ||
node.type !== 'nested-pseudo-class' || | ||
node.name !== 'local' || | ||
node.type !== 'pseudo' || | ||
node.value !== ':local' || | ||
node.nodes.length !== 1 | ||
@@ -25,19 +25,23 @@ ) { | ||
'composition is only allowed when selector is single :local class name not in "' + | ||
Tokenizer.stringify(selectors) + | ||
root + | ||
'", "' + | ||
Tokenizer.stringify(node) + | ||
node + | ||
'" is weird' | ||
); | ||
} | ||
node = node.nodes[0]; | ||
if (node.type !== 'selector' || node.nodes.length !== 1) { | ||
node = node.first; | ||
if (node.type !== 'selector' || node.length !== 1) { | ||
throw new Error( | ||
'composition is only allowed when selector is single :local class name not in "' + | ||
Tokenizer.stringify(selectors) + | ||
root + | ||
'", "' + | ||
Tokenizer.stringify(node) + | ||
node + | ||
'" is weird' | ||
); | ||
} | ||
node = node.nodes[0]; | ||
node = node.first; | ||
if (node.type !== 'class') { | ||
@@ -47,12 +51,35 @@ // 'id' is not possible, because you can't compose ids | ||
'composition is only allowed when selector is single :local class name not in "' + | ||
Tokenizer.stringify(selectors) + | ||
root + | ||
'", "' + | ||
Tokenizer.stringify(node) + | ||
node + | ||
'" is weird' | ||
); | ||
} | ||
return node.name; | ||
return node.value; | ||
}); | ||
} | ||
const whitespace = '[\\x20\\t\\r\\n\\f]'; | ||
const unescapeRegExp = new RegExp( | ||
'\\\\([\\da-f]{1,6}' + whitespace + '?|(' + whitespace + ')|.)', | ||
'ig' | ||
); | ||
function unescape(str) { | ||
return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => { | ||
const high = '0x' + escaped - 0x10000; | ||
// NaN means non-codepoint | ||
// Workaround erroneous numeric interpretation of +"0x" | ||
return high !== high || escapedWhitespace | ||
? escaped | ||
: high < 0 | ||
? // BMP codepoint | ||
String.fromCharCode(high + 0x10000) | ||
: // Supplemental Plane codepoint (surrogate pair) | ||
String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00); | ||
}); | ||
} | ||
const processor = postcss.plugin('postcss-modules-scope', function(options) { | ||
@@ -62,15 +89,27 @@ return css => { | ||
(options && options.generateScopedName) || processor.generateScopedName; | ||
const generateExportEntry = | ||
(options && options.generateExportEntry) || processor.generateExportEntry; | ||
const exports = Object.create(null); | ||
function exportScopedName(name) { | ||
function exportScopedName(name, rawName) { | ||
const scopedName = generateScopedName( | ||
name, | ||
rawName ? rawName : name, | ||
css.source.input.from, | ||
css.source.input.css | ||
); | ||
exports[name] = exports[name] || []; | ||
if (exports[name].indexOf(scopedName) < 0) { | ||
exports[name].push(scopedName); | ||
const exportEntry = generateExportEntry( | ||
rawName ? rawName : name, | ||
scopedName, | ||
css.source.input.from, | ||
css.source.input.css | ||
); | ||
const { key, value } = exportEntry; | ||
exports[key] = exports[key] || []; | ||
if (exports[key].indexOf(value) < 0) { | ||
exports[key].push(value); | ||
} | ||
return scopedName; | ||
@@ -80,18 +119,25 @@ } | ||
function localizeNode(node) { | ||
const newNode = Object.create(node); | ||
switch (node.type) { | ||
case 'selector': | ||
newNode.nodes = node.nodes.map(localizeNode); | ||
return newNode; | ||
node.nodes = node.map(localizeNode); | ||
return node; | ||
case 'class': | ||
return selectorParser.className({ | ||
value: exportScopedName( | ||
node.value, | ||
node.raws && node.raws.value ? node.raws.value : null | ||
), | ||
}); | ||
case 'id': { | ||
newNode.name = exportScopedName(node.name); | ||
return newNode; | ||
return selectorParser.id({ | ||
value: exportScopedName( | ||
node.value, | ||
node.raws && node.raws.value ? node.raws.value : null | ||
), | ||
}); | ||
} | ||
} | ||
throw new Error( | ||
node.type + | ||
' ("' + | ||
Tokenizer.stringify(node) + | ||
'") is not allowed in a :local block' | ||
`${node.type} ("${node}") is not allowed in a :local block` | ||
); | ||
@@ -102,15 +148,22 @@ } | ||
switch (node.type) { | ||
case 'nested-pseudo-class': | ||
if (node.name === 'local') { | ||
case 'pseudo': | ||
if (node.value === ':local') { | ||
if (node.nodes.length !== 1) { | ||
throw new Error('Unexpected comma (",") in :local block'); | ||
} | ||
return localizeNode(node.nodes[0]); | ||
const selector = localizeNode(node.first, node.spaces); | ||
// move the spaces that were around the psuedo selector to the first | ||
// non-container node | ||
selector.first.spaces = node.spaces; | ||
node.replaceWith(selector); | ||
return; | ||
} | ||
/* falls through */ | ||
case 'selectors': | ||
case 'root': | ||
case 'selector': { | ||
const newNode = Object.create(node); | ||
newNode.nodes = node.nodes.map(traverseNode); | ||
return newNode; | ||
node.each(traverseNode); | ||
break; | ||
} | ||
@@ -123,2 +176,3 @@ } | ||
const importedNames = {}; | ||
css.walkRules(rule => { | ||
@@ -134,10 +188,22 @@ if (/^:import\(.+\)$/.test(rule.selector)) { | ||
css.walkRules(rule => { | ||
const selector = Tokenizer.parse(rule.selector); | ||
const newSelector = traverseNode(selector); | ||
rule.selector = Tokenizer.stringify(newSelector); | ||
if ( | ||
rule.nodes && | ||
rule.selector.slice(0, 2) === '--' && | ||
rule.selector.slice(-1) === ':' | ||
) { | ||
// ignore custom property set | ||
return; | ||
} | ||
let parsedSelector = selectorParser().astSync(rule); | ||
rule.selector = traverseNode(parsedSelector.clone()).toString(); | ||
rule.walkDecls(/composes|compose-with/, decl => { | ||
const localNames = getSingleLocalNamesForComposes(selector); | ||
const localNames = getSingleLocalNamesForComposes(parsedSelector); | ||
const classes = decl.value.split(/\s+/); | ||
classes.forEach(className => { | ||
const global = /^global\(([^\)]+)\)$/.exec(className); | ||
if (global) { | ||
@@ -163,2 +229,3 @@ localNames.forEach(exportedName => { | ||
}); | ||
decl.remove(); | ||
@@ -168,6 +235,8 @@ }); | ||
rule.walkDecls(decl => { | ||
var tokens = decl.value.split(/(,|'[^']*'|"[^"]*")/); | ||
let tokens = decl.value.split(/(,|'[^']*'|"[^"]*")/); | ||
tokens = tokens.map((token, idx) => { | ||
if (idx === 0 || tokens[idx - 1] === ',') { | ||
const localMatch = /^(\s*):local\s*\((.+?)\)/.exec(token); | ||
if (localMatch) { | ||
@@ -186,2 +255,3 @@ return ( | ||
}); | ||
decl.value = tokens.join(''); | ||
@@ -194,3 +264,4 @@ }); | ||
if (/keyframes$/i.test(atrule.name)) { | ||
var localMatch = /^\s*:local\s*\((.+?)\)\s*$/.exec(atrule.params); | ||
const localMatch = /^\s*:local\s*\((.+?)\)\s*$/.exec(atrule.params); | ||
if (localMatch) { | ||
@@ -204,4 +275,6 @@ atrule.params = exportScopedName(localMatch[1]); | ||
const exportedNames = Object.keys(exports); | ||
if (exportedNames.length > 0) { | ||
const exportRule = postcss.rule({ selector: ':export' }); | ||
exportedNames.forEach(exportedName => | ||
@@ -211,5 +284,6 @@ exportRule.append({ | ||
value: exports[exportedName].join(' '), | ||
raws: { before: '\n ' } | ||
raws: { before: '\n ' }, | ||
}) | ||
); | ||
css.append(exportRule); | ||
@@ -220,3 +294,3 @@ } | ||
processor.generateScopedName = function(exportedName, path) { | ||
processor.generateScopedName = function(name, path) { | ||
const sanitisedPath = path | ||
@@ -226,5 +300,13 @@ .replace(/\.[^\.\/\\]+$/, '') | ||
.replace(/^_|_$/g, ''); | ||
return `_${sanitisedPath}__${exportedName}`; | ||
return `_${sanitisedPath}__${name}`.trim(); | ||
}; | ||
processor.generateExportEntry = function(name, scopedName) { | ||
return { | ||
key: unescape(name), | ||
value: unescape(scopedName), | ||
}; | ||
}; | ||
module.exports = processor; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
12738
6
5
249
+ Addedpostcss-selector-parser@6.1.2(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
- Removedcss-selector-tokenizer@^0.7.0
- Removedcss-selector-tokenizer@0.7.3(transitive)
- Removedfastparse@1.1.2(transitive)