postcss-modules-scope
Advanced tools
Comparing version 2.2.0 to 3.0.0-rc.0
# Change Log | ||
All notable changes to this project will be documented in this file. | ||
This project adheres to [Semantic Versioning](http://semver.org/). | ||
## [3.0.0-rc.0] - 2020-09-21 | ||
### BREAKING CHANGE | ||
- minimum supported `Node.js` version is `>= 10.13.0 || >= 12.13.0 || >= 14` | ||
- minimum supported `postcss` version is `^8.0.3` | ||
- `postcss` was moved to `peerDependencies`, you need to install `postcss` in your project before use the plugin | ||
## [2.2.0] - 2020-03-19 | ||
- added the `exportGlobals` option to export global classes and ids | ||
## [2.1.1] - 2019-03-05 | ||
### Fixed | ||
- add additional space after the escape sequence (#17) | ||
## [2.1.0] - 2019-03-05 | ||
### Fixed | ||
- handles properly selector with escaping characters (like: `.\31 a2b3c { color: red }`) | ||
### Feature | ||
- `generateExportEntry` option (allow to setup key and value for `:export {}` rule) |
{ | ||
"name": "postcss-modules-scope", | ||
"version": "2.2.0", | ||
"version": "3.0.0-rc.0", | ||
"description": "A CSS Modules transform to extract export statements from local-scope classes", | ||
"main": "src/index.js", | ||
"engines": { | ||
"node": ">= 6" | ||
"node": ">= 10.13.0 || >= 12.13.0 || >= 14" | ||
}, | ||
"scripts": { | ||
"lint": "eslint src test", | ||
"prettier": "prettier -l --ignore-path .gitignore . \"!test/test-cases\"", | ||
"eslint": "eslint --ignore-path .gitignore .", | ||
"lint": "yarn eslint && yarn prettier", | ||
"pretest": "yarn lint", | ||
"test": "mocha", | ||
"autotest": "chokidar src test -c 'yarn test'", | ||
"precover": "yarn lint", | ||
"cover": "nyc mocha", | ||
"travis": "yarn cover", | ||
"prepublish": "yarn run test" | ||
"test": "jest", | ||
"autotest": "jest --watch", | ||
"cover": "jest --coverage --collectCoverageFrom=\"src/**/*\"", | ||
"ci": "yarn pretest && yarn cover", | ||
"prepublishOnly": "yarn test" | ||
}, | ||
@@ -37,20 +38,15 @@ "repository": { | ||
"homepage": "https://github.com/css-modules/postcss-modules-scope", | ||
"prettier": { | ||
"semi": true, | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
}, | ||
"dependencies": { | ||
"postcss": "^7.0.6", | ||
"postcss-selector-parser": "^6.0.0" | ||
"postcss-selector-parser": "^6.0.3" | ||
}, | ||
"devDependencies": { | ||
"cssesc": "^3.0.0", | ||
"chokidar-cli": "^1.0.1", | ||
"codecov.io": "^0.1.2", | ||
"coveralls": "^3.0.2", | ||
"eslint": "^5.9.0", | ||
"mocha": "^6.0.2", | ||
"nyc": "^14.1.0" | ||
"eslint": "^7.9.0", | ||
"jest": "^26.4.2", | ||
"postcss": "^8.0.3", | ||
"prettier": "^2.1.2" | ||
}, | ||
"peerDependencies": { | ||
"postcss": "^8.0.0" | ||
} | ||
} |
@@ -27,4 +27,4 @@ # CSS Modules: Scope Locals & Extend | ||
```js | ||
import styles from './buttons.css' | ||
elem.innerHTML = `<button class="${styles.continueButton}">Continue</button>` | ||
import styles from "./buttons.css"; | ||
elem.innerHTML = `<button class="${styles.continueButton}">Continue</button>`; | ||
``` | ||
@@ -83,4 +83,4 @@ | ||
* Lines: [![Coverage Status](https://coveralls.io/repos/css-modules/postcss-modules-scope/badge.svg?branch=master)](https://coveralls.io/r/css-modules/postcss-modules-scope?branch=master) | ||
* Statements: [![codecov.io](http://codecov.io/github/css-modules/postcss-modules-scope/coverage.svg?branch=master)](http://codecov.io/github/css-modules/postcss-modules-scope?branch=master) | ||
- Lines: [![Coverage Status](https://coveralls.io/repos/css-modules/postcss-modules-scope/badge.svg?branch=master)](https://coveralls.io/r/css-modules/postcss-modules-scope?branch=master) | ||
- Statements: [![codecov.io](http://codecov.io/github/css-modules/postcss-modules-scope/coverage.svg?branch=master)](http://codecov.io/github/css-modules/postcss-modules-scope?branch=master) | ||
@@ -102,2 +102,3 @@ ## Development | ||
--- | ||
Glen Maddern, 2015. |
394
src/index.js
@@ -1,5 +0,4 @@ | ||
'use strict'; | ||
"use strict"; | ||
const postcss = require('postcss'); | ||
const selectorParser = require('postcss-selector-parser'); | ||
const selectorParser = require("postcss-selector-parser"); | ||
@@ -9,4 +8,4 @@ const hasOwnProperty = Object.prototype.hasOwnProperty; | ||
function getSingleLocalNamesForComposes(root) { | ||
return root.nodes.map(node => { | ||
if (node.type !== 'selector' || node.nodes.length !== 1) { | ||
return root.nodes.map((node) => { | ||
if (node.type !== "selector" || node.nodes.length !== 1) { | ||
throw new Error( | ||
@@ -20,4 +19,4 @@ `composition is only allowed when selector is single :local class name not in "${root}"` | ||
if ( | ||
node.type !== 'pseudo' || | ||
node.value !== ':local' || | ||
node.type !== "pseudo" || | ||
node.value !== ":local" || | ||
node.nodes.length !== 1 | ||
@@ -36,3 +35,3 @@ ) { | ||
if (node.type !== 'selector' || node.length !== 1) { | ||
if (node.type !== "selector" || node.length !== 1) { | ||
throw new Error( | ||
@@ -49,3 +48,3 @@ 'composition is only allowed when selector is single :local class name not in "' + | ||
if (node.type !== 'class') { | ||
if (node.type !== "class") { | ||
// 'id' is not possible, because you can't compose ids | ||
@@ -65,6 +64,6 @@ throw new Error( | ||
const whitespace = '[\\x20\\t\\r\\n\\f]'; | ||
const whitespace = "[\\x20\\t\\r\\n\\f]"; | ||
const unescapeRegExp = new RegExp( | ||
'\\\\([\\da-f]{1,6}' + whitespace + '?|(' + whitespace + ')|.)', | ||
'ig' | ||
"\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", | ||
"ig" | ||
); | ||
@@ -74,3 +73,3 @@ | ||
return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => { | ||
const high = '0x' + escaped - 0x10000; | ||
const high = "0x" + escaped - 0x10000; | ||
@@ -82,230 +81,235 @@ // NaN means non-codepoint | ||
: high < 0 | ||
? // BMP codepoint | ||
String.fromCharCode(high + 0x10000) | ||
: // Supplemental Plane codepoint (surrogate pair) | ||
String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00); | ||
? // 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) { | ||
return css => { | ||
const generateScopedName = | ||
(options && options.generateScopedName) || processor.generateScopedName; | ||
const generateExportEntry = | ||
(options && options.generateExportEntry) || processor.generateExportEntry; | ||
const exportGlobals = options && options.exportGlobals; | ||
const plugin = (options = {}) => { | ||
const generateScopedName = | ||
(options && options.generateScopedName) || plugin.generateScopedName; | ||
const generateExportEntry = | ||
(options && options.generateExportEntry) || plugin.generateExportEntry; | ||
const exportGlobals = options && options.exportGlobals; | ||
const exports = Object.create(null); | ||
return { | ||
postcssPlugin: "postcss-modules-scope", | ||
RootExit(root, { rule }) { | ||
const exports = Object.create(null); | ||
function exportScopedName(name, rawName) { | ||
const scopedName = generateScopedName( | ||
rawName ? rawName : name, | ||
css.source.input.from, | ||
css.source.input.css | ||
); | ||
const exportEntry = generateExportEntry( | ||
rawName ? rawName : name, | ||
scopedName, | ||
css.source.input.from, | ||
css.source.input.css | ||
); | ||
const { key, value } = exportEntry; | ||
function exportScopedName(name, rawName) { | ||
const scopedName = generateScopedName( | ||
rawName ? rawName : name, | ||
root.source.input.from, | ||
root.source.input.css | ||
); | ||
const exportEntry = generateExportEntry( | ||
rawName ? rawName : name, | ||
scopedName, | ||
root.source.input.from, | ||
root.source.input.css | ||
); | ||
const { key, value } = exportEntry; | ||
exports[key] = exports[key] || []; | ||
exports[key] = exports[key] || []; | ||
if (exports[key].indexOf(value) < 0) { | ||
exports[key].push(value); | ||
if (exports[key].indexOf(value) < 0) { | ||
exports[key].push(value); | ||
} | ||
return scopedName; | ||
} | ||
return scopedName; | ||
} | ||
function localizeNode(node) { | ||
switch (node.type) { | ||
case "selector": | ||
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": { | ||
return selectorParser.id({ | ||
value: exportScopedName( | ||
node.value, | ||
node.raws && node.raws.value ? node.raws.value : null | ||
), | ||
}); | ||
} | ||
} | ||
function localizeNode(node) { | ||
switch (node.type) { | ||
case 'selector': | ||
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': { | ||
return selectorParser.id({ | ||
value: exportScopedName( | ||
node.value, | ||
node.raws && node.raws.value ? node.raws.value : null | ||
), | ||
}); | ||
} | ||
throw new Error( | ||
`${node.type} ("${node}") is not allowed in a :local block` | ||
); | ||
} | ||
throw new Error( | ||
`${node.type} ("${node}") is not allowed in a :local block` | ||
); | ||
} | ||
function traverseNode(node) { | ||
switch (node.type) { | ||
case "pseudo": | ||
if (node.value === ":local") { | ||
if (node.nodes.length !== 1) { | ||
throw new Error('Unexpected comma (",") in :local block'); | ||
} | ||
function traverseNode(node) { | ||
switch (node.type) { | ||
case 'pseudo': | ||
if (node.value === ':local') { | ||
if (node.nodes.length !== 1) { | ||
throw new Error('Unexpected comma (",") in :local block'); | ||
} | ||
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; | ||
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; | ||
const nextNode = node.next(); | ||
const nextNode = node.next(); | ||
if ( | ||
nextNode && | ||
nextNode.type === "combinator" && | ||
nextNode.value === " " && | ||
/\\[A-F0-9]{1,6}$/.test(selector.last.value) | ||
) { | ||
selector.last.spaces.after = " "; | ||
} | ||
if ( | ||
nextNode && | ||
nextNode.type === 'combinator' && | ||
nextNode.value === ' ' && | ||
/\\[A-F0-9]{1,6}$/.test(selector.last.value) | ||
) { | ||
selector.last.spaces.after = ' '; | ||
node.replaceWith(selector); | ||
return; | ||
} | ||
node.replaceWith(selector); | ||
return; | ||
/* falls through */ | ||
case "root": | ||
case "selector": { | ||
node.each(traverseNode); | ||
break; | ||
} | ||
/* falls through */ | ||
case 'root': | ||
case 'selector': { | ||
node.each(traverseNode); | ||
break; | ||
case "id": | ||
case "class": | ||
if (exportGlobals) { | ||
exports[node.value] = [node.value]; | ||
} | ||
break; | ||
} | ||
case 'id': | ||
case 'class': | ||
if (exportGlobals) { | ||
exports[node.value] = [node.value]; | ||
} | ||
break; | ||
return node; | ||
} | ||
return node; | ||
} | ||
// Find any :import and remember imported names | ||
const importedNames = {}; | ||
// Find any :import and remember imported names | ||
const importedNames = {}; | ||
css.walkRules(rule => { | ||
if (/^:import\(.+\)$/.test(rule.selector)) { | ||
rule.walkDecls(decl => { | ||
importedNames[decl.prop] = true; | ||
}); | ||
} | ||
}); | ||
root.walkRules((rule) => { | ||
if (/^:import\(.+\)$/.test(rule.selector)) { | ||
rule.walkDecls((decl) => { | ||
importedNames[decl.prop] = true; | ||
}); | ||
} | ||
}); | ||
// Find any :local classes | ||
css.walkRules(rule => { | ||
if ( | ||
rule.nodes && | ||
rule.selector.slice(0, 2) === '--' && | ||
rule.selector.slice(-1) === ':' | ||
) { | ||
// ignore custom property set | ||
return; | ||
} | ||
// Find any :local classes | ||
root.walkRules((rule) => { | ||
if ( | ||
rule.nodes && | ||
rule.selector.slice(0, 2) === "--" && | ||
rule.selector.slice(-1) === ":" | ||
) { | ||
// ignore custom property set | ||
return; | ||
} | ||
let parsedSelector = selectorParser().astSync(rule); | ||
let parsedSelector = selectorParser().astSync(rule); | ||
rule.selector = traverseNode(parsedSelector.clone()).toString(); | ||
rule.selector = traverseNode(parsedSelector.clone()).toString(); | ||
rule.walkDecls(/composes|compose-with/, decl => { | ||
const localNames = getSingleLocalNamesForComposes(parsedSelector); | ||
const classes = decl.value.split(/\s+/); | ||
rule.walkDecls(/composes|compose-with/, (decl) => { | ||
const localNames = getSingleLocalNamesForComposes(parsedSelector); | ||
const classes = decl.value.split(/\s+/); | ||
classes.forEach(className => { | ||
const global = /^global\(([^\)]+)\)$/.exec(className); | ||
classes.forEach((className) => { | ||
const global = /^global\(([^)]+)\)$/.exec(className); | ||
if (global) { | ||
localNames.forEach(exportedName => { | ||
exports[exportedName].push(global[1]); | ||
}); | ||
} else if (hasOwnProperty.call(importedNames, className)) { | ||
localNames.forEach(exportedName => { | ||
exports[exportedName].push(className); | ||
}); | ||
} else if (hasOwnProperty.call(exports, className)) { | ||
localNames.forEach(exportedName => { | ||
exports[className].forEach(item => { | ||
exports[exportedName].push(item); | ||
if (global) { | ||
localNames.forEach((exportedName) => { | ||
exports[exportedName].push(global[1]); | ||
}); | ||
}); | ||
} else { | ||
throw decl.error( | ||
`referenced class name "${className}" in ${decl.prop} not found` | ||
); | ||
} | ||
} else if (hasOwnProperty.call(importedNames, className)) { | ||
localNames.forEach((exportedName) => { | ||
exports[exportedName].push(className); | ||
}); | ||
} else if (hasOwnProperty.call(exports, className)) { | ||
localNames.forEach((exportedName) => { | ||
exports[className].forEach((item) => { | ||
exports[exportedName].push(item); | ||
}); | ||
}); | ||
} else { | ||
throw decl.error( | ||
`referenced class name "${className}" in ${decl.prop} not found` | ||
); | ||
} | ||
}); | ||
decl.remove(); | ||
}); | ||
decl.remove(); | ||
}); | ||
rule.walkDecls((decl) => { | ||
let tokens = decl.value.split(/(,|'[^']*'|"[^"]*")/); | ||
rule.walkDecls(decl => { | ||
let tokens = decl.value.split(/(,|'[^']*'|"[^"]*")/); | ||
tokens = tokens.map((token, idx) => { | ||
if (idx === 0 || tokens[idx - 1] === ",") { | ||
const localMatch = /^(\s*):local\s*\((.+?)\)/.exec(token); | ||
tokens = tokens.map((token, idx) => { | ||
if (idx === 0 || tokens[idx - 1] === ',') { | ||
const localMatch = /^(\s*):local\s*\((.+?)\)/.exec(token); | ||
if (localMatch) { | ||
return ( | ||
localMatch[1] + | ||
exportScopedName(localMatch[2]) + | ||
token.substr(localMatch[0].length) | ||
); | ||
if (localMatch) { | ||
return ( | ||
localMatch[1] + | ||
exportScopedName(localMatch[2]) + | ||
token.substr(localMatch[0].length) | ||
); | ||
} else { | ||
return token; | ||
} | ||
} else { | ||
return token; | ||
} | ||
} else { | ||
return token; | ||
} | ||
}); | ||
decl.value = tokens.join(""); | ||
}); | ||
decl.value = tokens.join(''); | ||
}); | ||
}); | ||
// Find any :local keyframes | ||
css.walkAtRules(atrule => { | ||
if (/keyframes$/i.test(atrule.name)) { | ||
const localMatch = /^\s*:local\s*\((.+?)\)\s*$/.exec(atrule.params); | ||
// Find any :local keyframes | ||
root.walkAtRules((atrule) => { | ||
if (/keyframes$/i.test(atrule.name)) { | ||
const localMatch = /^\s*:local\s*\((.+?)\)\s*$/.exec(atrule.params); | ||
if (localMatch) { | ||
atrule.params = exportScopedName(localMatch[1]); | ||
if (localMatch) { | ||
atrule.params = exportScopedName(localMatch[1]); | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
// If we found any :locals, insert an :export rule | ||
const exportedNames = Object.keys(exports); | ||
// If we found any :locals, insert an :export rule | ||
const exportedNames = Object.keys(exports); | ||
if (exportedNames.length > 0) { | ||
const exportRule = postcss.rule({ selector: ':export' }); | ||
if (exportedNames.length > 0) { | ||
const exportRule = rule({ selector: ":export" }); | ||
exportedNames.forEach(exportedName => | ||
exportRule.append({ | ||
prop: exportedName, | ||
value: exports[exportedName].join(' '), | ||
raws: { before: '\n ' }, | ||
}) | ||
); | ||
exportedNames.forEach((exportedName) => | ||
exportRule.append({ | ||
prop: exportedName, | ||
value: exports[exportedName].join(" "), | ||
raws: { before: "\n " }, | ||
}) | ||
); | ||
css.append(exportRule); | ||
} | ||
root.append(exportRule); | ||
} | ||
}, | ||
}; | ||
}); | ||
}; | ||
processor.generateScopedName = function(name, path) { | ||
plugin.postcss = true; | ||
plugin.generateScopedName = function (name, path) { | ||
const sanitisedPath = path | ||
.replace(/\.[^\.\/\\]+$/, '') | ||
.replace(/[\W_]+/g, '_') | ||
.replace(/^_|_$/g, ''); | ||
.replace(/\.[^./\\]+$/, "") | ||
.replace(/[\W_]+/g, "_") | ||
.replace(/^_|_$/g, ""); | ||
@@ -315,3 +319,3 @@ return `_${sanitisedPath}__${name}`.trim(); | ||
processor.generateExportEntry = function(name, scopedName) { | ||
plugin.generateExportEntry = function (name, scopedName) { | ||
return { | ||
@@ -323,2 +327,2 @@ key: unescape(name), | ||
module.exports = processor; | ||
module.exports = plugin; |
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
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
14189
5
268
102
1
+ Addednanoid@3.3.7(transitive)
+ Addedpicocolors@1.1.1(transitive)
+ Addedpostcss@8.4.49(transitive)
+ Addedsource-map-js@1.2.1(transitive)
- Removedpostcss@^7.0.6
- Removedpicocolors@0.2.1(transitive)
- Removedpostcss@7.0.39(transitive)
- Removedsource-map@0.6.1(transitive)