diff --git a/packages/plugin-vue/src/index.ts b/packages/plugin-vue/src/index.ts
index 2e83d6d9..ff213fd7 100644
--- a/packages/plugin-vue/src/index.ts
+++ b/packages/plugin-vue/src/index.ts
@@ -13,7 +13,11 @@ import type * as _compiler from 'vue/compiler-sfc'
 import { version } from '../package.json'
 import { resolveCompiler } from './compiler'
 import { parseVueRequest } from './utils/query'
-import { getDescriptor, getSrcDescriptor } from './utils/descriptorCache'
+import {
+  getDescriptor,
+  getSrcDescriptor,
+  getTempSrcDescriptor,
+} from './utils/descriptorCache'
 import { getResolvedScript, typeDepToSFCMap } from './script'
 import { transformMain } from './main'
 import { handleHotUpdate, handleTypeDepChange } from './handleHotUpdate'
@@ -219,6 +223,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
       }
 
       const { filename, query } = parseVueRequest(id)
+
       // select corresponding block for sub-part virtual modules
       if (query.vue) {
         if (query.src) {
@@ -248,9 +253,11 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
     transform(code, id, opt) {
       const ssr = opt?.ssr === true
       const { filename, query } = parseVueRequest(id)
+
       if (query.raw || query.url) {
         return
       }
+
       if (!filter(filename) && !query.vue) {
         if (
           !query.vue &&
@@ -278,7 +285,8 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
       } else {
         // sub block request
         const descriptor = query.src
-          ? getSrcDescriptor(filename, query)!
+          ? getSrcDescriptor(filename, query) ||
+            getTempSrcDescriptor(filename, query)
           : getDescriptor(filename, options)!
 
         if (query.type === 'template') {
@@ -287,7 +295,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
           return transformStyle(
             code,
             descriptor,
-            Number(query.index),
+            Number(query.index || 0),
             options,
             this,
             filename,
diff --git a/packages/plugin-vue/src/utils/descriptorCache.ts b/packages/plugin-vue/src/utils/descriptorCache.ts
index 08316454..297f9648 100644
--- a/packages/plugin-vue/src/utils/descriptorCache.ts
+++ b/packages/plugin-vue/src/utils/descriptorCache.ts
@@ -81,6 +81,25 @@ export function getSrcDescriptor(
   return cache.get(filename)!
 }
 
+export function getTempSrcDescriptor(
+  filename: string,
+  query: VueQuery,
+): SFCDescriptor {
+  // this is only used for pre-compiled <style src> with scoped flag
+  return {
+    filename,
+    id: query.id || '',
+    styles: [
+      {
+        scoped: query.scoped,
+        loc: {
+          start: { line: 0, column: 0 },
+        },
+      },
+    ],
+  } as SFCDescriptor
+}
+
 export function setSrcDescriptor(
   filename: string,
   entry: SFCDescriptor,
diff --git a/packages/plugin-vue/src/utils/query.ts b/packages/plugin-vue/src/utils/query.ts
index 66fe103c..29417a22 100644
--- a/packages/plugin-vue/src/utils/query.ts
+++ b/packages/plugin-vue/src/utils/query.ts
@@ -7,6 +7,7 @@ export interface VueQuery {
   raw?: boolean
   url?: boolean
   scoped?: boolean
+  id?: string
 }
 
 export function parseVueRequest(id: string): {
diff --git a/playground/vue/Main.vue b/playground/vue/Main.vue
index 9ab11aac..fec9c40e 100644
--- a/playground/vue/Main.vue
+++ b/playground/vue/Main.vue
@@ -30,6 +30,9 @@
   <Url />
   <TsGeneric msg="hello" />
   <DefaultLangs />
+  <PreCompiled />
+  <PreCompiledExternalScoped />
+  <PreCompiledExternalCssModules />
 </template>
 
 <script setup lang="ts">
@@ -54,6 +57,9 @@ import { ref } from 'vue'
 import Url from './Url.vue'
 import TypeProps from './TypeProps.vue'
 import DefaultLangs from './DefaultLangs.vue'
+import PreCompiled from './pre-compiled/foo.vue'
+import PreCompiledExternalScoped from './pre-compiled/external-scoped.vue'
+import PreCompiledExternalCssModules from './pre-compiled/external-cssmodules.vue'
 
 const TsGeneric = defineAsyncComponent(() => import('./TsGeneric.vue'))
 
diff --git a/playground/vue/__tests__/vue.spec.ts b/playground/vue/__tests__/vue.spec.ts
index 850e7cfe..f8e9a4ba 100644
--- a/playground/vue/__tests__/vue.spec.ts
+++ b/playground/vue/__tests__/vue.spec.ts
@@ -344,3 +344,15 @@ describe('default langs', () => {
     expect(await getColor('.default-langs')).toBe('blue')
   })
 })
+
+describe('pre-compiled components', () => {
+  test('should work', async () => {
+    expect(await getColor('.pre-compiled-title')).toBe('red')
+  })
+  test('should work with external scoped style', async () => {
+    expect(await getColor('.pre-compiled-external-scoped-title')).toBe('red')
+  })
+  test('should work with external css modules', async () => {
+    expect(await getColor('.pre-compiled-external-cssmodules')).toBe('red')
+  })
+})
diff --git a/playground/vue/pre-compiled/external-cssmodules.vue.js b/playground/vue/pre-compiled/external-cssmodules.vue.js
new file mode 100644
index 00000000..3da12be5
--- /dev/null
+++ b/playground/vue/pre-compiled/external-cssmodules.vue.js
@@ -0,0 +1,59 @@
+import {
+  Fragment as _Fragment,
+  createElementBlock as _createElementBlock,
+  createElementVNode as _createElementVNode,
+  normalizeClass as _normalizeClass,
+  openBlock as _openBlock,
+} from 'vue'
+
+import style0 from './external.module.css?module=true&lang.module.css'
+
+const __sfc__ = {
+  data() {
+    return {
+      isRed: true,
+    }
+  },
+}
+
+function render(_ctx, _cache, $props, $setup, $data, $options) {
+  return (
+    _openBlock(),
+    _createElementBlock(
+      _Fragment,
+      null,
+      [
+        _createElementVNode(
+          'p',
+          {
+            class: _normalizeClass({
+              [_ctx.$style.red]: $data.isRed,
+              'pre-compiled-external-cssmodules': true,
+            }),
+          },
+          ' Am I red? ',
+          2 /* CLASS */,
+        ),
+        _createElementVNode(
+          'p',
+          {
+            class: _normalizeClass([
+              _ctx.$style.red,
+              _ctx.$style.bold,
+              'pre-compiled-external-cssmodules',
+            ]),
+          },
+          ' Red and bold ',
+          2 /* CLASS */,
+        ),
+      ],
+      64 /* STABLE_FRAGMENT */,
+    )
+  )
+}
+__sfc__.render = render
+const cssModules = {}
+cssModules['$style'] = style0
+__sfc__.__cssModules = cssModules
+__sfc__.__file = 'external-cssmodules.vue'
+export default __sfc__
diff --git a/playground/vue/pre-compiled/external-scoped.vue.js b/playground/vue/pre-compiled/external-scoped.vue.js
new file mode 100644
index 00000000..5aa8254f
--- /dev/null
+++ b/playground/vue/pre-compiled/external-scoped.vue.js
@@ -0,0 +1,61 @@
+import './external.css?vue&type=style&scoped=true&id=0d49ede6&src=0d49ede6&lang.css'
+
+import {
+  Fragment as _Fragment,
+  createElementBlock as _createElementBlock,
+  createElementVNode as _createElementVNode,
+  openBlock as _openBlock,
+  popScopeId as _popScopeId,
+  pushScopeId as _pushScopeId,
+  toDisplayString as _toDisplayString,
+  vModelText as _vModelText,
+  withDirectives as _withDirectives,
+  ref,
+} from 'vue'
+const __sfc__ = {
+  setup() {
+    const msg = ref('Hello World!')
+    return { msg }
+  },
+}
+
+const _withScopeId = (n) => (
+  _pushScopeId('data-v-0d49ede6'), (n = n()), _popScopeId(), n
+)
+const _hoisted_1 = { class: 'pre-compiled-external-scoped-title' }
+
+function render(_ctx, _cache, $props, $setup, $data, $options) {
+  return (
+    _openBlock(),
+    _createElementBlock(
+      _Fragment,
+      null,
+      [
+        _createElementVNode(
+          'h6',
+          _hoisted_1,
+          _toDisplayString($setup.msg),
+          1 /* TEXT */,
+        ),
+        _withDirectives(
+          _createElementVNode(
+            'input',
+            {
+              'onUpdate:modelValue':
+                _cache[0] || (_cache[0] = ($event) => ($setup.msg = $event)),
+            },
+            null,
+            512 /* NEED_PATCH */,
+          ),
+          [[_vModelText, $setup.msg]],
+        ),
+      ],
+      64 /* STABLE_FRAGMENT */,
+    )
+  )
+}
+__sfc__.render = render
+
+__sfc__.__scopeId = 'data-v-0d49ede6'
+__sfc__.__file = 'external-scoped.vue'
+export default __sfc__
diff --git a/playground/vue/pre-compiled/external.css b/playground/vue/pre-compiled/external.css
new file mode 100644
index 00000000..9e614629
--- /dev/null
+++ b/playground/vue/pre-compiled/external.css
@@ -0,0 +1,3 @@
+.pre-compiled-external-scoped-title {
+  color: red;
+}
diff --git a/playground/vue/pre-compiled/external.module.css b/playground/vue/pre-compiled/external.module.css
new file mode 100644
index 00000000..03adb4b5
--- /dev/null
+++ b/playground/vue/pre-compiled/external.module.css
@@ -0,0 +1,6 @@
+.red {
+  color: red;
+}
+.bold {
+  font-weight: bold;
+}
diff --git a/playground/vue/pre-compiled/foo.vue.js b/playground/vue/pre-compiled/foo.vue.js
new file mode 100644
index 00000000..8777b5e6
--- /dev/null
+++ b/playground/vue/pre-compiled/foo.vue.js
@@ -0,0 +1,55 @@
+import './foo.vue__0.css'
+
+import {
+  Fragment as _Fragment,
+  createElementBlock as _createElementBlock,
+  createElementVNode as _createElementVNode,
+  openBlock as _openBlock,
+  toDisplayString as _toDisplayString,
+  vModelText as _vModelText,
+  withDirectives as _withDirectives,
+  ref,
+} from 'vue'
+const __sfc__ = {
+  setup() {
+    const msg = ref('Hello World!')
+    return { msg }
+  },
+}
+
+const _hoisted_1 = { class: 'pre-compiled-title' }
+
+function render(_ctx, _cache, $props, $setup, $data, $options) {
+  return (
+    _openBlock(),
+    _createElementBlock(
+      _Fragment,
+      null,
+      [
+        _createElementVNode(
+          'h6',
+          _hoisted_1,
+          _toDisplayString($setup.msg),
+          1 /* TEXT */,
+        ),
+        _withDirectives(
+          _createElementVNode(
+            'input',
+            {
+              'onUpdate:modelValue':
+                _cache[0] || (_cache[0] = ($event) => ($setup.msg = $event)),
+            },
+            null,
+            512 /* NEED_PATCH */,
+          ),
+          [[_vModelText, $setup.msg]],
+        ),
+      ],
+      64 /* STABLE_FRAGMENT */,
+    )
+  )
+}
+__sfc__.render = render
+
+__sfc__.__file = 'foo.vue'
+export default __sfc__
diff --git a/playground/vue/pre-compiled/foo.vue__0.css b/playground/vue/pre-compiled/foo.vue__0.css
new file mode 100644
index 00000000..afa14cb5
--- /dev/null
+++ b/playground/vue/pre-compiled/foo.vue__0.css
@@ -0,0 +1,3 @@
+.pre-compiled-title {
+  color: red;
+}