mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 23:22:02 +00:00
refactor!: migrate to unimport
(#3386)
This commit is contained in:
parent
c942465f79
commit
cff2f37cc8
@ -1,35 +1,29 @@
|
||||
import { installModule, useNuxt } from '@nuxt/kit'
|
||||
import * as CompositionApi from '@vue/composition-api'
|
||||
import type { Preset } from 'unimport'
|
||||
import autoImports from '../../nuxt3/src/auto-imports/module'
|
||||
import { vuePreset, commonPresets, appPreset } from '../../nuxt3/src/auto-imports/presets'
|
||||
|
||||
const UnsupportedImports = new Set(['useAsyncData', 'useFetch'])
|
||||
const CapiHelpers = new Set(Object.keys(CompositionApi))
|
||||
|
||||
const ImportRewrites = {
|
||||
vue: '@vue/composition-api'
|
||||
}
|
||||
|
||||
export function setupAutoImports () {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
nuxt.hook('autoImports:extend', (autoImports) => {
|
||||
for (const autoImport of autoImports) {
|
||||
// Rewrite imports
|
||||
if (autoImport.from in ImportRewrites) {
|
||||
autoImport.from = ImportRewrites[autoImport.from]
|
||||
}
|
||||
// Disable unsupported imports
|
||||
if (UnsupportedImports.has(autoImport.name)) {
|
||||
autoImport.disabled = true
|
||||
}
|
||||
if (autoImport.from === '@vue/composition-api' && !CapiHelpers.has(autoImport.name)) {
|
||||
autoImport.disabled = true
|
||||
}
|
||||
const bridgePresets: Preset[] = [
|
||||
...commonPresets,
|
||||
{
|
||||
from: '#app',
|
||||
imports: [
|
||||
...appPreset.imports.filter(i => !UnsupportedImports.has(i as string)),
|
||||
'useNuxt2Meta'
|
||||
]
|
||||
},
|
||||
{
|
||||
from: '@vue/composition-api',
|
||||
imports: vuePreset.imports.filter(i => CapiHelpers.has(i as string))
|
||||
}
|
||||
]
|
||||
|
||||
// Add bridge-only auto-imports
|
||||
autoImports.push({ name: 'useNuxt2Meta', as: 'useNuxt2Meta', from: '#app' })
|
||||
})
|
||||
|
||||
nuxt.hook('modules:done', () => installModule(autoImports))
|
||||
nuxt.hook('modules:done', () => installModule(autoImports, { presets: bridgePresets }))
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
import * as CompositionApi from '@vue/composition-api'
|
||||
import { expect, describe, it } from 'vitest'
|
||||
|
||||
import { Nuxt3AutoImports } from '../../nuxt3/src/auto-imports/imports'
|
||||
|
||||
const excludedVueHelpers = [
|
||||
'EffectScope',
|
||||
'createApp',
|
||||
'createRef',
|
||||
'default',
|
||||
'del',
|
||||
'isRaw',
|
||||
'set',
|
||||
'useCSSModule',
|
||||
'version',
|
||||
'warn',
|
||||
'watchPostEffect',
|
||||
'watchSyncEffect'
|
||||
]
|
||||
|
||||
describe('auto-imports:vue', () => {
|
||||
for (const name of Object.keys(CompositionApi)) {
|
||||
if (excludedVueHelpers.includes(name)) {
|
||||
continue
|
||||
}
|
||||
it(`should register ${name} globally`, () => {
|
||||
expect(Nuxt3AutoImports.find(a => a.from === 'vue').names).to.include(name)
|
||||
})
|
||||
}
|
||||
})
|
@ -29,6 +29,7 @@
|
||||
"scule": "^0.2.1",
|
||||
"semver": "^7.3.5",
|
||||
"unctx": "^1.0.2",
|
||||
"unimport": "0.0.8",
|
||||
"untyped": "^0.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,14 +1,12 @@
|
||||
import type { AutoImport } from '../../schema/src/types/imports'
|
||||
import { Import } from 'unimport'
|
||||
import { useNuxt } from './context'
|
||||
import { assertNuxtCompatibility } from './compatibility'
|
||||
|
||||
export function addAutoImport (_autoImports: AutoImport | AutoImport[]) {
|
||||
export function addAutoImport (imports: Import | Import[]) {
|
||||
assertNuxtCompatibility({ bridge: true })
|
||||
|
||||
useNuxt().hook('autoImports:extend', (autoImports: AutoImport[]) => {
|
||||
for (const composable of (Array.isArray(_autoImports) ? _autoImports : [_autoImports])) {
|
||||
autoImports.push(composable)
|
||||
}
|
||||
useNuxt().hook('autoImports:extend', (autoImports) => {
|
||||
autoImports.push(...(Array.isArray(imports) ? imports : [imports]))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,7 @@
|
||||
"pathe": "^0.2.0",
|
||||
"scule": "^0.2.1",
|
||||
"ufo": "^0.7.11",
|
||||
"unimport": "0.0.8",
|
||||
"unplugin": "^0.4.0",
|
||||
"untyped": "^0.4.2",
|
||||
"vue": "^3.2.31",
|
||||
|
@ -2,44 +2,45 @@ import { promises as fsp, existsSync } from 'fs'
|
||||
import { parse as parsePath } from 'pathe'
|
||||
import { findExports } from 'mlly'
|
||||
import { camelCase } from 'scule'
|
||||
import { AutoImport } from '@nuxt/schema'
|
||||
import { resolveFiles } from '@nuxt/kit'
|
||||
import { filterInPlace } from './utils'
|
||||
import { Unimport } from 'unimport'
|
||||
|
||||
export async function scanForComposables (dir: string | string[], autoImports: AutoImport[]) {
|
||||
export async function scanForComposables (dir: string | string[], ctx: Unimport) {
|
||||
const performScan = async (entry: string) => {
|
||||
const files = await resolveFiles(entry, [
|
||||
'*.{ts,js,mjs,cjs,mts,cts}',
|
||||
'*/index.{ts,js,mjs,cjs,mts,cts}'
|
||||
])
|
||||
|
||||
await Promise.all(
|
||||
files.map(async (path) => {
|
||||
// Remove original entries from the same import (for build watcher)
|
||||
filterInPlace(autoImports, i => i.from !== path)
|
||||
await ctx.modifyDynamicImports(async (dynamicImports) => {
|
||||
await Promise.all(
|
||||
files.map(async (path) => {
|
||||
// Remove original entries from the same import (for build watcher)
|
||||
filterInPlace(dynamicImports, i => i.from !== path)
|
||||
|
||||
const code = await fsp.readFile(path, 'utf-8')
|
||||
const exports = findExports(code)
|
||||
const defaultExport = exports.find(i => i.type === 'default')
|
||||
const code = await fsp.readFile(path, 'utf-8')
|
||||
const exports = findExports(code)
|
||||
const defaultExport = exports.find(i => i.type === 'default')
|
||||
|
||||
if (defaultExport) {
|
||||
let name = parsePath(path).name
|
||||
if (name === 'index') {
|
||||
name = parsePath(path.split('/').slice(0, -1).join('/')).name
|
||||
}
|
||||
autoImports.push({ name: 'default', as: camelCase(name), from: path })
|
||||
}
|
||||
for (const exp of exports) {
|
||||
if (exp.type === 'named') {
|
||||
for (const name of exp.names) {
|
||||
autoImports.push({ name, as: name, from: path })
|
||||
if (defaultExport) {
|
||||
let name = parsePath(path).name
|
||||
if (name === 'index') {
|
||||
name = parsePath(path.split('/').slice(0, -1).join('/')).name
|
||||
}
|
||||
} else if (exp.type === 'declaration') {
|
||||
autoImports.push({ name: exp.name, as: exp.name, from: path })
|
||||
dynamicImports.push({ name: 'default', as: camelCase(name), from: path })
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
for (const exp of exports) {
|
||||
if (exp.type === 'named') {
|
||||
for (const name of exp.names) {
|
||||
dynamicImports.push({ name, as: name, from: path })
|
||||
}
|
||||
} else if (exp.type === 'declaration') {
|
||||
dynamicImports.push({ name: exp.name, as: exp.name, from: path })
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
for (const entry of Array.isArray(dir) ? dir : [dir]) {
|
||||
@ -48,3 +49,12 @@ export async function scanForComposables (dir: string | string[], autoImports: A
|
||||
await performScan(entry)
|
||||
}
|
||||
}
|
||||
|
||||
function filterInPlace<T> (arr: T[], predicate: (v: T) => any) {
|
||||
let i = arr.length
|
||||
while (i--) {
|
||||
if (!predicate(arr[i])) {
|
||||
arr.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
import type { AutoImport } from '@nuxt/schema'
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
|
||||
export interface AutoImportContext {
|
||||
autoImports: AutoImport[]
|
||||
matchRE: RegExp
|
||||
transform: {
|
||||
exclude: RegExp[]
|
||||
}
|
||||
map: Map<string, AutoImport>
|
||||
}
|
||||
|
||||
export function createAutoImportContext (opts): AutoImportContext {
|
||||
return {
|
||||
autoImports: [],
|
||||
map: new Map(),
|
||||
matchRE: /__never__/,
|
||||
transform: {
|
||||
exclude: opts.transform.exclude || [/node_modules/]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 => escapeRE(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,113 +0,0 @@
|
||||
import type { AutoImportSource } from '@nuxt/schema'
|
||||
|
||||
export const Nuxt3AutoImports: AutoImportSource[] = [
|
||||
// #app
|
||||
{
|
||||
from: '#app',
|
||||
names: [
|
||||
'useAsyncData',
|
||||
'useLazyAsyncData',
|
||||
'defineNuxtComponent',
|
||||
'useNuxtApp',
|
||||
'defineNuxtPlugin',
|
||||
'useRuntimeConfig',
|
||||
'useState',
|
||||
'useFetch',
|
||||
'useLazyFetch',
|
||||
'useCookie',
|
||||
'useRequestHeaders',
|
||||
'useRouter',
|
||||
'useRoute',
|
||||
'defineNuxtRouteMiddleware',
|
||||
'navigateTo',
|
||||
'abortNavigation',
|
||||
'addRouteMiddleware'
|
||||
]
|
||||
},
|
||||
// #meta
|
||||
{
|
||||
from: '#meta',
|
||||
names: [
|
||||
'useMeta'
|
||||
]
|
||||
},
|
||||
// vue-demi (mocked)
|
||||
{
|
||||
from: 'vue-demi',
|
||||
names: [
|
||||
'isVue2',
|
||||
'isVue3'
|
||||
]
|
||||
},
|
||||
// vue
|
||||
{
|
||||
from: 'vue',
|
||||
names: [
|
||||
// <script setup>
|
||||
'withCtx',
|
||||
'withDirectives',
|
||||
'withKeys',
|
||||
'withMemo',
|
||||
'withModifiers',
|
||||
'withScopeId',
|
||||
|
||||
// Lifecycle
|
||||
'onActivated',
|
||||
'onBeforeMount',
|
||||
'onBeforeUnmount',
|
||||
'onBeforeUpdate',
|
||||
'onDeactivated',
|
||||
'onErrorCaptured',
|
||||
'onMounted',
|
||||
'onRenderTracked',
|
||||
'onRenderTriggered',
|
||||
'onServerPrefetch',
|
||||
'onUnmounted',
|
||||
'onUpdated',
|
||||
|
||||
// Reactivity
|
||||
'computed',
|
||||
'customRef',
|
||||
'isProxy',
|
||||
'isReactive',
|
||||
'isReadonly',
|
||||
'isRef',
|
||||
'markRaw',
|
||||
'proxyRefs',
|
||||
'reactive',
|
||||
'readonly',
|
||||
'ref',
|
||||
'shallowReactive',
|
||||
'shallowReadonly',
|
||||
'shallowRef',
|
||||
'toRaw',
|
||||
'toRef',
|
||||
'toRefs',
|
||||
'triggerRef',
|
||||
'unref',
|
||||
'watch',
|
||||
'watchEffect',
|
||||
'isShallow',
|
||||
|
||||
// effect
|
||||
'effect',
|
||||
'effectScope',
|
||||
'getCurrentScope',
|
||||
'onScopeDispose',
|
||||
|
||||
// Component
|
||||
'defineComponent',
|
||||
'defineAsyncComponent',
|
||||
'getCurrentInstance',
|
||||
'h',
|
||||
'inject',
|
||||
'nextTick',
|
||||
'provide',
|
||||
'useAttrs',
|
||||
'useCssModule',
|
||||
'useCssVars',
|
||||
'useSlots',
|
||||
'useTransitionState'
|
||||
] as Array<keyof typeof import('vue')>
|
||||
}
|
||||
]
|
@ -1,21 +1,20 @@
|
||||
import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, addPluginTemplate, useNuxt } from '@nuxt/kit'
|
||||
import type { AutoImportsOptions } from '@nuxt/schema'
|
||||
import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, useNuxt, addPluginTemplate, logger } from '@nuxt/kit'
|
||||
import { isAbsolute, join, relative, resolve, normalize } from 'pathe'
|
||||
import { genDynamicImport } from 'knitwork'
|
||||
import { createUnimport, Import, toImports, Unimport } from 'unimport'
|
||||
import { AutoImportsOptions, ImportPresetWithDeperection } from '@nuxt/schema'
|
||||
import { TransformPlugin } from './transform'
|
||||
import { Nuxt3AutoImports } from './imports'
|
||||
import { defaultPresets } from './presets'
|
||||
import { scanForComposables } from './composables'
|
||||
import { toExports, toImports } from './utils'
|
||||
import { AutoImportContext, createAutoImportContext, updateAutoImportContext } from './context'
|
||||
|
||||
export default defineNuxtModule<AutoImportsOptions>({
|
||||
export default defineNuxtModule<Partial<AutoImportsOptions>>({
|
||||
meta: {
|
||||
name: 'auto-imports',
|
||||
configKey: 'autoImports'
|
||||
},
|
||||
defaults: {
|
||||
sources: Nuxt3AutoImports,
|
||||
presets: defaultPresets,
|
||||
global: false,
|
||||
imports: [],
|
||||
dirs: [],
|
||||
transform: {
|
||||
exclude: undefined
|
||||
@ -23,13 +22,23 @@ export default defineNuxtModule<AutoImportsOptions>({
|
||||
},
|
||||
async setup (options, nuxt) {
|
||||
// Allow modules extending sources
|
||||
await nuxt.callHook('autoImports:sources', options.sources)
|
||||
await nuxt.callHook('autoImports:sources', options.presets as ImportPresetWithDeperection[])
|
||||
|
||||
options.presets.forEach((i: ImportPresetWithDeperection) => {
|
||||
if (typeof i !== 'string' && i.names && !i.imports) {
|
||||
i.imports = i.names
|
||||
logger.warn('auto-imports: presets.names is deprecated, use presets.imports instead')
|
||||
}
|
||||
})
|
||||
|
||||
// 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(options)
|
||||
const ctx = createUnimport({
|
||||
presets: defaultPresets,
|
||||
imports: options.imports
|
||||
})
|
||||
|
||||
// composables/ dirs
|
||||
let composablesDirs = [
|
||||
@ -51,7 +60,7 @@ export default defineNuxtModule<AutoImportsOptions>({
|
||||
// Support for importing from '#imports'
|
||||
addTemplate({
|
||||
filename: 'imports.mjs',
|
||||
getContents: () => toExports(ctx.autoImports)
|
||||
getContents: () => ctx.toExports()
|
||||
})
|
||||
nuxt.options.alias['#imports'] = join(nuxt.options.buildDir, 'imports')
|
||||
|
||||
@ -62,30 +71,25 @@ export default defineNuxtModule<AutoImportsOptions>({
|
||||
addPluginTemplate({
|
||||
filename: 'auto-imports.mjs',
|
||||
getContents: () => {
|
||||
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 () => {};`
|
||||
const imports = ctx.getImports()
|
||||
const importStatement = toImports(imports)
|
||||
const globalThisSet = imports.map(i => `globalThis.${i.as} = ${i.as};`).join('\n')
|
||||
return `${importStatement}\n\n${globalThisSet}\n\nexport default () => {};`
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Transform to inject imports in production mode
|
||||
addVitePlugin(TransformPlugin.vite(ctx))
|
||||
addWebpackPlugin(TransformPlugin.webpack(ctx))
|
||||
addVitePlugin(TransformPlugin.vite({ ctx, options }))
|
||||
addWebpackPlugin(TransformPlugin.webpack({ ctx, options }))
|
||||
}
|
||||
|
||||
const regenerateAutoImports = async () => {
|
||||
// Resolve autoimports from sources
|
||||
ctx.autoImports = options.sources.flatMap(source => source.names.map(
|
||||
importName => typeof importName === 'string'
|
||||
? { name: importName, as: importName, from: source.from }
|
||||
: { name: importName.name, as: importName.as || importName.name, from: source.from }
|
||||
))
|
||||
// Scan composables/
|
||||
await scanForComposables(composablesDirs, ctx.autoImports)
|
||||
await scanForComposables(composablesDirs, ctx)
|
||||
// Allow modules extending
|
||||
await nuxt.callHook('autoImports:extend', ctx.autoImports)
|
||||
// Update context
|
||||
updateAutoImportContext(ctx)
|
||||
await ctx.modifyDynamicImports(async (imports) => {
|
||||
await nuxt.callHook('autoImports:extend', imports)
|
||||
})
|
||||
}
|
||||
|
||||
await regenerateAutoImports()
|
||||
@ -113,37 +117,34 @@ export default defineNuxtModule<AutoImportsOptions>({
|
||||
}
|
||||
})
|
||||
|
||||
function addDeclarationTemplates (ctx: AutoImportContext) {
|
||||
function addDeclarationTemplates (ctx: Unimport) {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
// Remove file extension for benefit of TypeScript
|
||||
const stripExtension = (path: string) => path.replace(/\.[a-z]+$/, '')
|
||||
|
||||
const resolved = {}
|
||||
const r = (id: string) => {
|
||||
if (resolved[id]) { return resolved[id] }
|
||||
let path = resolveAlias(id)
|
||||
const r = ({ from }: Import) => {
|
||||
if (resolved[from]) {
|
||||
return resolved[from]
|
||||
}
|
||||
let path = resolveAlias(from)
|
||||
if (isAbsolute(path)) {
|
||||
path = relative(join(nuxt.options.buildDir, 'types'), path)
|
||||
}
|
||||
|
||||
path = stripExtension(path)
|
||||
resolved[id] = path
|
||||
resolved[from] = path
|
||||
return path
|
||||
}
|
||||
|
||||
addTemplate({
|
||||
filename: 'imports.d.ts',
|
||||
getContents: () => toExports(ctx.autoImports.map(i => ({ ...i, from: stripExtension(i.from) })))
|
||||
getContents: () => ctx.toExports()
|
||||
})
|
||||
|
||||
addTemplate({
|
||||
filename: 'types/auto-imports.d.ts',
|
||||
getContents: () => `// Generated by auto imports
|
||||
declare global {
|
||||
${ctx.autoImports.map(i => ` const ${i.as}: typeof ${genDynamicImport(r(i.from), { wrapper: false })}['${i.name}']`).join('\n')}
|
||||
}
|
||||
|
||||
export {}
|
||||
`
|
||||
getContents: () => '// Generated by auto imports\n' + ctx.generateTypeDecarations(r)
|
||||
})
|
||||
}
|
||||
|
120
packages/nuxt3/src/auto-imports/presets.ts
Normal file
120
packages/nuxt3/src/auto-imports/presets.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import { defineUnimportPreset } from 'unimport'
|
||||
|
||||
export const commonPresets = [
|
||||
// #meta
|
||||
defineUnimportPreset({
|
||||
from: '#meta',
|
||||
imports: [
|
||||
'useMeta'
|
||||
]
|
||||
}),
|
||||
// vue-demi (mocked)
|
||||
defineUnimportPreset({
|
||||
from: 'vue-demi',
|
||||
imports: [
|
||||
'isVue2',
|
||||
'isVue3'
|
||||
]
|
||||
})
|
||||
]
|
||||
|
||||
export const appPreset = defineUnimportPreset({
|
||||
from: '#app',
|
||||
imports: [
|
||||
'useAsyncData',
|
||||
'useLazyAsyncData',
|
||||
'defineNuxtComponent',
|
||||
'useNuxtApp',
|
||||
'defineNuxtPlugin',
|
||||
'useRuntimeConfig',
|
||||
'useState',
|
||||
'useFetch',
|
||||
'useLazyFetch',
|
||||
'useCookie',
|
||||
'useRequestHeaders',
|
||||
'useRouter',
|
||||
'useRoute',
|
||||
'defineNuxtRouteMiddleware',
|
||||
'navigateTo',
|
||||
'abortNavigation',
|
||||
'addRouteMiddleware'
|
||||
]
|
||||
})
|
||||
|
||||
// vue
|
||||
export const vuePreset = defineUnimportPreset({
|
||||
from: 'vue',
|
||||
imports: [
|
||||
// <script setup>
|
||||
'withCtx',
|
||||
'withDirectives',
|
||||
'withKeys',
|
||||
'withMemo',
|
||||
'withModifiers',
|
||||
'withScopeId',
|
||||
|
||||
// Lifecycle
|
||||
'onActivated',
|
||||
'onBeforeMount',
|
||||
'onBeforeUnmount',
|
||||
'onBeforeUpdate',
|
||||
'onDeactivated',
|
||||
'onErrorCaptured',
|
||||
'onMounted',
|
||||
'onRenderTracked',
|
||||
'onRenderTriggered',
|
||||
'onServerPrefetch',
|
||||
'onUnmounted',
|
||||
'onUpdated',
|
||||
|
||||
// Reactivity
|
||||
'computed',
|
||||
'customRef',
|
||||
'isProxy',
|
||||
'isReactive',
|
||||
'isReadonly',
|
||||
'isRef',
|
||||
'markRaw',
|
||||
'proxyRefs',
|
||||
'reactive',
|
||||
'readonly',
|
||||
'ref',
|
||||
'shallowReactive',
|
||||
'shallowReadonly',
|
||||
'shallowRef',
|
||||
'toRaw',
|
||||
'toRef',
|
||||
'toRefs',
|
||||
'triggerRef',
|
||||
'unref',
|
||||
'watch',
|
||||
'watchEffect',
|
||||
'isShallow',
|
||||
|
||||
// effect
|
||||
'effect',
|
||||
'effectScope',
|
||||
'getCurrentScope',
|
||||
'onScopeDispose',
|
||||
|
||||
// Component
|
||||
'defineComponent',
|
||||
'defineAsyncComponent',
|
||||
'getCurrentInstance',
|
||||
'h',
|
||||
'inject',
|
||||
'nextTick',
|
||||
'provide',
|
||||
'useAttrs',
|
||||
'useCssModule',
|
||||
'useCssVars',
|
||||
'useSlots',
|
||||
'useTransitionState'
|
||||
] as Array<keyof typeof import('vue')>
|
||||
})
|
||||
|
||||
export const defaultPresets = [
|
||||
...commonPresets,
|
||||
appPreset,
|
||||
vuePreset
|
||||
]
|
@ -1,37 +1,9 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { parseQuery, parseURL } from 'ufo'
|
||||
import { toImports } from './utils'
|
||||
import { AutoImportContext } from './context'
|
||||
import { Unimport } from 'unimport'
|
||||
import { AutoImportsOptions } from '@nuxt/schema'
|
||||
|
||||
const excludeRE = [
|
||||
// imported from other module
|
||||
/\bimport\s*([\s\S]+?)\s*from\b/g,
|
||||
// defined as function
|
||||
/\bfunction\s*([\w_$]+?)\s*\(/g,
|
||||
// defined as local variable
|
||||
/\b(?:const|let|var)\s+?(\[[\s\S]*?\]|\{[\s\S]*?\}|[\s\S]+?)\s*?[=;\n]/g
|
||||
]
|
||||
|
||||
const importAsRE = /^.*\sas\s+/
|
||||
const separatorRE = /[,[\]{}\n]/g
|
||||
const multilineCommentsRE = /\/\*\s(.|[\r\n])*?\*\//gm
|
||||
const singlelineCommentsRE = /\/\/\s.*$/gm
|
||||
const templateLiteralRE = /\$\{(.*)\}/g
|
||||
const quotesRE = [
|
||||
/(["'])((?:\\\1|(?!\1)|.|\r)*?)\1/gm,
|
||||
/([`])((?:\\\1|(?!\1)|.|\n|\r)*?)\1/gm
|
||||
]
|
||||
|
||||
function stripCommentsAndStrings (code: string) {
|
||||
return code
|
||||
.replace(multilineCommentsRE, '')
|
||||
.replace(singlelineCommentsRE, '')
|
||||
.replace(templateLiteralRE, '` + $1 + `')
|
||||
.replace(quotesRE[0], '""')
|
||||
.replace(quotesRE[1], '``')
|
||||
}
|
||||
|
||||
export const TransformPlugin = createUnplugin((ctx: AutoImportContext) => {
|
||||
export const TransformPlugin = createUnplugin(({ ctx, options }: {ctx: Unimport, options: Partial<AutoImportsOptions> }) => {
|
||||
return {
|
||||
name: 'nuxt:auto-imports-transform',
|
||||
enforce: 'post',
|
||||
@ -39,8 +11,10 @@ export const TransformPlugin = createUnplugin((ctx: AutoImportContext) => {
|
||||
const { pathname, search } = parseURL(id)
|
||||
const { type, macro } = parseQuery(search)
|
||||
|
||||
const exclude = options.transform?.exclude || [/[\\/]node_modules[\\/]/]
|
||||
|
||||
// Exclude node_modules by default
|
||||
if (ctx.transform.exclude.some(pattern => id.match(pattern))) {
|
||||
if (exclude.some(pattern => id.match(pattern))) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -57,35 +31,15 @@ export const TransformPlugin = createUnplugin((ctx: AutoImportContext) => {
|
||||
return true
|
||||
}
|
||||
},
|
||||
transform (code) {
|
||||
// strip comments so we don't match on them
|
||||
const stripped = stripCommentsAndStrings(code)
|
||||
|
||||
// find all possible injection
|
||||
const matched = new Set(Array.from(stripped.matchAll(ctx.matchRE)).map(i => i[1]))
|
||||
|
||||
// remove those already defined
|
||||
for (const regex of excludeRE) {
|
||||
Array.from(stripped.matchAll(regex))
|
||||
.flatMap(i => [
|
||||
...(i[1]?.split(separatorRE) || []),
|
||||
...(i[2]?.split(separatorRE) || [])
|
||||
])
|
||||
.map(i => i.replace(importAsRE, '').trim())
|
||||
.forEach(i => matched.delete(i))
|
||||
async transform (_code, id) {
|
||||
const { code, s } = await ctx.injectImports(_code)
|
||||
if (code === _code) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!matched.size) {
|
||||
return null
|
||||
return {
|
||||
code,
|
||||
map: s.generateMap({ source: id, includeContent: true })
|
||||
}
|
||||
|
||||
// For webpack4/bridge support
|
||||
const isCJSContext = stripped.includes('require(')
|
||||
|
||||
const matchedImports = Array.from(matched).map(name => ctx.map.get(name)).filter(Boolean)
|
||||
const imports = toImports(matchedImports, isCJSContext)
|
||||
|
||||
return imports + code
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,47 +0,0 @@
|
||||
import type { AutoImport } from '@nuxt/schema'
|
||||
import { genExport, genImport, genString } from 'knitwork'
|
||||
|
||||
export function toImportModuleMap (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
|
||||
)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
export function toImports (autoImports: AutoImport[], isCJS = false) {
|
||||
const map = toImportModuleMap(autoImports, isCJS)
|
||||
if (isCJS) {
|
||||
return Object.entries(map)
|
||||
.map(([name, imports]) => `const { ${Array.from(imports).join(', ')} } = require(${genString(name)});`)
|
||||
.join('\n')
|
||||
} else {
|
||||
return Object.entries(map)
|
||||
.map(([name, imports]) => genImport(name, Array.from(imports)))
|
||||
.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
export function toExports (autoImports: AutoImport[]) {
|
||||
const map = toImportModuleMap(autoImports, false)
|
||||
return Object.entries(map)
|
||||
.map(([name, imports]) => genExport(name, Array.from(imports)))
|
||||
.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,35 +1,32 @@
|
||||
import { readFileSync } from 'fs'
|
||||
import type { AutoImport } from '@nuxt/schema'
|
||||
import { expect, describe, it } from 'vitest'
|
||||
import { join } from 'pathe'
|
||||
import { createCommonJS, findExports } from 'mlly'
|
||||
import * as VueFunctions from 'vue'
|
||||
import { AutoImportContext, updateAutoImportContext } from '../src/auto-imports/context'
|
||||
import { createUnimport, Import } from 'unimport'
|
||||
import { TransformPlugin } from '../src/auto-imports/transform'
|
||||
import { Nuxt3AutoImports } from '../src/auto-imports/imports'
|
||||
import { defaultPresets } from '../src/auto-imports/presets'
|
||||
|
||||
describe('auto-imports:transform', () => {
|
||||
const autoImports: AutoImport[] = [
|
||||
const imports: Import[] = [
|
||||
{ name: 'ref', as: 'ref', from: 'vue' },
|
||||
{ name: 'computed', as: 'computed', from: 'bar' },
|
||||
{ name: 'foo', as: 'foo', from: 'excluded' }
|
||||
]
|
||||
|
||||
const ctx = {
|
||||
autoImports,
|
||||
map: new Map(),
|
||||
transform: {
|
||||
exclude: [/excluded/]
|
||||
}
|
||||
} as AutoImportContext
|
||||
updateAutoImportContext(ctx)
|
||||
const ctx = createUnimport({
|
||||
imports
|
||||
})
|
||||
|
||||
const transformPlugin = TransformPlugin.raw(ctx, { framework: 'rollup' })
|
||||
const transform = (code: string) => transformPlugin.transform.call({ error: null, warn: null }, code, '')
|
||||
const transformPlugin = TransformPlugin.raw({ ctx, options: { transform: { exclude: [/node_modules/] } } }, { framework: 'rollup' })
|
||||
const transform = async (source: string) => {
|
||||
const { code } = await transformPlugin.transform.call({ error: null, warn: null }, source, '') || { code: null }
|
||||
return code
|
||||
}
|
||||
|
||||
it('should correct inject', async () => {
|
||||
expect(await transform('const a = ref(0)')).to.equal('import { ref } from "vue";const a = ref(0)')
|
||||
expect(await transform('import { computed as ref } from "foo"; const a = ref(0)')).to.include('import { computed } from "bar";')
|
||||
expect(await transform('const a = ref(0)')).toMatchInlineSnapshot('"import { ref } from \'vue\';const a = ref(0)"')
|
||||
expect(await transform('import { computed as ref } from "foo"; const a = ref(0)')).to.toMatchInlineSnapshot('"import { computed } from \'bar\';import { computed as ref } from \\"foo\\"; const a = ref(0)"')
|
||||
})
|
||||
|
||||
it('should ignore existing imported', async () => {
|
||||
@ -43,11 +40,14 @@ describe('auto-imports:transform', () => {
|
||||
|
||||
it('should ignore comments', async () => {
|
||||
const result = await transform('// import { computed } from "foo"\n;const a = computed(0)')
|
||||
expect(result).to.equal('import { computed } from "bar";// import { computed } from "foo"\n;const a = computed(0)')
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"import { computed } from 'bar';// import { computed } from \\"foo\\"
|
||||
;const a = computed(0)"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should exclude files from transform', () => {
|
||||
expect(transformPlugin.transformInclude.call({ error: null, warn: null }, 'excluded')).to.equal(false)
|
||||
it('should exclude files from transform', async () => {
|
||||
expect(await transform('excluded')).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
@ -64,7 +64,7 @@ describe('auto-imports:nuxt3', () => {
|
||||
continue
|
||||
}
|
||||
it(`should register ${name} globally`, () => {
|
||||
expect(Nuxt3AutoImports.find(a => a.from === '#app').names).to.include(name)
|
||||
expect(defaultPresets.find(a => a.from === '#app').imports).to.include(name)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
@ -175,7 +175,7 @@ describe('auto-imports:vue', () => {
|
||||
continue
|
||||
}
|
||||
it(`should register ${name} globally`, () => {
|
||||
expect(Nuxt3AutoImports.find(a => a.from === 'vue').names).toContain(name)
|
||||
expect(defaultPresets.find(a => a.from === 'vue').imports).toContain(name)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -27,7 +27,8 @@
|
||||
"postcss-import-resolver": "^2.0.0",
|
||||
"scule": "^0.2.1",
|
||||
"std-env": "^3.0.1",
|
||||
"ufo": "^0.7.11"
|
||||
"ufo": "^0.7.11",
|
||||
"unimport": "0.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.16.0 || ^16.11.0 || ^17.0.0"
|
||||
|
@ -4,7 +4,7 @@ import type { Compiler, Configuration, Stats } from 'webpack'
|
||||
import type { TSConfig } from 'pkg-types'
|
||||
import type { ModuleContainer } from './module'
|
||||
import type { NuxtTemplate, Nuxt, NuxtApp } from './nuxt'
|
||||
import type { AutoImport, AutoImportSource } from './imports'
|
||||
import type { Preset as ImportPreset, Import } from 'unimport'
|
||||
import type { NuxtConfig, NuxtOptions } from './config'
|
||||
import type { Component, ComponentsDir, ScanDir, ComponentsOptions } from './components'
|
||||
import { NuxtCompatibility, NuxtCompatibilityIssues } from '..'
|
||||
@ -52,6 +52,13 @@ export type NuxtLayout = {
|
||||
file: string
|
||||
}
|
||||
|
||||
export interface ImportPresetWithDeperection extends ImportPreset {
|
||||
/**
|
||||
* @deprecated renamed to `imports`
|
||||
*/
|
||||
names?: string[]
|
||||
}
|
||||
|
||||
export interface NuxtHooks {
|
||||
// Kit
|
||||
'kit:compatibility': (compatibility: NuxtCompatibility, issues: NuxtCompatibilityIssues) => HookResult
|
||||
@ -66,8 +73,8 @@ export interface NuxtHooks {
|
||||
'pages:layouts:extend': (layouts: NuxtLayout[]) => HookResult
|
||||
|
||||
// Auto imports
|
||||
'autoImports:sources': (autoImportSources: AutoImportSource[]) => HookResult
|
||||
'autoImports:extend': (autoImports: AutoImport[]) => HookResult
|
||||
'autoImports:sources': (presets: ImportPresetWithDeperection[]) => HookResult
|
||||
'autoImports:extend': (imports: Import[]) => HookResult
|
||||
'autoImports:dirs': (dirs: string[]) => HookResult
|
||||
|
||||
// Components
|
||||
|
@ -1,66 +1,9 @@
|
||||
export type IdentifierMap = Record<string, string>
|
||||
export type Identifiers = [string, string][]
|
||||
import { UnimportOptions } from 'unimport'
|
||||
|
||||
export interface AutoImport {
|
||||
/**
|
||||
* Export name to be imported
|
||||
*
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* Import as this name
|
||||
*/
|
||||
as: string
|
||||
/**
|
||||
* Module specifier to import from
|
||||
*/
|
||||
from: string
|
||||
/**
|
||||
* Disable auto import
|
||||
*/
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface AutoImportSource {
|
||||
/**
|
||||
* Exports from module for auto-import
|
||||
*
|
||||
*/
|
||||
names: (string | { name: string, as?: string })[]
|
||||
/**
|
||||
* Module specifier to import from
|
||||
*/
|
||||
from: string
|
||||
/**
|
||||
* Disable auto import source
|
||||
*/
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface AutoImportsOptions {
|
||||
/**
|
||||
* Auto import sources
|
||||
*/
|
||||
sources?: AutoImportSource[]
|
||||
|
||||
/**
|
||||
* [experimental] Use globalThis injection instead of transform for development
|
||||
*/
|
||||
global?: boolean
|
||||
|
||||
/**
|
||||
* Additional directories to scan composables from
|
||||
*
|
||||
* By default <rootDir>/composables is added
|
||||
*/
|
||||
export interface AutoImportsOptions extends UnimportOptions {
|
||||
dirs?: string[]
|
||||
|
||||
transform: {
|
||||
/**
|
||||
* Exclusion patterns for transforming files
|
||||
*
|
||||
* By default [/node_modules/] will be used
|
||||
*/
|
||||
global?: boolean,
|
||||
transform?: {
|
||||
exclude?: RegExp[]
|
||||
}
|
||||
}
|
||||
|
42
yarn.lock
42
yarn.lock
@ -2986,6 +2986,7 @@ __metadata:
|
||||
semver: ^7.3.5
|
||||
unbuild: latest
|
||||
unctx: ^1.0.2
|
||||
unimport: 0.0.8
|
||||
untyped: ^0.4.2
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@ -3148,6 +3149,7 @@ __metadata:
|
||||
std-env: ^3.0.1
|
||||
ufo: ^0.7.11
|
||||
unbuild: latest
|
||||
unimport: 0.0.5
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@ -13829,6 +13831,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"magic-string@npm:^0.26.0":
|
||||
version: 0.26.0
|
||||
resolution: "magic-string@npm:0.26.0"
|
||||
dependencies:
|
||||
sourcemap-codec: ^1.4.8
|
||||
checksum: 2065de79cdbb76fe1b073244d51a2094cabf317e95a4ce385d7ddbe1d1f16b864c0f2a1ae514872251e64edc44bf56c8bb6a7dbe5eff9c9c236f877bcdfec9f9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"magic-string@npm:^0.26.1":
|
||||
version: 0.26.1
|
||||
resolution: "magic-string@npm:0.26.1"
|
||||
@ -15496,6 +15507,7 @@ __metadata:
|
||||
scule: ^0.2.1
|
||||
ufo: ^0.7.11
|
||||
unbuild: latest
|
||||
unimport: 0.0.8
|
||||
unplugin: ^0.4.0
|
||||
untyped: ^0.4.2
|
||||
vue: ^3.2.31
|
||||
@ -21184,6 +21196,34 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unimport@npm:0.0.5":
|
||||
version: 0.0.5
|
||||
resolution: "unimport@npm:0.0.5"
|
||||
dependencies:
|
||||
"@rollup/pluginutils": ^4.1.2
|
||||
escape-string-regexp: ^5.0.0
|
||||
local-pkg: ^0.4.1
|
||||
magic-string: ^0.26.0
|
||||
mlly: ^0.4.3
|
||||
unplugin: ^0.3.3
|
||||
checksum: 6514320ff1771f699dc6c69b7a7086fe6a62ca1ece7a4877798e89964bf3b9b2450631266a49dfa5785ba8939e4f30517333e4639ecf44e97077fb3128a946d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unimport@npm:0.0.8":
|
||||
version: 0.0.8
|
||||
resolution: "unimport@npm:0.0.8"
|
||||
dependencies:
|
||||
"@rollup/pluginutils": ^4.1.2
|
||||
escape-string-regexp: ^5.0.0
|
||||
local-pkg: ^0.4.1
|
||||
magic-string: ^0.26.0
|
||||
mlly: ^0.4.3
|
||||
unplugin: ^0.3.3
|
||||
checksum: ac6e2ef860b274c28093117c23071aaa35f484f88ff23cd25a4f4d114c24e72969f46f729b26023546ffdc1016ef42e5fc399d14389c0c0374f4a74b17ff1955
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"union-value@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "union-value@npm:1.0.1"
|
||||
@ -21303,7 +21343,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unplugin@npm:^0.3.0, unplugin@npm:^0.3.2":
|
||||
"unplugin@npm:^0.3.0, unplugin@npm:^0.3.2, unplugin@npm:^0.3.3":
|
||||
version: 0.3.3
|
||||
resolution: "unplugin@npm:0.3.3"
|
||||
dependencies:
|
||||
|
Loading…
Reference in New Issue
Block a user