所以...感谢@Tholle 的评论,我想通了...
type CSSStyle = {[key: string] : (string|boolean)};
type CSSProperty = CSSStyle | {[key:string]: CSSStyle};
type Styles = {[key: string] : CSSProperty};
interface IStyleGenerator {
generateCSS: (props: Styles, standart: string) => string;
}
const CaseHelper = {
snakeToCamel: s => s.replace(/(\-\w)/g, function(m){return m[1].toUpperCase();}),
camelToSnake: c => c.replace(/([A-Z])/g, a => `-${a.toLowerCase()}`)
}
class LiberStyle implements IStyleGenerator {
readonly defaultNamespace : string = 'standart';
/**
*
* @param propsStyle the style field from the props
* @param standartStyle the css rules as string
*/
generateCSS(propsStyle: Styles, standartStyle: string) : string {
let rules = standartStyle.split('\n').map(a => a.trim()); // O(n * k);
const defaultStyle = {};
let namespace = this.defaultNamespace;
defaultStyle[namespace] = {};
const namespacePattern = /&:(\w+).+/;
const hasDoubleMark = new RegExp(/:/);
for(let rule of rules) {
if(rule.length <= 0) continue;
rule = rule.trim();
if(rule.length <= 2) continue;
if(rule === '}') {
namespace = this.defaultNamespace
} else {
if(rule.length >= 2 && rule[0] === '&' && rule[1] === ':') {
namespace = rule.replace(namespacePattern, '$1');
defaultStyle[namespace] = {};
} else {
if(!hasDoubleMark.test(rule)) continue;
const [key, value] = this.extractKeyValue(rule);
defaultStyle[namespace][key] = value;
}
}
}
const propsRemappedStyle = {};
propsRemappedStyle[this.defaultNamespace] = {};
Object['entries'](propsStyle).forEach(entry => {
const [nmspace, rules] = entry;
if(typeof rules === 'object') {
propsRemappedStyle[nmspace] = rules
} else {
propsRemappedStyle[this.defaultNamespace][nmspace] = rules;
}
})
return this.stringifyRules(propsRemappedStyle, defaultStyle);
}
/**
*
* PRIVATE METHODS ---------------------------------------------
*
*/
extractKeyValue(rule : string) {
let [key, value] = rule.split(':');
value = value.trim();
key = CaseHelper.snakeToCamel(key.trim());
return [key, value.substring(0,value.length - 1)];
}
myInclude(array : any[], value : string) {
let found = false;
array.forEach((e:any) => {
found = e === value;
return found;
})
return found;
}
getNamespaceKeys(propKeys : any[]= [], defaultKeys : any[] = []) {
let keys = defaultKeys.filter(style => !this.myInclude(propKeys,style));
let ret = keys.concat(propKeys);
// console.log({ret , propKeys, defaultKeys});
return ret;
}
getKeysAndNamespace(propStyles : Styles, defaultStyles : Styles) {
let namespacedKeys = Object.keys(propStyles)
let namespaceMissingKeys = Object.keys(defaultStyles).filter((nmspace:string) => (!this.myInclude(namespacedKeys,nmspace) && (!this.defaultNamespace)));
namespacedKeys = namespacedKeys.concat(namespaceMissingKeys);
let allKeys = {};
namespacedKeys.forEach(namespace => {
allKeys[namespace] = this.getNamespaceKeys(Object.keys(propStyles[namespace] || {}), Object.keys(defaultStyles[namespace] || {}));
})
return { keys: allKeys, namespaces: namespacedKeys };
}
stringifyNamespace(namespace, keys, propRules, defaultRules) {
let unifiedRules = '';
let tab = namespace !== this.defaultNamespace ? '\t\t' : '\t';
const camelToSnake = CaseHelper.camelToSnake;
keys.forEach(key => {
unifiedRules = `${unifiedRules}\n${tab}${camelToSnake(key)}: ${propRules[key] ? propRules[key] : defaultRules[key]};`
});
return namespace !== this.defaultNamespace ? `\t&:${namespace} {${unifiedRules}\n\t}` : unifiedRules;
}
stringifyRules(propsStyle : Styles, defaultStyle : Styles) {
const { keys, namespaces } = this.getKeysAndNamespace(propsStyle, defaultStyle);
// console.log({keys, namespaces, propsStyle, defaultStyle });
return namespaces.reduce((final, namespace) => {
return `${final}\n${this.stringifyNamespace(namespace, keys[namespace], propsStyle[namespace], defaultStyle[namespace])}`
}, '');
}
}
export default new LiberStyle();
代码并不完美...但您可以使用它来创建这样的包装器:
import LiberStyle from './liberStyle';
interface A {
}
interface B {
}
function generic(oi) {
return
}
const __props__ : B = {
style: {
height: '40px',
hover: {
color: 'red'
}
},
show: true
}
/**
* This is just a function I created to mimick styled.div because I didnt have it installed on the PC I wrote this code
*/
function styled_div<T>(staticStrings: TemplateStringsArray, ...params) {
let merged = '';
const size = Math.max(staticStrings.length, params.length);
for(let i=0; i < size; i++) {
if(staticStrings[i]) {
merged = merged + staticStrings[i].trim();
}
if(params[i]) {
if(typeof params[i] === 'function') {
merged = merged + params[i](__props__);
} else if(typeof params[i] === 'object') {
merged = merged + JSON.parse(params[i]);
} else if(typeof params[i] === 'string') {
merged = merged + params[i].trim();
} else {
merged = merged + params[i];
}
}
}
return merged;
}
function merge(props, staticStrings: TemplateStringsArray, params: any[]) {
let merged = '';
const size = Math.max(staticStrings.length, params.length);
for(let i=0; i < size; i++) {
if(staticStrings[i]) {
merged = merged + staticStrings[i].trim();
}
if(params[i]) {
if(typeof params[i] === 'function') {
merged = merged + params[i](props);
} else if(typeof params[i] === 'object') {
merged = merged + JSON.parse(params[i]);
} else {
merged = merged + params[i].trim();
}
}
}
return merged;
}
const styled = {
div: function div<T>(staticStrings: TemplateStringsArray, ...params) {
return styled_div<T>`
${props => {
const m = merge(props, staticStrings, params);
//console.log({ style: props.style, m});
return LiberStyle.generateCSS(props.style,m)}
}
`
},
}
const waka = styled.div<A>`
display: ${props => props.show ? 'block' : 'none'};
height: 20px;
width: 30px;
`;
console.log(waka);
它并不完美,但如果有人遇到同样的问题,我希望它能给你一些启示......