mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 09:25:54 +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
|
||||
|
||||
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
|
||||
'autoImports:sources': (autoImportSources: AutoImportSource[]) => HookResult
|
||||
'autoImports:extend': (autoImports: AutoImport[]) => HookResult
|
||||
'autoImports:dirs': (dirs: string[]) => HookResult
|
||||
|
||||
// Components
|
||||
'components:dirs': (dirs: ComponentsOptions['dirs']) => HookResult
|
||||
|
@ -46,4 +46,10 @@ export interface AutoImportsOptions {
|
||||
* [experimental] Use globalThis injection instead of transform for development
|
||||
*/
|
||||
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
|
||||
const dLoad = debounce(load, 250)
|
||||
const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 1 })
|
||||
watcher.on('all', (_event, file) => {
|
||||
watcher.on('all', (event, file) => {
|
||||
if (!currentNuxt) { return }
|
||||
if (file.startsWith(currentNuxt.options.buildDir)) { return }
|
||||
if (file.match(/nuxt\.config\.(js|ts|mjs|cjs)$/)) {
|
||||
dLoad(true, `${relative(rootDir, file)} updated`)
|
||||
}
|
||||
if (['addDir', 'unlinkDir'].includes(_event) && file.match(/pages$/)) {
|
||||
dLoad(true, `Directory \`pages/\` ${_event === 'addDir' ? 'created' : 'removed'}`)
|
||||
}
|
||||
if (['addDir', 'unlinkDir'].includes(_event) && file.match(/components$/)) {
|
||||
dLoad(true, `Directory \`components/\` ${_event === 'addDir' ? 'created' : 'removed'}`)
|
||||
}
|
||||
if (['add', 'unlink'].includes(_event) && file.match(/app\.(js|ts|mjs|jsx|tsx|vue)$/)) {
|
||||
dLoad(true, `\`${relative(rootDir, file)}\` ${_event === 'add' ? 'created' : 'removed'}`)
|
||||
|
||||
const isDirChange = ['addDir', 'unlinkDir'].includes(event)
|
||||
const isFileChange = ['add', 'unlink'].includes(event)
|
||||
const reloadDirs = ['pages', 'components', 'composables']
|
||||
|
||||
if (isDirChange) {
|
||||
const dir = reloadDirs.find(dir => file.endsWith(dir))
|
||||
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 { isAbsolute, relative, resolve } from 'pathe'
|
||||
import { isAbsolute, join, relative, resolve, normalize } from 'pathe'
|
||||
import { TransformPlugin } from './transform'
|
||||
import { Nuxt3AutoImports } from './imports'
|
||||
import { scanForComposables } from './composables'
|
||||
import { toImports } from './utils'
|
||||
import { AutoImportContext, createAutoImportContext, updateAutoImportContext } from './context'
|
||||
|
||||
export default defineNuxtModule<AutoImportsOptions>({
|
||||
name: 'auto-imports',
|
||||
configKey: 'autoImports',
|
||||
defaults: {
|
||||
sources: Nuxt3AutoImports,
|
||||
global: false
|
||||
global: false,
|
||||
dirs: []
|
||||
},
|
||||
async setup (options, nuxt) {
|
||||
// Allow modules extending sources
|
||||
@ -18,91 +22,96 @@ export default defineNuxtModule<AutoImportsOptions>({
|
||||
// Filter disabled sources
|
||||
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
|
||||
let autoImports: AutoImport[] = []
|
||||
for (const source of options.sources) {
|
||||
for (const importName of source.names) {
|
||||
if (typeof importName === 'string') {
|
||||
autoImports.push({ name: importName, as: importName, from: source.from })
|
||||
ctx.autoImports.push({ name: importName, as: importName, from: source.from })
|
||||
} 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
|
||||
await nuxt.callHook('autoImports:extend', autoImports)
|
||||
// composables/ dirs
|
||||
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
|
||||
const usedNames = new Set()
|
||||
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
|
||||
// Transpile and injection
|
||||
// @ts-ignore temporary disabled due to #746
|
||||
if (nuxt.options.dev && options.global) {
|
||||
// Add all imports to globalThis in development mode
|
||||
addPluginTemplate({
|
||||
filename: 'auto-imports.mjs',
|
||||
src: '',
|
||||
getContents: () => {
|
||||
const imports = toImports(autoImports)
|
||||
const globalThisSet = autoImports.map(i => `globalThis.${i.as} = ${i.as};`).join('\n')
|
||||
const imports = toImports(ctx.autoImports)
|
||||
const globalThisSet = ctx.autoImports.map(i => `globalThis.${i.as} = ${i.as};`).join('\n')
|
||||
return `${imports}\n\n${globalThisSet}\n\nexport default () => {};`
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Transform to inject imports in production mode
|
||||
addVitePlugin(TransformPlugin.vite(autoImports))
|
||||
addWebpackPlugin(TransformPlugin.webpack(autoImports))
|
||||
addVitePlugin(TransformPlugin.vite(ctx))
|
||||
addWebpackPlugin(TransformPlugin.webpack(ctx))
|
||||
}
|
||||
|
||||
// Add types
|
||||
const resolved = {}
|
||||
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)
|
||||
const updateAutoImports = async () => {
|
||||
// Scan composables/
|
||||
for (const composablesDir of composablesDirs) {
|
||||
await scanForComposables(composablesDir, ctx.autoImports)
|
||||
}
|
||||
// Remove file extension for benefit of TypeScript
|
||||
path = path.replace(/\.[a-z]+$/, '')
|
||||
resolved[id] = path
|
||||
return path
|
||||
// Allow modules extending
|
||||
await nuxt.callHook('autoImports:extend', ctx.autoImports)
|
||||
// Update context
|
||||
updateAutoImportContext(ctx)
|
||||
// Generate types
|
||||
generateDts(ctx)
|
||||
}
|
||||
await updateAutoImports()
|
||||
|
||||
addTemplate({
|
||||
filename: 'auto-imports.d.ts',
|
||||
write: true,
|
||||
getContents: () => `// Generated by auto imports
|
||||
declare global {
|
||||
${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') })
|
||||
// Watch composables/ directory
|
||||
nuxt.hook('builder:watch', async (_, path) => {
|
||||
const _resolved = resolve(nuxt.options.srcDir, path)
|
||||
if (composablesDirs.find(dir => _resolved.startsWith(dir))) {
|
||||
await updateAutoImports()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function toImports (autoImports: AutoImport[]) {
|
||||
const map: Record<string, Set<string>> = {}
|
||||
for (const autoImport of autoImports) {
|
||||
if (!map[autoImport.from]) {
|
||||
map[autoImport.from] = new Set()
|
||||
function generateDts (ctx: AutoImportContext) {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
const resolved = {}
|
||||
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}';`)
|
||||
.join('\n')
|
||||
|
||||
addTemplate({
|
||||
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 { parseQuery, parseURL } from 'ufo'
|
||||
import type { AutoImport } from '@nuxt/kit'
|
||||
import { toImports } from './utils'
|
||||
import { AutoImportContext } from './context'
|
||||
|
||||
const excludeRE = [
|
||||
// imported from other module
|
||||
@ -22,15 +23,7 @@ function stripeComments (code: string) {
|
||||
.replace(singlelineCommentsRE, '')
|
||||
}
|
||||
|
||||
export const TransformPlugin = createUnplugin((autoImports: AutoImport[]) => {
|
||||
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)
|
||||
}
|
||||
|
||||
export const TransformPlugin = createUnplugin((ctx: AutoImportContext) => {
|
||||
return {
|
||||
name: 'nuxt-auto-imports-transform',
|
||||
enforce: 'post',
|
||||
@ -60,7 +53,7 @@ export const TransformPlugin = createUnplugin((autoImports: AutoImport[]) => {
|
||||
const withoutComment = stripeComments(code)
|
||||
|
||||
// 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
|
||||
for (const regex of excludeRE) {
|
||||
@ -78,28 +71,11 @@ export const TransformPlugin = createUnplugin((autoImports: AutoImport[]) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const modules: Record<string, string[]> = {}
|
||||
|
||||
// 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
|
||||
// For webpack4/bridge support
|
||||
const isCJSContext = code.includes('require(')
|
||||
|
||||
// stringify import
|
||||
const imports = !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('')
|
||||
const matchedImports = Array.from(matched).map(name => ctx.map.get(name)).filter(Boolean)
|
||||
const imports = toImports(matchedImports, isCJSContext)
|
||||
|
||||
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 { expect } from 'chai'
|
||||
import { AutoImportContext, updateAutoImportContext } from '../src/auto-imports/context'
|
||||
import { TransformPlugin } from '../src/auto-imports/transform'
|
||||
|
||||
describe('auto-imports:transform', () => {
|
||||
@ -8,7 +9,10 @@ describe('auto-imports:transform', () => {
|
||||
{ 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, '')
|
||||
|
||||
it('should correct inject', async () => {
|
||||
|
@ -9278,6 +9278,14 @@ __metadata:
|
||||
languageName: unknown
|
||||
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":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-with-layouts@workspace:examples/with-layouts"
|
||||
|
Loading…
Reference in New Issue
Block a user