diff --git a/examples/with-components/app.vue b/examples/with-components/app.vue
index 14eebea575..599adac638 100644
--- a/examples/with-components/app.vue
+++ b/examples/with-components/app.vue
@@ -1,5 +1,6 @@
+
diff --git a/examples/with-components/components/Nuxt3.vue b/examples/with-components/components/Nuxt3.vue
new file mode 100644
index 0000000000..0faa5c7efe
--- /dev/null
+++ b/examples/with-components/components/Nuxt3.vue
@@ -0,0 +1,5 @@
+
+
+ From Nuxt 3
+
+
diff --git a/examples/with-components/nuxt.config.ts b/examples/with-components/nuxt.config.ts
index 854fc6bfc5..15046bd316 100644
--- a/examples/with-components/nuxt.config.ts
+++ b/examples/with-components/nuxt.config.ts
@@ -1,4 +1,5 @@
import { defineNuxtConfig } from '@nuxt/kit'
export default defineNuxtConfig({
+ vite: false
})
diff --git a/packages/components/package.json b/packages/components/package.json
index 6c6346a8a4..688f2b8c62 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -16,6 +16,7 @@
"globby": "^11.0.4",
"scule": "^0.2.1",
"ufo": "^0.7.7",
+ "unplugin": "^0.0.5",
"upath": "^2.0.1"
},
"devDependencies": {
diff --git a/packages/components/src/loader.ts b/packages/components/src/loader.ts
new file mode 100644
index 0000000000..4bbd454846
--- /dev/null
+++ b/packages/components/src/loader.ts
@@ -0,0 +1,47 @@
+import { createUnplugin } from 'unplugin'
+import { parseQuery, parseURL } from 'ufo'
+import { Component } from './types'
+
+interface LoaderOptions {
+ getComponents(): Component[]
+}
+
+export const loaderPlugin = createUnplugin((options: LoaderOptions) => ({
+ name: 'nuxt-components-loader',
+ enforce: 'post',
+ transformInclude (id) {
+ const { pathname, search } = parseURL(id)
+ const query = parseQuery(search)
+ // we only transform render functions
+ // from `type=template` (in Webpack) and bare `.vue` file (in Vite)
+ return pathname.endsWith('.vue') && (query.type === 'template' || !search)
+ },
+ transform (code) {
+ return transform(code, options.getComponents())
+ }
+}))
+
+function findComponent (components: Component[], name:string) {
+ return components.find(({ pascalName, kebabName }) => [pascalName, kebabName].includes(name))
+}
+
+function transform (content: string, components: Component[]) {
+ let num = 0
+ let imports = ''
+ const map = new Map()
+
+ // replace `_resolveComponent("...")` to direct import
+ const newContent = content.replace(/ _resolveComponent\("(.*?)"\)/g, (full, name) => {
+ const component = findComponent(components, name)
+ if (component) {
+ const identifier = map.get(component) || `__nuxt_component_${num++}`
+ map.set(component, identifier)
+ imports += `import ${identifier} from "${component.filePath}";`
+ return ` ${identifier}`
+ }
+ // no matched
+ return full
+ })
+
+ return `${imports}\n${newContent}`
+}
diff --git a/packages/components/src/module.ts b/packages/components/src/module.ts
index 94232c1860..5371302fa2 100644
--- a/packages/components/src/module.ts
+++ b/packages/components/src/module.ts
@@ -1,8 +1,9 @@
import fs from 'fs'
-import { defineNuxtModule, resolveAlias } from '@nuxt/kit'
+import { defineNuxtModule, resolveAlias, addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
import { resolve } from 'upath'
import { scanComponents } from './scan'
-import type { ComponentsDir } from './types'
+import type { Component, ComponentsDir } from './types'
+import { loaderPlugin } from './loader'
const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string'
const isDirectory = (p: string) => { try { return fs.statSync(p).isDirectory() } catch (_e) { return false } }
@@ -14,6 +15,7 @@ export default defineNuxtModule({
},
setup (options, nuxt) {
let componentDirs = []
+ let components: Component[] = []
// Resolve dirs
nuxt.hook('app:resolve', async () => {
@@ -56,7 +58,7 @@ export default defineNuxtModule({
// Scan components and add to plugin
nuxt.hook('app:templates', async (app) => {
- const components = await scanComponents(componentDirs, nuxt.options.srcDir!)
+ components = await scanComponents(componentDirs, nuxt.options.srcDir!)
await nuxt.callHook('components:extend', components)
if (!components.length) {
return
@@ -87,5 +89,11 @@ export default defineNuxtModule({
await nuxt.callHook('builder:generateApp')
}
})
+
+ if (!nuxt.options.dev) {
+ const options = { getComponents: () => components }
+ addWebpackPlugin(loaderPlugin.webpack(options))
+ addVitePlugin(loaderPlugin.vite(options))
+ }
}
})
diff --git a/yarn.lock b/yarn.lock
index 6a7091b54d..df197e734c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1413,6 +1413,7 @@ __metadata:
scule: ^0.2.1
ufo: ^0.7.7
unbuild: ^0.4.0
+ unplugin: ^0.0.5
upath: ^2.0.1
peerDependencies:
vue: 3.1.5
@@ -11955,6 +11956,26 @@ typescript@^4.3.5:
languageName: node
linkType: hard
+"unplugin@npm:^0.0.5":
+ version: 0.0.5
+ resolution: "unplugin@npm:0.0.5"
+ dependencies:
+ webpack-virtual-modules: ^0.4.3
+ peerDependencies:
+ rollup: ^2.50.0
+ vite: ^2.3.0
+ webpack: ^5.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ vite:
+ optional: true
+ webpack:
+ optional: true
+ checksum: 37f66f585365a9d7c26c03cc971329391839b13505942548be48ad834d7d5c68576e4defce92f9845bddd154c93340f8a1773337fce797f1cac1a1a34d6aac26
+ languageName: node
+ linkType: hard
+
"unstorage@npm:^0.2.3":
version: 0.2.3
resolution: "unstorage@npm:0.2.3"