const path = require('path')
|
const hash = require('hash-sum')
|
const parser = require('@babel/parser')
|
|
const {
|
parseComponent,
|
compile,
|
compileToFunctions,
|
ssrCompile,
|
ssrCompileToFunctions
|
} = require('@dcloudio/vue-cli-plugin-uni/packages/vue-template-compiler')
|
|
const traverseScript = require('./script/traverse')
|
const generateScript = require('./script/generate')
|
const traverseTemplate = require('./template/traverse')
|
const generateTemplate = require('./template/generate')
|
|
const compilerModule = require('./module')
|
const compilerModuleUniad = require('./module.uniad')
|
|
const compilerAlipayModule = require('./module-alipay')
|
const compilerToutiaoModule = require('./module-toutiao')
|
|
const generateCodeFrame = require('./codeframe')
|
|
const {
|
isComponent,
|
isUnaryTag
|
} = require('./util')
|
|
const {
|
module: autoComponentsModule,
|
compileTemplate
|
} = require('./auto-components')
|
|
const isWin = /^win/.test(process.platform)
|
|
const normalizePath = path => (isWin ? path.replace(/\\/g, '/') : path)
|
|
module.exports = {
|
compile (source, options = {}) {
|
if (Array.isArray(options.modules)) {
|
options.modules.push(compilerModuleUniad)
|
}
|
|
if ( // 启用摇树优化后,需要过滤内置组件
|
!options.autoComponentResourcePath ||
|
options.autoComponentResourcePath.indexOf('@dcloudio/uni-h5/src') === -1
|
) {
|
(options.modules || (options.modules = [])).push(autoComponentsModule)
|
}
|
if (!options.modules) {
|
options.modules = []
|
}
|
|
// transformAssetUrls
|
options.modules.push(require('./asset-url'))
|
options.modules.push(require('./bool-attr'))
|
|
options.isUnaryTag = isUnaryTag
|
// 将 autoComponents 挂在 isUnaryTag 上边
|
options.isUnaryTag.autoComponents = new Set()
|
|
options.preserveWhitespace = false
|
if (options.service) {
|
options.modules.push(require('./app/service'))
|
options.optimize = false // 启用 staticRenderFns
|
// domProps => attrs
|
options.mustUseProp = () => false
|
options.isReservedTag = (tagName) => !isComponent(tagName) // 非组件均为内置
|
options.getTagNamespace = () => false
|
|
try {
|
return compileTemplate(source, options, compile)
|
} catch (e) {
|
console.error(source)
|
throw e
|
}
|
} else if (options.view) {
|
options.modules.push(require('./app/view'))
|
options.optimize = false // 暂不启用 staticRenderFns
|
options.isUnaryTag = isUnaryTag
|
options.isReservedTag = (tagName) => false // 均为组件
|
try {
|
return compileTemplate(source, options, compile)
|
} catch (e) {
|
console.error(source)
|
throw e
|
}
|
} else if (options['quickapp-native']) {
|
// 后续改版,应统一由具体包实现
|
options.modules.push(require('@dcloudio/uni-quickapp-native/lib/compiler-module'))
|
}
|
|
if (!options.mp) { // h5,quickapp-native
|
return compileTemplate(source, options, compile)
|
}
|
|
options.modules.push(compilerModule)
|
|
if (options.mp.platform === 'mp-alipay') {
|
options.modules.push(compilerAlipayModule)
|
} else if (options.mp.platform === 'mp-toutiao' || options.mp.platform === 'mp-lark') {
|
options.modules.push(compilerToutiaoModule)
|
}
|
|
const res = compileTemplate(source, Object.assign(options, {
|
optimize: false
|
}), compile)
|
|
options.mp.platform = require('./mp')(options.mp.platform)
|
|
options.mp.scopeId = options.scopeId
|
|
options.mp.resourcePath = options.resourcePath
|
if (options.resourcePath) {
|
options.mp.hashId = hash(options.resourcePath)
|
} else {
|
options.mp.hashId = ''
|
}
|
|
options.mp.globalUsingComponents = options.globalUsingComponents || Object.create(null)
|
|
options.mp.filterModules = Object.keys(options.filterModules || {})
|
|
// (可用的原生微信小程序组件,global+scoped)
|
options.mp.wxComponents = options.wxComponents || Object.create(null)
|
Object.assign(options.mp.wxComponents, {
|
'uniad-plugin': 'plugin://uni-ad/ad'
|
})
|
|
const state = {
|
ast: {},
|
script: '',
|
template: '',
|
errors: new Set(),
|
tips: new Set(),
|
options: options.mp
|
}
|
// console.log(`function render(){${res.render}}`)
|
const ast = parser.parse(`function render(){${res.render}}`)
|
let template = ''
|
|
try {
|
res.render = generateScript(traverseScript(ast, state), state)
|
template = generateTemplate(traverseTemplate(ast, state), state)
|
} catch (e) {
|
console.error(e)
|
throw new Error('Compile failed at ' + options.resourcePath.replace(
|
path.extname(options.resourcePath),
|
'.vue'
|
))
|
}
|
|
res.specialMethods = state.options.specialMethods || new Set()
|
delete state.options.specialMethods
|
|
res.files = state.files || {}
|
delete state.files
|
|
// resolve scoped slots
|
res.generic = state.generic || []
|
delete state.generic
|
|
// define scoped slots
|
res.componentGenerics = state.componentGenerics || {}
|
delete state.componentGenerics
|
|
state.errors.forEach(msg => {
|
res.errors.push({
|
msg
|
})
|
})
|
|
const resourcePath = options.resourcePath.replace(path.extname(options.resourcePath), '')
|
|
state.tips.forEach(msg => {
|
console.log(`提示:${msg}
|
at ${resourcePath}.vue:1`)
|
})
|
|
/**
|
* TODO
|
* 方案0.最佳方案是在 loader 中直接 emitFile,但目前 vue template-loader 不好介入,自定义的 compiler 结果又无法顺利返回给 loader
|
* 方案1.通过 loader 传递 emitFile 来提交生成 wxml,需要一个 template loader 来给自定义 compier 增加 emitFile
|
* 方案2.缓存 wxml 内容,由 plugin 生成 assets 来提交生成 wxml
|
* ...暂时使用方案1
|
*/
|
if (options.emitFile) {
|
// cache
|
if (process.env.UNI_USING_CACHE) {
|
const oldEmitFile = options.emitFile
|
process.UNI_CACHE_TEMPLATES = {}
|
options.emitFile = function emitFile (name, content) {
|
const absolutePath = path.resolve(process.env.UNI_OUTPUT_DIR, name)
|
process.UNI_CACHE_TEMPLATES[absolutePath] = content
|
oldEmitFile(name, content)
|
}
|
}
|
|
if (options.updateSpecialMethods) {
|
options.updateSpecialMethods(resourcePath, [...res.specialMethods])
|
}
|
const filterTemplate = []
|
options.mp.filterModules.forEach(name => {
|
const filterModule = options.filterModules[name]
|
if (filterModule.type !== 'renderjs' && filterModule.attrs.lang !== 'renderjs') {
|
if (
|
filterModule.attrs &&
|
filterModule.attrs.src &&
|
filterModule.attrs.src.indexOf('@/') === 0
|
) {
|
const src = filterModule.attrs.src
|
filterModule.attrs.src = normalizePath(path.relative(
|
path.dirname(resourcePath), src.replace('@/', '')
|
))
|
}
|
filterTemplate.push(
|
options.mp.platform.createFilterTag(
|
options.filterTagName,
|
filterModule
|
)
|
)
|
}
|
})
|
|
if (filterTemplate.length) {
|
template = filterTemplate.join('\n') + '\n' + template
|
}
|
|
if (
|
process.UNI_ENTRY[resourcePath] &&
|
process.env.UNI_PLATFORM !== 'app-plus' &&
|
process.env.UNI_PLATFORM !== 'h5'
|
) {
|
// 检查是否启用 shadow
|
let colorType = false
|
const pageJsonStr = options.getJsonFile(resourcePath)
|
if (pageJsonStr) {
|
try {
|
const windowJson = JSON.parse(pageJsonStr)
|
if (process.env.UNI_PLATFORM === 'mp-alipay') {
|
colorType = windowJson.allowsBounceVertical === 'NO' &&
|
windowJson.navigationBarShadow &&
|
windowJson.navigationBarShadow.colorType
|
} else {
|
colorType = windowJson.disableScroll &&
|
windowJson.navigationBarShadow &&
|
windowJson.navigationBarShadow.colorType
|
}
|
} catch (e) {}
|
}
|
if (colorType) {
|
template = options.getShadowTemplate(colorType) + template
|
}
|
}
|
|
options.emitFile(options.resourcePath, template)
|
if (res.files) {
|
Object.keys(res.files).forEach(name => {
|
options.emitFile(name, res.files[name])
|
})
|
}
|
|
if (state.options.usingGlobalComponents) {
|
options.updateUsingGlobalComponents(
|
resourcePath,
|
state.options.usingGlobalComponents
|
)
|
}
|
|
if (
|
res.generic &&
|
res.generic.length &&
|
options.updateGenericComponents
|
) {
|
options.updateGenericComponents(
|
resourcePath,
|
res.generic
|
)
|
}
|
if (
|
res.componentGenerics &&
|
Object.keys(res.componentGenerics).length &&
|
options.updateComponentGenerics
|
) {
|
options.updateComponentGenerics(
|
resourcePath,
|
res.componentGenerics
|
)
|
}
|
} else {
|
res.template = template
|
}
|
return res
|
},
|
parseComponent,
|
compileToFunctions,
|
ssrCompile,
|
ssrCompileToFunctions,
|
generateCodeFrame
|
}
|