UNPKG

magix-composer

Version:

compile html, style and javascript files into javascript

450 lines (446 loc) 16.8 kB
/* js内容处理 mx单文件转换->开始编译钩子(beforeProcessor,es6->es3)->js中的@规则识别及代码检查->处理样式->处理模板->处理js代码片断->编译结束钩子->缓存文件内容 */ let util = require('util'); let chalk = require('chalk'); let fd = require('./util-fd'); let jsMx = require('./js-mx'); let jsDeps = require('./js-deps'); let cssProcessor = require('./css'); let tmplProcessor = require('./tmpl'); let atpath = require('./util-atpath'); let jsWrapper = require('./js-wrapper'); let configs = require('./util-config'); let md5 = require('./util-md5'); let utils = require('./util'); let slog = require('./util-log'); let fileCache = require('./js-fcache'); let jsSnippet = require('./js-snippet'); let jsReplacer = require('./js-replacer'); let jsHeader = require('./js-header'); let acorn = require('./js-acorn'); let { galleryProcessed, revisableGReg, atViewPrefix } = require('./util-const'); let deps = require('./util-deps'); //let httpProtocolReg = /^['"`]https?:/i; let lineBreakReg = /\r\n?|\n|\u2028|\u2029/; let mxTailReg = /\.m?mx$/; let stringReg = /^['"]/; //文件内容处理,主要是把各个处理模块串起来 let moduleIdReg = /@(mx:)?(?:moduleId|id)/; let cssFileReg = /@(?:mx:)?(?:[\w\.\-\/\\]+?)\.(?:css|less|mx|style)/; let cssFileGlobalReg = new RegExp(cssFileReg, 'g'); let jsFileReg = /([a-z,&]+)?@([\w\.\-\/\\]+\.(?:[jt]s))/; let doubleAtReg = /@@/g; let isGalleryConfig = file => { let cfg = configs.galleriesDynamicRequires[file]; if (cfg) { delete cfg[galleryProcessed]; let files = deps.getConfigDependents(file); for (let p in files) { fileCache.clear(p); } return true; } return false; }; let isExcludeFile = file => { let ex = false; for (let r of configs.excludesReg) { if (r.test(file)) { ex = true; break; } } return ex; }; /* '#snippet'; '#exclude(define,beforeProcessor,after)'; */ let processContent = (from, to, content, inwatch) => { if (!content) content = fd.read(from); let contentInfo; if (mxTailReg.test(from)) { contentInfo = jsMx.process(content, from); content = contentInfo.script; } let headers = jsHeader(content); content = headers.content; let key = [inwatch, headers.addWrapper].join('\x00'); let fInfo = fileCache.get(from, key); if (fInfo) { return Promise.resolve(fInfo); } let before = Promise.resolve(content); let moduleId = utils.extractModuleId(from); let psychic = { fileDeps: {}, to, from, moduleId, debug: configs.debug, content, exclude: isExcludeFile(from), pkgName: moduleId.slice(0, moduleId.indexOf('/')), moduleFileName: moduleId.substring(moduleId.lastIndexOf('/') + 1), shortFrom: from.replace(configs.moduleIdRemovedPath, '').substring(1), addWrapper: headers.addWrapper, checker: configs.checker, loader: headers.loader || configs.loaderType, loaderFactory: configs.loaderFactory, isSnippet: headers.isSnippet, exRequires: headers.exRequires, noRequires: headers.noRequires, processContent }; if (isGalleryConfig(from)) { if (configs.log && inwatch) { slog.ever('[MXC Tip(js-content)] reload:', chalk.blue(from)); } delete require.cache[from]; psychic.galleryConfigFile = true; psychic.isSnippet = true; return Promise.resolve(psychic); } if (psychic.exclude) { headers.ignoreAllProcessor = true; psychic.addWrapper = false; } psychic.exRequires.push(`"${moduleId}"`); //let originalContent = content; if (headers.execBeforeProcessor) { let result = configs.compileJSStart(content, psychic); if (util.isString(result)) { before = Promise.resolve(result); } else if (result && util.isFunction(result.then)) { before = result; } } if (configs.log && inwatch) { slog.ever('[MXC Tip(js-content)] compile:', chalk.blue(from)); } return before.then(content => { if (util.isString(content)) { psychic.content = content; } return jsDeps.process(psychic); }).then(e => { //console.log(e.content); let newRequires = []; if (!e.noRequires) { for (let req of e.requires) { req = req.slice(1, -1); let idx = req.indexOf('/'); let mName = idx === -1 ? null : req.substring(0, idx); let p, full; if (mName === e.pkgName) { p = atpath.resolvePath(`"@${req}"`, e.moduleId); } else { p = `"${req}"`; } full = atpath.resolvePath('"@' + p.slice(1, -1) + '"', e.moduleId); if (e.exRequires.indexOf(p) == -1 && e.exRequires.indexOf(full) == -1 && newRequires.indexOf(p) == -1 && newRequires.indexOf(full) == -1) { newRequires.push(`"${req}"`); } } } e.requires.length = 0; e.requires.push(...newRequires); return Promise.resolve(e); }).then(e => { if (headers.ignoreAllProcessor) { return Promise.resolve(e); } let tmpl = e.addWrapper ? jsWrapper(e) : e.content; let ast; let comments = {}; try { ast = acorn.parse(tmpl, comments, e.from); } catch (ex) { let msg = [chalk.red(`[MXC Error(js-content)]`), 'Parse js ast error:', chalk.red(ex.message)]; let arr = tmpl.split(lineBreakReg); let line = ex.loc.line - 1; if (arr[line]) { msg.push('near code:', chalk.green(arr[line])); } msg.push(chalk.red('js file: ' + e.from)); slog.ever.apply(slog, msg); return Promise.reject(ex); } let modifiers = []; let toTops = []; let toBottoms = []; let processString = (node, tl) => { //存储字符串,减少分析干扰 if (!tl) { if (!stringReg.test(node.raw)) return; } let add = false; let raw = node.raw; if (!configs.debug) { node.raw = raw.replace(revisableGReg, m => { add = true; return md5(m, 'revisableString', configs.revisableStringPrefix); }); raw = node.raw; } if (moduleIdReg.test(raw)) { let m = raw.match(moduleIdReg); let c = raw[0] + m[0] + raw[0]; if (tl || c == raw) { raw = tl ? e.moduleId : raw[0] + e.moduleId + raw[0]; node.raw = raw; add = true; } } else if (configs.fileReplacerPrefixesReg.test(raw)) { let m = raw.match(configs.fileReplacerPrefixesReg); let c = raw[0] + m[0] + raw[0]; if (tl || c == raw) { node.raw = raw.replace('@', '\x12@'); add = true; } } else if (cssFileReg.test(raw)) { node.raw = raw.replace(cssFileGlobalReg, (m, offset) => { let c = raw.charAt(offset - 1); if (c == '@') return m.substring(1); return m.replace('@', '\x12@'); }).replace(doubleAtReg, '@'); add = true; } else if (configs.htmlFileReg.test(raw)) { let m = raw.match(configs.htmlFileReg); let c = raw[0] + m[0] + raw[0]; if (tl || c == raw) { node.raw = raw.replace(configs.htmlFileGlobalReg, m => { return m.replace('@', `\x12@`); }); add = true; } } else if (jsFileReg.test(raw)) { let m = raw.match(jsFileReg); let c = raw[0] + m[0] + raw[0]; if (tl || c == raw) { let replacement = ''; raw.replace(jsFileReg, (m, actions, file) => { if (actions) { actions = actions.split(','); let toTop = false, toBottom = false; for (let a of actions) { if (a == 'top') { if (!toBottom) { toTop = true; } } else if (a == 'bottom') { if (!toTop) { toBottom = true; } } } replacement = JSON.stringify('@' + file).replace('@', '\x12@'); if (toTop) { toTops.push(replacement); replacement = ''; if (tl) { node.start--; node.end++; } } else if (toBottom) { toBottoms.push(replacement); replacement = ''; if (tl) { node.start--; node.end++; } } } else { replacement = raw.replace(/@/g, '\x12@'); } }); node.raw = replacement; add = true; } } else { // //字符串以@开头,且包含/ if (!tl) { raw = raw.slice(1, -1); } let prefix = raw.substring(0, atViewPrefix.length); let rest = raw.substring(atViewPrefix.length); if (prefix == atViewPrefix) { if (rest.indexOf('./') > 0) { //console.log(rest); raw = atpath.resolvePath('"@' + rest + '"', e.moduleId); if (tl) { raw = raw.slice(1, -1); } //console.log(raw); node.raw = raw; add = true; } else if (rest[0] == '~') { let newRest = configs.resolveVirtual(rest); if (newRest && newRest != rest) { let dest = JSON.stringify(newRest); //console.log(dest, tl); if (tl) { dest = dest.slice(1, -1); } node.raw = dest; add = true; } } } } if (add) { modifiers.push({ start: node.start, end: node.end, content: node.raw }); } }; acorn.walk(ast, { Property(node) { if (node.key.type == 'Literal') { processString(node.key); } }, Literal: processString, TemplateLiteral(node) { for (let q of node.quasis) { q.raw = q.value.raw; processString(q, true); } } }); modifiers.sort((a, b) => { //根据start大小排序,这样修改后的fn才是正确的 return a.start - b.start; }); for (let i = modifiers.length - 1, m; i >= 0; i--) { m = modifiers[i]; tmpl = tmpl.substring(0, m.start) + m.content + tmpl.substring(m.end); } if (toTops.length) { tmpl = toTops.join(';\r\n') + '\r\n' + tmpl; } if (toBottoms.length) { tmpl = tmpl + '\r\n' + toBottoms.join(';\r\n'); } e.content = tmpl; return Promise.resolve(e); }).then(e => { return jsReplacer(e); }).then(e => { if (headers.ignoreAllProcessor) { return Promise.resolve(e); } return jsSnippet(e); }).then(e => { if (headers.ignoreAllProcessor) { return Promise.resolve(e); } if (contentInfo) e.contentInfo = contentInfo; return cssProcessor(e); }).then(e => { if (headers.ignoreAllProcessor) { return Promise.resolve(e); } return tmplProcessor(e); }).then(e => { if (e.addedWrapper) { let mxViews = e.tmplMxViewsArray || []; let addDeps = configs.tmplAddViewsToDependencies; if (e.noRequires || !addDeps) mxViews = []; mxViews = mxViews.concat(e.tmplComponents || []); //console.log(mxViews); let reqs = [], vars = []; for (let v of mxViews) { let i = v.indexOf('/'); let mName = i === -1 ? v : v.substring(0, i); let p, full; if (mName === e.pkgName) { p = atpath.resolvePath('"@' + v + '"', e.moduleId); full = v[0] == '@' ? p : atpath.resolvePath('"@' + p.slice(1, -1) + '"', e.moduleId); } else { full = v; p = `"${v}"`; } if (e.deps.indexOf(p) === -1 && e.deps.indexOf(full) === -1 && e.exRequires.indexOf(p) === -1 && e.exRequires.indexOf(full) == -1) { let prefix = '', type = ''; if (e.loader == 'module') { prefix = 'import '; type = 'import'; } else { type = 'require'; } let reqInfo = { prefix, type, tail: ';', vId: '', mId: p.slice(1, -1), full, from: 'view', raw: 'mx-view="' + v + '"' }; let replacement = jsDeps.getReqReplacement(reqInfo, e, true); vars.push(replacement); if (reqInfo.mId) { let dId = JSON.stringify(reqInfo.mId); reqs.push(dId); } } } reqs = reqs.join(','); if (e.requires.length && reqs) { reqs = ',' + reqs; } if (e.quickStaticVars) { for (let v of e.quickStaticVars) { let c = `let ${v.key}`; if (v.value) { c += `=${v.value}`; } c += ';'; vars.push(c); } } e.content = e.content.replace(e.requiresAnchorKey, reqs); e.content = e.content.replace(e.varsAnchorKey, vars.join('\r\n')); } return e; }).then(e => { let after = Promise.resolve(e); if (headers.execAfterProcessor) { let processor = configs.compileAfterProcessor || configs.compileJSEnd; let result = processor(e.content, e); if (util.isString(result)) { e.content = result; } else if (result && util.isFunction(result.then)) { after = result.then(temp => { if (util.isString(temp)) { e.content = temp; temp = e; } return Promise.resolve(temp); }); } } return after; }).then(e => { fileCache.add(e.from, key, e); return e; }); }; module.exports = { process: processContent };