|
| 1 | +// for mp |
| 2 | +const compiler = require('mpvue-template-compiler') |
| 3 | +const htmlBeautify = require('js-beautify').html |
| 4 | + |
| 5 | +const babel = require('babel-core') |
| 6 | +const { parseConfig, parseComponentsDeps } = require('./parse') |
| 7 | +const { genScript, genStyle, genPageWxml } = require('./templates') |
| 8 | + |
| 9 | +const { cacheFileInfo, getFileInfo, getCompNameBySrc, resolveTarget, covertCCVar, cacheSlots, getSlots } = require('./util') |
| 10 | + |
| 11 | +const htmlBeautifyOptions = { |
| 12 | + wrap_line_length: '80', |
| 13 | + indent_size: 2, |
| 14 | + preserve_newlines: true, |
| 15 | + max_preserve_newlines: 0, |
| 16 | + e4x: true, |
| 17 | + unformatted: ['a', 'span', 'img', 'code', 'pre', 'sub', 'sup', 'em', 'strong', 'b', 'i', 'u', 'strike', 'big', 'small', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'] |
| 18 | +} |
| 19 | + |
| 20 | +function createSlotsWxml (emitFile, slots) { |
| 21 | + cacheSlots(slots) |
| 22 | + const allSlots = getSlots() |
| 23 | + const content = Object.keys(allSlots).map(v => allSlots[v].code).join('\n') |
| 24 | + if (content.trim()) { |
| 25 | + emitFile('components/slots.wxml', htmlBeautify(content, htmlBeautifyOptions)) |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +// 调用 compiler 生成 wxml |
| 30 | +function genComponentWxml (compiled, options, emitFile, emitError, emitWarning) { |
| 31 | + options.components['slots'] = { src: 'slots', name: 'slots' } |
| 32 | + const { code: wxmlCodeStr, compiled: cp, slots } = compiler.compileToWxml(compiled, options) |
| 33 | + const { mpErrors, mpTips } = cp |
| 34 | + |
| 35 | + // 缓存 slots,延迟编译 |
| 36 | + createSlotsWxml(emitFile, slots) |
| 37 | + |
| 38 | + if (mpErrors && mpErrors.length) { |
| 39 | + emitError( |
| 40 | + `\n Error compiling template:\n` + |
| 41 | + mpErrors.map(e => ` - ${e}`).join('\n') + '\n' |
| 42 | + ) |
| 43 | + } |
| 44 | + if (mpTips && mpTips.length) { |
| 45 | + emitWarning( |
| 46 | + mpTips.map(e => ` - ${e}`).join('\n') + '\n' |
| 47 | + ) |
| 48 | + } |
| 49 | + return htmlBeautify(wxmlCodeStr, htmlBeautifyOptions) |
| 50 | +} |
| 51 | + |
| 52 | +function createWxml (emitWarning, emitError, emitFile, resourcePath, rootComponent, compiled, html) { |
| 53 | + const { pageType, moduleId, components, src } = getFileInfo(resourcePath) || {} |
| 54 | + |
| 55 | + // 这儿一个黑魔法,和 webpack 约定的规范写法有点偏差! |
| 56 | + if (!pageType || (components && !components.isCompleted)) { |
| 57 | + return setTimeout(createWxml, 20, ...arguments) |
| 58 | + } |
| 59 | + |
| 60 | + let wxmlContent = '' |
| 61 | + let wxmlSrc = '' |
| 62 | + |
| 63 | + if (rootComponent) { |
| 64 | + const componentName = getCompNameBySrc(rootComponent) |
| 65 | + wxmlContent = genPageWxml(componentName) |
| 66 | + wxmlSrc = src |
| 67 | + } else { |
| 68 | + // TODO, 这儿传 options 进去 |
| 69 | + // { |
| 70 | + // components: { |
| 71 | + // 'com-a': { src: '../../components/comA$hash', name: 'comA$hash' } |
| 72 | + // }, |
| 73 | + // pageType: 'component', |
| 74 | + // name: 'comA$hash', |
| 75 | + // moduleId: 'moduleId' |
| 76 | + // } |
| 77 | + const name = getCompNameBySrc(resourcePath) |
| 78 | + const options = { components, pageType, name, moduleId } |
| 79 | + wxmlContent = genComponentWxml(compiled, options, emitFile, emitError, emitWarning) |
| 80 | + wxmlSrc = `components/${name}` |
| 81 | + } |
| 82 | + |
| 83 | + emitFile(`${wxmlSrc}.wxml`, wxmlContent) |
| 84 | +} |
| 85 | + |
| 86 | +// 编译出 wxml |
| 87 | +function compileWxml (compiled, html) { |
| 88 | + return createWxml(this.emitWarning, this.emitError, this.emitFile, this.resourcePath, null, compiled, html) |
| 89 | +} |
| 90 | + |
| 91 | +// 针对 .vue 单文件的脚本逻辑的处理 |
| 92 | +// 处理出当前单文件组件的子组件依赖 |
| 93 | +function compileMPScript (script, optioins, moduleId) { |
| 94 | + const { metadata } = babel.transform(script.content, { plugins: [parseComponentsDeps] }) |
| 95 | + |
| 96 | + // metadata: importsMap, components |
| 97 | + const { importsMap, components: originComponents } = metadata |
| 98 | + |
| 99 | + // 处理子组件的信息 |
| 100 | + const components = {} |
| 101 | + if (originComponents) { |
| 102 | + const allP = Object.keys(originComponents).map(k => { |
| 103 | + return new Promise((resolve, reject) => { |
| 104 | + this.resolve(this.context, originComponents[k], (err, realSrc) => { |
| 105 | + if (err) return reject(err) |
| 106 | + const com = covertCCVar(k) |
| 107 | + const comName = getCompNameBySrc(realSrc) |
| 108 | + components[com] = { src: comName, name: comName } |
| 109 | + resolve() |
| 110 | + }) |
| 111 | + }) |
| 112 | + }) |
| 113 | + Promise.all(allP) |
| 114 | + .then(res => { |
| 115 | + components.isCompleted = true |
| 116 | + }) |
| 117 | + .catch(err => { |
| 118 | + console.error(err) |
| 119 | + components.isCompleted = true |
| 120 | + }) |
| 121 | + } else { |
| 122 | + components.isCompleted = true |
| 123 | + } |
| 124 | + |
| 125 | + const fileInfo = resolveTarget(this.resourcePath, optioins.mpInfo) |
| 126 | + cacheFileInfo(this.resourcePath, fileInfo, { importsMap, components, moduleId }) |
| 127 | + |
| 128 | + return script |
| 129 | +} |
| 130 | + |
| 131 | +// checkMPEntry 针对 entry main.js 的入口处理 |
| 132 | +// 编译出 app, page 的入口js/wxml/json |
| 133 | + |
| 134 | +const startPageReg = /^\^/ |
| 135 | + |
| 136 | +function compileMP (content, optioins) { |
| 137 | + const { resourcePath, emitError, emitFile, emitWarning, resolve, context } = this |
| 138 | + const { metadata } = babel.transform(content, { plugins: [parseConfig] }) |
| 139 | + |
| 140 | + // metadata: config |
| 141 | + const { config, rootComponent } = metadata |
| 142 | + |
| 143 | + const fileInfo = resolveTarget(resourcePath, optioins.mpInfo) |
| 144 | + cacheFileInfo(resourcePath, fileInfo) |
| 145 | + const { src, name, isApp, isPage } = fileInfo |
| 146 | + |
| 147 | + if (isApp || isPage) { |
| 148 | + // 生成入口 json |
| 149 | + if (config) { |
| 150 | + const configObj = config.value |
| 151 | + |
| 152 | + // 只有 app 才处理 pages |
| 153 | + if (isApp) { |
| 154 | + const pages = (configObj.pages || []).concat(optioins.pages) |
| 155 | + |
| 156 | + // ^ 开头的放在第一个 |
| 157 | + const startPageIndex = pages.findIndex(v => startPageReg.test(v)) |
| 158 | + if (startPageIndex !== -1) { |
| 159 | + const startPage = pages[startPageIndex].slice(1) |
| 160 | + pages.splice(startPageIndex, 1) |
| 161 | + pages.unshift(startPage) |
| 162 | + } |
| 163 | + configObj.pages = [...new Set(pages)] |
| 164 | + } |
| 165 | + emitFile(`${src}.json`, JSON.stringify(configObj, null, ' ')) |
| 166 | + } |
| 167 | + |
| 168 | + // 生成入口 js |
| 169 | + emitFile(`${src}.js`, genScript(name, isPage)) |
| 170 | + |
| 171 | + // 生成入口 wxss |
| 172 | + emitFile(`${src}.wxss`, genStyle(name, isPage)) |
| 173 | + |
| 174 | + // 这儿应该异步在所有的模块都清晰后再生成 |
| 175 | + // 生成入口 wxml |
| 176 | + if (isPage && rootComponent) { |
| 177 | + resolve(context, rootComponent, (err, rootComponentSrc) => { |
| 178 | + if (err) return |
| 179 | + // 这儿需要搞定 根组件的 路径 |
| 180 | + createWxml(emitWarning, emitError, emitFile, resourcePath, rootComponentSrc) |
| 181 | + }) |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + return content |
| 186 | +} |
| 187 | + |
| 188 | +module.exports = { compileWxml, compileMPScript, compileMP } |
0 commit comments