diff --git a/src/core/context.ts b/src/core/context.ts
index 7bdadd91..79a25767 100644
--- a/src/core/context.ts
+++ b/src/core/context.ts
@@ -8,6 +8,7 @@ import { pascalCase, getNameFromFilePath, resolveAlias, matchGlobs, parseId } fr
 import { resolveOptions } from './options'
 import { searchComponents } from './fs/glob'
 import { generateDeclaration } from './declaration'
+import { generateIdeHelper } from './ideHelper'
 import transformer from './transformer'
 
 const debug = {
@@ -15,6 +16,7 @@ const debug = {
   search: Debug('unplugin-vue-components:context:search'),
   hmr: Debug('unplugin-vue-components:context:hmr'),
   decleration: Debug('unplugin-vue-components:decleration'),
+  ideHelper: Debug('unplugin-vue-components:ideHelper'),
   env: Debug('unplugin-vue-components:env'),
 }
 
@@ -37,6 +39,7 @@ export class Context {
   ) {
     this.options = resolveOptions(rawOptions, this.root)
     this.generateDeclaration = throttle(500, false, this.generateDeclaration.bind(this))
+    this.generateIdeHelper = throttle(500, false, this.generateIdeHelper.bind(this))
     this.setTransformer(this.options.transformer)
   }
 
@@ -130,6 +133,7 @@ export class Context {
 
   onUpdate(path: string) {
     this.generateDeclaration()
+    this.generateIdeHelper()
 
     if (!this._server)
       return
@@ -251,6 +255,14 @@ export class Context {
     generateDeclaration(this, this.options.root, this.options.dts)
   }
 
+  generateIdeHelper() {
+    if (!this.options.generateIdeHelper)
+      return
+
+    debug.ideHelper('generating')
+    generateIdeHelper(this, this.options.root, this.options.generateIdeHelper)
+  }
+
   get componentNameMap() {
     return this._componentNameMap
   }
diff --git a/src/core/ideHelper.ts b/src/core/ideHelper.ts
new file mode 100644
index 00000000..433b6c45
--- /dev/null
+++ b/src/core/ideHelper.ts
@@ -0,0 +1,74 @@
+import { dirname, relative, isAbsolute } from 'path'
+import { promises as fs, existsSync } from 'fs'
+import { notNullish, slash } from '@antfu/utils'
+import { Context } from './context'
+import { getVueVersion } from './options'
+import { getTransformedPath } from './utils'
+
+export async function generateIdeHelper(ctx: Context, root: string, filepath: string) {
+  const imports: Record<string, string> = Object.fromEntries(
+    Object.values({
+      ...ctx.componentNameMap,
+      ...ctx.componentCustomMap,
+    })
+      .map(({ path, name, importName }) => {
+        if (!name)
+          return undefined
+        path = getTransformedPath(path, ctx)
+        const related = isAbsolute(path)
+          ? `./${relative(dirname(filepath), path)}`
+          : path
+
+        let entry = 'import '
+        if (importName)
+          entry += `{ ${importName} as ${name} }`
+        else
+          entry += name
+        entry += ` from '${slash(related)}';`
+        return [name, entry]
+      })
+      .filter(notNullish),
+  )
+
+  if (!Object.keys(imports).length)
+    return
+
+  const originalContent = existsSync(filepath) ? await fs.readFile(filepath, 'utf-8') : ''
+
+  const lines = Object.entries(imports)
+    .sort((a, b) => a[0].localeCompare(b[0]))
+
+  let code = `// generated by unplugin-vue-components
+// We suggest you to NOT commit this file into source control
+// Read more: https://github.com/antfu/unplugin-vue-components/issues/135
+`
+
+  if (getVueVersion() === 'vue3') {
+    // @see https://youtrack.jetbrains.com/issue/WEB-48239
+    code += `import { createApp } from "vue";
+    
+${lines.map(line => line[1]).join('\n')}
+
+const app = createApp({});
+
+const Vue = app
+
+${lines.map(line => `Vue.component('${line[0]}', ${line[0]});`).join('\n')}
+
+app.mount("body");
+
+`
+  }
+  else {
+    code += `import Vue from "vue";
+    
+${lines.map(line => line[1]).join('\n')}
+
+${lines.map(line => `Vue.component('${line[0]}', ${line[0]});\nVue.component('Lazy${line[0]}', ${line[0]});`).join('\n')}
+
+`
+  }
+
+  if (code !== originalContent)
+    await fs.writeFile(filepath, code, 'utf-8')
+}
diff --git a/src/core/options.ts b/src/core/options.ts
index e1a78faa..9841468d 100644
--- a/src/core/options.ts
+++ b/src/core/options.ts
@@ -19,6 +19,8 @@ export const defaultOptions: Omit<Required<Options>, 'include' | 'exclude' | 'tr
   importPathTransform: v => v,
 
   allowOverrides: false,
+
+  generateIdeHelper: false,
 }
 
 function normalizeResolvers(resolvers: (ComponentResolver | ComponentResolver[])[]): ComponentResolverObject[] {
@@ -60,6 +62,16 @@ export function resolveOptions(options: Options, root: string): ResolvedOptions
         ? options.dts
         : 'components.d.ts',
     )
+
+  resolved.generateIdeHelper = !options.generateIdeHelper
+    ? false
+    : resolve(
+      root,
+      typeof options.generateIdeHelper === 'string'
+        ? options.generateIdeHelper
+        : '.components.gen.js',
+    )
+
   resolved.root = root
   resolved.transformer = options.transformer || getVueVersion() || 'vue3'
   resolved.directives = (typeof options.directives === 'boolean')
@@ -71,7 +83,7 @@ export function resolveOptions(options: Options, root: string): ResolvedOptions
   return resolved
 }
 
-function getVueVersion() {
+export function getVueVersion() {
   try {
     // eslint-disable-next-line @typescript-eslint/no-var-requires
     const vue = require('vue')
diff --git a/src/core/transformer.ts b/src/core/transformer.ts
index ee53c58a..bbc87506 100644
--- a/src/core/transformer.ts
+++ b/src/core/transformer.ts
@@ -15,7 +15,7 @@ export interface ResolveResult {
   replace: (resolved: string) => void
 }
 
-export default function tranformer(ctx: Context, transformer: SupportedTransformer): Transformer {
+export default function transformer(ctx: Context, transformer: SupportedTransformer): Transformer {
   return async(code, id, path) => {
     ctx.searchGlob()
 
diff --git a/src/core/unplugin.ts b/src/core/unplugin.ts
index 32365858..8f01fca3 100644
--- a/src/core/unplugin.ts
+++ b/src/core/unplugin.ts
@@ -26,6 +26,7 @@ export default createUnplugin<Options>((options = {}) => {
       try {
         const result = await ctx.transform(code, id)
         ctx.generateDeclaration()
+        ctx.generateIdeHelper()
         return result
       }
       catch (e) {
@@ -44,6 +45,7 @@ export default createUnplugin<Options>((options = {}) => {
         if (options.dts) {
           ctx.searchGlob()
           ctx.generateDeclaration()
+          ctx.generateIdeHelper()
         }
 
         if (config.build.watch && config.command === 'build')
diff --git a/src/types.ts b/src/types.ts
index 9c9045b1..6250117c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -138,6 +138,15 @@ export interface Options {
    * @default undefined
    */
   directives?: boolean
+
+  /**
+   * Generate components.js helper for IntelliJ IDEs.
+   *
+   * Accept boolean or a path related to project root.
+   *
+   * @default false
+   */
+  generateIdeHelper?: boolean | string
 }
 
 export type ResolvedOptions = Omit<
@@ -151,6 +160,7 @@ Required<Options>,
   resolvedDirs: string[]
   globs: string[]
   dts: string | false
+  generateIdeHelper: string | false
   root: string
 }