magix-combine
Version:
合并Magix View的html,js,css成一个js文件,并检测html,js,css中可能存在的问题
370 lines • 12.2 kB
JavaScript
/*
http://www.w3school.com.cn/cssref/css_selectors.asp
简易parser,只处理类与标签,其中
processRules 参考了这个:https://github.com/fb55/css-what/blob/master/index.js
思路:跳过不必要处理的css,在处理规则时,跳过{ }
*/
let cache = Object.create(null);
let nameReg = /^(?:\\.|[\w\-\u00c0-\uFFFF])+/;
//modified version of https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L87
let attrReg = /^\s*((?:\\.|[\w\u00c0-\uFFFF\-])+)\s*(?:(\S?)=\s*(?:(['"])(.*?)\3|(#?(?:\\.|[\w\u00c0-\uFFFF\-])*)|)|)\s*(i)?\]/;
let isWhitespace = c => {
return c === ' ' || c === '\n' || c === '\t' || c === '\f' || c === '\r';
};
let nonEmptyReg = /^\S+$/;
let atRuleSearchContent = {
document: 1,
supports: 1,
media: 1
};
let atRuleIgnoreContent = {
page: 1,
global: 1,
property: 1,
'-webkit-keyframes': 1,
'-moz-keyframes': 1,
'-ms-keyframes': 1,
'-o-keyframes': 1,
keyframes: 1,
'font-face': 1,
viewport: 1,
'counter-style': 1,
'font-feature-values': 1
};
let unpackPseudos = {
has: 1,
not: 1,
matches: 1,
where: 1//
};
let quotes = {
'"': 1,
'\'': 1
};
let ignoreTags = {
html: 1,
body: 1,
tbody: 1,
thead: 1,
tfoot: 1,
tr: 1,
th: 1,
td: 1,
col: 1,
caption: 1,
colgroup: 1
};
let selectorPower = {
TAG: 1,
ATTR: 100,
CLASS: 10000,
ID: 1000000
};
let parse = (css, file) => {
let tokens = [];
let nests = [];
let nestsLocker = Object.create(null);
let current = 0;
let max = css.length;
let c;
let stripWhitespaceAndGo = offset => {
while (isWhitespace(css.charAt(current))) current++;
current += offset;
};
let getArround = () => {
//let start = Math.max(0, current - 10);
let end = Math.min(css.length, current + 40);
return css.substring(current - 1, end);
};
let getNameAndGo = () => {
let sub = css.substr(current);
let id;
let matches = sub.match(nameReg);
if (matches) {
id = matches[0];
current += id.length;
} else {
throw {
message: '[MXC Error(css-parser)] get name error',
file: file,
extract: getArround()
};
}
return id;
};
let skipAtRule = () => {
//let sc = current;
do {
let tc = css.charAt(current);
if (tc == ';' || tc == '\r' || tc == '\n' || tc == '{') {
current++;
break;
}
current++;
} while (current < max);
//let ec = current;
//console.log('ignore at rule', css.substring(sc, ec));
};
let skipAtRuleUntilLeftBrace = () => {
//let sc = current;
do {
let tc = css.charAt(current);
if (tc == '{') {
current++;
break;
}
current++;
} while (current < max);
//let ec = current;
//console.log('ignore at rule expr', css.substring(sc, ec));
};
let skipAtRuleContent = () => {
let count = 0;
//let sc = current;
current = css.indexOf('{', current);
while (current >= 0 && current < max) {
let tc = css.charAt(current);
if (tc == '{') {
count++;
} else if (tc == '}') {
count--;
if (!count) {
current++;
break;
}
}
current++;
}
//let ec = current;
//console.log('ignore content', css.substring(sc, ec));
};
let overSelectors = 0,
selectorStart = 0;
let takeSelector = (offset) => {
if (overSelectors > 0) { //1 标签 100属性 10000类 1000000 id
//debugger;
if (!offset) offset = 0;
let s = css.substring(selectorStart, current + offset).trim(); //
if (nonEmptyReg.test(s)) { //无空格写法 如a.b.c a[text][href] a.span.red
if (overSelectors < 300) { //3*ATTR;
return;
} else if (overSelectors > selectorPower.CLASS && overSelectors < 3 * selectorPower.CLASS) {
return;
}
}
if (overSelectors <= 303) { //3*selectorPower.ATTR + 3*selectorPower.TAG
overSelectors %= selectorPower.ATTR;
} else if (overSelectors >= selectorPower.CLASS && overSelectors <= 20200) {
//2*selectorPower.CLASS+2*selectorPower.ATTR
overSelectors %= selectorPower.CLASS;
overSelectors %= selectorPower.ATTR;
if (overSelectors && overSelectors <= 3) { //类与标签混用
overSelectors = 4; //不建议混用
}
}
if (overSelectors && overSelectors > 3 * selectorPower.TAG) {
if (!nestsLocker[s]) {
nestsLocker[s] = 1;
nests.push(s);
}
}
}
};
let processRules = () => {
let prev = '',
pseudos = [];
overSelectors = 0;
selectorStart = current;
while (current < max) {
stripWhitespaceAndGo(0);
let tc = css.charAt(current);
if (tc == '/') {
current = css.indexOf('*/', current) + 2;
break;
} else if (tc == '@') {
break;
} else if (tc == ',') {//.title,.name {} #app{}
prev = '';
takeSelector();
overSelectors = 0;
current++;
selectorStart = current;
} else if (tc == '{') {
takeSelector();
current++;
let ti = css.indexOf('}', current);
if (ti != -1) {
current = ti;
} else {
throw {
message: '[MXC Error(css-parser)] missing right brace',
file: file,
extract: getArround()
};
}
} else if (tc == '}') {
current++;
break;
} else if (tc === '.' || tc === '#') {
current++;
let sc = current;
let isGlobal = false;
if (tc == '.') {
isGlobal = css.charAt(current) == '@';
if (isGlobal) {
current++;
}
}
let id = getNameAndGo();
overSelectors += tc === '.' ? selectorPower.CLASS : selectorPower.ID;
if (tc == '.') {
tokens.push({
type: prev = 'class',
name: id,
start: sc,
end: current,
isGlobal
});
} else if (tc == '#') {
tokens.push({
type: prev = 'id',
name: id,
start: sc,
end: current
});
}
} else if (tc === '[') {
current++;
let temp = css.substr(current);
let matches = temp.match(attrReg);
if (!matches) {
throw {
message: '[MXC Error(css-parser)] bad attribute',
file: file,
extract: getArround()
};
}
if (!prev) {
tokens.push({
type: 'sattr',
name: matches[1],
start: current,
end: current + matches[0].length
});
}
overSelectors += selectorPower.ATTR;
prev = 'attr';
current += matches[0].length;
} else if (tc === ':') {
if (css.charAt(current + 1) === ':') {
current += 2;
getNameAndGo();
continue;
}
current++;
let begin = current;
let id = getNameAndGo();
if (css.charAt(current) === '(') {
//debugger;
if (unpackPseudos.hasOwnProperty(id)) {
let quot = css.charAt(current + 1);
let quoted = quot in quotes;
current += quoted + 1;
pseudos.push({
quoted,
selectorStart,
overSelectors
});
prev = '';
selectorStart = current;
} else {
let ti = css.indexOf(')', current);
if (ti > -1) {
current = ti + 1;
if (id == 'global') {
let range = css.substring(begin - 1, current);
tokens.push({
type: 'global',
start: begin - 1,
content: range.slice(8, -1),
end: current
});
}
}
}
}
} else if (tc == ')') {
current++;
if (pseudos.length) {
let last = pseudos.pop();
takeSelector(last.quoted ? -2 : -1);
overSelectors = last.overSelectors;
selectorStart = last.selectorStart;
takeSelector();
} else {
prev = '';
selectorStart = current;
overSelectors = 0;
}
} else if (nameReg.test(css.substr(current))) {
let sc = current;
let id = getNameAndGo();
tokens.push({
type: prev = 'tag',
name: id,
start: sc,
end: current
});
if (!ignoreTags[id]) {
overSelectors += selectorPower.TAG;
}
} else {
current++;
}
}
};
while (current < max) {
stripWhitespaceAndGo(0);
c = css.charAt(current);
if (c === '@') {
let start = current;
current++;
let name = getNameAndGo();
if (atRuleSearchContent.hasOwnProperty(name)) {
skipAtRuleUntilLeftBrace();
processRules();
} else if (atRuleIgnoreContent.hasOwnProperty(name)) {
//let start = current;
let cStart = current;
skipAtRuleContent();
if (name == 'global') {
let left = css.indexOf('{', cStart);
tokens.push({
type: 'global',
start: start,
end: current,
content: css.slice(left + 1, current - 1)
});
}
} else {
skipAtRule();
if (name == 'import') {
nests.push(css.substring(start, current - 1));
}
}
} else {
processRules();
}
}
return {
tokens,
nests
};
};
module.exports = (css, file) => {
let key = file + '@' + css;
if (cache[key]) {
return cache[key];
}
return (cache[key] = parse(css, file));
};