Skip to content

Commit dcb95dd

Browse files
authored
Merge pull request #24 from mpvue/develop
Develop
2 parents f3387e0 + b996432 commit dcb95dd

File tree

6 files changed

+269
-155
lines changed

6 files changed

+269
-155
lines changed

lib/mp-compiler/index.js

+136-79
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ const compiler = require('mpvue-template-compiler')
44
const babel = require('babel-core')
55
const path = require('path')
66
const fs = require('fs')
7+
const deepEqual = require('deep-equal')
78

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

@@ -22,22 +23,7 @@ const {
2223
getPageSrc
2324
} = require('./util')
2425

25-
let emitFileTimer = null
26-
27-
function createSlotsWxml (emitFile, slots, importCode) {
28-
cacheSlots(slots, importCode)
29-
const content = getSlots()
30-
// 100 delay 比较符合当前策略
31-
const delay = 100
32-
if (content.trim()) {
33-
if (emitFileTimer) {
34-
clearTimeout(emitFileTimer)
35-
}
36-
emitFileTimer = setTimeout(function () {
37-
emitFile('components/slots.wxml', htmlBeautify(content))
38-
}, delay)
39-
}
40-
}
26+
let slotsHookAdded = false
4127

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

4834
// 缓存 slots,延迟编译
49-
createSlotsWxml(emitFile, slots, importCode)
35+
cacheSlots(slots, importCode)
5036

5137
if (mpErrors && mpErrors.length) {
5238
emitError(
@@ -62,43 +48,66 @@ function genComponentWxml (compiled, options, emitFile, emitError, emitWarning)
6248
return htmlBeautify(wxmlCodeStr)
6349
}
6450

65-
function createWxml (emitWarning, emitError, emitFile, resourcePath, rootComponent, compiled, html) {
66-
const { pageType, moduleId, components, src } = getFileInfo(resourcePath) || {}
67-
68-
// 这儿一个黑魔法,和 webpack 约定的规范写法有点偏差!
69-
if (!pageType || (components && !components.isCompleted)) {
70-
return setTimeout(createWxml, 20, ...arguments)
71-
}
72-
73-
let wxmlContent = ''
74-
let wxmlSrc = ''
51+
function createAppWxml (emitFile, resourcePath, rootComponent) {
52+
const { src } = getFileInfo(resourcePath) || {}
53+
const componentName = getCompNameBySrc(rootComponent)
54+
const wxmlContent = genPageWxml(componentName, src)
55+
const wxmlSrc = src
56+
emitFile(`${wxmlSrc}.wxml`, wxmlContent)
57+
}
58+
// 更新全局组件时,需要重新生成wxml,用这个字段保存所有需要更新的页面及其参数
59+
const cacheCreateWxmlFns = {}
7560

76-
if (rootComponent) {
77-
const componentName = getCompNameBySrc(rootComponent)
78-
wxmlContent = genPageWxml(componentName, src)
79-
wxmlSrc = src
80-
} else {
81-
// TODO, 这儿传 options 进去
82-
// {
83-
// components: {
84-
// 'com-a': { src: '../../components/comA$hash', name: 'comA$hash' }
85-
// },
86-
// pageType: 'component',
87-
// name: 'comA$hash',
88-
// moduleId: 'moduleId'
89-
// }
90-
const name = getCompNameBySrc(resourcePath)
91-
const options = { components, pageType, name, moduleId }
92-
wxmlContent = genComponentWxml(compiled, options, emitFile, emitError, emitWarning)
93-
wxmlSrc = `components/${name}`
94-
}
61+
function createWxml (emitWarning, emitError, emitFile, resourcePath, rootComponent, compiled, html) {
62+
cacheCreateWxmlFns[resourcePath] = arguments
63+
const { pageType, moduleId, components } = getFileInfo(resourcePath) || {}
64+
65+
// TODO, 这儿传 options 进去
66+
// {
67+
// components: {
68+
// 'com-a': { src: '../../components/comA$hash', name: 'comA$hash' }
69+
// },
70+
// pageType: 'component',
71+
// name: 'comA$hash',
72+
// moduleId: 'moduleId'
73+
// }
74+
const name = getCompNameBySrc(resourcePath)
75+
const options = { components, pageType, name, moduleId }
76+
const wxmlContent = genComponentWxml(compiled, options, emitFile, emitError, emitWarning)
77+
const wxmlSrc = `components/${name}`
9578

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

9982
// 编译出 wxml
10083
function compileWxml (compiled, html) {
101-
return createWxml(this.emitWarning, this.emitError, this.emitFile, this.resourcePath, null, compiled, html)
84+
if (!slotsHookAdded) {
85+
// avoid add hook several times during compilation
86+
slotsHookAdded = true
87+
// TODO: support webpack4
88+
this._compilation.plugin('seal', () => {
89+
const content = getSlots()
90+
if (content.trim()) {
91+
this.emitFile('components/slots.wxml', htmlBeautify(content))
92+
}
93+
// reset flag after slots file emited
94+
slotsHookAdded = false
95+
})
96+
}
97+
return new Promise(resolve => {
98+
const pollComponentsStatus = () => {
99+
const { pageType, components } = getFileInfo(this.resourcePath) || {}
100+
if (!pageType || (components && !components.isCompleted)) {
101+
setTimeout(pollComponentsStatus, 20)
102+
} else {
103+
resolve()
104+
}
105+
}
106+
pollComponentsStatus()
107+
})
108+
.then(() => {
109+
createWxml(this.emitWarning, this.emitError, this.emitFile, this.resourcePath, null, compiled, html)
110+
})
102111
}
103112

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

125134
// 处理子组件的信息
126135
const components = {}
136+
const fileInfo = resolveTarget(this.resourcePath, this.options.entry)
127137
if (originComponents) {
128-
const allP = Object.keys(originComponents).map(k => {
129-
return new Promise((resolve, reject) => {
130-
this.resolve(this.context, originComponents[k], (err, realSrc) => {
131-
if (err) return reject(err)
132-
const com = covertCCVar(k)
133-
const comName = getCompNameBySrc(realSrc)
134-
components[com] = { src: comName, name: comName }
135-
resolve()
136-
})
137-
})
138+
resolveSrc(originComponents, components, this.resolve, this.context).then(() => {
139+
resolveComponent(this.resourcePath, fileInfo, importsMap, components, moduleId)
140+
}).catch(err => {
141+
console.error(err)
142+
resolveComponent(this.resourcePath, fileInfo, importsMap, components, moduleId)
138143
})
139-
Promise.all(allP)
140-
.then(res => {
141-
components.isCompleted = true
142-
})
143-
.catch(err => {
144-
console.error(err)
145-
components.isCompleted = true
146-
})
147144
} else {
148-
components.isCompleted = true
145+
resolveComponent(this.resourcePath, fileInfo, importsMap, components, moduleId)
149146
}
150147

151-
const fileInfo = resolveTarget(this.resourcePath, this.options.entry)
152-
cacheFileInfo(this.resourcePath, fileInfo, { importsMap, components, moduleId })
153-
154148
return script
155149
}
156150

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

160154
const startPageReg = /^\^/
161-
155+
let globalComponents
162156
function compileMP (content, mpOptioins) {
163-
const { resourcePath, emitError, emitFile, emitWarning, resolve, context, options } = this
164-
165-
const babelrc = getBabelrc(mpOptioins.globalBabelrc)
166-
const { metadata } = babel.transform(content, { extends: babelrc, plugins: [parseConfig] })
167-
168-
// metadata: config
169-
const { config, rootComponent } = metadata
157+
const { resourcePath, emitFile, resolve, context, options } = this
170158

171159
const fileInfo = resolveTarget(resourcePath, options.entry)
172160
cacheFileInfo(resourcePath, fileInfo)
173161
const { src, name, isApp, isPage } = fileInfo
162+
if (isApp) {
163+
// 解析前将可能存在的全局组件清空
164+
clearGlobalComponents()
165+
}
166+
167+
const babelrc = getBabelrc(mpOptioins.globalBabelrc)
168+
// app入口进行全局component解析
169+
const { metadata } = babel.transform(content, { extends: babelrc, plugins: isApp ? [parseConfig, parseGlobalComponents] : [parseConfig] })
170+
171+
// metadata: config
172+
const { config, rootComponent, globalComponents: globalComps } = metadata
173+
174+
if (isApp) {
175+
// 保存旧数据,用于对比
176+
const oldGlobalComponents = globalComponents
177+
// 开始解析组件路径时把全局组件清空,解析完成后再进行赋值,标志全局组件解析完成
178+
globalComponents = null
179+
180+
// 解析全局组件的路径
181+
const components = {}
182+
resolveSrc(globalComps, components, resolve, context).then(() => {
183+
handleResult(components)
184+
}).catch(err => {
185+
console.error(err)
186+
handleResult(components)
187+
})
188+
const handleResult = components => {
189+
globalComponents = components
190+
// 热更时,如果全局组件更新,需要重新生成所有的wxml
191+
if (oldGlobalComponents && !deepEqual(oldGlobalComponents, globalComponents)) {
192+
// 更新所有页面的组件
193+
Object.keys(cacheResolveComponents).forEach(k => {
194+
resolveComponent(...cacheResolveComponents[k])
195+
})
196+
// 重新生成所有wxml
197+
Object.keys(cacheCreateWxmlFns).forEach(k => {
198+
createWxml(...cacheCreateWxmlFns[k])
199+
})
200+
}
201+
}
202+
}
174203

175204
if (isApp || isPage) {
176205
// 生成入口 json
@@ -205,12 +234,40 @@ function compileMP (content, mpOptioins) {
205234
resolve(context, rootComponent, (err, rootComponentSrc) => {
206235
if (err) return
207236
// 这儿需要搞定 根组件的 路径
208-
createWxml(emitWarning, emitError, emitFile, resourcePath, rootComponentSrc)
237+
createAppWxml(emitFile, resourcePath, rootComponentSrc)
209238
})
210239
}
211240
}
212241

213242
return content
214243
}
215244

245+
function resolveSrc (originComponents, components, resolveFn, context) {
246+
return Promise.all(Object.keys(originComponents).map(k => {
247+
return new Promise((resolve, reject) => {
248+
resolveFn(context, originComponents[k], (err, realSrc) => {
249+
if (err) return reject(err)
250+
const com = covertCCVar(k)
251+
const comName = getCompNameBySrc(realSrc)
252+
components[com] = { src: comName, name: comName }
253+
resolve()
254+
})
255+
})
256+
}))
257+
}
258+
259+
const cacheResolveComponents = {}
260+
function resolveComponent (resourcePath, fileInfo, importsMap, localComponents, moduleId) {
261+
// 需要等待全局组件解析完成
262+
if (!globalComponents) {
263+
setTimeout(resolveComponent, 20, ...arguments)
264+
} else {
265+
// 保存当前所有参数,在热更时如果全局组件发生变化,需要进行组件更新
266+
cacheResolveComponents[resourcePath] = arguments
267+
const components = Object.assign({}, globalComponents, localComponents)
268+
components.isCompleted = true
269+
cacheFileInfo(resourcePath, fileInfo, { importsMap, components, moduleId })
270+
}
271+
}
272+
216273
module.exports = { compileWxml, compileMPScript, compileMP }

lib/mp-compiler/parse-ts.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ let ts
22
try {
33
ts = require('typescript')
44
} catch (e) {
5-
5+
// console.error(e)
66
}
77

88
function parseComponentsDeps (scriptContent) {
9+
if (ts === null) {
10+
throw new Error('Please run `npm install -S typescript` to install TypeScript.')
11+
}
912
const sourceFile = ts.createSourceFile('test', scriptContent, ts.ScriptTarget.ESNext, /* setParentNodes */ true)
1013
return delint(sourceFile)
1114
}
@@ -21,7 +24,10 @@ function delint (sourceFile) {
2124
if (node.expression.expression && node.expression.expression.escapedText === 'Component') {
2225
const compArgs = node.expression.arguments
2326
if (compArgs && compArgs.length === 1) {
24-
const vueClassArg = compArgs[0]
27+
let vueClassArg = compArgs[0]
28+
if (vueClassArg.kind === ts.SyntaxKind.AsExpression) { // @Component({ components: ...,} as any)
29+
vueClassArg = vueClassArg.expression
30+
}
2531
if (vueClassArg.properties) {
2632
vueClassArg.properties.forEach((classProp) => {
2733
// 处理components属性

lib/mp-compiler/parse.js

+36-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ const configVisitor = {
5555
}
5656

5757
const arg = path.node.arguments[0]
58+
59+
if (!arg) {
60+
return
61+
}
62+
5863
const v = arg.type === 'Identifier' ? importsMap[arg.name] : importsMap['App']
5964
metadata.rootComponent = v || importsMap['index'] || importsMap['main']
6065
}
@@ -98,4 +103,34 @@ function parseComponentsDeps (babel) {
98103
return { visitor: componentsVisitor }
99104
}
100105

101-
module.exports = { parseConfig, parseComponentsDeps }
106+
// 解析全局components
107+
let globalComponents = {}
108+
const globalComponentsVisitor = {
109+
CallExpression (path) {
110+
const { callee, arguments: args } = path.node
111+
const { metadata } = path.hub.file
112+
if (!callee.object || !callee.property) {
113+
return
114+
}
115+
if (callee.object.name === 'Vue' && callee.property.name === 'component') {
116+
if (!args[0] || args[0].type !== 'StringLiteral') {
117+
throw new Error('Vue.component()的第一个参数必须为静态字符串')
118+
}
119+
if (!args[1]) {
120+
throw new Error('Vue.component()需要两个参数')
121+
}
122+
const { importsMap } = getImportsMap(metadata)
123+
globalComponents[args[0].value] = importsMap[args[1].name]
124+
}
125+
metadata.globalComponents = globalComponents
126+
}
127+
}
128+
129+
function parseGlobalComponents (babel) {
130+
return { visitor: globalComponentsVisitor }
131+
}
132+
133+
function clearGlobalComponents () {
134+
globalComponents = {}
135+
}
136+
module.exports = { parseConfig, parseComponentsDeps, parseGlobalComponents, clearGlobalComponents }

0 commit comments

Comments
 (0)