Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 小程序组件自调用时自动添加到usingComponents #4913

Open
wants to merge 12 commits into
base: next
Choose a base branch
from
87 changes: 87 additions & 0 deletions packages/uni-cli-shared/__tests__/usingComponents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,5 +301,92 @@ export function createApp() {
`const _easycom_test = ()=>import('${inputDir}/components/test/test.vue')`
)
})
describe(`recursion`, () => {
test('base', async () => {
await testLocal(
`
const _sfc_main = {
};
const __BINDING_COMPONENTS__ = '{"index":{"name":"_component_index","type":"unknown"}}';
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {};
}
import "${filename}?vue&type=style&index=0&lang.css";
import _export_sfc from "plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
`,
{
index: '/pages/index/index',
},
``
)
})

test('use defineComponent', async () => {
await testLocal(
`import { defineComponent as _defineComponent } from "vue";
const __BINDING_COMPONENTS__ = '{"test":{"name":"test","type":"unknown"}}';

export default /* @__PURE__ */ _defineComponent({
__name: "test",
setup(__props) {
return (_ctx, _cache) => {
return {};
};
}
});
import "${inputDir}/pages/index/index.vue?vue&type=style&index=0&lang.css";
`,
{
test: '/pages/index/index',
},
``
)
})

test('export variable', async () => {
await testLocal(
`import { defineComponent as _defineComponent } from "vue";
const __BINDING_COMPONENTS__ = '{"test":{"name":"test","type":"unknown"}}';
const component = _defineComponent({
__name: "test",
setup(__props) {
return (_ctx, _cache) => {
return {};
};
}
});

export default component;

import "${inputDir}/pages/index/index.vue?vue&type=style&index=0&lang.css";
`,
{
test: '/pages/index/index',
},
``
)
})
test('use plugin-vue:export-helper', async () => {
await testLocal(
`
const _sfc_main = {
__name: 'Test'
};
const __BINDING_COMPONENTS__ = '{"test":{"name":"_component_test","type":"unknown"}}';
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {};
}
import "${filename}?vue&type=style&index=0&lang.css";
import _export_sfc from "\0plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
`,
{
test: '/pages/index/index',
},
``
)
})
})
})
})
135 changes: 133 additions & 2 deletions packages/uni-cli-shared/src/mp/usingComponents.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import {
type ExportDefaultDeclaration,
type IfStatement,
type ImportDeclaration,
type Node,
type ObjectExpression,
type ObjectProperty,
type Program,
type Statement,
type StringLiteral,
isBlockStatement,
isCallExpression,
isExportDefaultDeclaration,
isExpression,
isIdentifier,
isIfStatement,
isImportDeclaration,
isImportDefaultSpecifier,
isImportSpecifier,
isMemberExpression,
isObjectExpression,
isObjectProperty,
Expand All @@ -28,6 +34,7 @@ import { BINDING_COMPONENTS, EXTNAME_VUE_RE } from '../constants'
import { isAppVue, normalizeMiniProgramFilename, removeExt } from '../utils'
import { cleanUrl, parseVueRequest } from '../vite/utils'
import { addMiniProgramUsingComponents } from '../json/mp/jsonFile'
import path from 'path'

type BindingComponents = Record<
string,
Expand Down Expand Up @@ -190,11 +197,111 @@ export async function updateMiniProgramGlobalComponents(
}
}

function parseVueComponentName(filename: string) {
acyza marked this conversation as resolved.
Show resolved Hide resolved
let name = path.basename(removeExt(filename))
acyza marked this conversation as resolved.
Show resolved Hide resolved

const ast = scriptDescriptors.get(filename)?.ast
if (!ast) return name

// 获取默认导出定义
const exportDefaultDecliaration = ast.body.find((node) =>
isExportDefaultDeclaration(node)
) as ExportDefaultDeclaration | undefined

if (!exportDefaultDecliaration) return name

// 获取vue的defineComponent导入变量名和plugin-vue:export-helper默认导入的本地变量名
let defineComponentLocalName: string | null = null
let exportHelperLocalName: string | null = null

for (const node of ast.body) {
if (!isImportDeclaration(node)) continue
acyza marked this conversation as resolved.
Show resolved Hide resolved
if (node.source.value === 'vue') {
const importSpecifer = node.specifiers.find(
(specifer) =>
isImportSpecifier(specifer) &&
isIdentifier(specifer.imported, { name: 'defineComponent' })
)
if (isImportSpecifier(importSpecifer)) {
defineComponentLocalName = importSpecifer.local.name
}
} else if (
// plugin-vue:export-helper前可能有\0
acyza marked this conversation as resolved.
Show resolved Hide resolved
/^\0?plugin-vue:export-helper$/.test(node.source.value)
) {
const importSpecifer = node.specifiers.find((specifer) =>
isImportDefaultSpecifier(specifer)
)
if (isImportDefaultSpecifier(importSpecifer)) {
exportHelperLocalName = importSpecifer.local.name
}
}
}

let { declaration } = exportDefaultDecliaration
// 如果默认导出调用plugin-vue:export-helper默认导入的方法则取方法的第一个参数
acyza marked this conversation as resolved.
Show resolved Hide resolved
if (
exportHelperLocalName &&
isCallExpression(declaration) &&
isIdentifier(declaration.callee, { name: exportHelperLocalName }) &&
isExpression(declaration.arguments[0])
) {
declaration = declaration.arguments[0]
}

// 获取组件定义对象
let defineComponentDeclaration: ObjectExpression | null = null

// 如果declaration是变量则尝试查找该变量
if (isIdentifier(declaration)) {
acyza marked this conversation as resolved.
Show resolved Hide resolved
const { name } = declaration
for (const node of ast.body) {
if (isVariableDeclaration(node)) {
const declarator = node.declarations.find((declarator) =>
isIdentifier(declarator.id, { name })
)
if (declarator?.init) {
declaration = declarator.init
}
} else if (isExportDefaultDeclaration(node)) {
break
}
}
}

if (isObjectExpression(declaration)) {
defineComponentDeclaration = declaration
} else if (
defineComponentLocalName &&
acyza marked this conversation as resolved.
Show resolved Hide resolved
isCallExpression(declaration) &&
isIdentifier(declaration.callee, { name: defineComponentLocalName }) &&
isObjectExpression(declaration.arguments[0])
) {
defineComponentDeclaration = declaration.arguments[0]
}

if (!defineComponentDeclaration) return name

// 尝试从组件定义对象中获取组件名
for (const prop of defineComponentDeclaration.properties) {
if (
isObjectProperty(prop) &&
isIdentifier(prop.key) &&
/^(__)?name$/.test(prop.key.name) &&
isStringLiteral(prop.value)
) {
return prop.value.value || name
}
}
return name
}

function createUsingComponents(
bindingComponents: BindingComponents,
imports: ImportDeclaration[],
inputDir: string,
normalizeComponentName: (name: string) => string
normalizeComponentName: (name: string) => string,
filename?: string
) {
const usingComponents: Record<string, string> = {}
imports.forEach(({ source: { value }, specifiers: [specifier] }) => {
Expand All @@ -211,6 +318,27 @@ function createUsingComponents(
)
}
})

if (filename) {
const componentName = normalizeComponentName(
hyphenate(parseVueComponentName(filename))
)

if (
!Object.keys(bindingComponents).find(
(v) =>
normalizeComponentName(hyphenate(bindingComponents[v].tag)) ===
componentName
)
)
return usingComponents
if (!usingComponents[componentName]) {
usingComponents[componentName] = addLeadingSlash(
removeExt(normalizeMiniProgramFilename(filename, inputDir))
)
}
}

return usingComponents
}

Expand Down Expand Up @@ -250,7 +378,8 @@ export function updateMiniProgramComponentsByMainFilename(
bindingComponents,
imports,
inputDir,
normalizeComponentName
normalizeComponentName,
mainFilename
)
)
}
Expand Down Expand Up @@ -347,6 +476,7 @@ interface ParseDescriptor {
}
export interface ScriptDescriptor extends TemplateDescriptor {
setupBindingComponents: BindingComponents
ast: Program
}

async function parseGlobalDescriptor(
Expand Down Expand Up @@ -396,6 +526,7 @@ export async function parseScriptDescriptor(
bindingComponents: parseComponents(ast),
setupBindingComponents: findBindingComponents(ast.body),
imports,
ast,
}

scriptDescriptors.set(filename, descriptor)
Expand Down