From 1c720c3d619a1a512ef381445fa6eacda860263d Mon Sep 17 00:00:00 2001 From: Simon He <13917107469@163.com> Date: Thu, 9 Mar 2023 21:40:48 +0800 Subject: [PATCH 1/4] fix: script style auto import --- examples/vite-vue3/components.d.ts | 15 ++- src/core/transforms/component.ts | 30 +++++- src/core/transforms/directive/index.ts | 4 +- src/core/utils.ts | 7 ++ test/__snapshots__/dts.test.ts.snap | 2 +- test/__snapshots__/search.test.ts.snap | 2 +- .../stringifyComponentImport.test.ts.snap | 2 +- test/__snapshots__/transform.test.ts.snap | 8 +- .../__snapshots__/element-plus.test.ts.snap | 98 ++++++++++++++++++- test/resolvers/element-plus.test.ts | 95 ++++++++++++++++++ 10 files changed, 244 insertions(+), 19 deletions(-) diff --git a/examples/vite-vue3/components.d.ts b/examples/vite-vue3/components.d.ts index 521affc5..e83e7151 100644 --- a/examples/vite-vue3/components.d.ts +++ b/examples/vite-vue3/components.d.ts @@ -1,5 +1,7 @@ -// generated by unplugin-vue-components -// We suggest you to commit this file into source control +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 import '@vue/runtime-core' @@ -10,11 +12,17 @@ declare module '@vue/runtime-core' { Avatar: typeof import('./src/components/global/avatar.vue')['default'] Book: typeof import('./src/components/book/index.vue')['default'] CollapseCollapseFolderAndCollapseFolderAndComponentPrefixes: typeof import('./src/components/collapse/collapseFolderAnd/CollapseFolderAndComponentPrefixes.vue')['default'] + CollapseCollapseFolderCollapseFolderAndComponentFromRoot: typeof import('./src/components/collapse/collapseFolder/CollapseFolderAndComponentFromRoot.vue')['default'] + CollapseCollapseFolderFolderAndComponentPartially: typeof import('./src/components/collapse/collapseFolder/FolderAndComponentPartially.vue')['default'] ComponentA: typeof import('./src/components/ComponentA.vue')['default'] ComponentAsync: typeof import('./src/components/ComponentAsync.vue')['default'] ComponentB: typeof import('./src/components/ComponentB.vue')['default'] ComponentC: typeof import('./src/components/component-c.vue')['default'] ComponentD: typeof import('./src/components/ComponentD.vue')['default'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + ElMessage: typeof import('element-plus/es')['ElMessage'] + ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] IFaSolidDiceFive: typeof import('~icons/fa-solid/dice-five')['default'] IHeroiconsOutlineMenuAlt2: typeof import('~icons/heroicons-outline/menu-alt2')['default'] 'IMdi:diceD12': typeof import('~icons/mdi/dice-d12')['default'] @@ -32,4 +40,7 @@ declare module '@vue/runtime-core' { VanRadioGroup: typeof import('vant/es')['RadioGroup'] VanRate: typeof import('vant/es')['Rate'] } + export interface ComponentCustomProperties { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + } } diff --git a/src/core/transforms/component.ts b/src/core/transforms/component.ts index 9cd36b32..a57f510e 100644 --- a/src/core/transforms/component.ts +++ b/src/core/transforms/component.ts @@ -1,6 +1,6 @@ import Debug from 'debug' import type MagicString from 'magic-string' -import { pascalCase, stringifyComponentImport } from '../utils' +import { pascalCase, removeDuplicatesPrepend, stringifyComponentImport, stringifyImport } from '../utils' import type { Context } from '../context' import type { ResolveResult } from '../transformer' import type { SupportedTransformer } from '../..' @@ -57,11 +57,37 @@ export default async function transformComponent(code: string, transformer: Supp const component = await ctx.findComponent(name, 'component', [sfcPath]) if (component) { const varName = `__unplugin_components_${no}` - s.prepend(`${stringifyComponentImport({ ...component, as: varName }, ctx)};\n`) + removeDuplicatesPrepend(`${stringifyComponentImport({ ...component, as: varName }, ctx)}`, s) no += 1 replace(varName) } } + // Prevent templates and imports from being duplicated + const onlyStyle = onlyInjectStyle(code).filter(item => !results.some(({ rawName }) => rawName === item)) + + for (const rawName of onlyStyle) { + const name = pascalCase(rawName) + ctx.updateUsageMap(sfcPath, [name]) + const component = await ctx.findComponent(name, 'component', [sfcPath]) + if (!component || !component.sideEffects) + continue + + removeDuplicatesPrepend(`${(component.sideEffects as string[]).map(stringifyImport).join(';')}`, s) + } + debug(`^ (${no})`) } + +function onlyInjectStyle(code: string) { + const results: string[] = [] + const matcher = code.match(/const __returned__ = {(.*)}/) + if (matcher) { + for (const match of matcher[1].matchAll(/get (\w+)\(\)/g) || []) { + const matchedName = match[1] + if (!results.includes(matchedName)) + results.push(matchedName) + } + } + return results +} diff --git a/src/core/transforms/directive/index.ts b/src/core/transforms/directive/index.ts index 30c07d29..38ff2b20 100644 --- a/src/core/transforms/directive/index.ts +++ b/src/core/transforms/directive/index.ts @@ -1,6 +1,6 @@ import Debug from 'debug' import type MagicString from 'magic-string' -import { pascalCase, stringifyComponentImport } from '../../utils' +import { pascalCase, removeDuplicatesPrepend, stringifyComponentImport } from '../../utils' import type { Context } from '../../context' import type { SupportedTransformer } from '../../..' import { DIRECTIVE_IMPORT_PREFIX } from '../../constants' @@ -23,7 +23,7 @@ export default async function transformDirective(code: string, transformer: Supp continue const varName = `__unplugin_directives_${no}` - s.prepend(`${stringifyComponentImport({ ...directive, as: varName }, ctx)};\n`) + removeDuplicatesPrepend(`${stringifyComponentImport({ ...directive, as: varName }, ctx)}`, s) no += 1 replace(varName) } diff --git a/src/core/utils.ts b/src/core/utils.ts index 87f69866..681d3c1b 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -225,3 +225,10 @@ export function resolveImportPath(importName: string): string | undefined { preserveSymlinks: false, }) } + +export function removeDuplicatesPrepend(content: string, s: MagicString) { + const r = !(s as any).intro + ? `${content};` + : `${content.split(';').filter(item => !(s as any).intro.includes(item)).join(';')};\n` + return s.prepend(r) +} diff --git a/test/__snapshots__/dts.test.ts.snap b/test/__snapshots__/dts.test.ts.snap index 99dc4c5f..8f35d023 100644 --- a/test/__snapshots__/dts.test.ts.snap +++ b/test/__snapshots__/dts.test.ts.snap @@ -1,4 +1,4 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`dts > components only 1`] = ` "/* eslint-disable */ diff --git a/test/__snapshots__/search.test.ts.snap b/test/__snapshots__/search.test.ts.snap index 27284a11..e635cc73 100644 --- a/test/__snapshots__/search.test.ts.snap +++ b/test/__snapshots__/search.test.ts.snap @@ -1,4 +1,4 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`search > should with namespace & collapse 1`] = ` [ diff --git a/test/__snapshots__/stringifyComponentImport.test.ts.snap b/test/__snapshots__/stringifyComponentImport.test.ts.snap index 002db3de..a6403cb5 100644 --- a/test/__snapshots__/stringifyComponentImport.test.ts.snap +++ b/test/__snapshots__/stringifyComponentImport.test.ts.snap @@ -1,4 +1,4 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`stringifyComponentImport > importName 1`] = `"import { a as Test } from 'test'"`; diff --git a/test/__snapshots__/transform.test.ts.snap b/test/__snapshots__/transform.test.ts.snap index 21f607f4..92147690 100644 --- a/test/__snapshots__/transform.test.ts.snap +++ b/test/__snapshots__/transform.test.ts.snap @@ -1,10 +1,9 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Component and directive as same name > vue2 transform should work 1`] = ` { "code": "/* unplugin-vue-components disabled */import __unplugin_directives_0 from 'test/directive/Loading'; import __unplugin_components_0 from 'test/component/Loading'; - var render = function () { this.$options.directives[\\"loading\\"] = __unplugin_directives_0; var _vm = this @@ -27,7 +26,6 @@ exports[`Component and directive as same name > vue2.7 transform should work 1`] { "code": "/* unplugin-vue-components disabled */import __unplugin_directives_0 from 'test/directive/Loading'; import __unplugin_components_0 from 'test/component/Div'; - import { defineComponent as _defineComponent } from \\"vue\\"; const _sfc_main = /* @__PURE__ */ _defineComponent({ __name: \\"App\\", @@ -48,7 +46,6 @@ exports[`Component and directive as same name > vue3 transform should work 1`] = { "code": "/* unplugin-vue-components disabled */import __unplugin_directives_0 from 'test/directive/ElInfiniteScroll'; import __unplugin_components_0 from 'test/component/ElInfiniteScroll'; - const render = (_ctx, _cache) => { const _component_el_infinite_scroll = __unplugin_components_0 const _directive_el_infinite_scroll = __unplugin_directives_0 @@ -67,7 +64,6 @@ exports[`transform > vue2 transform should work 1`] = ` { "code": "/* unplugin-vue-components disabled */import __unplugin_directives_0 from 'test/directive/Loading'; import __unplugin_components_0 from 'test/component/TestComp'; - var render = function () { this.$options.directives[\\"loading\\"] = __unplugin_directives_0; var _vm = this @@ -89,7 +85,6 @@ this.$options.directives[\\"loading\\"] = __unplugin_directives_0; exports[`transform > vue2 transform with jsx should work 1`] = ` { "code": "/* unplugin-vue-components disabled */import __unplugin_components_0 from 'test/component/TestComp'; - export default { render(){ return h(__unplugin_components_0, { @@ -107,7 +102,6 @@ exports[`transform > vue3 transform should work 1`] = ` { "code": "/* unplugin-vue-components disabled */import __unplugin_directives_0 from 'test/directive/Loading'; import __unplugin_components_0 from 'test/component/TestComp'; - const render = (_ctx, _cache) => { const _component_test_comp = __unplugin_components_0 const _directive_loading = __unplugin_directives_0 diff --git a/test/resolvers/__snapshots__/element-plus.test.ts.snap b/test/resolvers/__snapshots__/element-plus.test.ts.snap index 9b448706..9b035d83 100644 --- a/test/resolvers/__snapshots__/element-plus.test.ts.snap +++ b/test/resolvers/__snapshots__/element-plus.test.ts.snap @@ -1,11 +1,103 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Element Plus Resolver > auto load dynamically created component styles 1`] = ` +{ + "code": "/* unplugin-vue-components disabled */import { ElLoadingDirective as __unplugin_directives_0 } from 'element-plus/es';import 'element-plus/es/components/loading/style/css'; +import 'element-plus/es/components/message-box/style/css'; +import 'element-plus/es/components/message/style/css'; +import { ElConfigProvider as __unplugin_components_1 } from 'element-plus/es';import 'element-plus/es/components/config-provider/style/css'; +import { ElButton as __unplugin_components_0 } from 'element-plus/es';import 'element-plus/es/components/base/style/css';import 'element-plus/es/components/button/style/css'; + import { ref } from 'vue' + import { ElMessage, ElMessageBox } from 'element-plus' + + const _sfc_main = { + __name: 'App', + setup(__props, { expose }) { + expose(); + + const loading = ref(true) + const test1 = (_) => { + ElMessage.success('message') + } + const test2 = (_) => { + ElMessageBox.confirm( + 'ElMessageBox', + '提示', + { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning', + center: true, + }, + ) + } + + const __returned__ = { loading, test1, test2, ref, get ElMessage() { return ElMessage }, get ElMessageBox() { return ElMessageBox } } + Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true }) + return __returned__ + } + + } + import { createTextVNode as _createTextVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, resolveDirective as _resolveDirective, openBlock as _openBlock, createBlock as _createBlock, withDirectives as _withDirectives, Fragment as _Fragment, createElementBlock as _createElementBlock } from \\"vue\\" + + function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_el_button = __unplugin_components_0 + const _component_el_config_provider = __unplugin_components_1 + const _directive_loading = __unplugin_directives_0 + + return (_openBlock(), _createElementBlock(_Fragment, null, [ + _createVNode(_component_el_config_provider, { namespace: \\"ep\\" }, { + default: _withCtx(() => [ + _createVNode(_component_el_button, { onClick: $setup.test1 }, { + default: _withCtx(() => [ + _createTextVNode(\\" el-message \\") + ]), + _: 1 /* STABLE */ + }), + _createVNode(_component_el_button, { onClick: $setup.test2 }, { + default: _withCtx(() => [ + _createTextVNode(\\" el-messageBox \\") + ]), + _: 1 /* STABLE */ + }), + _withDirectives((_openBlock(), _createBlock(_component_el_button, null, { + default: _withCtx(() => [ + _createTextVNode(\\" loading... \\") + ]), + _: 1 /* STABLE */ + })), [ + [_directive_loading, $setup.loading] + ]) + ]), + _: 1 /* STABLE */ + }), + _createVNode($setup[\\"ElMessage\\"]) + ], 64 /* STABLE_FRAGMENT */)) + } + + + _sfc_main.__hmrId = \\"7a7a37b1\\" + typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main) + import.meta.hot.accept(mod => { + if (!mod) return + const { default: updated, _rerender_only } = mod + if (_rerender_only) { + __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render) + } else { + __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated) + } + }) + import _export_sfc from 'plugin-vue:export-helper' + export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__file',\\"/Users/hejian/Documents/Github/unplugin-vue-components/examples/vite-vue3/src/App.vue\\"]]) +", +} +`; exports[`Element Plus Resolver > components and directives should be transformed 1`] = ` { - "code": "/* unplugin-vue-components disabled */import { ElLoadingDirective as __unplugin_directives_0 } from 'element-plus/es';import 'element-plus/es/components/base/style/css';import 'element-plus/es/components/loading/style/css'; + "code": "/* unplugin-vue-components disabled */import { ElLoadingDirective as __unplugin_directives_0 } from 'element-plus/es';import 'element-plus/es/components/loading/style/css'; import { Apple as __unplugin_components_1 } from '@element-plus/icons-vue'; import { ElButton as __unplugin_components_0 } from 'element-plus/es';import 'element-plus/es/components/base/style/css';import 'element-plus/es/components/button/style/css'; - (_ctx, _cache) => { const _component_el_button = __unplugin_components_0 const _component_el_icon_apple = __unplugin_components_1 diff --git a/test/resolvers/element-plus.test.ts b/test/resolvers/element-plus.test.ts index a8ce6032..22476f08 100644 --- a/test/resolvers/element-plus.test.ts +++ b/test/resolvers/element-plus.test.ts @@ -28,4 +28,99 @@ describe('Element Plus Resolver', () => { ctx.sourcemap = false expect(await ctx.transform(code, '')).toMatchSnapshot() }) + + it('auto load dynamically created component styles', async () => { + const code = ` + import { ref } from 'vue' + import { ElMessage, ElMessageBox } from 'element-plus' + + const _sfc_main = { + __name: 'App', + setup(__props, { expose }) { + expose(); + + const loading = ref(true) + const test1 = (_) => { + ElMessage.success('message') + } + const test2 = (_) => { + ElMessageBox.confirm( + 'ElMessageBox', + '提示', + { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning', + center: true, + }, + ) + } + + const __returned__ = { loading, test1, test2, ref, get ElMessage() { return ElMessage }, get ElMessageBox() { return ElMessageBox } } + Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true }) + return __returned__ + } + + } + import { createTextVNode as _createTextVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, resolveDirective as _resolveDirective, openBlock as _openBlock, createBlock as _createBlock, withDirectives as _withDirectives, Fragment as _Fragment, createElementBlock as _createElementBlock } from "vue" + + function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_el_button = _resolveComponent("el-button") + const _component_el_config_provider = _resolveComponent("el-config-provider") + const _directive_loading = _resolveDirective("loading") + + return (_openBlock(), _createElementBlock(_Fragment, null, [ + _createVNode(_component_el_config_provider, { namespace: "ep" }, { + default: _withCtx(() => [ + _createVNode(_component_el_button, { onClick: $setup.test1 }, { + default: _withCtx(() => [ + _createTextVNode(" el-message ") + ]), + _: 1 /* STABLE */ + }), + _createVNode(_component_el_button, { onClick: $setup.test2 }, { + default: _withCtx(() => [ + _createTextVNode(" el-messageBox ") + ]), + _: 1 /* STABLE */ + }), + _withDirectives((_openBlock(), _createBlock(_component_el_button, null, { + default: _withCtx(() => [ + _createTextVNode(" loading... ") + ]), + _: 1 /* STABLE */ + })), [ + [_directive_loading, $setup.loading] + ]) + ]), + _: 1 /* STABLE */ + }), + _createVNode($setup["ElMessage"]) + ], 64 /* STABLE_FRAGMENT */)) + } + + + _sfc_main.__hmrId = "7a7a37b1" + typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main) + import.meta.hot.accept(mod => { + if (!mod) return + const { default: updated, _rerender_only } = mod + if (_rerender_only) { + __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render) + } else { + __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated) + } + }) + import _export_sfc from 'plugin-vue:export-helper' + export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__file',"/Users/hejian/Documents/Github/unplugin-vue-components/examples/vite-vue3/src/App.vue"]]) +` + + const ctx = new Context({ + resolvers: [ElementPlusResolver({})], + transformer: 'vue3', + directives: true, + }) + ctx.sourcemap = false + expect(await ctx.transform(code, '')).toMatchSnapshot() + }) }) From 9336b2dc81b62abbcf034dc7d5450200dce58ffc Mon Sep 17 00:00:00 2001 From: Simon He <13917107469@163.com> Date: Thu, 9 Mar 2023 21:44:43 +0800 Subject: [PATCH 2/4] chore: update --- examples/vite-vue3/components.d.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/examples/vite-vue3/components.d.ts b/examples/vite-vue3/components.d.ts index e83e7151..521affc5 100644 --- a/examples/vite-vue3/components.d.ts +++ b/examples/vite-vue3/components.d.ts @@ -1,7 +1,5 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// Generated by unplugin-vue-components +// generated by unplugin-vue-components +// We suggest you to commit this file into source control // Read more: https://github.com/vuejs/core/pull/3399 import '@vue/runtime-core' @@ -12,17 +10,11 @@ declare module '@vue/runtime-core' { Avatar: typeof import('./src/components/global/avatar.vue')['default'] Book: typeof import('./src/components/book/index.vue')['default'] CollapseCollapseFolderAndCollapseFolderAndComponentPrefixes: typeof import('./src/components/collapse/collapseFolderAnd/CollapseFolderAndComponentPrefixes.vue')['default'] - CollapseCollapseFolderCollapseFolderAndComponentFromRoot: typeof import('./src/components/collapse/collapseFolder/CollapseFolderAndComponentFromRoot.vue')['default'] - CollapseCollapseFolderFolderAndComponentPartially: typeof import('./src/components/collapse/collapseFolder/FolderAndComponentPartially.vue')['default'] ComponentA: typeof import('./src/components/ComponentA.vue')['default'] ComponentAsync: typeof import('./src/components/ComponentAsync.vue')['default'] ComponentB: typeof import('./src/components/ComponentB.vue')['default'] ComponentC: typeof import('./src/components/component-c.vue')['default'] ComponentD: typeof import('./src/components/ComponentD.vue')['default'] - ElButton: typeof import('element-plus/es')['ElButton'] - ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] - ElMessage: typeof import('element-plus/es')['ElMessage'] - ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] IFaSolidDiceFive: typeof import('~icons/fa-solid/dice-five')['default'] IHeroiconsOutlineMenuAlt2: typeof import('~icons/heroicons-outline/menu-alt2')['default'] 'IMdi:diceD12': typeof import('~icons/mdi/dice-d12')['default'] @@ -40,7 +32,4 @@ declare module '@vue/runtime-core' { VanRadioGroup: typeof import('vant/es')['RadioGroup'] VanRate: typeof import('vant/es')['Rate'] } - export interface ComponentCustomProperties { - vLoading: typeof import('element-plus/es')['ElLoadingDirective'] - } } From d5388c6aa9d6a20152d250796baf5b339cfa4e06 Mon Sep 17 00:00:00 2001 From: Simon He <13917107469@163.com> Date: Fri, 10 Mar 2023 11:20:57 +0800 Subject: [PATCH 3/4] chore: add ant-design-vue test --- src/core/resolvers/antdv.ts | 7 +- src/core/transforms/component.ts | 34 ++++-- .../__snapshots__/ant-design-vue.test.ts.snap | 105 ++++++++++++++++++ test/resolvers/ant-design-vue.test.ts | 66 +++++++++++ 4 files changed, 200 insertions(+), 12 deletions(-) create mode 100644 test/resolvers/__snapshots__/ant-design-vue.test.ts.snap create mode 100644 test/resolvers/ant-design-vue.test.ts diff --git a/src/core/resolvers/antdv.ts b/src/core/resolvers/antdv.ts index e3386628..239861c3 100644 --- a/src/core/resolvers/antdv.ts +++ b/src/core/resolvers/antdv.ts @@ -85,11 +85,14 @@ const matchComponents: IMatcher[] = [ pattern: /^Layout/, styleDir: 'layout', }, + { + pattern: /^Message/, + styleDir: 'message', + }, { pattern: /^Menu|^SubMenu/, styleDir: 'menu', }, - { pattern: /^Table/, styleDir: 'table', @@ -237,7 +240,7 @@ function getSideEffects(compName: string, options: AntDesignVueResolverOptions): return `${packageName}/${lib}/${styleDir}/style/css` } } -const primitiveNames = ['Affix', 'Anchor', 'AnchorLink', 'AutoComplete', 'AutoCompleteOptGroup', 'AutoCompleteOption', 'Alert', 'Avatar', 'AvatarGroup', 'BackTop', 'Badge', 'BadgeRibbon', 'Breadcrumb', 'BreadcrumbItem', 'BreadcrumbSeparator', 'Button', 'ButtonGroup', 'Calendar', 'Card', 'CardGrid', 'CardMeta', 'Collapse', 'CollapsePanel', 'Carousel', 'Cascader', 'Checkbox', 'CheckboxGroup', 'Col', 'Comment', 'ConfigProvider', 'DatePicker', 'MonthPicker', 'WeekPicker', 'RangePicker', 'QuarterPicker', 'Descriptions', 'DescriptionsItem', 'Divider', 'Dropdown', 'DropdownButton', 'Drawer', 'Empty', 'Form', 'FormItem', 'FormItemRest', 'Grid', 'Input', 'InputGroup', 'InputPassword', 'InputSearch', 'Textarea', 'Image', 'ImagePreviewGroup', 'InputNumber', 'Layout', 'LayoutHeader', 'LayoutSider', 'LayoutFooter', 'LayoutContent', 'List', 'ListItem', 'ListItemMeta', 'Menu', 'MenuDivider', 'MenuItem', 'MenuItemGroup', 'SubMenu', 'Mentions', 'MentionsOption', 'Modal', 'Statistic', 'StatisticCountdown', 'PageHeader', 'Pagination', 'Popconfirm', 'Popover', 'Progress', 'Radio', 'RadioButton', 'RadioGroup', 'Rate', 'Result', 'Row', 'Select', 'SelectOptGroup', 'SelectOption', 'Skeleton', 'SkeletonButton', 'SkeletonAvatar', 'SkeletonInput', 'SkeletonImage', 'Slider', 'Space', 'Spin', 'Steps', 'Step', 'Switch', 'Table', 'TableColumn', 'TableColumnGroup', 'TableSummary', 'TableSummaryRow', 'TableSummaryCell', 'Transfer', 'Tree', 'TreeNode', 'DirectoryTree', 'TreeSelect', 'TreeSelectNode', 'Tabs', 'TabPane', 'Tag', 'CheckableTag', 'TimePicker', 'TimeRangePicker', 'Timeline', 'TimelineItem', 'Tooltip', 'Typography', 'TypographyLink', 'TypographyParagraph', 'TypographyText', 'TypographyTitle', 'Upload', 'UploadDragger', 'LocaleProvider'] +const primitiveNames = ['Affix', 'Anchor', 'AnchorLink', 'AutoComplete', 'AutoCompleteOptGroup', 'AutoCompleteOption', 'Alert', 'Avatar', 'AvatarGroup', 'BackTop', 'Badge', 'BadgeRibbon', 'Breadcrumb', 'BreadcrumbItem', 'BreadcrumbSeparator', 'Button', 'ButtonGroup', 'Calendar', 'Card', 'CardGrid', 'CardMeta', 'Collapse', 'CollapsePanel', 'Carousel', 'Cascader', 'Checkbox', 'CheckboxGroup', 'Col', 'Comment', 'ConfigProvider', 'DatePicker', 'MonthPicker', 'WeekPicker', 'RangePicker', 'QuarterPicker', 'Descriptions', 'DescriptionsItem', 'Divider', 'Dropdown', 'DropdownButton', 'Drawer', 'Empty', 'Form', 'FormItem', 'FormItemRest', 'Grid', 'Input', 'InputGroup', 'InputPassword', 'InputSearch', 'Textarea', 'Image', 'ImagePreviewGroup', 'InputNumber', 'Layout', 'LayoutHeader', 'LayoutSider', 'LayoutFooter', 'LayoutContent', 'List', 'ListItem', 'ListItemMeta', 'Menu', 'MenuDivider', 'MenuItem', 'MenuItemGroup', 'SubMenu', 'Mentions', 'MentionsOption', 'Modal', 'Statistic', 'StatisticCountdown', 'PageHeader', 'Pagination', 'Popconfirm', 'Popover', 'Progress', 'Radio', 'RadioButton', 'RadioGroup', 'Rate', 'Result', 'Row', 'Select', 'SelectOptGroup', 'SelectOption', 'Skeleton', 'SkeletonButton', 'SkeletonAvatar', 'SkeletonInput', 'SkeletonImage', 'Slider', 'Space', 'Spin', 'Steps', 'Step', 'Switch', 'Table', 'TableColumn', 'TableColumnGroup', 'TableSummary', 'TableSummaryRow', 'TableSummaryCell', 'Transfer', 'Tree', 'TreeNode', 'DirectoryTree', 'TreeSelect', 'TreeSelectNode', 'Tabs', 'TabPane', 'Tag', 'CheckableTag', 'TimePicker', 'TimeRangePicker', 'Timeline', 'TimelineItem', 'Tooltip', 'Typography', 'TypographyLink', 'TypographyParagraph', 'TypographyText', 'TypographyTitle', 'Upload', 'UploadDragger', 'LocaleProvider', 'Message'] const prefix = 'A' let antdvNames: Set diff --git a/src/core/transforms/component.ts b/src/core/transforms/component.ts index a57f510e..a92e54e9 100644 --- a/src/core/transforms/component.ts +++ b/src/core/transforms/component.ts @@ -1,3 +1,4 @@ +import { toArray } from '@antfu/utils' import Debug from 'debug' import type MagicString from 'magic-string' import { pascalCase, removeDuplicatesPrepend, stringifyComponentImport, stringifyImport } from '../utils' @@ -45,9 +46,19 @@ const resolveVue3 = (code: string, s: MagicString) => { return results } +const componentUi = [{ + prefix: 'A', + name: 'ant-design-vue', +}, { + prefix: '', + name: 'element-plus', +}, { + prefix: '', + name: 'vant', +}] + export default async function transformComponent(code: string, transformer: SupportedTransformer, s: MagicString, ctx: Context, sfcPath: string) { let no = 0 - const results = transformer === 'vue2' ? resolveVue2(code, s) : resolveVue3(code, s) for (const { rawName, replace } of results) { @@ -65,15 +76,15 @@ export default async function transformComponent(code: string, transformer: Supp // Prevent templates and imports from being duplicated const onlyStyle = onlyInjectStyle(code).filter(item => !results.some(({ rawName }) => rawName === item)) - for (const rawName of onlyStyle) { const name = pascalCase(rawName) ctx.updateUsageMap(sfcPath, [name]) const component = await ctx.findComponent(name, 'component', [sfcPath]) if (!component || !component.sideEffects) continue + const sideEffects = toArray(component.sideEffects) - removeDuplicatesPrepend(`${(component.sideEffects as string[]).map(stringifyImport).join(';')}`, s) + removeDuplicatesPrepend(`${sideEffects.map(stringifyImport).join(';')}`, s) } debug(`^ (${no})`) @@ -81,13 +92,16 @@ export default async function transformComponent(code: string, transformer: Supp function onlyInjectStyle(code: string) { const results: string[] = [] - const matcher = code.match(/const __returned__ = {(.*)}/) - if (matcher) { - for (const match of matcher[1].matchAll(/get (\w+)\(\)/g) || []) { - const matchedName = match[1] - if (!results.includes(matchedName)) - results.push(matchedName) - } + for (const ui of componentUi) { + const { name, prefix } = ui + const ulReg = new RegExp(`import {(.*)} from ['"]${name}["']`) + const matcher = code.match(ulReg) + if (!matcher) + continue + results.push(...matcher[1].split(',').map((i) => { + i = i.trim() + return `${prefix}${i[0].toUpperCase() + i.slice(1)}` + })) } return results } diff --git a/test/resolvers/__snapshots__/ant-design-vue.test.ts.snap b/test/resolvers/__snapshots__/ant-design-vue.test.ts.snap new file mode 100644 index 00000000..35ce27f0 --- /dev/null +++ b/test/resolvers/__snapshots__/ant-design-vue.test.ts.snap @@ -0,0 +1,105 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Ant Design Vue Resolver > components and directives should be transformed 1`] = ` +{ + "code": "/* unplugin-vue-components disabled */import 'ant-design-vue/es/message/style/css'; +import { Button as __unplugin_components_0 } from 'ant-design-vue/es';import 'ant-design-vue/es/button/style/css'; + import { defineComponent as _defineComponent } from \\"vue\\"; +' + + 'import { message } from \\"ant-design-vue\\"; +' + + 'import { ElMessage } from \\"element-plus\\"; +' + + 'const _sfc_main = /* @__PURE__ */ _defineComponent({ +' + + ' __name: \\"App\\", +' + + ' setup(__props, { expose }) { +' + + ' expose(); +' + + ' const test1 = () => { +' + + ' ElMessage.success(\\"message\\"); +' + + ' }; +' + + ' const handleClick = () => { +' + + ' message.success(\\"\\\\u6D4B\\\\u8BD5 jsx \\\\u4F7F\\\\u7528 a-button \\\\u548C mesage\\"); +' + + ' }; +' + + ' const __returned__ = { test1, handleClick }; +' + + ' Object.defineProperty(__returned__, \\"__isScriptSetup\\", { enumerable: false, value: true }); +' + + ' return __returned__; +' + + ' } +' + + '}); +' + + 'import { createTextVNode as _createTextVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\"; +' + + 'const _withScopeId = (n) => (_pushScopeId(\\"data-v-7a7a37b1\\"), n = n(), _popScopeId(), n); +' + + 'const _hoisted_1 = { class: \\"block\\" }; +' + + 'function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { +' + + ' const _component_a_button = __unplugin_components_0; +' + + ' return _openBlock(), _createElementBlock(\\"div\\", _hoisted_1, [ +' + + ' _createVNode(_component_a_button, { onClick: $setup.handleClick }, { +' + + ' default: _withCtx(() => [ +' + + ' _createTextVNode(\\" \\\\u70B9\\\\u6211 \\") +' + + ' ]), +' + + ' _: 1 +' + + ' /* STABLE */ +' + + ' }) +' + + ' ]); +' + + '} +' + + 'import \\"/Users/hejian/Documents/Github/unplugin-vue-components/examples/vite-vue3/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css\\"; +' + + '_sfc_main.__hmrId = \\"7a7a37b1\\"; +' + + 'typeof __VUE_HMR_RUNTIME__ !== \\"undefined\\" && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main); +' + + 'import.meta.hot.accept((mod) => { +' + + ' if (!mod) +' + + ' return; +' + + ' const { default: updated, _rerender_only } = mod; +' + + ' if (_rerender_only) { +' + + ' __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render); +' + + ' } else { +' + + ' __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated); +' + + ' } +' + + '}); +' + + 'import _export_sfc from \\"\\\\0plugin-vue:export-helper\\"; +' + + 'export default /* @__PURE__ */ _export_sfc(_sfc_main, [[\\"render\\", _sfc_render], [\\"__scopeId\\", \\"data-v-7a7a37b1\\"], [\\"__file\\", \\"/Users/hejian/Documents/Github/unplugin-vue-components/examples/vite-vue3/src/App.vue\\"]]); + +", +} +`; diff --git a/test/resolvers/ant-design-vue.test.ts b/test/resolvers/ant-design-vue.test.ts new file mode 100644 index 00000000..95299b54 --- /dev/null +++ b/test/resolvers/ant-design-vue.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from 'vitest' +import { AntDesignVueResolver } from '../../src/resolvers' +import { Context } from '../../src/core/context' + +describe('Ant Design Vue Resolver', () => { + it('components and directives should be transformed', async () => { + const code = ` + import { defineComponent as _defineComponent } from "vue";\n' + + 'import { message } from "ant-design-vue";\n' + + 'import { ElMessage } from "element-plus";\n' + + 'const _sfc_main = /* @__PURE__ */ _defineComponent({\n' + + ' __name: "App",\n' + + ' setup(__props, { expose }) {\n' + + ' expose();\n' + + ' const test1 = () => {\n' + + ' ElMessage.success("message");\n' + + ' };\n' + + ' const handleClick = () => {\n' + + ' message.success("\\u6D4B\\u8BD5 jsx \\u4F7F\\u7528 a-button \\u548C mesage");\n' + + ' };\n' + + ' const __returned__ = { test1, handleClick };\n' + + ' Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });\n' + + ' return __returned__;\n' + + ' }\n' + + '});\n' + + 'import { createTextVNode as _createTextVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue";\n' + + 'const _withScopeId = (n) => (_pushScopeId("data-v-7a7a37b1"), n = n(), _popScopeId(), n);\n' + + 'const _hoisted_1 = { class: "block" };\n' + + 'function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {\n' + + ' const _component_a_button = _resolveComponent("a-button");\n' + + ' return _openBlock(), _createElementBlock("div", _hoisted_1, [\n' + + ' _createVNode(_component_a_button, { onClick: $setup.handleClick }, {\n' + + ' default: _withCtx(() => [\n' + + ' _createTextVNode(" \\u70B9\\u6211 ")\n' + + ' ]),\n' + + ' _: 1\n' + + ' /* STABLE */\n' + + ' })\n' + + ' ]);\n' + + '}\n' + + 'import "/Users/hejian/Documents/Github/unplugin-vue-components/examples/vite-vue3/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";\n' + + '_sfc_main.__hmrId = "7a7a37b1";\n' + + 'typeof __VUE_HMR_RUNTIME__ !== "undefined" && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main);\n' + + 'import.meta.hot.accept((mod) => {\n' + + ' if (!mod)\n' + + ' return;\n' + + ' const { default: updated, _rerender_only } = mod;\n' + + ' if (_rerender_only) {\n' + + ' __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render);\n' + + ' } else {\n' + + ' __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated);\n' + + ' }\n' + + '});\n' + + 'import _export_sfc from "\\0plugin-vue:export-helper";\n' + + 'export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-7a7a37b1"], ["__file", "/Users/hejian/Documents/Github/unplugin-vue-components/examples/vite-vue3/src/App.vue"]]);\n +` + + const ctx = new Context({ + resolvers: [AntDesignVueResolver({})], + transformer: 'vue3', + directives: true, + }) + ctx.sourcemap = false + expect(await ctx.transform(code, '')).toMatchSnapshot() + }) +}) From 46a1850fdd9d7c4ebfcb06afb5853ceabab07887 Mon Sep 17 00:00:00 2001 From: Simon He <13917107469@163.com> Date: Mon, 13 Mar 2023 14:07:27 +0800 Subject: [PATCH 4/4] chore: update --- src/core/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/utils.ts b/src/core/utils.ts index 681d3c1b..f9e6b652 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -6,6 +6,7 @@ import { getPackageInfo, isPackageExists, } from 'local-pkg' +import type MagicString from 'magic-string' import type { ComponentInfo, ImportInfo, ImportInfoLegacy, Options, ResolvedOptions } from '../types' import type { Context } from './context' import { DISABLE_COMMENT } from './constants' pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy