UNPKG

magix-composer

Version:

compile html, style and javascript files into javascript

409 lines (406 loc) 20.3 kB
/* 样式处理入口 读取js代码中的样式占位规则,把占位规则处理成真实的内容 */ let cssnano = require('cssnano'); let path = require('path'); let configs = require('./util-config'); let atpath = require('./util-atpath'); let cssFileRead = require('./css-read'); let deps = require('./util-deps'); let cssChecker = require('./checker-css'); let cssGlobal = require('./css-global'); let cssComment = require('./css-comment'); let utils = require('./util'); let cloneAssign = utils.cloneAssign; let cssTransform = require('./css-transform'); let { styleInJSFileReg, cssVarRefReg, cssRefReg } = require('./util-const'); //处理css文件 //另外一个思路是:解析出js中的字符串,然后在字符串中做替换就会更保险,目前先不这样做。 //https://github.com/Automattic/xgettext-js //处理js文件中如 'global@x.less' '@x.less:selector' 'ref@../x.scss' 等各种情况 //"abc(@style.css:xx)yyzz" //[ref="@../default.css:inmain"] .open{ // color:red //} let cssVarReg = /var\s*\(\s*([^)\s]+)\s*(?=[,)])/g; let sep = path.sep; module.exports = e => { let globalNamesMap = Object.create(null); let globalVarsMap = Object.create(null); let cssContentCache = Object.create(null); configs.scopedCss.forEach(sc => { deps.addFileDepend(sc, e.from, e.to); cssChecker.hostAddStyle(e.from, sc); }); return cssGlobal.process({ context: e }).then(gInfo => { return new Promise((resolve, reject) => { cloneAssign(globalNamesMap, gInfo.namesMap); cloneAssign(globalVarsMap, gInfo.varsMap); e.cssNamesMap = globalNamesMap; e.cssVarsMap = globalVarsMap; styleInJSFileReg.lastIndex = 0; if (styleInJSFileReg.test(e.content)) { //有需要处理的@规则 styleInJSFileReg.lastIndex = 0; let count = 0; let tempMatchToFile = Object.create(null); let folder = path.dirname(e.from); let storeHostUsed = (scoped, file, selectors, vars, varsIsGlobal) => { if (scoped) { if (selectors) { let dest = gInfo.declaredFiles.selectors[selectors]; if (dest) { cssChecker.storeHostUsed(e.from, dest, { selectors: { [selectors]: 1 } }); } else { cssChecker.storeUnexist(e.from, 'selectors ' + selectors + ' from scoped.style'); } } if (vars) { if (varsIsGlobal) { cssChecker.storeStyleGlobalVars(e.from, vars); } else { let dest = gInfo.declaredFiles.vars[s]; if (dest) { cssChecker.storeHostUsed(e.from, dest, { vars: { [s]: 1 } }); } else { cssChecker.storeUnexist(e.from, 'vars ' + s + ' from scoped.style'); } } } } else { let temp = {}; if (selectors) { temp.selectors = { [selectors]: 1 }; } if (vars) { temp.vars = { [vars]: 1 }; } cssChecker.storeHostUsed(e.from, file, temp); } }; let processVars = (c, f, lf) => { return c.replace(cssVarReg, (m, key) => { let r = globalVarsMap[key]; if (!r) { if (cssVarRefReg.test(key)) { while (cssVarRefReg.test(m)) { m = m.replace(cssVarRefReg, (_1, _2, fn, ext, key) => { return cssTransform.varRefProcessor(lf, fn, ext, key, { globalCssVarsMap: gInfo.varsMap }); }); } return m; } else { let { isGlobal, key: k2 } = cssTransform.processVar(key); //console.log(lf,key,k2,m); if (isGlobal) { r = k2; cssChecker.storeStyleGlobalVars(lf, key); } else { //cssChecker.storeStyleGlobalVars(lf, key); return m; } } } return `var(${r}`; }); }; let resume = () => { e.content = e.content.replace(styleInJSFileReg, (m, left, q, prefix, name, ext, key, right, tail) => { if (!prefix && !key && (left != '(' || right != ')')) { m = m.replace('\x12@', '@'); return m; } let info = tempMatchToFile[m]; let { file, scopedStyle, shortCssFile } = info; let fileName = path.basename(file); let r = cssContentCache[file]; //从缓存中获取当前文件的信息 //如果不存在就返回一个不存在的提示 if (!r.exists) { m = name + ext; cssChecker.storeUnexist(e.from, m); if (key) { return (q || '') + `unfound file:${name}${ext}` + (q || '') + (tail || ''); } return [(left || '') + '\'$throw_' + name + ext + '\'', q + 'unfound style file:' + name + ext + q + (right || '')]; } let fileContent = r.css; let cssNamesKey = cssTransform.genCssNamesKey(file); let cssNamesMap, cssVarsMap, newContent, addToGlobalCSS; if (scopedStyle) { cssNamesMap = globalNamesMap; cssVarsMap = globalVarsMap; } else { addToGlobalCSS = key ? false : true; //有后缀时也不添加到全局 if (!r.namesMap) { cssNamesMap = Object.create(null); cssVarsMap = Object.create(null); fileContent = fileContent.replace(cssRefReg, (m, q, f, ext, selector) => { let s = cssTransform.refProcessor(file, f, ext, selector, { globalCssNamesMap: globalNamesMap, globalCssDeclaredFiles: gInfo.declaredFiles }); return s; }); try { newContent = cssTransform.cssContentProcessor(fileContent, { shortFile: shortCssFile, file, namesKey: cssNamesKey, namesMap: cssNamesMap, varsMap: cssVarsMap }); } catch (ex) { reject(ex); } cssChecker.storeStyleDeclared(file, { vars: newContent.vars, selectors: newContent.selectors, tagsOrAttrs: newContent.tagsOrAttrs }); fileContent = newContent.content; r.namesMap = cssNamesMap; r.varsMap = cssVarsMap; r.fileContent = fileContent; if (addToGlobalCSS) { cloneAssign(globalNamesMap, cssNamesMap); cloneAssign(globalVarsMap, cssVarsMap); } } else { cssNamesMap = r.namesMap; cssVarsMap = r.varsMap; fileContent = r.fileContent; if (addToGlobalCSS) { cloneAssign(globalNamesMap, cssNamesMap); cloneAssign(globalVarsMap, cssVarsMap); } } } let replacement; if (prefix == 'ref') { //如果是引用css则什么都不用做 replacement = ''; tail = ''; } else if (key) { //仅读取文件中的某个名称 let c; if (key.startsWith('--')) { let { isGlobal, key: k2 } = cssTransform.processVar(key); if (isGlobal) { c = k2; } else { c = cssVarsMap[key]; if (!c) { if (configs.selectorSilentErrorCss) { c = key; } else { c = 'unfound-[' + key + ']-from-' + fileName; } } } storeHostUsed(scopedStyle, file, null, key, isGlobal); } else { c = cssNamesMap[key]; if (!c) { if (configs.selectorSilentErrorCss) { c = key; } else { c = 'unfound-[' + key + ']-from-' + fileName; } } storeHostUsed(scopedStyle, file, key); } replacement = q + c + q; } else { //输出整个css文件内容 let uniqueKey = ''; if (prefix != 'compiled') { uniqueKey = JSON.stringify(cssNamesKey) + ','; } if (configs.debug) { if (r.map) { fileContent += r.map; fileContent = processVars(fileContent, shortCssFile, file); let c = JSON.stringify(fileContent); c = configs.applyStyleProcessor(c, shortCssFile, cssNamesKey, e); replacement = uniqueKey + c; } else if (r.styles) { replacement = '['; for (let s of r.styles) { s.css = processVars(s.css, s.short, s.file); let c = JSON.stringify(s.css + (s.map || '')); c = configs.applyStyleProcessor(c, s.short, s.key, e); replacement += JSON.stringify(s.key) + ',' + c + ','; } replacement = replacement.slice(0, -1); replacement += ']'; } else { fileContent = processVars(fileContent, shortCssFile, file); let c = JSON.stringify(fileContent); c = configs.applyStyleProcessor(c, shortCssFile, cssNamesKey, e); replacement = uniqueKey + c; } } else { fileContent = processVars(fileContent, shortCssFile, file); let c = JSON.stringify(fileContent); c = configs.applyStyleProcessor(c, shortCssFile, cssNamesKey, e); replacement = uniqueKey + c; } } tail = tail ? tail : ''; return (left || '') + replacement + (right || '') + tail; }); resolve(e); }; let check = () => { count--; if (!count && !check.$resume) { //依赖的文件全部读取完毕 check.$resume = true; resume(); } }; let setFileCSS = (file, shortCssFile, css) => { let p = configs.cssContentProcessor(css, shortCssFile, e); if (!p.then) { p = Promise.resolve(p); } p.then(css => { cssContentCache[file].css = css; check(); }, error => { if (e.contentInfo) { file += '@' + e.contentInfo.fileName; } reject(error); check(); }); }; let processFile = (match, name, ext, file) => { count++; //记录当前文件个数,因为文件读取是异步,我们等到当前模块依赖的css都读取完毕后才可以继续处理 let scopedStyle = false; let refInnerStyle = e.contentInfo && name == 'style'; let shortCssFile; if (name == 'scoped' && ext == '.style') { file = name + ext; scopedStyle = true; shortCssFile = file; } else { name = atpath.resolveName(name, e.moduleId); //先处理名称 if (refInnerStyle) { file = e.from; } else { e.fileDeps[file] = 1; } shortCssFile = file.replace(configs.moduleIdRemovedPath, '').substring(1); } tempMatchToFile[match] = { scopedStyle, file, shortCssFile }; if (!cssContentCache[file]) { //文件尚未读取 cssContentCache[file] = 1; let promise; if (scopedStyle) { promise = Promise.resolve({ exists: true, content: gInfo.style, styles: gInfo.styles }); } else { promise = cssFileRead(file, e, match, ext, refInnerStyle); } promise.then(info => { //写入缓存,因为同一个view.js中可能对同一个css文件多次引用 cssContentCache[file] = { exists: info.exists, css: '' }; if (info.exists && info.content) { cssContentCache[file].map = info.map; cssContentCache[file].styles = info.styles; if (!configs.debug) { cssnano.process(info.content, Object.assign({}, configs.cssnano) ).then(r => { setFileCSS(file, shortCssFile, r.css); }, error => { if (e.contentInfo) { file += '@' + e.contentInfo.fileName; } reject(error); check(); }); } else { let cssStr = info.content; cssStr = cssComment.clean(cssStr); setFileCSS(file, shortCssFile, cssStr); } } else { check(); } }).catch(reject); } else { check(); } }; let tasks = []; let doTask = () => { if (tasks.length) { let i = 0; while (i < tasks.length) { processFile.apply(null, tasks[i++]); } } else { resume(); } }; e.content.replace(styleInJSFileReg, (m, left, q, prefix, name, ext, key, right) => { if ((key || prefix) || (left == '(' && right == ')')) { //console.log(name); name = atpath.resolveName(name, e.moduleId); let file = path.resolve(folder + sep + name + ext); if (configs.scopedCssMap[file]) { name = 'scoped'; ext = '.style'; } if (name != 'scoped' || ext != '.style') { cssChecker.hostAddStyle(e.from, file); deps.addFileDepend(file, e.from, e.to); } tasks.push([m, name, ext, file]); } }); doTask(); } else { resolve(e); } }); }); };