magix-composer
Version:
compile html, style and javascript files into javascript
478 lines (465 loc) • 16.7 kB
JavaScript
let path = require('path');
let utils = require('./util');
let atpath = require('./util-atpath');
let md5 = require('./util-md5');
let configs = require('./util-config');
let cssChecker = require('./checker-css');
let cssParser = require('./css-parser');
let cssStringName = require('./css-string-name');
let {
cssIdGlobalPrefix,
} = require('./util-const');
let sep = path.sep;
let slashReg = /[\/\.~#@]/g;
let ignoreTags = {
html: 1,
body: 1,
'[mx-view]': 1
};
let ruleEndReg = /(?=[;\r\n])/;
//let trimQ = /^['"]|['"]$/g;
//以@开始的名称,如@font-face
//charset不处理,压缩器会自动处理
let fontfaceReg = /@font-face\s*\{([^\{\}]*)\}/g;
//keyframes,如@-webkit-keyframes xx
let keyframesReg = /(^|[\s\}])(@(?:-webkit-|-moz-|-o-|-ms-)?keyframes)\s+(['"])?([\w\-]+)\3/g;
let cssContentReg = /\{([^\{\}]*)\}/g;
let fixCssKeyName = (key, rules) => {
if (key.startsWith(';')) {
rules.push(';');
key = key.substring(1);
}
if (key.startsWith('\r')) {
rules.push('\r');
key = key.substring(1);
}
if (key.startsWith('\n')) {
rules.push('\n');
key = key.substring(1);
}
return key;
};
/*
对于@font-face @keyframes 和 animation-name
在哪个文件中定义就在哪个文件中使用,比如在 x.css中
@font-face{
}
定义的font-face,只能在 x.css 中通过
.css-selector{font-family:'ref font-face'}使用
在其它文件中通过.css-selector使用定义的这个 @font-face
不允许其它文件中直接使用这个 font-family
*/
let cssAtRuleProcessor = (fileContent, cssNamesKey, file) => {
let kfContents = Object.create(null);
let ffContents = Object.create(null);
//先处理keyframes
fileContent = fileContent.replace(keyframesReg, (m, head, keyframe, q, kname) => {
//把名称保存下来,因为还要修改使用的地方
if (!kfContents[kname]) {
kfContents[kname] = genCssSelector(kname, cssNamesKey, null, 'md5CssSelectorResult@rule_kf');
cssChecker.storeStyleDeclared(file, {
atRules: {
['@keyframes ' + kname]: 1
}
});
}
q = q || '';
return head + keyframe + ' ' + q + kfContents[kname] + q;
});
//处理其它@规则,这里只处理了font-face
fileContent = fileContent.replace(fontfaceReg, (match, content) => {
let rules = content.split(ruleEndReg);
let newRules = [];
for (let rule of rules) {
let parts = rule.split(':');
if (parts.length == 2) {
let [key, value] = parts;
key = fixCssKeyName(key, newRules);
if (key.trim() === 'font-family') {
let fname = cssStringName.parseFont(value)[0];
if (!ffContents[fname]) {
ffContents[fname] = cssStringName.stringifyFont([genCssSelector(fname, cssNamesKey, null, 'md5CssSelectorResult@rule_ff')]);
cssChecker.storeStyleDeclared(file, {
atRules: {
['@font-face ' + fname]: 1
}
});
}
newRules.push('font-family:' + ffContents[fname]);
} else {
newRules.push(`${key}:${value}`);
}
} else {
newRules.push(rule);
}
}
return `@font-face{${newRules.join('')}}`;
});
fileContent = fileContent.replace(cssContentReg, (_, content) => {
let rules = content.split(ruleEndReg);
let newContent = [];
for (let rule of rules) {
let parts = rule.split(':');
if (parts.length == 2) {
let [key, value] = parts;
key = fixCssKeyName(key, newContent);
key = key.trim();
value = value.trim();
if (key == 'font-family') {
let names = cssStringName.parseFont(value);
if (names.length == 1) {
let fname = names[0];
if (ffContents[fname]) {
let tn = ffContents[fname];
cssChecker.storeStyleUsed(file, file, {
atRules: {
[`@font-face ${fname}`]: tn
}
});
value = tn;
}
}
newContent.push(key + ':' + value);
} else if (key == 'font') {
let r = cssStringName.unpackFont(value);
if (r.succ) {
let names = cssStringName.parseFont(r.right);
if (names.length == 1) {
let fname = names[0];
if (ffContents[fname]) {
let tn = ffContents[fname];
cssChecker.storeStyleUsed(file, file, {
atRules: {
[`@font-face ${fname}`]: tn
}
});
value = r.left + ' ' + tn;
}
}
}
newContent.push(key + ':' + value);
} else if (key.endsWith('animation-name')) {
if (kfContents[value]) {
let tn = kfContents[value];
cssChecker.storeStyleUsed(file, file, {
atRules: {
[`@keyframes ${value}`]: tn
}
});
value = tn;
}
newContent.push(key + ':' + value);
} else if (key.endsWith('animation')) {
let subs = value.split(' ');
let newSubs = [];
for (let s of subs) {
s = s.trim();
if (kfContents[s]) {
let tn = kfContents[s];
cssChecker.storeStyleUsed(file, file, {
atRules: {
[`@keyframes ${s}`]: tn
}
});
s = tn;
}
newSubs.push(s);
}
newContent.push(key + ':' + newSubs.join(' '));
} else if (key.startsWith('grid')) {
let names = cssStringName.getGridNames(key, value);
for (let i = names.length; i--;) {
let n = names[i];
if (n.content != '.') {
value = value.substring(0, n.start) + genCssSelector(n.content, cssNamesKey, null, 'md5CssSelectorResult@grid') + value.substring(n.end);
}
}
newContent.push(key + ':' + value);
} else {
newContent.push(`${key}:${value}`);
}
} else {
newContent.push(rule);
}
}
return '{' + newContent.join('') + '}';
});
//console.log(fileContent);
return fileContent;
};
let genCssNamesKey = (file, ignorePrefix) => {
//获取模块的id
let cssId;
if (configs.debug) {
cssId = utils.extractModuleId(file, true);
cssId = '_' + cssId.replace(slashReg, '_') + '_';
} else {
cssId = md5(file, 'md5CssFileResult');
}
//css前缀是配置项中的前缀加上模块的md5信息
if (!ignorePrefix) {
if (configs.projectName) {
cssId = configs.projectName + '-' + cssId;
}
}
return cssId;
};
let genCssSelector = (selector, cssNameKey, reservedNames, key) => {
let mappedName = selector;
if (configs.debug) { //压缩,我们采用md5处理,同样的name要生成相同的key
if (cssNameKey) {
mappedName = cssNameKey + '-' + mappedName;
}
} else {
let prefix = configs.projectName ? configs.projectName + '-' : '';
mappedName = md5(selector + '\x00' + cssNameKey, key || 'md5CssSelectorResult', prefix, false, reservedNames);
if (configs.selectorDSEndReg.test(selector)) {
mappedName += '-';
}
}
return mappedName;
};
let refNameProcessor = (relateFile, file, ext, name, e) => {
if (file == 'scoped' && ext == '.style') {
if (e) {
let sname = e.globalCssNamesMap[name];
if (!sname) {
sname = `[ref="not found ${name} from @{${file}${ext}}"]`;
cssChecker.storeStyleUsed(relateFile, '/' + file + ext, {
selectors: {
[name]: sname
}
});
} else {
let f = e.globalCssDeclaredFiles.selectors[name] || '~~selector error~~';
cssChecker.storeStyleUsed(relateFile, f, {
selectors: {
[name]: sname
}
});
}
return sname;
} else {
throw new Error('[MXC Error(css-transform)] unsupport use scoped.style in ' + relateFile);
}
} else {
/*
a.css
[ref='@./b.css:good'] .name{
}
file='path/to/b.css';
relateFile='path/to/a.css';
b good a
*/
file = path.resolve(path.dirname(relateFile) + sep + file + ext);
if (e && configs.scopedCssMap[file]) {
let sname = e.globalCssNamesMap[name];
if (!sname) {
sname = `[ref="not found ${name} from @{${file}}"]`;
}
cssChecker.storeStyleUsed(relateFile, file, {
selectors: {
[name]: sname
}
});
return sname;
}
let p = name.replace(configs.selectorKeepNameReg, '$1');
let t = name.replace(p, '');
let id = genCssSelector(p, genCssNamesKey(file)) + t;
cssChecker.storeStyleUsed(relateFile, file, {
selectors: {
[name]: id
}
});
return id;
}
};
let refProcessor = (relateFile, file, ext, name, e) => {
return `:global(.${refNameProcessor(relateFile, file, ext, name, e)})`;
};
let processVar = key => {
let isGlobal = false;
if (key.startsWith(`--${cssIdGlobalPrefix}`)) {
if (!configs.debug) {
key = '--' + md5(key, 'md5CssVarsResult', configs.projectName + '-');
}
isGlobal = true;
}
return {
isGlobal,
key
};
};
let varRefProcessor = (relateFile, file, ext, name, e) => {
if (file == 'scoped' && ext == '.style') {
if (e) {
let sname = e.globalCssVarsMap[name];
if (!sname) {
sname = `"not found ${name} from @{${file}${ext}}"`;
cssChecker.storeStyleUsed(relateFile, '/' + file + ext, {
vars: {
[name]: sname
}
});
} else {
let f = e.globalCssDeclaredFiles.selectors[name] || '~~var error~~';
cssChecker.storeStyleUsed(relateFile, f, {
vars: {
[name]: sname
}
});
}
return sname;
} else {
throw new Error('[MXC Error(css-transform)] unsupport use scoped.style in ' + relateFile);
}
} else {
file = path.resolve(path.dirname(relateFile) + sep + file + ext);
let { isGlobal, key } = processVar(name);
if (isGlobal) {
cssChecker.storeStyleUsed(relateFile, file, {
vars: {
[name]: sname
}
});
return key;
}
if (e && configs.scopedCssMap[file]) {
let sname = e.globalCssVarsMap[name];
if (!sname) {
sname = `"not found ${name} from @{${file}}"`;
}
cssChecker.storeStyleUsed(relateFile, file, {
vars: {
[name]: sname
}
});
return sname;
}
let id = genCssSelector(name, genCssNamesKey(file));
id = '--' + id;
cssChecker.storeStyleUsed(relateFile, file, {
vars: {
[name]: id
}
});
return id;
}
};
let cssContentProcessor = (css, ctx) => {
/*
ctx:{
shortfile,
file,
namesKey,
namesMap,
varsMap,
duplicateNames
}
*/
let pInfo = cssParser(css, ctx.shortFile, ctx.refAtRules);
if (pInfo.nests.length) { //标记过于复杂的样式规则
cssChecker.storeStyleComplex(ctx.file, pInfo.nests);
}
let tokens = pInfo.tokens;
let modifiers = [];
let tagsOrAttrs = Object.create(null),
selectors = Object.create(null),
vars = Object.create(null);
for (let token of tokens) {
let id = token.name;
if (token.type == 'tag') {
if (!ignoreTags[id]) { //标签或属性选择器
tagsOrAttrs[id] = 1;
}
} else if (token.type == 'attr') {
//[mx-view^="@./path/to/view"]
let value = token.value;
if (token.name == 'mx-view' &&
value &&
(value.startsWith('@') ||
value.startsWith('.'))) {
if (value.startsWith('.')) {
value = '@' + value;
}
let cssId = utils.extractModuleId(ctx.file);
let mId = atpath.resolvePath(value, cssId);
let newAttr = `[mx-view${token.ctrl || ''}=${token.quote}${mId}${token.quote}${token.ignoreCase ? ' i' : ''}]`;
modifiers.push({
start: token.start,
end: token.end,
content: newAttr
});
}
if (token.first) {
id = '[' + id + ']';
}
if (!ignoreTags[id]) { //标签或属性选择器
tagsOrAttrs[id] = 1;
}
} else if (token.type == 'class') {
let result = id;
let p, i;
p = id.replace(configs.selectorKeepNameReg, '$1');
let t = id.replace(p, '');
i = genCssSelector(p, ctx.namesKey, ctx.globalReservedMap);
if (t) {
result = i + t;
ctx.namesMap[p] = i;
selectors[p] = i;
} else {
result = i;
}
ctx.namesMap[id] = result;
modifiers.push({
start: token.start,
end: token.end,
content: result
});
selectors[id] = result;
} else if (token.type == 'global') {
modifiers.push({
start: token.start,
end: token.end,
content: token.content
});
}
}
let vs = pInfo.vars;
for (let v of vs) {
let { isGlobal, key } = processVar(v.name);
if (isGlobal) {
i = key;
} else {
i = '--' + genCssSelector(v.name, ctx.namesKey, null, 'md5CssVarsResult');
}
ctx.varsMap[v.name] = i;
vars[v.name] = i;
modifiers.push({
start: v.start,
end: v.end,
content: i
});
}
modifiers.sort((a, b) => a.start - b.start);
for (let i = modifiers.length; i--;) {
let m = modifiers[i];
css = css.substring(0, m.start) + m.content + css.substring(m.end);
}
css = cssAtRuleProcessor(css, ctx.namesKey, ctx.file);
return {
content: css,
tagsOrAttrs,
selectors,
vars
};
};
module.exports = {
refProcessor,
cssContentProcessor,
genCssNamesKey,
genCssSelector,
varRefProcessor,
processVar
}