mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 17:35:57 +00:00
feat: auto-import for composables (#1176)
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
parent
f950fe0d8a
commit
550a9f2e12
@ -6,4 +6,41 @@ head.title: Composables directory
|
|||||||
|
|
||||||
# Composables directory
|
# Composables directory
|
||||||
|
|
||||||
Nuxt will soon support a `composables/` directory to auto import your Vue composables into your application when used, learn more on the [open issue](https://github.com/nuxt/framework/issues/639).
|
Nuxt 3 supports `composables/` directory to auto import your Vue composables into your application and use using auto imports!
|
||||||
|
|
||||||
|
|
||||||
|
Example: (using named exports)
|
||||||
|
|
||||||
|
```js [composables/useFoo.ts]
|
||||||
|
import { useState } from '#app'
|
||||||
|
|
||||||
|
export const useFoo () {
|
||||||
|
return useState('foo', () => 'bar')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example: (using default export)
|
||||||
|
|
||||||
|
```js [composables/use-foo.ts or composables/useFoo.ts]
|
||||||
|
import { useState } from '#app'
|
||||||
|
|
||||||
|
// It will be available as useFoo()
|
||||||
|
export default function () {
|
||||||
|
return 'foo'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now auto import it:
|
||||||
|
|
||||||
|
```vue [app.vue]
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
{{ foo }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const foo = useFoo()
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
17
examples/with-composables/app.vue
Normal file
17
examples/with-composables/app.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
{{ a }}
|
||||||
|
{{ b }}
|
||||||
|
{{ c }}
|
||||||
|
{{ d }}
|
||||||
|
{{ foo }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const a = useA()
|
||||||
|
const b = useB()
|
||||||
|
const c = useC()
|
||||||
|
const d = useD()
|
||||||
|
const foo = useFoo()
|
||||||
|
</script>
|
23
examples/with-composables/composables/use-foo.ts
Normal file
23
examples/with-composables/composables/use-foo.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useState } from '#app'
|
||||||
|
|
||||||
|
export function useA () {
|
||||||
|
return 'a'
|
||||||
|
}
|
||||||
|
|
||||||
|
function useB () {
|
||||||
|
return 'b'
|
||||||
|
}
|
||||||
|
|
||||||
|
function _useC () {
|
||||||
|
return 'c'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useD = () => {
|
||||||
|
return 'd'
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useB, _useC as useC }
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return useState('foo', () => 'bar')
|
||||||
|
}
|
4
examples/with-composables/nuxt.config.ts
Normal file
4
examples/with-composables/nuxt.config.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { defineNuxtConfig } from 'nuxt3'
|
||||||
|
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
})
|
12
examples/with-composables/package.json
Normal file
12
examples/with-composables/package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "example-with-composables",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"nuxt3": "latest"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"build": "nuxt build",
|
||||||
|
"start": "node .output/server/index.mjs"
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,7 @@ export interface NuxtHooks {
|
|||||||
// Auto imports
|
// Auto imports
|
||||||
'autoImports:sources': (autoImportSources: AutoImportSource[]) => HookResult
|
'autoImports:sources': (autoImportSources: AutoImportSource[]) => HookResult
|
||||||
'autoImports:extend': (autoImports: AutoImport[]) => HookResult
|
'autoImports:extend': (autoImports: AutoImport[]) => HookResult
|
||||||
|
'autoImports:dirs': (dirs: string[]) => HookResult
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
'components:dirs': (dirs: ComponentsOptions['dirs']) => HookResult
|
'components:dirs': (dirs: ComponentsOptions['dirs']) => HookResult
|
||||||
|
@ -46,4 +46,10 @@ export interface AutoImportsOptions {
|
|||||||
* [experimental] Use globalThis injection instead of transform for development
|
* [experimental] Use globalThis injection instead of transform for development
|
||||||
*/
|
*/
|
||||||
global?: boolean
|
global?: boolean
|
||||||
|
/**
|
||||||
|
* Additional directories to scan composables from
|
||||||
|
*
|
||||||
|
* By default <rootDir>/composables is added
|
||||||
|
*/
|
||||||
|
dirs?: []
|
||||||
}
|
}
|
||||||
|
@ -74,20 +74,26 @@ export default defineNuxtCommand({
|
|||||||
// TODO: Watcher service, modules, and requireTree
|
// TODO: Watcher service, modules, and requireTree
|
||||||
const dLoad = debounce(load, 250)
|
const dLoad = debounce(load, 250)
|
||||||
const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 1 })
|
const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 1 })
|
||||||
watcher.on('all', (_event, file) => {
|
watcher.on('all', (event, file) => {
|
||||||
if (!currentNuxt) { return }
|
if (!currentNuxt) { return }
|
||||||
if (file.startsWith(currentNuxt.options.buildDir)) { return }
|
if (file.startsWith(currentNuxt.options.buildDir)) { return }
|
||||||
if (file.match(/nuxt\.config\.(js|ts|mjs|cjs)$/)) {
|
if (file.match(/nuxt\.config\.(js|ts|mjs|cjs)$/)) {
|
||||||
dLoad(true, `${relative(rootDir, file)} updated`)
|
dLoad(true, `${relative(rootDir, file)} updated`)
|
||||||
}
|
}
|
||||||
if (['addDir', 'unlinkDir'].includes(_event) && file.match(/pages$/)) {
|
|
||||||
dLoad(true, `Directory \`pages/\` ${_event === 'addDir' ? 'created' : 'removed'}`)
|
const isDirChange = ['addDir', 'unlinkDir'].includes(event)
|
||||||
}
|
const isFileChange = ['add', 'unlink'].includes(event)
|
||||||
if (['addDir', 'unlinkDir'].includes(_event) && file.match(/components$/)) {
|
const reloadDirs = ['pages', 'components', 'composables']
|
||||||
dLoad(true, `Directory \`components/\` ${_event === 'addDir' ? 'created' : 'removed'}`)
|
|
||||||
}
|
if (isDirChange) {
|
||||||
if (['add', 'unlink'].includes(_event) && file.match(/app\.(js|ts|mjs|jsx|tsx|vue)$/)) {
|
const dir = reloadDirs.find(dir => file.endsWith(dir))
|
||||||
dLoad(true, `\`${relative(rootDir, file)}\` ${_event === 'add' ? 'created' : 'removed'}`)
|
if (dir) {
|
||||||
|
dLoad(true, `Directory \`${dir}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
|
||||||
|
}
|
||||||
|
} else if (isFileChange) {
|
||||||
|
if (file.match(/app\.(js|ts|mjs|jsx|tsx|vue)$/)) {
|
||||||
|
dLoad(true, `\`${relative(rootDir, file)}\` ${event === 'add' ? 'created' : 'removed'}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
39
packages/nuxt3/src/auto-imports/composables.ts
Normal file
39
packages/nuxt3/src/auto-imports/composables.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { promises as fsp, existsSync } from 'fs'
|
||||||
|
import { parse as parsePath, join } from 'pathe'
|
||||||
|
import globby from 'globby'
|
||||||
|
import { findExports } from 'mlly'
|
||||||
|
import { camelCase } from 'scule'
|
||||||
|
import { AutoImport } from '@nuxt/kit'
|
||||||
|
import { filterInPlace } from './utils'
|
||||||
|
|
||||||
|
export async function scanForComposables (dir: string, autoImports: AutoImport[]) {
|
||||||
|
if (!existsSync(dir)) { return }
|
||||||
|
|
||||||
|
const files = await globby(['*.{ts,js,tsx,jsx,mjs,cjs,mts,cts}'], { cwd: dir })
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
files.map(async (file) => {
|
||||||
|
const importPath = join(dir, file)
|
||||||
|
|
||||||
|
// Remove original entries from the same import (for build watcher)
|
||||||
|
filterInPlace(autoImports, i => i.from !== importPath)
|
||||||
|
|
||||||
|
const code = await fsp.readFile(join(dir, file), 'utf-8')
|
||||||
|
const exports = findExports(code)
|
||||||
|
const defaultExport = exports.find(i => i.type === 'default')
|
||||||
|
|
||||||
|
if (defaultExport) {
|
||||||
|
autoImports.push({ name: 'default', as: camelCase(parsePath(file).name), from: importPath })
|
||||||
|
}
|
||||||
|
for (const exp of exports) {
|
||||||
|
if (exp.type === 'named') {
|
||||||
|
for (const name of exp.names) {
|
||||||
|
autoImports.push({ name, as: name, from: importPath })
|
||||||
|
}
|
||||||
|
} else if (exp.type === 'declaration') {
|
||||||
|
autoImports.push({ name: exp.name, as: exp.name, from: importPath })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
42
packages/nuxt3/src/auto-imports/context.ts
Normal file
42
packages/nuxt3/src/auto-imports/context.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import type { AutoImport } from '@nuxt/kit'
|
||||||
|
|
||||||
|
export interface AutoImportContext {
|
||||||
|
autoImports: AutoImport[]
|
||||||
|
matchRE: RegExp
|
||||||
|
map: Map<string, AutoImport>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAutoImportContext (): AutoImportContext {
|
||||||
|
return {
|
||||||
|
autoImports: [],
|
||||||
|
map: new Map(),
|
||||||
|
matchRE: /__never__/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateAutoImportContext (ctx: AutoImportContext) {
|
||||||
|
// Detect duplicates
|
||||||
|
const usedNames = new Set()
|
||||||
|
for (const autoImport of ctx.autoImports) {
|
||||||
|
if (usedNames.has(autoImport.as)) {
|
||||||
|
autoImport.disabled = true
|
||||||
|
console.warn(`Disabling duplicate auto import '${autoImport.as}' (imported from '${autoImport.from}')`)
|
||||||
|
} else {
|
||||||
|
usedNames.add(autoImport.as)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out disabled auto imports
|
||||||
|
ctx.autoImports = ctx.autoImports.filter(i => i.disabled !== true)
|
||||||
|
|
||||||
|
// Create regex
|
||||||
|
ctx.matchRE = new RegExp(`\\b(${ctx.autoImports.map(i => i.as).join('|')})\\b`, 'g')
|
||||||
|
|
||||||
|
// Create map
|
||||||
|
ctx.map.clear()
|
||||||
|
for (const autoImport of ctx.autoImports) {
|
||||||
|
ctx.map.set(autoImport.as, autoImport)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
@ -1,15 +1,19 @@
|
|||||||
import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, addPluginTemplate, AutoImport } from '@nuxt/kit'
|
import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, addPluginTemplate, useNuxt } from '@nuxt/kit'
|
||||||
import type { AutoImportsOptions } from '@nuxt/kit'
|
import type { AutoImportsOptions } from '@nuxt/kit'
|
||||||
import { isAbsolute, relative, resolve } from 'pathe'
|
import { isAbsolute, join, relative, resolve, normalize } from 'pathe'
|
||||||
import { TransformPlugin } from './transform'
|
import { TransformPlugin } from './transform'
|
||||||
import { Nuxt3AutoImports } from './imports'
|
import { Nuxt3AutoImports } from './imports'
|
||||||
|
import { scanForComposables } from './composables'
|
||||||
|
import { toImports } from './utils'
|
||||||
|
import { AutoImportContext, createAutoImportContext, updateAutoImportContext } from './context'
|
||||||
|
|
||||||
export default defineNuxtModule<AutoImportsOptions>({
|
export default defineNuxtModule<AutoImportsOptions>({
|
||||||
name: 'auto-imports',
|
name: 'auto-imports',
|
||||||
configKey: 'autoImports',
|
configKey: 'autoImports',
|
||||||
defaults: {
|
defaults: {
|
||||||
sources: Nuxt3AutoImports,
|
sources: Nuxt3AutoImports,
|
||||||
global: false
|
global: false,
|
||||||
|
dirs: []
|
||||||
},
|
},
|
||||||
async setup (options, nuxt) {
|
async setup (options, nuxt) {
|
||||||
// Allow modules extending sources
|
// Allow modules extending sources
|
||||||
@ -18,91 +22,96 @@ export default defineNuxtModule<AutoImportsOptions>({
|
|||||||
// Filter disabled sources
|
// Filter disabled sources
|
||||||
options.sources = options.sources.filter(source => source.disabled !== true)
|
options.sources = options.sources.filter(source => source.disabled !== true)
|
||||||
|
|
||||||
|
// Create a context to share state between module internals
|
||||||
|
const ctx = createAutoImportContext()
|
||||||
|
|
||||||
// Resolve autoimports from sources
|
// Resolve autoimports from sources
|
||||||
let autoImports: AutoImport[] = []
|
|
||||||
for (const source of options.sources) {
|
for (const source of options.sources) {
|
||||||
for (const importName of source.names) {
|
for (const importName of source.names) {
|
||||||
if (typeof importName === 'string') {
|
if (typeof importName === 'string') {
|
||||||
autoImports.push({ name: importName, as: importName, from: source.from })
|
ctx.autoImports.push({ name: importName, as: importName, from: source.from })
|
||||||
} else {
|
} else {
|
||||||
autoImports.push({ name: importName.name, as: importName.as || importName.name, from: source.from })
|
ctx.autoImports.push({ name: importName.name, as: importName.as || importName.name, from: source.from })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow modules extending resolved imports
|
// composables/ dirs
|
||||||
await nuxt.callHook('autoImports:extend', autoImports)
|
let composablesDirs = [
|
||||||
|
join(nuxt.options.srcDir, 'composables'),
|
||||||
|
...options.dirs
|
||||||
|
]
|
||||||
|
await nuxt.callHook('autoImports:dirs', composablesDirs)
|
||||||
|
composablesDirs = composablesDirs.map(dir => normalize(dir))
|
||||||
|
|
||||||
// Disable duplicate auto imports
|
// Transpile and injection
|
||||||
const usedNames = new Set()
|
// @ts-ignore temporary disabled due to #746
|
||||||
for (const autoImport of autoImports) {
|
|
||||||
if (usedNames.has(autoImport.as)) {
|
|
||||||
autoImport.disabled = true
|
|
||||||
console.warn(`Disabling duplicate auto import '${autoImport.as}' (imported from '${autoImport.from}')`)
|
|
||||||
} else {
|
|
||||||
usedNames.add(autoImport.as)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter disabled imports
|
|
||||||
autoImports = autoImports.filter(i => i.disabled !== true)
|
|
||||||
|
|
||||||
// temporary disable #746
|
|
||||||
// @ts-ignore
|
|
||||||
if (nuxt.options.dev && options.global) {
|
if (nuxt.options.dev && options.global) {
|
||||||
// Add all imports to globalThis in development mode
|
// Add all imports to globalThis in development mode
|
||||||
addPluginTemplate({
|
addPluginTemplate({
|
||||||
filename: 'auto-imports.mjs',
|
filename: 'auto-imports.mjs',
|
||||||
src: '',
|
src: '',
|
||||||
getContents: () => {
|
getContents: () => {
|
||||||
const imports = toImports(autoImports)
|
const imports = toImports(ctx.autoImports)
|
||||||
const globalThisSet = autoImports.map(i => `globalThis.${i.as} = ${i.as};`).join('\n')
|
const globalThisSet = ctx.autoImports.map(i => `globalThis.${i.as} = ${i.as};`).join('\n')
|
||||||
return `${imports}\n\n${globalThisSet}\n\nexport default () => {};`
|
return `${imports}\n\n${globalThisSet}\n\nexport default () => {};`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Transform to inject imports in production mode
|
// Transform to inject imports in production mode
|
||||||
addVitePlugin(TransformPlugin.vite(autoImports))
|
addVitePlugin(TransformPlugin.vite(ctx))
|
||||||
addWebpackPlugin(TransformPlugin.webpack(autoImports))
|
addWebpackPlugin(TransformPlugin.webpack(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add types
|
const updateAutoImports = async () => {
|
||||||
const resolved = {}
|
// Scan composables/
|
||||||
const r = (id: string) => {
|
for (const composablesDir of composablesDirs) {
|
||||||
if (resolved[id]) { return resolved[id] }
|
await scanForComposables(composablesDir, ctx.autoImports)
|
||||||
let path = resolveAlias(id, nuxt.options.alias)
|
|
||||||
if (isAbsolute(path)) {
|
|
||||||
path = relative(nuxt.options.buildDir, path)
|
|
||||||
}
|
}
|
||||||
// Remove file extension for benefit of TypeScript
|
// Allow modules extending
|
||||||
path = path.replace(/\.[a-z]+$/, '')
|
await nuxt.callHook('autoImports:extend', ctx.autoImports)
|
||||||
resolved[id] = path
|
// Update context
|
||||||
return path
|
updateAutoImportContext(ctx)
|
||||||
|
// Generate types
|
||||||
|
generateDts(ctx)
|
||||||
}
|
}
|
||||||
|
await updateAutoImports()
|
||||||
|
|
||||||
addTemplate({
|
// Watch composables/ directory
|
||||||
filename: 'auto-imports.d.ts',
|
nuxt.hook('builder:watch', async (_, path) => {
|
||||||
write: true,
|
const _resolved = resolve(nuxt.options.srcDir, path)
|
||||||
getContents: () => `// Generated by auto imports
|
if (composablesDirs.find(dir => _resolved.startsWith(dir))) {
|
||||||
declare global {
|
await updateAutoImports()
|
||||||
${autoImports.map(i => ` const ${i.as}: typeof import('${r(i.from)}')['${i.name}']`).join('\n')}
|
}
|
||||||
}\nexport {}`
|
|
||||||
})
|
|
||||||
nuxt.hook('prepare:types', ({ references }) => {
|
|
||||||
references.push({ path: resolve(nuxt.options.buildDir, 'auto-imports.d.ts') })
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function toImports (autoImports: AutoImport[]) {
|
function generateDts (ctx: AutoImportContext) {
|
||||||
const map: Record<string, Set<string>> = {}
|
const nuxt = useNuxt()
|
||||||
for (const autoImport of autoImports) {
|
|
||||||
if (!map[autoImport.from]) {
|
const resolved = {}
|
||||||
map[autoImport.from] = new Set()
|
const r = (id: string) => {
|
||||||
|
if (resolved[id]) { return resolved[id] }
|
||||||
|
let path = resolveAlias(id, nuxt.options.alias)
|
||||||
|
if (isAbsolute(path)) {
|
||||||
|
path = relative(nuxt.options.buildDir, path)
|
||||||
}
|
}
|
||||||
map[autoImport.from].add(autoImport.as)
|
// Remove file extension for benefit of TypeScript
|
||||||
|
path = path.replace(/\.[a-z]+$/, '')
|
||||||
|
resolved[id] = path
|
||||||
|
return path
|
||||||
}
|
}
|
||||||
return Object.entries(map)
|
|
||||||
.map(([name, imports]) => `import { ${Array.from(imports).join(', ')} } from '${name}';`)
|
addTemplate({
|
||||||
.join('\n')
|
filename: 'auto-imports.d.ts',
|
||||||
|
write: true,
|
||||||
|
getContents: () => `// Generated by auto imports
|
||||||
|
declare global {
|
||||||
|
${ctx.autoImports.map(i => ` const ${i.as}: typeof import('${r(i.from)}')['${i.name}']`).join('\n')}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {}
|
||||||
|
`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { parseQuery, parseURL } from 'ufo'
|
import { parseQuery, parseURL } from 'ufo'
|
||||||
import type { AutoImport } from '@nuxt/kit'
|
import { toImports } from './utils'
|
||||||
|
import { AutoImportContext } from './context'
|
||||||
|
|
||||||
const excludeRE = [
|
const excludeRE = [
|
||||||
// imported from other module
|
// imported from other module
|
||||||
@ -22,15 +23,7 @@ function stripeComments (code: string) {
|
|||||||
.replace(singlelineCommentsRE, '')
|
.replace(singlelineCommentsRE, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TransformPlugin = createUnplugin((autoImports: AutoImport[]) => {
|
export const TransformPlugin = createUnplugin((ctx: AutoImportContext) => {
|
||||||
const matchRE = new RegExp(`\\b(${autoImports.map(i => i.as).join('|')})\\b`, 'g')
|
|
||||||
|
|
||||||
// Create an internal map for faster lookup
|
|
||||||
const autoImportMap = new Map<string, AutoImport>()
|
|
||||||
for (const autoImport of autoImports) {
|
|
||||||
autoImportMap.set(autoImport.as, autoImport)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'nuxt-auto-imports-transform',
|
name: 'nuxt-auto-imports-transform',
|
||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
@ -60,7 +53,7 @@ export const TransformPlugin = createUnplugin((autoImports: AutoImport[]) => {
|
|||||||
const withoutComment = stripeComments(code)
|
const withoutComment = stripeComments(code)
|
||||||
|
|
||||||
// find all possible injection
|
// find all possible injection
|
||||||
const matched = new Set(Array.from(withoutComment.matchAll(matchRE)).map(i => i[1]))
|
const matched = new Set(Array.from(withoutComment.matchAll(ctx.matchRE)).map(i => i[1]))
|
||||||
|
|
||||||
// remove those already defined
|
// remove those already defined
|
||||||
for (const regex of excludeRE) {
|
for (const regex of excludeRE) {
|
||||||
@ -78,28 +71,11 @@ export const TransformPlugin = createUnplugin((autoImports: AutoImport[]) => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const modules: Record<string, string[]> = {}
|
// For webpack4/bridge support
|
||||||
|
|
||||||
// group by module name
|
|
||||||
Array.from(matched).forEach((name) => {
|
|
||||||
const moduleName = autoImportMap.get(name).from
|
|
||||||
if (!modules[moduleName]) {
|
|
||||||
modules[moduleName] = []
|
|
||||||
}
|
|
||||||
modules[moduleName].push(name)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Needed for webpack4/bridge support
|
|
||||||
const isCJSContext = code.includes('require(')
|
const isCJSContext = code.includes('require(')
|
||||||
|
|
||||||
// stringify import
|
const matchedImports = Array.from(matched).map(name => ctx.map.get(name)).filter(Boolean)
|
||||||
const imports = !isCJSContext
|
const imports = toImports(matchedImports, isCJSContext)
|
||||||
? Object.entries(modules)
|
|
||||||
.map(([moduleName, names]) => `import { ${names.join(',')} } from '${moduleName}';`)
|
|
||||||
.join('')
|
|
||||||
: Object.entries(modules)
|
|
||||||
.map(([moduleName, names]) => `const { ${names.join(',')} } = require('${moduleName}');`)
|
|
||||||
.join('')
|
|
||||||
|
|
||||||
return imports + code
|
return imports + code
|
||||||
}
|
}
|
||||||
|
36
packages/nuxt3/src/auto-imports/utils.ts
Normal file
36
packages/nuxt3/src/auto-imports/utils.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import type { AutoImport } from '@nuxt/kit'
|
||||||
|
|
||||||
|
export function toImports (autoImports: AutoImport[], isCJS = false) {
|
||||||
|
const aliasKeyword = isCJS ? ' : ' : ' as '
|
||||||
|
|
||||||
|
const map: Record<string, Set<string>> = {}
|
||||||
|
for (const autoImport of autoImports) {
|
||||||
|
if (!map[autoImport.from]) {
|
||||||
|
map[autoImport.from] = new Set()
|
||||||
|
}
|
||||||
|
map[autoImport.from].add(
|
||||||
|
autoImport.name === autoImport.as
|
||||||
|
? autoImport.name
|
||||||
|
: autoImport.name + aliasKeyword + autoImport.as
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCJS) {
|
||||||
|
return Object.entries(map)
|
||||||
|
.map(([name, imports]) => `const { ${Array.from(imports).join(', ')} } = require('${name}');`)
|
||||||
|
.join('\n')
|
||||||
|
} else {
|
||||||
|
return Object.entries(map)
|
||||||
|
.map(([name, imports]) => `import { ${Array.from(imports).join(', ')} } from '${name}';`)
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterInPlace<T> (arr: T[], predicate: (v: T) => any) {
|
||||||
|
let i = arr.length
|
||||||
|
while (i--) {
|
||||||
|
if (!predicate(arr[i])) {
|
||||||
|
arr.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import type { AutoImport } from '@nuxt/kit'
|
import type { AutoImport } from '@nuxt/kit'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
|
import { AutoImportContext, updateAutoImportContext } from '../src/auto-imports/context'
|
||||||
import { TransformPlugin } from '../src/auto-imports/transform'
|
import { TransformPlugin } from '../src/auto-imports/transform'
|
||||||
|
|
||||||
describe('auto-imports:transform', () => {
|
describe('auto-imports:transform', () => {
|
||||||
@ -8,7 +9,10 @@ describe('auto-imports:transform', () => {
|
|||||||
{ name: 'computed', as: 'computed', from: 'bar' }
|
{ name: 'computed', as: 'computed', from: 'bar' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const transformPlugin = TransformPlugin.raw(autoImports, { framework: 'rollup' })
|
const ctx = { autoImports, map: new Map() } as AutoImportContext
|
||||||
|
updateAutoImportContext(ctx)
|
||||||
|
|
||||||
|
const transformPlugin = TransformPlugin.raw(ctx, { framework: 'rollup' })
|
||||||
const transform = (code: string) => transformPlugin.transform.call({ error: null, warn: null }, code, '')
|
const transform = (code: string) => transformPlugin.transform.call({ error: null, warn: null }, code, '')
|
||||||
|
|
||||||
it('should correct inject', async () => {
|
it('should correct inject', async () => {
|
||||||
|
@ -9278,6 +9278,14 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"example-with-composables@workspace:examples/with-composables":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "example-with-composables@workspace:examples/with-composables"
|
||||||
|
dependencies:
|
||||||
|
nuxt3: latest
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"example-with-layouts@workspace:examples/with-layouts":
|
"example-with-layouts@workspace:examples/with-layouts":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "example-with-layouts@workspace:examples/with-layouts"
|
resolution: "example-with-layouts@workspace:examples/with-layouts"
|
||||||
|
Loading…
Reference in New Issue
Block a user