提交 91ea409e 编写于 作者: fxy060608's avatar fxy060608

feat(cli): auto import components

上级 e000e9d8
......@@ -82,9 +82,11 @@ function updateComponentJson (name, jsonObj, usingComponents = true, type = 'Com
const oldJsonStr = getJsonFile(name)
if (oldJsonStr) { // update
if (usingComponents) { // merge usingComponents
if (usingComponents) { // merge usingComponents
// 其实直接拿新的 merge 到旧的应该就行
const oldJsonObj = JSON.parse(oldJsonStr)
jsonObj.usingComponents = oldJsonObj.usingComponents || {}
jsonObj.usingComponents = oldJsonObj.usingComponents || {}
jsonObj.usingAutoImportComponents = oldJsonObj.usingAutoImportComponents || {}
if (oldJsonObj.usingGlobalComponents) { // 复制 global components(针对不支持全局 usingComponents 的平台)
jsonObj.usingGlobalComponents = oldJsonObj.usingGlobalComponents
}
......@@ -118,6 +120,22 @@ function updateUsingGlobalComponents (name, usingGlobalComponents) {
}
}
function updateUsingAutoImportComponents (name, usingAutoImportComponents) {
const oldJsonStr = getJsonFile(name)
if (oldJsonStr) { // update
const jsonObj = JSON.parse(oldJsonStr)
jsonObj.usingAutoImportComponents = usingAutoImportComponents
const newJsonStr = JSON.stringify(jsonObj, null, 2)
if (newJsonStr !== oldJsonStr) {
updateJsonFile(name, newJsonStr)
}
} else { // add
updateJsonFile(name, {
usingAutoImportComponents
})
}
}
function updateUsingComponents (name, usingComponents, type) {
if (type === 'Component') {
componentSet.add(name)
......@@ -260,7 +278,7 @@ module.exports = {
// 先简单处理,该方案不好,
// 后续为 pages-loader 增加 cache-loader,
// 然后其他修改 json 的地方也要定制 cache-loader
store () {
store () {
const pagesJsonPath = path.resolve(process.env.UNI_INPUT_DIR, 'pages.json')
const filepath = path.resolve(
process.env.UNI_CLI_CONTEXT,
......@@ -293,7 +311,7 @@ module.exports = {
clearCache()
} catch (e) {}
return
}
}
const pagesJsonPath = path.resolve(process.env.UNI_INPUT_DIR, 'pages.json')
const mtimeMs = fs.statSync(pagesJsonPath).mtimeMs
const jsonCache = require(filepath)
......@@ -328,6 +346,7 @@ module.exports = {
updateUsingComponents,
updateUsingGlobalComponents,
updateAppJsonUsingComponents,
updateUsingAutoImportComponents,
updateComponentGenerics,
updateGenericComponents,
getChangedJsonFileMap,
......
......@@ -308,7 +308,7 @@ function getGlobalUsingComponentsCode () {
return ''
}
return generateGlobalUsingComponentsCode(usingComponents)
}
}
function getUsingComponentsCode (pagePath) {
const usingComponents = usingComponentsPages[pagePath]
......@@ -328,16 +328,16 @@ function addPageUsingComponents (pagePath, usingComponents) {
// 存储自动组件
const autoComponentMap = {}
function addAutoComponent (name) {
const options = process.UNI_AUTO_COMPONENTS
const opt = options.find(opt => opt.test(name))
function addAutoComponent (name) {
const options = process.UNI_AUTO_COMPONENTS
const opt = options.find(opt => opt.pattern.test(name))
if (!opt) { // 不匹配
return (autoComponentMap[name] = true) // cache
}
return (autoComponentMap[name] = {
name,
identifier: capitalize(camelize(name + '-auto-import')),
source: name.replace(opt.test, opt.replacement)
source: name.replace(opt.pattern, opt.replacement)
})
}
......@@ -363,8 +363,8 @@ module.exports = {
getPagesJson,
parsePagesJson,
pagesJsonJsFileName,
getAutoComponents,
addPageUsingComponents,
getAutoComponents,
addPageUsingComponents,
getUsingComponentsCode,
generateUsingComponentsCode,
getGlobalUsingComponentsCode,
......
......@@ -59,7 +59,7 @@ function genWxs(wxs, state) {
module.exports = function generate(node, state) {
return [
`<uni-shadow-root class="${state.shadowRootHost}">${genChildren(node).trim()}</uni-shadow-root>`,
`<uni-shadow-root${state.shadowRootHost?(` class="${state.shadowRootHost}"`):''}>${genChildren(node).trim()}</uni-shadow-root>`,
...genWxs(state.wxs, state)
]
}
......@@ -310,7 +310,15 @@ describe('mp:compiler-extra', () => {
assertCodegen(
`<view class="input-list" v-for="(item,index) in dataList" :key="item.id"><input v-model.trim="dataList2[index].val" /></view>`,
`<block wx:for="{{dataList}}" wx:for-item="item" wx:for-index="index" wx:key="id"><view class="input-list"><input data-event-opts="{{[['input',[['__set_model',['$0','val','$event',['trim']],['dataList2.'+index+'']]]],['blur',[['$forceUpdate']]]]}}" value="{{dataList2[index].val}}" bindinput="__e" bindblur="__e"/></view></block>`
)
)
assertCodegen(
` <view>
<view v-for="item in list[idx]" :key="item.id" class="mid-item-title" @click="m1(item)">
<view class="mid-item-icon" @click.stop="m2(item)"></view>
</view>
</view>`,
`<view><block wx:for="{{list[idx]}}" wx:for-item="item" wx:for-index="__i0__" wx:key="id"><view data-event-opts="{{[['tap',[['m1',['$0'],[[['list.'+idx+'','id',item.id]]]]]]]}}" class="mid-item-title" bindtap="__e"><view data-event-opts="{{[['tap',[['m2',['$0'],[[['list.'+idx+'','id',item.id]]]]]]]}}" class="mid-item-icon" catchtap="__e"></view></view></block></view>`
)
})
it('generate class binding', () => {
......
const compiler = require('../lib')
const res = compiler.compile(
`
<view class="custom-class">
<uni-badge/>
<uni-badge/>
<uni-tag/>
<uni-tag/>
<view>
<view v-for="item in list[idx]" :key="item.id" class="mid-item-title" @click="mabc1(item)">
<view class="mid-item-icon" @click.stop="mabc2(item)"></view>
</view>
</view>
`, {
miniprogram: true,
......
const path = require('path')
const {
isComponent
} = require('./util')
module.exports = {
const {
removeExt
} = require('../../uni-cli-shared/lib/util')
const {
getAutoComponents
} = require('../../uni-cli-shared/lib/pages')
const {
updateUsingAutoImportComponents
} = require('../../uni-cli-shared/lib/cache')
function formatSource (source) {
if (source.indexOf('@/') === 0) { // 根目录
source = source.replace('@/', '')
} else { // node_modules
source = 'node-modules/' + source
}
return removeExt(source)
}
function getWebpackChunkName (source) {
return formatSource(source)
}
function updateMPUsingAutoImportComponents (autoComponents, options) {
const resourcePath = options.resourcePath.replace(path.extname(options.resourcePath), '')
const usingAutoImportComponents = Object.create(null)
autoComponents.forEach(({
name,
source
}) => {
usingAutoImportComponents[name] = '/' + formatSource(source)
})
updateUsingAutoImportComponents(resourcePath, usingAutoImportComponents) // 更新json
}
function generateAutoComponentsCode (autoComponents, dynamic = false) {
const components = []
autoComponents.forEach(({
name,
source
}) => {
if (dynamic) {
components.push(`'${name}': ()=>import(/* webpackChunkName: "${getWebpackChunkName(source)}" */'${source}')`)
} else {
components.push(`'${name}': require('${source}').default`)
}
})
return `var components = {${components.join(',')}}`
}
function compileTemplate (source, options, compile) {
const res = compile(source, options)
const autoComponents = getAutoComponents([...(options.isUnaryTag.autoComponents || [])])
if (autoComponents.length) {
// console.log('检测到的自定义组件:' + JSON.stringify(autoComponents))
res.components = generateAutoComponentsCode(autoComponents, options.mp)
} else {
res.components = 'var components;'
}
if (options.mp) { // 小程序 更新 json 每次编译都要调整,保证热更新时增减组件一致
updateMPUsingAutoImportComponents(autoComponents || [], options)
}
return res
}
const compilerModule = {
preTransformNode (el, options) {
if (isComponent(el.tag)) {
if (isComponent(el.tag) && el.tag !== 'App') { // App.vue
// 挂在 isUnaryTag 上边,可以保证外部访问到
(options.isUnaryTag.autoComponents || (options.isUnaryTag.autoComponents = new Set())).add(el.tag)
}
}
}
module.exports = {
compileTemplate,
module: compilerModule
}
......@@ -23,52 +23,44 @@ const compilerAlipayModule = require('./module-alipay')
const generateCodeFrame = require('./codeframe')
const {
isComponent,
isComponent,
isUnaryTag
} = require('./util')
function compileTemplate (source, options) {
const res = compile(source, options)
console.log(options)
const {
autoComponents
} = options.isUnaryTag
if (autoComponents) {
console.log('检测到的自定义组件:' + [...autoComponents])
}
res.components = `{
'uni-badge': require('@components/uni-badge/uni-badge.vue').default,
'uni-tag': require('@components/uni-tag/uni-tag.vue').default
}`
return res
}
const {
module: autoComponentsModule,
compileTemplate
} = require('./auto-components')
module.exports = {
compile (source, options = {}) {
(options.modules || (options.modules = [])).push(require('./auto-components'))
options.isUnaryTag = isUnaryTag
(options.modules || (options.modules = [])).push(autoComponentsModule)
options.isUnaryTag = isUnaryTag
// 将 autoComponents 挂在 isUnaryTag 上边
options.isUnaryTag.autoComponents = new Set()
options.preserveWhitespace = false
if (options.service) {
(options.modules || (options.modules = [])).push(require('./app/service'))
options.optimize = false // 启用 staticRenderFns
options.optimize = false // 启用 staticRenderFns
// domProps => attrs
options.mustUseProp = () => false
options.isReservedTag = (tagName) => !isComponent(tagName) // 非组件均为内置
options.getTagNamespace = () => false
try {
return compileTemplate(source, options)
return compileTemplate(source, options, compile)
} catch (e) {
console.error(source)
throw e
}
} else if (options.view) {
(options.modules || (options.modules = [])).push(require('./app/view'))
options.optimize = false // 暂不启用 staticRenderFns
options.optimize = false // 暂不启用 staticRenderFns
options.isUnaryTag = isUnaryTag
options.isReservedTag = (tagName) => false // 均为组件
try {
return compileTemplate(source, options)
return compileTemplate(source, options, compile)
} catch (e) {
console.error(source)
throw e
......@@ -76,7 +68,7 @@ module.exports = {
}
if (!options.mp) { // h5
return compileTemplate(source, options)
return compileTemplate(source, options, compile)
}
(options.modules || (options.modules = [])).push(compilerModule)
......
......@@ -81,17 +81,17 @@ function genElement (ast, state, isRoot = false) {
const names = Object.keys(ast.attr)
const props = names.length
? ' ' +
names
.map(name => {
if (name.includes(':else')) {
return name
}
if (ast.attr[name] === '' && name !== 'value') { // value属性需要保留=''
return name
}
return `${name}="${ast.attr[name]}"`
})
.join(' ')
names
.map(name => {
if (name.includes(':else')) {
return name
}
if (ast.attr[name] === '' && name !== 'value') { // value属性需要保留=''
return name
}
return `${name}="${ast.attr[name]}"`
})
.join(' ')
: ''
if (SELF_CLOSING_TAGS.includes(ast.type)) {
return `<${ast.type}${props}/>`
......@@ -123,7 +123,7 @@ module.exports = function generate (ast, state) {
const replaceCodes = state.options.replaceCodes
if (replaceCodes) {
Object.keys(replaceCodes).forEach(key => {
code = code.replace(key, replaceCodes[key])
code = code.replace(new RegExp(key.replace('$', '\\$'), 'g'), replaceCodes[key])
})
}
......
const {
module: autoComponentsModule,
compileTemplate
} = require('@dcloudio/uni-template-compiler/lib/auto-components')
const {
isUnaryTag
} = require('@dcloudio/uni-template-compiler/lib/util')
const TAGS = [
'ad',
'text',
......@@ -92,10 +101,20 @@ if (process.env.UNI_USING_NVUE_COMPILER) {
})
}
const compiler = require('weex-template-compiler')
const oldCompile = compiler.compile
compiler.compile = function (source, options = {}) {
(options.modules || (options.modules = [])).push(autoComponentsModule)
options.isUnaryTag = isUnaryTag
// 将 autoComponents 挂在 isUnaryTag 上边
options.isUnaryTag.autoComponents = new Set()
options.preserveWhitespace = false
return compileTemplate(source, options, oldCompile)
}
module.exports = {
preserveWhitespace: false,
isAppNVue: true,
compiler: require('weex-template-compiler'),
compiler,
compilerOptions: {
modules
}
......
......@@ -234,7 +234,7 @@ const v3 = {
.rule('vue')
.test([/\.vue$/, /\.nvue$/])
.use('vue-loader') // service 层移除 style 节点,view 层返回固定 script
.loader(path.resolve(__dirname, '../../packages/vue-loader/lib'))
.loader(require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vue-loader'))
.tap(options => Object.assign(options, {
isAppService,
isAppView,
......
......@@ -256,6 +256,8 @@ const moduleAlias = require('module-alias')
moduleAlias.addAlias('vue-template-compiler', '@dcloudio/vue-cli-plugin-uni/packages/vue-template-compiler')
moduleAlias.addAlias('@megalo/template-compiler', '@dcloudio/vue-cli-plugin-uni/packages/@megalo/template-compiler')
moduleAlias.addAlias('mpvue-template-compiler', '@dcloudio/vue-cli-plugin-uni/packages/mpvue-template-compiler')
// vue-loader
moduleAlias.addAlias('vue-loader', '@dcloudio/vue-cli-plugin-uni/packages/vue-loader')
if (process.env.UNI_USING_V3 && process.env.UNI_PLATFORM === 'app-plus') {
moduleAlias.addAlias('vue-style-loader', '@dcloudio/vue-cli-plugin-uni/packages/app-vue-style-loader')
......@@ -321,10 +323,8 @@ if (
}
process.UNI_AUTO_COMPONENTS = [{
test (str) {
return new RegExp('uni-(.*)').test(str)
},
replacement: '@components/uni-$1/uni-$1.vue'
pattern: new RegExp('uni-(.*)'),
replacement: '@/components/uni-$1/uni-$1.vue'
}]
runByHBuilderX && console.log(`正在编译中...`)
......
......@@ -26,6 +26,7 @@ module.exports = function modifyVueLoader (webpackConfig, compilerOptions, api)
.rule('vue')
.test([/\.vue$/, /\.nvue$/])
.use('vue-loader')
.loader(require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vue-loader'))
.tap(options => Object.assign(options, {
compiler: getPlatformCompiler(),
compilerOptions: Object.assign({
......
......@@ -9,7 +9,8 @@ module.exports = function genStyleInjectionCode (
resourcePath,
stringifyRequest,
needsHotReload,
needsExplicitInjection // fixed by xxxxxx nvue 时,指定了 target 为 node,激活 needsExplicitInjection
needsExplicitInjection, // fixed by xxxxxx nvue 时,指定了 target 为 node,激活 needsExplicitInjection
platform
) {
let styleImportsCode = ``
let styleInjectionCode = ``
......@@ -85,29 +86,42 @@ module.exports = function genStyleInjectionCode (
if (style.module) genCSSModulesCode(style, request, i)
}
})
} else { // fixed by xxxxxx nvue style
styleInjectionCode = `if(!this.options.style){
this.options.style = {}
}
if(Vue.prototype.__merge_style && Vue.prototype.__$appStyle__){
Vue.prototype.__merge_style(Vue.prototype.__$appStyle__, this.options.style)
}
`
styles.forEach((style, i) => {
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleInjectionCode += (
`if(Vue.prototype.__merge_style){
Vue.prototype.__merge_style(require(${request}).default, this.options.style)
}else{
Object.assign(this.options.style,require(${request}).default)
}\n`//fixed by xxxxxx 简单处理,与 weex-vue-loader 保持一致
//`var style${i} = require(${request})\n` +
//`if (style${i}.__inject__) style${i}.__inject__(context)\n`
)
if (style.module) genCSSModulesCode(style, request, i)
} else { // fixed by xxxxxx (app-plus v3 view style and app-nvue style)
if (platform === 'app-vue') {
styles.forEach((style, i) => {
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleInjectionCode += (
`var style${i} = require(${request})\n` +
`if (style${i}.__inject__) style${i}.__inject__(context)\n`
)
if (style.module) genCSSModulesCode(style, request, i)
}
})
} else {
styleInjectionCode = `if(!this.options.style){
this.options.style = {}
}
})
if(Vue.prototype.__merge_style && Vue.prototype.__$appStyle__){
Vue.prototype.__merge_style(Vue.prototype.__$appStyle__, this.options.style)
}
`
styles.forEach((style, i) => {
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleInjectionCode += (
`if(Vue.prototype.__merge_style){
Vue.prototype.__merge_style(require(${request}).default, this.options.style)
}else{
Object.assign(this.options.style,require(${request}).default)
}\n`//fixed by xxxxxx 简单处理,与 weex-vue-loader 保持一致
//`var style${i} = require(${request})\n` +
//`if (style${i}.__inject__) style${i}.__inject__(context)\n`
)
if (style.module) genCSSModulesCode(style, request, i)
}
})
}
}
if (!needsExplicitInjection && !hasCSSModules) {
......
......@@ -114,7 +114,7 @@ module.exports = function (source) {
let templateRequest
if (descriptor.template) {
// fixed by xxxxxx
recyclable = options.isAppNVue && !!(template.attrs && template.attrs.recyclable)
recyclable = options.isAppNVue && !!(descriptor.template.attrs && descriptor.template.attrs.recyclable)
const src = descriptor.template.src || resourcePath
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ``
......@@ -167,8 +167,8 @@ var component = normalizer(
${hasFunctional ? `true` : `false`},
${options.isAppNVue ? `null`: (/injectStyles/.test(stylesCode) ? `injectStyles` : `null`)},
${hasScoped ? JSON.stringify(id) : `null`},
${isServer ? JSON.stringify(hash(request)) : `null`}
${isShadow ? `,true` : ``},
${isServer ? JSON.stringify(hash(request)) : `null`},
${isShadow ? `true` : `false`},
components
)
`.trim() + `\n`
......
......@@ -12,13 +12,19 @@ export default function normalizeComponent (
injectStyles,
scopeId,
moduleIdentifier, /* server only */
shadowMode /* vue-cli only */
shadowMode, /* vue-cli only */
components // fixed by xxxxxx auto components
) {
// Vue.extend constructor export interop
var options = typeof scriptExports === 'function'
? scriptExports.options
: scriptExports
// fixed by xxxxxx auto components
if (components) {
options.components = Object.assign(components, options.components || {})
}
// render functions
if (render) {
options.render = render
......
......@@ -81,7 +81,6 @@ function getStylesCode(loaderContext) {
(shortFilePath + '\n' + source) :
shortFilePath
)
stylesCode = genStylesCode(
loaderContext,
descriptor.styles,
......@@ -89,7 +88,8 @@ function getStylesCode(loaderContext) {
resourcePath,
stringifyRequest,
needsHotReload,
true // needs explicit injection?
true, // needs explicit injection?
'app-vue'
)
}
return stylesCode.replace(/main\.js/g, 'App.vue')
......
......@@ -91,14 +91,13 @@ module.exports = function generateJson (compilation) {
const jsonFileMap = getChangedJsonFileMap()
for (let name of jsonFileMap.keys()) {
const jsonObj = JSON.parse(jsonFileMap.get(name))
// customUsingComponents
if (jsonObj.customUsingComponents && Object.keys(jsonObj.customUsingComponents)) {
if (jsonObj.customUsingComponents && Object.keys(jsonObj.customUsingComponents).length) {
jsonObj.usingComponents = Object.assign(jsonObj.customUsingComponents, jsonObj.usingComponents)
}
delete jsonObj.customUsingComponents
// usingGlobalComponents
if (jsonObj.usingGlobalComponents && Object.keys(jsonObj.usingGlobalComponents)) {
if (jsonObj.usingGlobalComponents && Object.keys(jsonObj.usingGlobalComponents).length) {
jsonObj.usingComponents = Object.assign(jsonObj.usingGlobalComponents, jsonObj.usingComponents)
}
delete jsonObj.usingGlobalComponents
......@@ -142,6 +141,12 @@ module.exports = function generateJson (compilation) {
delete jsonObj.genericComponents
// usingAutoImportComponents
if (jsonObj.usingAutoImportComponents && Object.keys(jsonObj.usingAutoImportComponents).length) {
jsonObj.usingComponents = Object.assign(jsonObj.usingAutoImportComponents, jsonObj.usingComponents)
}
delete jsonObj.usingAutoImportComponents
if (process.env.UNI_PLATFORM !== 'app-plus' && process.env.UNI_PLATFORM !== 'h5') {
delete jsonObj.navigationBarShadow
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册