From 1e4a1f4a8e0f8a788f5ae8e7043308a348cca6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Tue, 23 May 2023 19:19:17 +0800 Subject: [PATCH] feat: add `exportExpose` (#376) --- .changeset/cool-badgers-hang.md | 9 ++ docs/guide/configurations.md | 2 + packages/export-expose/README.md | 3 + packages/export-expose/package.json | 102 +++++++++++++++ packages/export-expose/src/api.ts | 1 + packages/export-expose/src/core/index.ts | 119 ++++++++++++++++++ packages/export-expose/src/esbuild.ts | 3 + packages/export-expose/src/index.ts | 47 +++++++ packages/export-expose/src/rollup.ts | 3 + packages/export-expose/src/vite.ts | 3 + packages/export-expose/src/webpack.ts | 3 + .../tests/__snapshots__/fixtures.test.ts.snap | 69 ++++++++++ packages/export-expose/tests/fixtures.test.ts | 13 ++ .../export-expose/tests/fixtures/basic.vue | 12 ++ .../tests/fixtures/error/export-all.vue | 3 + .../tests/fixtures/error/export-default.vue | 5 + .../tests/fixtures/export-from-other.vue | 3 + .../tests/fixtures/namespace-export.vue | 3 + .../export-expose/tests/fixtures/rename.vue | 7 ++ .../export-expose/tests/fixtures/types.ts | 5 + packages/export-expose/tsup.config.ts | 1 + packages/macros/package.json | 1 + packages/macros/src/index.ts | 17 ++- packages/volar/index.d.ts | 10 ++ packages/volar/package.json | 1 + packages/volar/src/common.ts | 5 +- packages/volar/src/export-expose.ts | 97 ++++++++++++++ packages/volar/src/export-props.ts | 16 ++- playground/vue3/rollup.config.mjs | 6 + playground/vue3/src/App.vue | 6 + .../vue3/src/examples/export-expose/child.vue | 6 + .../vue3/src/examples/export-expose/index.vue | 11 ++ playground/vue3/tsconfig.json | 8 ++ playground/vue3/vite.config.ts | 6 + pnpm-lock.yaml | 22 ++++ 35 files changed, 625 insertions(+), 3 deletions(-) create mode 100644 .changeset/cool-badgers-hang.md create mode 100644 packages/export-expose/README.md create mode 100644 packages/export-expose/package.json create mode 100644 packages/export-expose/src/api.ts create mode 100644 packages/export-expose/src/core/index.ts create mode 100644 packages/export-expose/src/esbuild.ts create mode 100644 packages/export-expose/src/index.ts create mode 100644 packages/export-expose/src/rollup.ts create mode 100644 packages/export-expose/src/vite.ts create mode 100644 packages/export-expose/src/webpack.ts create mode 100644 packages/export-expose/tests/__snapshots__/fixtures.test.ts.snap create mode 100644 packages/export-expose/tests/fixtures.test.ts create mode 100644 packages/export-expose/tests/fixtures/basic.vue create mode 100644 packages/export-expose/tests/fixtures/error/export-all.vue create mode 100644 packages/export-expose/tests/fixtures/error/export-default.vue create mode 100644 packages/export-expose/tests/fixtures/export-from-other.vue create mode 100644 packages/export-expose/tests/fixtures/namespace-export.vue create mode 100644 packages/export-expose/tests/fixtures/rename.vue create mode 100644 packages/export-expose/tests/fixtures/types.ts create mode 100644 packages/export-expose/tsup.config.ts create mode 100644 packages/volar/src/export-expose.ts create mode 100644 playground/vue3/src/examples/export-expose/child.vue create mode 100644 playground/vue3/src/examples/export-expose/index.vue diff --git a/.changeset/cool-badgers-hang.md b/.changeset/cool-badgers-hang.md new file mode 100644 index 000000000..0f99006b7 --- /dev/null +++ b/.changeset/cool-badgers-hang.md @@ -0,0 +1,9 @@ +--- +'unplugin-vue-macros': minor +'@vue-macros/export-expose': patch +'@vue-macros/volar': minor +--- + +- Disable `exportExpose` and `exportProps` by default. +- Introduce `exportExpose`. +- Add `include` option for Volar plugins (`exportExpose` and `exportProps`). diff --git a/docs/guide/configurations.md b/docs/guide/configurations.md index a72f79149..f15bf507c 100644 --- a/docs/guide/configurations.md +++ b/docs/guide/configurations.md @@ -8,6 +8,8 @@ All features are enabled by default except the following. - `defineSlots` (Vue >= 3.3) - `hoistStatic` (Vue >= 3.3) - `shortEmits` (Vue >= 3.3) +- `exportExpose` +- `exportProps` You can disable them by setting the option to `false`. diff --git a/packages/export-expose/README.md b/packages/export-expose/README.md new file mode 100644 index 000000000..10ad20a2e --- /dev/null +++ b/packages/export-expose/README.md @@ -0,0 +1,3 @@ +# @vue-macros/export-expose [![npm](https://img.shields.io/npm/v/@vue-macros/export-expose.svg)](https://npmjs.com/package/@vue-macros/export-expose) + +Please refer to [README.md](https://github.com/sxzz/vue-macros#readme) diff --git a/packages/export-expose/package.json b/packages/export-expose/package.json new file mode 100644 index 000000000..103bba2cc --- /dev/null +++ b/packages/export-expose/package.json @@ -0,0 +1,102 @@ +{ + "name": "@vue-macros/export-expose", + "version": "0.0.0", + "packageManager": "pnpm@8.5.1", + "description": "export-expose feature from Vue Macros.", + "keywords": [ + "vue-macros", + "macros", + "vue", + "sfc", + "setup", + "script-setup", + "export-expose", + "unplugin" + ], + "license": "MIT", + "homepage": "https://github.com/sxzz/vue-macros#readme", + "bugs": { + "url": "https://github.com/sxzz/vue-macros/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sxzz/vue-macros.git", + "directory": "packages/export-expose" + }, + "author": "三咲智子 ", + "files": [ + "dist", + "*.d.ts" + ], + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "dev": "./src/index.ts", + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.mjs" + }, + "./api": { + "dev": "./src/api.ts", + "types": "./dist/api.d.ts", + "require": "./dist/api.js", + "import": "./dist/api.mjs" + }, + "./esbuild": { + "dev": "./src/esbuild.ts", + "types": "./dist/esbuild.d.ts", + "require": "./dist/esbuild.js", + "import": "./dist/esbuild.mjs" + }, + "./rollup": { + "dev": "./src/rollup.ts", + "types": "./dist/rollup.d.ts", + "require": "./dist/rollup.js", + "import": "./dist/rollup.mjs" + }, + "./vite": { + "dev": "./src/vite.ts", + "types": "./dist/vite.d.ts", + "require": "./dist/vite.js", + "import": "./dist/vite.mjs" + }, + "./webpack": { + "dev": "./src/webpack.ts", + "types": "./dist/webpack.d.ts", + "require": "./dist/webpack.js", + "import": "./dist/webpack.mjs" + }, + "./*": [ + "./*", + "./*.d.ts" + ] + }, + "typesVersions": { + "<=4.9": { + "*": [ + "./dist/*", + "./*" + ] + } + }, + "scripts": { + "build": "tsup && tsx ../../scripts/postbuild.mts", + "dev": "DEV=true tsup" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "dependencies": { + "@vue-macros/common": "workspace:~", + "@vue/compiler-sfc": "^3.3.4", + "unplugin": "^1.3.1" + }, + "devDependencies": { + "vue": "^3.3.4" + }, + "engines": { + "node": ">=16.14.0" + } +} diff --git a/packages/export-expose/src/api.ts b/packages/export-expose/src/api.ts new file mode 100644 index 000000000..46d458ad7 --- /dev/null +++ b/packages/export-expose/src/api.ts @@ -0,0 +1 @@ +export * from './core' diff --git a/packages/export-expose/src/core/index.ts b/packages/export-expose/src/core/index.ts new file mode 100644 index 000000000..8d779fa7e --- /dev/null +++ b/packages/export-expose/src/core/index.ts @@ -0,0 +1,119 @@ +import { + HELPER_PREFIX, + MagicString, + getTransformResult, + parseSFC, +} from '@vue-macros/common' +import { extractIdentifiers } from '@vue/compiler-sfc' + +const MACROS_VAR_PREFIX = `${HELPER_PREFIX}expose_` + +export function transformExportExpose(code: string, id: string) { + const { scriptSetup, getSetupAst } = parseSFC(code, id) + if (!scriptSetup) return + + const s = new MagicString(code) + const nodes = getSetupAst()!.body + const offset = scriptSetup.loc.start.offset + + const exposed: Record = {} + + let i = 0 + for (const stmt of nodes) { + const start = offset + stmt.start! + const end = start + 6 /* 'export'.length */ + + if (stmt.type === 'ExportNamedDeclaration' && stmt.exportKind === 'value') { + if (stmt.declaration) { + if ( + stmt.declaration.type === 'VariableDeclaration' && + !stmt.declaration.declare + ) { + for (const decl of stmt.declaration.declarations) { + for (const id of extractIdentifiers(decl.id)) { + exposed[id.name] = id.name + } + } + } else if ( + (stmt.declaration.type === 'FunctionDeclaration' || + stmt.declaration.type === 'ClassDeclaration' || + stmt.declaration.type === 'TSEnumDeclaration') && + !stmt.declaration.declare + ) { + exposed[stmt.declaration.id!.name] = stmt.declaration.id!.name + } + + s.remove(start, end) + } else { + for (const specifier of stmt.specifiers) { + let exported: string, local: string + if (specifier.type === 'ExportSpecifier') { + if (specifier.exportKind === 'type') continue + + exported = + specifier.exported.type === 'Identifier' + ? specifier.exported.name + : specifier.exported.value + + if (stmt.source) { + // rename variable + local = MACROS_VAR_PREFIX + String(i++) + if (specifier.local.name === exported) { + s.overwriteNode( + specifier.local, + `${specifier.local.name} as ${local}`, + { offset } + ) + } else { + s.overwriteNode(specifier.exported, local, { offset }) + } + } else { + local = specifier.local.name + } + } else if (specifier.type === 'ExportNamespaceSpecifier') { + local = MACROS_VAR_PREFIX + String(i++) + exported = specifier.exported.name + + s.overwriteNode(specifier.exported, local, { offset }) + } else continue + + exposed[exported] = local + } + + if (stmt.source) { + s.overwrite(start, end, 'import') + } else { + s.removeNode(stmt, { offset }) + } + } + } else if ( + stmt.type === 'ExportAllDeclaration' && + stmt.exportKind === 'value' + ) { + throw new Error( + 'export from another module is not supported. Please import and export separately.' + ) + } else if (stmt.type === 'ExportDefaultDeclaration') { + throw new Error( + 'export default is not supported. Please use named export.' + ) + } + } + + if (Object.keys(exposed).length === 0) return + + let codegen = '' + for (const [exported, local] of Object.entries(exposed)) { + codegen += `\n ` + if (exported === local) { + codegen += `${exported},` + } else { + codegen += `${exported}: ${local},` + } + } + codegen = `defineExpose({${codegen}\n})` + + s.prependLeft(scriptSetup.loc.end.offset, `${codegen}\n`) + + return getTransformResult(s, id) +} diff --git a/packages/export-expose/src/esbuild.ts b/packages/export-expose/src/esbuild.ts new file mode 100644 index 000000000..71f1e0952 --- /dev/null +++ b/packages/export-expose/src/esbuild.ts @@ -0,0 +1,3 @@ +import unplugin from '.' + +export default unplugin.esbuild diff --git a/packages/export-expose/src/index.ts b/packages/export-expose/src/index.ts new file mode 100644 index 000000000..1bb46da57 --- /dev/null +++ b/packages/export-expose/src/index.ts @@ -0,0 +1,47 @@ +import { createUnplugin } from 'unplugin' +import { + type BaseOptions, + type MarkRequired, + REGEX_SETUP_SFC, + REGEX_VUE_SFC, + REGEX_VUE_SUB, + createFilter, + detectVueVersion, +} from '@vue-macros/common' +import { transformExportExpose } from './core' + +export { transformExportExpose as transformDefineProps } from './core' + +export type Options = BaseOptions +export type OptionsResolved = MarkRequired + +function resolveOption(options: Options): OptionsResolved { + const version = options.version || detectVueVersion() + return { + include: [REGEX_VUE_SFC, REGEX_SETUP_SFC, REGEX_VUE_SUB], + ...options, + version, + } +} + +const name = 'unplugin-vue-export-expose' + +export default createUnplugin( + (userOptions = {}) => { + const options = resolveOption(userOptions) + const filter = createFilter(options) + + return { + name, + enforce: 'pre', + + transformInclude(id) { + return filter(id) + }, + + transform(code, id) { + return transformExportExpose(code, id) + }, + } + } +) diff --git a/packages/export-expose/src/rollup.ts b/packages/export-expose/src/rollup.ts new file mode 100644 index 000000000..ed6909cd3 --- /dev/null +++ b/packages/export-expose/src/rollup.ts @@ -0,0 +1,3 @@ +import unplugin from '.' + +export default unplugin.rollup diff --git a/packages/export-expose/src/vite.ts b/packages/export-expose/src/vite.ts new file mode 100644 index 000000000..589f4b964 --- /dev/null +++ b/packages/export-expose/src/vite.ts @@ -0,0 +1,3 @@ +import unplugin from '.' + +export default unplugin.vite diff --git a/packages/export-expose/src/webpack.ts b/packages/export-expose/src/webpack.ts new file mode 100644 index 000000000..83091ee31 --- /dev/null +++ b/packages/export-expose/src/webpack.ts @@ -0,0 +1,3 @@ +import unplugin from '.' + +export default unplugin.webpack diff --git a/packages/export-expose/tests/__snapshots__/fixtures.test.ts.snap b/packages/export-expose/tests/__snapshots__/fixtures.test.ts.snap new file mode 100644 index 000000000..b7c791528 --- /dev/null +++ b/packages/export-expose/tests/__snapshots__/fixtures.test.ts.snap @@ -0,0 +1,69 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`fixtures > ./fixtures/basic.vue 1`] = ` +" +" +`; + +exports[`fixtures > ./fixtures/error/export-all.vue 1`] = `"export from another module is not supported. Please import and export separately."`; + +exports[`fixtures > ./fixtures/error/export-default.vue 1`] = `"export default is not supported. Please use named export."`; + +exports[`fixtures > ./fixtures/export-from-other.vue 1`] = ` +" +" +`; + +exports[`fixtures > ./fixtures/namespace-export.vue 1`] = ` +" +" +`; + +exports[`fixtures > ./fixtures/rename.vue 1`] = ` +" +" +`; diff --git a/packages/export-expose/tests/fixtures.test.ts b/packages/export-expose/tests/fixtures.test.ts new file mode 100644 index 000000000..4d9523048 --- /dev/null +++ b/packages/export-expose/tests/fixtures.test.ts @@ -0,0 +1,13 @@ +import { testFixtures } from '@vue-macros/test-utils' +import { describe } from 'vitest' +import { transformExportExpose } from '../src/core' + +describe('fixtures', async () => { + await testFixtures( + import.meta.glob('./fixtures/**/*.vue', { + eager: true, + as: 'raw', + }), + (args, id, code) => transformExportExpose(code, id)?.code + ) +}) diff --git a/packages/export-expose/tests/fixtures/basic.vue b/packages/export-expose/tests/fixtures/basic.vue new file mode 100644 index 000000000..c201785ce --- /dev/null +++ b/packages/export-expose/tests/fixtures/basic.vue @@ -0,0 +1,12 @@ + diff --git a/packages/export-expose/tests/fixtures/error/export-all.vue b/packages/export-expose/tests/fixtures/error/export-all.vue new file mode 100644 index 000000000..88c5c9835 --- /dev/null +++ b/packages/export-expose/tests/fixtures/error/export-all.vue @@ -0,0 +1,3 @@ + diff --git a/packages/export-expose/tests/fixtures/error/export-default.vue b/packages/export-expose/tests/fixtures/error/export-default.vue new file mode 100644 index 000000000..db7d0ad16 --- /dev/null +++ b/packages/export-expose/tests/fixtures/error/export-default.vue @@ -0,0 +1,5 @@ + diff --git a/packages/export-expose/tests/fixtures/export-from-other.vue b/packages/export-expose/tests/fixtures/export-from-other.vue new file mode 100644 index 000000000..f9b86d47e --- /dev/null +++ b/packages/export-expose/tests/fixtures/export-from-other.vue @@ -0,0 +1,3 @@ + diff --git a/packages/export-expose/tests/fixtures/namespace-export.vue b/packages/export-expose/tests/fixtures/namespace-export.vue new file mode 100644 index 000000000..97bfb2026 --- /dev/null +++ b/packages/export-expose/tests/fixtures/namespace-export.vue @@ -0,0 +1,3 @@ + diff --git a/packages/export-expose/tests/fixtures/rename.vue b/packages/export-expose/tests/fixtures/rename.vue new file mode 100644 index 000000000..f06e3ad67 --- /dev/null +++ b/packages/export-expose/tests/fixtures/rename.vue @@ -0,0 +1,7 @@ + diff --git a/packages/export-expose/tests/fixtures/types.ts b/packages/export-expose/tests/fixtures/types.ts new file mode 100644 index 000000000..30025c9d8 --- /dev/null +++ b/packages/export-expose/tests/fixtures/types.ts @@ -0,0 +1,5 @@ +export interface Foo { + foo: string +} + +export const foo = 'foo' diff --git a/packages/export-expose/tsup.config.ts b/packages/export-expose/tsup.config.ts new file mode 100644 index 000000000..1605eae16 --- /dev/null +++ b/packages/export-expose/tsup.config.ts @@ -0,0 +1 @@ +export { default } from '../../tsup.config.js' diff --git a/packages/macros/package.json b/packages/macros/package.json index 37ecd5455..6bf76c9bc 100644 --- a/packages/macros/package.json +++ b/packages/macros/package.json @@ -93,6 +93,7 @@ "@vue-macros/define-render": "workspace:*", "@vue-macros/define-slots": "workspace:*", "@vue-macros/devtools": "workspace:^", + "@vue-macros/export-expose": "workspace:*", "@vue-macros/export-props": "workspace:*", "@vue-macros/hoist-static": "workspace:*", "@vue-macros/named-template": "workspace:*", diff --git a/packages/macros/src/index.ts b/packages/macros/src/index.ts index 555196f3c..8ab172db6 100644 --- a/packages/macros/src/index.ts +++ b/packages/macros/src/index.ts @@ -36,6 +36,9 @@ import VueDefineRender, { import VueDefineSlots, { type Options as OptionsDefineSlots, } from '@vue-macros/define-slots' +import VueExportExpose, { + type Options as OptionsExportExpose, +} from '@vue-macros/export-expose' import VueExportProps, { type Options as OptionsExportProps, } from '@vue-macros/export-props' @@ -71,6 +74,7 @@ export interface FeatureOptionsMap { definePropsRefs: OptionsDefinePropsRefs defineRender: OptionsDefineRender defineSlots: OptionsDefineSlots + exportExpose: OptionsExportExpose exportProps: OptionsExportProps hoistStatic: OptionsHoistStatic namedTemplate: OptionsNamedTemplate @@ -123,6 +127,7 @@ export function resolveOptions({ definePropsRefs, defineRender, defineSlots, + exportExpose, exportProps, hoistStatic, namedTemplate, @@ -183,7 +188,16 @@ export function resolveOptions({ { version }, version < 3.3 ), - exportProps: resolveSubOptions<'exportProps'>(exportProps, { version }), + exportExpose: resolveSubOptions<'exportExpose'>( + exportExpose, + { version }, + false + ), + exportProps: resolveSubOptions<'exportProps'>( + exportProps, + { version }, + false + ), hoistStatic: resolveSubOptions<'hoistStatic'>(hoistStatic, { version }), namedTemplate: resolveSubOptions<'namedTemplate'>(namedTemplate, { version, @@ -258,6 +272,7 @@ export default createCombinePlugin( resolvePlugin(VueShortEmits, framework, options.shortEmits), resolvePlugin(VueDefineModels, framework, options.defineModels), resolvePlugin(VueDefineSlots, framework, options.defineSlots), + resolvePlugin(VueExportExpose, framework, options.exportExpose), resolvePlugin( VueReactivityTransform, framework, diff --git a/packages/volar/index.d.ts b/packages/volar/index.d.ts index 001c66156..26ca723c2 100644 --- a/packages/volar/index.d.ts +++ b/packages/volar/index.d.ts @@ -1,3 +1,5 @@ +import { type FilterPattern } from '@rollup/pluginutils' + export interface VolarOptions { defineModels?: { unified?: boolean @@ -5,4 +7,12 @@ export interface VolarOptions { shortVmodel?: { prefix?: '::' | '$' | '*' } + exportExpose?: { + include?: FilterPattern + exclude?: FilterPattern + } + exportProps?: { + include?: FilterPattern + exclude?: FilterPattern + } } diff --git a/packages/volar/package.json b/packages/volar/package.json index db377d6c0..98de8f1c3 100644 --- a/packages/volar/package.json +++ b/packages/volar/package.json @@ -48,6 +48,7 @@ } }, "dependencies": { + "@rollup/pluginutils": "^5.0.2", "@volar/language-core": "1.6.9", "@volar/vue-language-core": "1.6.5", "@vue-macros/common": "workspace:~", diff --git a/packages/volar/src/common.ts b/packages/volar/src/common.ts index da5303b83..60e2aa561 100644 --- a/packages/volar/src/common.ts +++ b/packages/volar/src/common.ts @@ -4,6 +4,7 @@ import { replace, } from '@volar/vue-language-core' import { type FileRangeCapabilities } from '@volar/language-core' +import { type VolarOptions } from '..' export function getVueLibraryName(vueVersion: number) { return vueVersion < 2.7 ? '@vue/runtime-dom' : 'vue' @@ -47,6 +48,8 @@ export function addEmits( return true } -export function getVolarOptions(vueCompilerOptions: VueCompilerOptions) { +export function getVolarOptions( + vueCompilerOptions: VueCompilerOptions +): VolarOptions | undefined { return vueCompilerOptions.vueMacros } diff --git a/packages/volar/src/export-expose.ts b/packages/volar/src/export-expose.ts new file mode 100644 index 000000000..05a8e6c0b --- /dev/null +++ b/packages/volar/src/export-expose.ts @@ -0,0 +1,97 @@ +import { FileKind } from '@volar/language-core' +import { + FileRangeCapabilities, + type Segment, + type Sfc, + type VueEmbeddedFile, + type VueLanguagePlugin, + replace, + replaceSourceRange, +} from '@volar/vue-language-core' +import { createFilter } from '@rollup/pluginutils' +import { type VolarOptions } from '..' +import { getVolarOptions } from './common' + +function transform({ + fileName, + file, + sfc, + ts, + volarOptions, +}: { + fileName: string + file: VueEmbeddedFile + sfc: Sfc + ts: typeof import('typescript/lib/tsserverlibrary') + volarOptions: NonNullable +}) { + const filter = createFilter( + volarOptions.include || /.*/, + volarOptions.exclude + ) + if (!filter(fileName)) return + + const exposed: Record> = {} + for (const stmt of sfc.scriptSetupAst!.statements) { + if (!ts.isVariableStatement(stmt)) continue + const exportModifier = stmt.modifiers?.find( + (m) => m.kind === ts.SyntaxKind.ExportKeyword + ) + if (!exportModifier) continue + + const start = exportModifier.getStart(sfc.scriptSetupAst!) + const end = exportModifier.getEnd() + replaceSourceRange(file.content, 'scriptSetup', start, end) + + for (const decl of stmt.declarationList.declarations) { + if (!ts.isIdentifier(decl.name)) continue + const name = decl.name.text + const start = decl.name.getStart(sfc.scriptSetupAst!) + + exposed[name] = [name, 'scriptSetup', start, FileRangeCapabilities.full] + } + } + + if (Object.keys(exposed).length === 0) return + + const exposedStrings = Object.entries(exposed).flatMap(([key, value]) => [ + `${key}: `, + value, + ',\n', + ]) + + replace( + file.content, + 'return {\n', + 'return {\n...{ ', + ...exposedStrings, + ' },\n' + ) +} + +const plugin: VueLanguagePlugin = ({ + vueCompilerOptions, + modules: { typescript: ts }, +}) => { + return { + name: 'vue-macros-export-expose', + version: 1, + resolveEmbeddedFile(fileName, sfc, embeddedFile) { + if ( + embeddedFile.kind !== FileKind.TypeScriptHostFile || + !sfc.scriptSetup || + !sfc.scriptSetupAst + ) + return + + transform({ + fileName, + file: embeddedFile, + sfc, + ts, + volarOptions: getVolarOptions(vueCompilerOptions)?.exportExpose || {}, + }) + }, + } +} +export = plugin diff --git a/packages/volar/src/export-props.ts b/packages/volar/src/export-props.ts index b8a7a0fd5..05eadb984 100644 --- a/packages/volar/src/export-props.ts +++ b/packages/volar/src/export-props.ts @@ -5,19 +5,31 @@ import { type VueLanguagePlugin, replaceSourceRange, } from '@volar/vue-language-core' -import { addProps, getVueLibraryName } from './common' +import { createFilter } from '@rollup/pluginutils' +import { type VolarOptions } from '..' +import { addProps, getVolarOptions, getVueLibraryName } from './common' function transform({ file, sfc, ts, vueLibName, + volarOptions, + fileName, }: { + fileName: string file: VueEmbeddedFile sfc: Sfc ts: typeof import('typescript/lib/tsserverlibrary') vueLibName: string + volarOptions: NonNullable }) { + const filter = createFilter( + volarOptions.include || /.*/, + volarOptions.exclude + ) + if (!filter(fileName)) return + const props: Record = {} let changed = false for (const stmt of sfc.scriptSetupAst!.statements) { @@ -75,6 +87,8 @@ const plugin: VueLanguagePlugin = ({ sfc, vueLibName, ts, + fileName, + volarOptions: getVolarOptions(vueCompilerOptions)?.exportProps || {}, }) }, } diff --git a/playground/vue3/rollup.config.mjs b/playground/vue3/rollup.config.mjs index 0ffcdc472..1631a2dc8 100644 --- a/playground/vue3/rollup.config.mjs +++ b/playground/vue3/rollup.config.mjs @@ -21,6 +21,12 @@ export default defineConfig({ defineSlots: true, defineModels: true, namedTemplate: false, + exportProps: { + include: [/export-props.*\.vue$/], + }, + exportExpose: { + include: [/export-expose.*\.vue$/], + }, plugins: { vue: Vue({ diff --git a/playground/vue3/src/App.vue b/playground/vue3/src/App.vue index 669e8070a..39ba2d343 100644 --- a/playground/vue3/src/App.vue +++ b/playground/vue3/src/App.vue @@ -8,6 +8,7 @@ import DefineModelReactivityVue from './examples/define-models/reactivity-transf import DefineModelRuntimeVue from './examples/define-models/runtime/parent.vue' import DefineSlotsVue from './examples/define-slots/parent.vue' import ExportPropsVue from './examples/export-props/index.vue' +import ExportExposeVue from './examples/export-expose/index.vue' import BetterDefineVue from './examples/better-define/index.vue' // import NamedTemplateVue from './examples/named-template/index.vue' import SetupBlockVue from './examples/setup-block/index.vue' @@ -48,6 +49,11 @@ import Full from './examples/full.setup' +
+ exportExpose + +
+
hostStatic diff --git a/playground/vue3/src/examples/export-expose/child.vue b/playground/vue3/src/examples/export-expose/child.vue new file mode 100644 index 000000000..21a43dc5b --- /dev/null +++ b/playground/vue3/src/examples/export-expose/child.vue @@ -0,0 +1,6 @@ + + + diff --git a/playground/vue3/src/examples/export-expose/index.vue b/playground/vue3/src/examples/export-expose/index.vue new file mode 100644 index 000000000..d70ccd269 --- /dev/null +++ b/playground/vue3/src/examples/export-expose/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/playground/vue3/tsconfig.json b/playground/vue3/tsconfig.json index 8336d05e4..3690053f3 100644 --- a/playground/vue3/tsconfig.json +++ b/playground/vue3/tsconfig.json @@ -23,12 +23,20 @@ "@vue-macros/volar/define-props-refs", "@vue-macros/volar/short-vmodel", "@vue-macros/volar/define-slots", + "@vue-macros/volar/export-expose", "@vue-macros/volar/export-props" ], "experimentalDefinePropProposal": "kevinEdition", + "vueMacros": { "shortVmodel": { "prefix": "$" + }, + "exportExpose": { + "include": ["**/export-expose/**"] + }, + "exportProps": { + "include": ["**/export-props/**"] } } }, diff --git a/playground/vue3/vite.config.ts b/playground/vue3/vite.config.ts index c288edec0..4a3dc5cf4 100644 --- a/playground/vue3/vite.config.ts +++ b/playground/vue3/vite.config.ts @@ -18,6 +18,12 @@ export default defineConfig({ defineSlots: true, defineModels: true, namedTemplate: false, + exportProps: { + include: [/export-props.*\.vue$/], + }, + exportExpose: { + include: [/export-expose.*\.vue$/], + }, plugins: { vue: Vue({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6afc31c05..74bc17c95 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -329,6 +329,22 @@ importers: specifier: ^4.3.8 version: 4.3.8(@types/node@20.2.3) + packages/export-expose: + dependencies: + '@vue-macros/common': + specifier: workspace:~ + version: link:../common + '@vue/compiler-sfc': + specifier: ^3.3.4 + version: 3.3.4 + unplugin: + specifier: ^1.3.1 + version: 1.3.1 + devDependencies: + vue: + specifier: ^3.3.4 + version: 3.3.4 + packages/export-props: dependencies: '@vue-macros/common': @@ -383,6 +399,9 @@ importers: '@vue-macros/devtools': specifier: workspace:^ version: link:../devtools + '@vue-macros/export-expose': + specifier: workspace:* + version: link:../export-expose '@vue-macros/export-props': specifier: workspace:* version: link:../export-props @@ -589,6 +608,9 @@ importers: packages/volar: dependencies: + '@rollup/pluginutils': + specifier: ^5.0.2 + version: 5.0.2(rollup@3.23.0) '@volar/language-core': specifier: 1.6.9 version: 1.6.9