magix-combine
Version:
合并Magix View的html,js,css成一个js文件,并检测html,js,css中可能存在的问题
272 lines (270 loc) • 12 kB
JavaScript
/*
全局样式的处理
1. 不推荐的global样式
2. 全局scoped样式
https://github.com/thx/magix-combine/issues/24
*/
let path = require('path');
let configs = require('./util-config');
let checker = require('./checker');
let cssFileRead = require('./css-read');
let cssAtRule = require('./css-atrule');
let cssParser = require('./css-parser');
let cssComment = require('./css-comment');
let {
cssRefReg,
refProcessor,
genCssNamesKey,
cssNameNewProcessor,
cssNameGlobalProcessor,
genCssSelector
} = require('./css-selector');
let globalCssNamesMap = Object.create(null);
let globalCssNamesInFiles = Object.create(null);
let globalCssTagsInFiles = Object.create(null);
let scopedStyle = '';
let scopedStyles = [];
let globalStyle = '';
let globalStyles = [];
let globalReservedMap = Object.create(null);
let globalPromise;
let processGlobal = ctx => { //处理全局样式,因全局样式过于自由,不建议使用
globalCssNamesMap = Object.create(null); //样式名映射,原始到压缩的映射
globalCssNamesInFiles = Object.create(null); //样式名到文件的映射
globalCssTagsInFiles = Object.create(null); //标签名到样式的映射
globalStyle = '';
globalStyles = [];
let globalGuid = Date.now(); //guid
/*
guid 2种情况
1. 全局样式在多个文件中出现重名时,合并这些全局样式
2. 局部样式与全局重名时,只使用局部样式
该guid只有全局的情况下才会传递,因此通过该guid识别如何处理样式规则
*/
return new Promise((resolve, reject) => {
let list = configs.globalCss; //全局配置
if (!list || !list.length) { //没有配置
resolve(ctx);
} else {
let add = info => {
let cssNamesMap = Object.create(null);
let fileTags = Object.create(null);
if (info.exists && info.content) {
let cmtStore = Object.create(null);
let currentFile = info.file;
let shortFile = currentFile.replace(configs.moduleIdRemovedPath, '').substring(1);
let cssNamesKey = genCssNamesKey(configs.debug ? currentFile : 'scoped.style');
let css = cssComment.store(info.content, cmtStore);//.replace(cssCommentReg, '');
try {
cssNameGlobalProcessor(css, {
shortFile, //短文件名
globalGuid: globalGuid,
namesMap: globalCssNamesMap,
namesToFiles: globalCssNamesInFiles,
cNamesMap: cssNamesMap, //单个文件中名称映射
file: currentFile,
fileTags: fileTags, //文件中声明了哪些标签样式
tagsToFiles: globalCssTagsInFiles //标签在哪些文件里
});
} catch (e) {
reject(e);
}
Object.assign(globalReservedMap, cssNamesMap);
let rules = cssAtRule.extractRules(css);
for (let r of rules) {
globalReservedMap[r] = 1;
}
css = cssComment.recover(css, cmtStore);
//添加到检测信息中,编译完成时统一检测
checker.CSS.fileToTags(currentFile, fileTags, ctx.inwatch);
checker.CSS.fileToSelectors(currentFile, cssNamesMap, ctx.inwatch);
globalStyle += css;
globalStyles.push({
css,
short: shortFile,
map: info.map,
key: cssNamesKey
});
}
};
let ps = [];
for (let i = 0, ext; i < list.length; i++) {
ext = path.extname(list[i]);
ps.push(cssFileRead(list[i], ctx.context, '', ext)); //读取
}
Promise.all(ps).then(rs => {
for (let i = 0; i < rs.length; i++) {
add(rs[i]);
}
for (let p in globalCssNamesInFiles) {
if (p.slice(-2, -1) == '!') continue;
let sameSelectors = globalCssNamesInFiles[p];
let values = Object.keys(sameSelectors);
if (values.length > 1) { //处理同一个样式名存在多个文件中,即重名的情况
globalCssNamesInFiles[p + '!r'] = values;
}
}
resolve(ctx);
}).catch(reject);
}
});
};
let processScope = ctx => {
scopedStyle = '';
scopedStyles = [];
//console.log('process scoped'.red);
return new Promise((resolve, reject) => { //处理scoped样式
let list = configs.scopedCss;
if (!list || !list.length) {
resolve(ctx);
} else {
let add = i => {
let cssNamesMap = Object.create(null);
let cssTagsMap = Object.create(null);
let currentFile = i.file;
let cssNamesKey = genCssNamesKey(configs.debug ? currentFile : 'scoped.style');
let shortFile = currentFile.replace(configs.moduleIdRemovedPath, '').substring(1);
if (i.exists && i.content) {
let cmtStore = Object.create(null);
let c = cssComment.store(i.content, cmtStore);//.replace(cssCommentReg, '');
c = c.replace(cssRefReg, (m, q, file, ext, selector) => {
return refProcessor(i.file, file, ext, selector, globalReservedMap);
});
try {
c = cssNameNewProcessor(c, {
shortFile,
namesMap: globalCssNamesMap,
namesToFiles: globalCssNamesInFiles,
namesKey: cssNamesKey,
cNamesMap: cssNamesMap,
globalReservedMap,
cNamesToFiles: globalCssNamesInFiles,
addToGlobalCSS: true,
file: currentFile,
fileTags: cssTagsMap,
tagsToFiles: globalCssTagsInFiles
});
} catch (e) {
reject(e);
}
//console.log(globalCssNamesInFiles);
//console.log(cssNamesMap);
c = cssAtRule(c, cssNamesKey, true, {
globalReservedMap
});
c = cssComment.recover(c, cmtStore);
checker.CSS.fileToSelectors(currentFile, cssNamesMap, ctx.inwatch);
checker.CSS.fileToTags(currentFile, cssTagsMap, ctx.inwatch);
scopedStyles.push({
css: c,
map: i.map,
short: shortFile,
key: cssNamesKey
});
//scopedStyle += c;
} else if (!i.exists) { //未找到
checker.CSS.markUnexists(currentFile, '/scoped.style');
//scopedStyle += ` .unfound[file="${currentFile}"]{}`;
scopedStyles.push({
css: `.unfound[file="${currentFile}"]{}`,
map: i.map,
short: shortFile,
key: cssNamesKey
});
}
};
let ps = [];
for (let i = 0, ext; i < list.length; i++) {
ext = path.extname(list[i]);
ps.push(cssFileRead(list[i], ctx.context, '', ext));
}
Promise.all(ps).then(rs => {
for (let i = 0; i < rs.length; i++) {
add(rs[i]);
}
//if (!configs.compressCss) {
let hasSameName = false;
let sToKeys = Object.create(null); //重名
let namesToFiles = globalCssNamesInFiles;
let namesMap = globalCssNamesMap;
for (let p in namesToFiles) {
if (p.slice(-2, -1) == '!') continue;
let sameSelectors = namesToFiles[p + '!s'];
let values = Object.values(sameSelectors); //处理重名的情况
if (values.length > 1) {
hasSameName = true;
namesToFiles[p + '!r'] = values;
let key = '';
let pf = p.replace(configs.selectorKeepNameReg, '$1');
let tl = p.replace(pf, '');
if (configs.debug) {
//非压缩时,采用这个重名在这几个文件中的路径做为key,如 mx-app-snippets-list-and-app-snippets-form
let keys = [],
k;
for (let i = 0; i < values.length; i++) {
k = genCssNamesKey(values[i], i);
keys.push(k);
}
key = genCssSelector(pf, keys.join('-and-'), globalReservedMap) + tl;
} else { //压缩
key = genCssSelector(pf, genCssNamesKey(values[0]), globalReservedMap) + tl;
}
namesMap[p] = key;
for (let z in sameSelectors) {
sToKeys[z] = namesMap[p]; //重名的特殊处理
}
}
}
for (let s of scopedStyles) {
let { tokens } = cssParser(s.css, s.short);
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
let id = token.name;
if (token.type == 'class') {
if (sToKeys[id]) { //修改样式,只处理重名的,因为要对重名的样式重新命名
s.css = s.css.substring(0, token.start) + sToKeys[id] + s.css.substring(token.end);
}
}
}
scopedStyle += s.css;
}
resolve(ctx);
}).catch(reject);
}
});
};
module.exports = {
process(info) {
if (!globalPromise) {
globalPromise = Promise.resolve(info);
globalPromise = globalPromise.then(processGlobal).then(processScope).then(() => {
//console.log('out',globalCssNamesMap,globalCssNamesInFiles);
return {
globalReservedMap,
globalStyle,
globalStyles,
globalCssNamesMap,
globalCssNamesInFiles,
globalCssTagsInFiles,
scopedStyle,
scopedStyles
};
});
}
return globalPromise;
},
addReserved(reserved) {
Object.assign(globalReservedMap, reserved);
},
reset(file) {
let { globalCssMap, scopedCssMap } = configs;
if (file && (globalCssMap && globalCssMap[file] ||
scopedCssMap && scopedCssMap[file])) {
globalPromise = null;
cssAtRule.reset();
}
},
clear() {
globalPromise = null;
}
};