draft: refactor project so it sits on top of a standalone app
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [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)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
ISC License (ISC)
|
||||
|
||||
Copyright (c) 2015, Glen Maddern
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# CSS Modules: Scope Locals & Extend
|
||||
|
||||
[](https://travis-ci.org/css-modules/postcss-modules-scope)
|
||||
|
||||
Transforms:
|
||||
|
||||
```css
|
||||
:local(.continueButton) {
|
||||
color: green;
|
||||
}
|
||||
```
|
||||
|
||||
into:
|
||||
|
||||
```css
|
||||
:export {
|
||||
continueButton: __buttons_continueButton_djd347adcxz9;
|
||||
}
|
||||
.__buttons_continueButton_djd347adcxz9 {
|
||||
color: green;
|
||||
}
|
||||
```
|
||||
|
||||
so it doesn't pollute CSS global scope and can be simply used in JS like so:
|
||||
|
||||
```js
|
||||
import styles from './buttons.css'
|
||||
elem.innerHTML = `<button class="${styles.continueButton}">Continue</button>`
|
||||
```
|
||||
|
||||
## Composition
|
||||
|
||||
Since we're exporting class names, there's no reason to export only one. This can give us some really useful reuse of styles:
|
||||
|
||||
```css
|
||||
.globalButtonStyle {
|
||||
background: white;
|
||||
border: 1px solid;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.globalButtonStyle:hover {
|
||||
box-shadow: 0 0 4px -2px;
|
||||
}
|
||||
:local(.continueButton) {
|
||||
compose-with: globalButtonStyle;
|
||||
color: green;
|
||||
}
|
||||
```
|
||||
|
||||
becomes:
|
||||
|
||||
```
|
||||
.globalButtonStyle {
|
||||
background: white;
|
||||
border: 1px solid;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.globalButtonStyle:hover {
|
||||
box-shadow: 0 0 4px -2px;
|
||||
}
|
||||
:local(.continueButton) {
|
||||
compose-with: globalButtonStyle;
|
||||
color: green;
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** you can also use `composes` as a shorthand for `compose-with`
|
||||
|
||||
## Local-by-default & reuse across files
|
||||
|
||||
You're looking for [CSS Modules](https://github.com/css-modules/css-modules). It uses this plugin as well as a few others, and it's amazing.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
[](https://travis-ci.org/css-modules/postcss-modules-scope)
|
||||
|
||||
* Lines: [](https://coveralls.io/r/css-modules/postcss-modules-scope?branch=master)
|
||||
* Statements: [](http://codecov.io/github/css-modules/postcss-modules-scope?branch=master)
|
||||
|
||||
## Development
|
||||
|
||||
- `npm autotest` will watch `src` and `test` for changes and run the tests, and transpile the ES6 to ES5 on success
|
||||
|
||||
## License
|
||||
|
||||
ISC
|
||||
|
||||
## With thanks
|
||||
|
||||
- Mark Dalgleish
|
||||
- Tobias Koppers
|
||||
- Guy Bedford
|
||||
|
||||
---
|
||||
Glen Maddern, 2015.
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"_from": "postcss-modules-scope@^2.1.1",
|
||||
"_id": "postcss-modules-scope@2.2.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==",
|
||||
"_location": "/postcss-modules-scope",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "postcss-modules-scope@^2.1.1",
|
||||
"name": "postcss-modules-scope",
|
||||
"escapedName": "postcss-modules-scope",
|
||||
"rawSpec": "^2.1.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^2.1.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/css-loader"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz",
|
||||
"_shasum": "385cae013cc7743f5a7d7602d1073a89eaae62ee",
|
||||
"_spec": "postcss-modules-scope@^2.1.1",
|
||||
"_where": "O:\\zero\\zero.Web\\node_modules\\css-loader",
|
||||
"author": {
|
||||
"name": "Glen Maddern"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/css-modules/postcss-modules-scope/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"postcss": "^7.0.6",
|
||||
"postcss-selector-parser": "^6.0.0"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "A CSS Modules transform to extract export statements from local-scope classes",
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^1.0.1",
|
||||
"codecov.io": "^0.1.2",
|
||||
"coveralls": "^3.0.2",
|
||||
"cssesc": "^3.0.0",
|
||||
"eslint": "^5.9.0",
|
||||
"mocha": "^6.0.2",
|
||||
"nyc": "^14.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"homepage": "https://github.com/css-modules/postcss-modules-scope",
|
||||
"keywords": [
|
||||
"css-modules",
|
||||
"postcss",
|
||||
"plugin"
|
||||
],
|
||||
"license": "ISC",
|
||||
"main": "src/index.js",
|
||||
"name": "postcss-modules-scope",
|
||||
"prettier": {
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/css-modules/postcss-modules-scope.git"
|
||||
},
|
||||
"scripts": {
|
||||
"autotest": "chokidar src test -c 'yarn test'",
|
||||
"cover": "nyc mocha",
|
||||
"lint": "eslint src test",
|
||||
"precover": "yarn lint",
|
||||
"prepublish": "yarn run test",
|
||||
"pretest": "yarn lint",
|
||||
"test": "mocha",
|
||||
"travis": "yarn cover"
|
||||
},
|
||||
"version": "2.2.0"
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
'use strict';
|
||||
|
||||
const postcss = require('postcss');
|
||||
const selectorParser = require('postcss-selector-parser');
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
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 "${root}"`
|
||||
);
|
||||
}
|
||||
|
||||
node = node.nodes[0];
|
||||
|
||||
if (
|
||||
node.type !== 'pseudo' ||
|
||||
node.value !== ':local' ||
|
||||
node.nodes.length !== 1
|
||||
) {
|
||||
throw new Error(
|
||||
'composition is only allowed when selector is single :local class name not in "' +
|
||||
root +
|
||||
'", "' +
|
||||
node +
|
||||
'" is weird'
|
||||
);
|
||||
}
|
||||
|
||||
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 "' +
|
||||
root +
|
||||
'", "' +
|
||||
node +
|
||||
'" is weird'
|
||||
);
|
||||
}
|
||||
|
||||
node = node.first;
|
||||
|
||||
if (node.type !== 'class') {
|
||||
// 'id' is not possible, because you can't compose ids
|
||||
throw new Error(
|
||||
'composition is only allowed when selector is single :local class name not in "' +
|
||||
root +
|
||||
'", "' +
|
||||
node +
|
||||
'" is weird'
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
return css => {
|
||||
const generateScopedName =
|
||||
(options && options.generateScopedName) || processor.generateScopedName;
|
||||
const generateExportEntry =
|
||||
(options && options.generateExportEntry) || processor.generateExportEntry;
|
||||
const exportGlobals = options && options.exportGlobals;
|
||||
|
||||
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;
|
||||
|
||||
exports[key] = exports[key] || [];
|
||||
|
||||
if (exports[key].indexOf(value) < 0) {
|
||||
exports[key].push(value);
|
||||
}
|
||||
|
||||
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
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (
|
||||
nextNode &&
|
||||
nextNode.type === 'combinator' &&
|
||||
nextNode.value === ' ' &&
|
||||
/\\[A-F0-9]{1,6}$/.test(selector.last.value)
|
||||
) {
|
||||
selector.last.spaces.after = ' ';
|
||||
}
|
||||
|
||||
node.replaceWith(selector);
|
||||
|
||||
return;
|
||||
}
|
||||
/* falls through */
|
||||
case 'root':
|
||||
case 'selector': {
|
||||
node.each(traverseNode);
|
||||
break;
|
||||
}
|
||||
case 'id':
|
||||
case 'class':
|
||||
if (exportGlobals) {
|
||||
exports[node.value] = [node.value];
|
||||
}
|
||||
break;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// Find any :import and remember imported names
|
||||
const importedNames = {};
|
||||
|
||||
css.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;
|
||||
}
|
||||
|
||||
let parsedSelector = selectorParser().astSync(rule);
|
||||
|
||||
rule.selector = traverseNode(parsedSelector.clone()).toString();
|
||||
|
||||
rule.walkDecls(/composes|compose-with/, decl => {
|
||||
const localNames = getSingleLocalNamesForComposes(parsedSelector);
|
||||
const classes = decl.value.split(/\s+/);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
throw decl.error(
|
||||
`referenced class name "${className}" in ${decl.prop} not found`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
decl.remove();
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
if (localMatch) {
|
||||
return (
|
||||
localMatch[1] +
|
||||
exportScopedName(localMatch[2]) +
|
||||
token.substr(localMatch[0].length)
|
||||
);
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
if (localMatch) {
|
||||
atrule.params = exportScopedName(localMatch[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If we found any :locals, insert an :export rule
|
||||
const exportedNames = Object.keys(exports);
|
||||
|
||||
if (exportedNames.length > 0) {
|
||||
const exportRule = postcss.rule({ selector: ':export' });
|
||||
|
||||
exportedNames.forEach(exportedName =>
|
||||
exportRule.append({
|
||||
prop: exportedName,
|
||||
value: exports[exportedName].join(' '),
|
||||
raws: { before: '\n ' },
|
||||
})
|
||||
);
|
||||
|
||||
css.append(exportRule);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
processor.generateScopedName = function(name, path) {
|
||||
const sanitisedPath = path
|
||||
.replace(/\.[^\.\/\\]+$/, '')
|
||||
.replace(/[\W_]+/g, '_')
|
||||
.replace(/^_|_$/g, '');
|
||||
|
||||
return `_${sanitisedPath}__${name}`.trim();
|
||||
};
|
||||
|
||||
processor.generateExportEntry = function(name, scopedName) {
|
||||
return {
|
||||
key: unescape(name),
|
||||
value: unescape(scopedName),
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = processor;
|
||||
Reference in New Issue
Block a user