Skip to content

Commit

Permalink
Merge pull request #24 from mpvue/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
aOrz authored May 19, 2018
2 parents f3387e0 + b996432 commit dcb95dd
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 155 deletions.
215 changes: 136 additions & 79 deletions lib/mp-compiler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ const compiler = require('mpvue-template-compiler')
const babel = require('babel-core')
const path = require('path')
const fs = require('fs')
const deepEqual = require('deep-equal')

const { parseConfig, parseComponentsDeps } = require('./parse')
const { parseConfig, parseComponentsDeps, parseGlobalComponents, clearGlobalComponents } = require('./parse')
const { parseComponentsDeps: parseComponentsDepsTs } = require('./parse-ts')
const { genScript, genStyle, genPageWxml } = require('./templates')

Expand All @@ -22,22 +23,7 @@ const {
getPageSrc
} = require('./util')

let emitFileTimer = null

function createSlotsWxml (emitFile, slots, importCode) {
cacheSlots(slots, importCode)
const content = getSlots()
// 100 delay 比较符合当前策略
const delay = 100
if (content.trim()) {
if (emitFileTimer) {
clearTimeout(emitFileTimer)
}
emitFileTimer = setTimeout(function () {
emitFile('components/slots.wxml', htmlBeautify(content))
}, delay)
}
}
let slotsHookAdded = false

// 调用 compiler 生成 wxml
function genComponentWxml (compiled, options, emitFile, emitError, emitWarning) {
Expand All @@ -46,7 +32,7 @@ function genComponentWxml (compiled, options, emitFile, emitError, emitWarning)
const { mpErrors, mpTips } = cp

// 缓存 slots,延迟编译
createSlotsWxml(emitFile, slots, importCode)
cacheSlots(slots, importCode)

if (mpErrors && mpErrors.length) {
emitError(
Expand All @@ -62,43 +48,66 @@ function genComponentWxml (compiled, options, emitFile, emitError, emitWarning)
return htmlBeautify(wxmlCodeStr)
}

function createWxml (emitWarning, emitError, emitFile, resourcePath, rootComponent, compiled, html) {
const { pageType, moduleId, components, src } = getFileInfo(resourcePath) || {}

// 这儿一个黑魔法,和 webpack 约定的规范写法有点偏差!
if (!pageType || (components && !components.isCompleted)) {
return setTimeout(createWxml, 20, ...arguments)
}

let wxmlContent = ''
let wxmlSrc = ''
function createAppWxml (emitFile, resourcePath, rootComponent) {
const { src } = getFileInfo(resourcePath) || {}
const componentName = getCompNameBySrc(rootComponent)
const wxmlContent = genPageWxml(componentName, src)
const wxmlSrc = src
emitFile(`${wxmlSrc}.wxml`, wxmlContent)
}
// 更新全局组件时,需要重新生成wxml,用这个字段保存所有需要更新的页面及其参数
const cacheCreateWxmlFns = {}

if (rootComponent) {
const componentName = getCompNameBySrc(rootComponent)
wxmlContent = genPageWxml(componentName, src)
wxmlSrc = src
} else {
// TODO, 这儿传 options 进去
// {
// components: {
// 'com-a': { src: '../../components/comA$hash', name: 'comA$hash' }
// },
// pageType: 'component',
// name: 'comA$hash',
// moduleId: 'moduleId'
// }
const name = getCompNameBySrc(resourcePath)
const options = { components, pageType, name, moduleId }
wxmlContent = genComponentWxml(compiled, options, emitFile, emitError, emitWarning)
wxmlSrc = `components/${name}`
}
function createWxml (emitWarning, emitError, emitFile, resourcePath, rootComponent, compiled, html) {
cacheCreateWxmlFns[resourcePath] = arguments
const { pageType, moduleId, components } = getFileInfo(resourcePath) || {}

// TODO, 这儿传 options 进去
// {
// components: {
// 'com-a': { src: '../../components/comA$hash', name: 'comA$hash' }
// },
// pageType: 'component',
// name: 'comA$hash',
// moduleId: 'moduleId'
// }
const name = getCompNameBySrc(resourcePath)
const options = { components, pageType, name, moduleId }
const wxmlContent = genComponentWxml(compiled, options, emitFile, emitError, emitWarning)
const wxmlSrc = `components/${name}`

emitFile(`${wxmlSrc}.wxml`, wxmlContent)
}

// 编译出 wxml
function compileWxml (compiled, html) {
return createWxml(this.emitWarning, this.emitError, this.emitFile, this.resourcePath, null, compiled, html)
if (!slotsHookAdded) {
// avoid add hook several times during compilation
slotsHookAdded = true
// TODO: support webpack4
this._compilation.plugin('seal', () => {
const content = getSlots()
if (content.trim()) {
this.emitFile('components/slots.wxml', htmlBeautify(content))
}
// reset flag after slots file emited
slotsHookAdded = false
})
}
return new Promise(resolve => {
const pollComponentsStatus = () => {
const { pageType, components } = getFileInfo(this.resourcePath) || {}
if (!pageType || (components && !components.isCompleted)) {
setTimeout(pollComponentsStatus, 20)
} else {
resolve()
}
}
pollComponentsStatus()
})
.then(() => {
createWxml(this.emitWarning, this.emitError, this.emitFile, this.resourcePath, null, compiled, html)
})
}

// 针对 .vue 单文件的脚本逻辑的处理
Expand All @@ -124,53 +133,73 @@ function compileMPScript (script, mpOptioins, moduleId) {

// 处理子组件的信息
const components = {}
const fileInfo = resolveTarget(this.resourcePath, this.options.entry)
if (originComponents) {
const allP = Object.keys(originComponents).map(k => {
return new Promise((resolve, reject) => {
this.resolve(this.context, originComponents[k], (err, realSrc) => {
if (err) return reject(err)
const com = covertCCVar(k)
const comName = getCompNameBySrc(realSrc)
components[com] = { src: comName, name: comName }
resolve()
})
})
resolveSrc(originComponents, components, this.resolve, this.context).then(() => {
resolveComponent(this.resourcePath, fileInfo, importsMap, components, moduleId)
}).catch(err => {
console.error(err)
resolveComponent(this.resourcePath, fileInfo, importsMap, components, moduleId)
})
Promise.all(allP)
.then(res => {
components.isCompleted = true
})
.catch(err => {
console.error(err)
components.isCompleted = true
})
} else {
components.isCompleted = true
resolveComponent(this.resourcePath, fileInfo, importsMap, components, moduleId)
}

const fileInfo = resolveTarget(this.resourcePath, this.options.entry)
cacheFileInfo(this.resourcePath, fileInfo, { importsMap, components, moduleId })

return script
}

// checkMPEntry 针对 entry main.js 的入口处理
// 编译出 app, page 的入口js/wxml/json

const startPageReg = /^\^/

let globalComponents
function compileMP (content, mpOptioins) {
const { resourcePath, emitError, emitFile, emitWarning, resolve, context, options } = this

const babelrc = getBabelrc(mpOptioins.globalBabelrc)
const { metadata } = babel.transform(content, { extends: babelrc, plugins: [parseConfig] })

// metadata: config
const { config, rootComponent } = metadata
const { resourcePath, emitFile, resolve, context, options } = this

const fileInfo = resolveTarget(resourcePath, options.entry)
cacheFileInfo(resourcePath, fileInfo)
const { src, name, isApp, isPage } = fileInfo
if (isApp) {
// 解析前将可能存在的全局组件清空
clearGlobalComponents()
}

const babelrc = getBabelrc(mpOptioins.globalBabelrc)
// app入口进行全局component解析
const { metadata } = babel.transform(content, { extends: babelrc, plugins: isApp ? [parseConfig, parseGlobalComponents] : [parseConfig] })

// metadata: config
const { config, rootComponent, globalComponents: globalComps } = metadata

if (isApp) {
// 保存旧数据,用于对比
const oldGlobalComponents = globalComponents
// 开始解析组件路径时把全局组件清空,解析完成后再进行赋值,标志全局组件解析完成
globalComponents = null

// 解析全局组件的路径
const components = {}
resolveSrc(globalComps, components, resolve, context).then(() => {
handleResult(components)
}).catch(err => {
console.error(err)
handleResult(components)
})
const handleResult = components => {
globalComponents = components
// 热更时,如果全局组件更新,需要重新生成所有的wxml
if (oldGlobalComponents && !deepEqual(oldGlobalComponents, globalComponents)) {
// 更新所有页面的组件
Object.keys(cacheResolveComponents).forEach(k => {
resolveComponent(...cacheResolveComponents[k])
})
// 重新生成所有wxml
Object.keys(cacheCreateWxmlFns).forEach(k => {
createWxml(...cacheCreateWxmlFns[k])
})
}
}
}

if (isApp || isPage) {
// 生成入口 json
Expand Down Expand Up @@ -205,12 +234,40 @@ function compileMP (content, mpOptioins) {
resolve(context, rootComponent, (err, rootComponentSrc) => {
if (err) return
// 这儿需要搞定 根组件的 路径
createWxml(emitWarning, emitError, emitFile, resourcePath, rootComponentSrc)
createAppWxml(emitFile, resourcePath, rootComponentSrc)
})
}
}

return content
}

function resolveSrc (originComponents, components, resolveFn, context) {
return Promise.all(Object.keys(originComponents).map(k => {
return new Promise((resolve, reject) => {
resolveFn(context, originComponents[k], (err, realSrc) => {
if (err) return reject(err)
const com = covertCCVar(k)
const comName = getCompNameBySrc(realSrc)
components[com] = { src: comName, name: comName }
resolve()
})
})
}))
}

const cacheResolveComponents = {}
function resolveComponent (resourcePath, fileInfo, importsMap, localComponents, moduleId) {
// 需要等待全局组件解析完成
if (!globalComponents) {
setTimeout(resolveComponent, 20, ...arguments)
} else {
// 保存当前所有参数,在热更时如果全局组件发生变化,需要进行组件更新
cacheResolveComponents[resourcePath] = arguments
const components = Object.assign({}, globalComponents, localComponents)
components.isCompleted = true
cacheFileInfo(resourcePath, fileInfo, { importsMap, components, moduleId })
}
}

module.exports = { compileWxml, compileMPScript, compileMP }
10 changes: 8 additions & 2 deletions lib/mp-compiler/parse-ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ let ts
try {
ts = require('typescript')
} catch (e) {

// console.error(e)
}

function parseComponentsDeps (scriptContent) {
if (ts === null) {
throw new Error('Please run `npm install -S typescript` to install TypeScript.')
}
const sourceFile = ts.createSourceFile('test', scriptContent, ts.ScriptTarget.ESNext, /* setParentNodes */ true)
return delint(sourceFile)
}
Expand All @@ -21,7 +24,10 @@ function delint (sourceFile) {
if (node.expression.expression && node.expression.expression.escapedText === 'Component') {
const compArgs = node.expression.arguments
if (compArgs && compArgs.length === 1) {
const vueClassArg = compArgs[0]
let vueClassArg = compArgs[0]
if (vueClassArg.kind === ts.SyntaxKind.AsExpression) { // @Component({ components: ...,} as any)
vueClassArg = vueClassArg.expression
}
if (vueClassArg.properties) {
vueClassArg.properties.forEach((classProp) => {
// 处理components属性
Expand Down
37 changes: 36 additions & 1 deletion lib/mp-compiler/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ const configVisitor = {
}

const arg = path.node.arguments[0]

if (!arg) {
return
}

const v = arg.type === 'Identifier' ? importsMap[arg.name] : importsMap['App']
metadata.rootComponent = v || importsMap['index'] || importsMap['main']
}
Expand Down Expand Up @@ -98,4 +103,34 @@ function parseComponentsDeps (babel) {
return { visitor: componentsVisitor }
}

module.exports = { parseConfig, parseComponentsDeps }
// 解析全局components
let globalComponents = {}
const globalComponentsVisitor = {
CallExpression (path) {
const { callee, arguments: args } = path.node
const { metadata } = path.hub.file
if (!callee.object || !callee.property) {
return
}
if (callee.object.name === 'Vue' && callee.property.name === 'component') {
if (!args[0] || args[0].type !== 'StringLiteral') {
throw new Error('Vue.component()的第一个参数必须为静态字符串')
}
if (!args[1]) {
throw new Error('Vue.component()需要两个参数')
}
const { importsMap } = getImportsMap(metadata)
globalComponents[args[0].value] = importsMap[args[1].name]
}
metadata.globalComponents = globalComponents
}
}

function parseGlobalComponents (babel) {
return { visitor: globalComponentsVisitor }
}

function clearGlobalComponents () {
globalComponents = {}
}
module.exports = { parseConfig, parseComponentsDeps, parseGlobalComponents, clearGlobalComponents }
Loading

0 comments on commit dcb95dd

Please sign in to comment.