mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat(auto-imports): allow extending with config and hooks (#1167)
This commit is contained in:
parent
807f4a325f
commit
0ab477cad0
@ -1,82 +1,28 @@
|
||||
import { installModule, useNuxt } from '@nuxt/kit'
|
||||
import autoImports from '../../nuxt3/src/auto-imports/module'
|
||||
|
||||
// TODO: implement these: https://github.com/nuxt/framework/issues/549
|
||||
const disabled = [
|
||||
'useAsyncData',
|
||||
'asyncData',
|
||||
'useFetch'
|
||||
]
|
||||
const UnsupportedImports = new Set(['useAsyncData', 'useFetch'])
|
||||
|
||||
const identifiers = {
|
||||
'#app': [
|
||||
'defineNuxtComponent',
|
||||
'useNuxtApp',
|
||||
'defineNuxtPlugin',
|
||||
'useRoute',
|
||||
'useRouter',
|
||||
'useRuntimeConfig',
|
||||
'useState'
|
||||
],
|
||||
'#meta': [
|
||||
'useMeta'
|
||||
],
|
||||
'@vue/composition-api': [
|
||||
// lifecycle
|
||||
'onActivated',
|
||||
'onBeforeMount',
|
||||
'onBeforeUnmount',
|
||||
'onBeforeUpdate',
|
||||
'onDeactivated',
|
||||
'onErrorCaptured',
|
||||
'onMounted',
|
||||
'onServerPrefetch',
|
||||
'onUnmounted',
|
||||
'onUpdated',
|
||||
|
||||
// reactivity,
|
||||
'computed',
|
||||
'customRef',
|
||||
'isReadonly',
|
||||
'isRef',
|
||||
'markRaw',
|
||||
'reactive',
|
||||
'readonly',
|
||||
'ref',
|
||||
'shallowReactive',
|
||||
'shallowReadonly',
|
||||
'shallowRef',
|
||||
'toRaw',
|
||||
'toRef',
|
||||
'toRefs',
|
||||
'triggerRef',
|
||||
'unref',
|
||||
'watch',
|
||||
'watchEffect',
|
||||
|
||||
// component
|
||||
'defineComponent',
|
||||
'defineAsyncComponent',
|
||||
'getCurrentInstance',
|
||||
'h',
|
||||
'inject',
|
||||
'nextTick',
|
||||
'provide',
|
||||
'useCssModule'
|
||||
]
|
||||
}
|
||||
|
||||
const defaultIdentifiers = {}
|
||||
for (const pkg in identifiers) {
|
||||
for (const id of identifiers[pkg]) {
|
||||
defaultIdentifiers[id] = pkg
|
||||
}
|
||||
const ImportRewrites = {
|
||||
vue: '@vue/composition-api',
|
||||
'vue-router': '#app'
|
||||
}
|
||||
|
||||
export async function setupAutoImports () {
|
||||
const nuxt = useNuxt()
|
||||
nuxt.options.autoImports = nuxt.options.autoImports || {}
|
||||
nuxt.options.autoImports.disabled = nuxt.options.autoImports.disabled || disabled
|
||||
nuxt.options.autoImports.identifiers = Object.assign({}, defaultIdentifiers, nuxt.options.autoImports.identifiers)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await installModule(nuxt, autoImports)
|
||||
}
|
||||
|
@ -21,3 +21,4 @@ export * from './types/hooks'
|
||||
export * from './types/module'
|
||||
export * from './types/nuxt'
|
||||
export * from './types/components'
|
||||
export * from './types/imports'
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ConfigSchema as _ConfigSchema } from '../../schema/config'
|
||||
import { ModuleInstallOptions } from './module'
|
||||
import { NuxtHooks } from './hooks'
|
||||
import { AutoImportsOptions } from './imports'
|
||||
import { ComponentsOptions } from './components'
|
||||
|
||||
export interface ConfigSchema extends _ConfigSchema {
|
||||
@ -12,6 +13,7 @@ export interface ConfigSchema extends _ConfigSchema {
|
||||
|
||||
// TODO: Move to schema when untyped supports type annotation
|
||||
vite: boolean | import('vite').InlineConfig
|
||||
autoImports: AutoImportsOptions
|
||||
}
|
||||
|
||||
export interface NuxtOptions extends ConfigSchema { }
|
||||
|
@ -1,9 +1,10 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'http'
|
||||
import type { Compiler, Configuration, Stats } from 'webpack'
|
||||
import type { TSConfig } from 'pkg-types'
|
||||
import type { NuxtConfig, NuxtOptions } from '..'
|
||||
import type { ModuleContainer } from '../module/container'
|
||||
import type { NuxtTemplate, Nuxt, NuxtApp } from './nuxt'
|
||||
import type { NuxtTemplate, Nuxt, NuxtApp } from '../types/nuxt'
|
||||
import type { AutoImport, AutoImportSource } from '../types/imports'
|
||||
import type { NuxtConfig, NuxtOptions } from './config'
|
||||
import type { Component, ComponentsDir, ScanDir, ComponentsOptions } from './components'
|
||||
|
||||
type HookResult = Promise<void> | void
|
||||
@ -37,7 +38,11 @@ export interface NuxtHooks {
|
||||
'app:templatesGenerated': (app: NuxtApp) => HookResult
|
||||
'builder:generateApp': () => HookResult
|
||||
|
||||
// components
|
||||
// Auto imports
|
||||
'autoImports:sources': (autoImportSources: AutoImportSource[]) => HookResult
|
||||
'autoImports:extend': (autoImports: AutoImport[]) => HookResult
|
||||
|
||||
// Components
|
||||
'components:dirs': (dirs: ComponentsOptions['dirs']) => HookResult
|
||||
'components:extend': (components: (Component | ComponentsDir | ScanDir)[]) => HookResult
|
||||
|
||||
|
49
packages/kit/src/types/imports.ts
Normal file
49
packages/kit/src/types/imports.ts
Normal file
@ -0,0 +1,49 @@
|
||||
export type IdentifierMap = Record<string, string>
|
||||
export type Identifiers = [string, string][]
|
||||
|
||||
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
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
const identifiers = {
|
||||
'#app': [
|
||||
'useAsyncData',
|
||||
'defineNuxtComponent',
|
||||
'useNuxtApp',
|
||||
'defineNuxtPlugin',
|
||||
'useRuntimeConfig',
|
||||
'useState',
|
||||
'useFetch'
|
||||
],
|
||||
'#meta': [
|
||||
'useMeta'
|
||||
],
|
||||
vue: [
|
||||
// lifecycle
|
||||
'onActivated',
|
||||
'onBeforeMount',
|
||||
'onBeforeUnmount',
|
||||
'onBeforeUpdate',
|
||||
'onDeactivated',
|
||||
'onErrorCaptured',
|
||||
'onMounted',
|
||||
'onServerPrefetch',
|
||||
'onUnmounted',
|
||||
'onUpdated',
|
||||
|
||||
// reactivity,
|
||||
'computed',
|
||||
'customRef',
|
||||
'isReadonly',
|
||||
'isRef',
|
||||
'markRaw',
|
||||
'reactive',
|
||||
'readonly',
|
||||
'ref',
|
||||
'shallowReactive',
|
||||
'shallowReadonly',
|
||||
'shallowRef',
|
||||
'toRaw',
|
||||
'toRef',
|
||||
'toRefs',
|
||||
'triggerRef',
|
||||
'unref',
|
||||
'watch',
|
||||
'watchEffect',
|
||||
|
||||
// component
|
||||
'defineComponent',
|
||||
'defineAsyncComponent',
|
||||
'getCurrentInstance',
|
||||
'h',
|
||||
'inject',
|
||||
'nextTick',
|
||||
'provide',
|
||||
'useCssModule'
|
||||
],
|
||||
'vue-router': [
|
||||
'useRoute',
|
||||
'useRouter'
|
||||
]
|
||||
}
|
||||
|
||||
export const defaultIdentifiers = {}
|
||||
for (const pkg in identifiers) {
|
||||
for (const id of identifiers[pkg]) {
|
||||
defaultIdentifiers[id] = pkg
|
||||
}
|
||||
}
|
79
packages/nuxt3/src/auto-imports/imports.ts
Normal file
79
packages/nuxt3/src/auto-imports/imports.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import type { AutoImportSource } from '@nuxt/kit'
|
||||
|
||||
export const Nuxt3AutoImports: AutoImportSource[] = [
|
||||
// #app
|
||||
{
|
||||
from: '#app',
|
||||
names: [
|
||||
'useAsyncData',
|
||||
'defineNuxtComponent',
|
||||
'useNuxtApp',
|
||||
'defineNuxtPlugin',
|
||||
'useRuntimeConfig',
|
||||
'useState',
|
||||
'useFetch'
|
||||
]
|
||||
},
|
||||
// #meta
|
||||
{
|
||||
from: '#meta',
|
||||
names: [
|
||||
'useMeta'
|
||||
]
|
||||
},
|
||||
// vue-router
|
||||
{
|
||||
from: 'vue-router',
|
||||
names: [
|
||||
'useRoute',
|
||||
'useRouter'
|
||||
]
|
||||
},
|
||||
// vue
|
||||
{
|
||||
from: 'vue',
|
||||
names: [
|
||||
// Lifecycle
|
||||
'onActivated',
|
||||
'onBeforeMount',
|
||||
'onBeforeUnmount',
|
||||
'onBeforeUpdate',
|
||||
'onDeactivated',
|
||||
'onErrorCaptured',
|
||||
'onMounted',
|
||||
'onServerPrefetch',
|
||||
'onUnmounted',
|
||||
'onUpdated',
|
||||
|
||||
// Reactivity
|
||||
'computed',
|
||||
'customRef',
|
||||
'isReadonly',
|
||||
'isRef',
|
||||
'markRaw',
|
||||
'reactive',
|
||||
'readonly',
|
||||
'ref',
|
||||
'shallowReactive',
|
||||
'shallowReadonly',
|
||||
'shallowRef',
|
||||
'toRaw',
|
||||
'toRef',
|
||||
'toRefs',
|
||||
'triggerRef',
|
||||
'unref',
|
||||
'watch',
|
||||
'watchEffect',
|
||||
|
||||
// Component
|
||||
'defineComponent',
|
||||
'defineAsyncComponent',
|
||||
'getCurrentInstance',
|
||||
'h',
|
||||
'inject',
|
||||
'nextTick',
|
||||
'provide',
|
||||
'useCssModule'
|
||||
]
|
||||
}
|
||||
]
|
@ -1,35 +1,69 @@
|
||||
import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, addPluginTemplate } from '@nuxt/kit'
|
||||
import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, resolveAlias, addPluginTemplate, AutoImport } from '@nuxt/kit'
|
||||
import type { AutoImportsOptions } from '@nuxt/kit'
|
||||
import { isAbsolute, relative, resolve } from 'pathe'
|
||||
import type { Identifiers, AutoImportsOptions } from './types'
|
||||
import { TransformPlugin } from './transform'
|
||||
import { defaultIdentifiers } from './identifiers'
|
||||
import { Nuxt3AutoImports } from './imports'
|
||||
|
||||
export default defineNuxtModule<AutoImportsOptions>({
|
||||
name: 'auto-imports',
|
||||
configKey: 'autoImports',
|
||||
defaults: { identifiers: defaultIdentifiers },
|
||||
setup ({ disabled = [], identifiers }, nuxt) {
|
||||
for (const key of disabled) {
|
||||
delete identifiers[key]
|
||||
defaults: {
|
||||
sources: Nuxt3AutoImports,
|
||||
global: false
|
||||
},
|
||||
async setup (options, nuxt) {
|
||||
// Allow modules extending sources
|
||||
await nuxt.callHook('autoImports:sources', options.sources)
|
||||
|
||||
// Filter disabled sources
|
||||
options.sources = options.sources.filter(source => source.disabled !== true)
|
||||
|
||||
// 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 })
|
||||
} else {
|
||||
autoImports.push({ name: importName.name, as: importName.as || importName.name, from: source.from })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow modules extending resolved imports
|
||||
await nuxt.callHook('autoImports:extend', autoImports)
|
||||
|
||||
// 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
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (nuxt.options.dev && false) {
|
||||
// @ts-ignore
|
||||
if (nuxt.options.dev && options.global) {
|
||||
// Add all imports to globalThis in development mode
|
||||
addPluginTemplate({
|
||||
filename: 'auto-imports.mjs',
|
||||
src: '',
|
||||
getContents: () => {
|
||||
const imports = toImports(Object.entries(identifiers))
|
||||
const globalThisSet = Object.keys(identifiers).map(name => `globalThis.${name} = ${name};`).join('\n')
|
||||
const imports = toImports(autoImports)
|
||||
const globalThisSet = 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(identifiers))
|
||||
addWebpackPlugin(TransformPlugin.webpack(identifiers))
|
||||
addVitePlugin(TransformPlugin.vite(autoImports))
|
||||
addWebpackPlugin(TransformPlugin.webpack(autoImports))
|
||||
}
|
||||
|
||||
// Add types
|
||||
@ -51,7 +85,7 @@ export default defineNuxtModule<AutoImportsOptions>({
|
||||
write: true,
|
||||
getContents: () => `// Generated by auto imports
|
||||
declare global {
|
||||
${Object.entries(identifiers).map(([api, moduleName]) => ` const ${api}: typeof import('${r(moduleName)}')['${api}']`).join('\n')}
|
||||
${autoImports.map(i => ` const ${i.as}: typeof import('${r(i.as)}')['${i.name}']`).join('\n')}
|
||||
}\nexport {}`
|
||||
})
|
||||
nuxt.hook('prepare:types', ({ references }) => {
|
||||
@ -60,16 +94,14 @@ ${Object.entries(identifiers).map(([api, moduleName]) => ` const ${api}: typeof
|
||||
}
|
||||
})
|
||||
|
||||
function toImports (identifiers: Identifiers) {
|
||||
function toImports (autoImports: AutoImport[]) {
|
||||
const map: Record<string, Set<string>> = {}
|
||||
|
||||
identifiers.forEach(([name, moduleName]) => {
|
||||
if (!map[moduleName]) {
|
||||
map[moduleName] = new Set()
|
||||
for (const autoImport of autoImports) {
|
||||
if (!map[autoImport.from]) {
|
||||
map[autoImport.from] = new Set()
|
||||
}
|
||||
map[moduleName].add(name)
|
||||
})
|
||||
|
||||
map[autoImport.from].add(autoImport.as)
|
||||
}
|
||||
return Object.entries(map)
|
||||
.map(([name, imports]) => `import { ${Array.from(imports).join(', ')} } from '${name}';`)
|
||||
.join('\n')
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { parseQuery, parseURL } from 'ufo'
|
||||
import { IdentifierMap } from './types'
|
||||
import type { AutoImport } from '@nuxt/kit'
|
||||
|
||||
const excludeRE = [
|
||||
// imported from other module
|
||||
@ -22,8 +22,14 @@ function stripeComments (code: string) {
|
||||
.replace(singlelineCommentsRE, '')
|
||||
}
|
||||
|
||||
export const TransformPlugin = createUnplugin((map: IdentifierMap) => {
|
||||
const matchRE = new RegExp(`\\b(${Object.keys(map).join('|')})\\b`, 'g')
|
||||
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)
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'nuxt-auto-imports-transform',
|
||||
@ -76,7 +82,7 @@ export const TransformPlugin = createUnplugin((map: IdentifierMap) => {
|
||||
|
||||
// group by module name
|
||||
Array.from(matched).forEach((name) => {
|
||||
const moduleName = map[name]!
|
||||
const moduleName = autoImportMap.get(name).from
|
||||
if (!modules[moduleName]) {
|
||||
modules[moduleName] = []
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
export type IdentifierMap = Record<string, string>
|
||||
export type Identifiers = [string, string][]
|
||||
|
||||
export interface AutoImportsOptions {
|
||||
identifiers?: IdentifierMap
|
||||
disabled?: string[]
|
||||
}
|
@ -1,13 +1,19 @@
|
||||
import type { AutoImport } from '@nuxt/kit'
|
||||
import { expect } from 'chai'
|
||||
import { TransformPlugin } from '../src/auto-imports/transform'
|
||||
|
||||
describe('module:auto-imports:build', () => {
|
||||
const { transform: _transform } = TransformPlugin.raw({ ref: 'vue', computed: 'bar' }, {} as any)
|
||||
const transform = (code: string) => _transform.call({} as any, code, '')
|
||||
describe('auto-imports:transform', () => {
|
||||
const autoImports: AutoImport[] = [
|
||||
{ name: 'ref', as: 'ref', from: 'vue' },
|
||||
{ name: 'computed', as: 'computed', from: 'bar' }
|
||||
]
|
||||
|
||||
const transformPlugin = TransformPlugin.raw(autoImports, { framework: 'rollup' })
|
||||
const transform = (code: string) => transformPlugin.transform.call({ error: null, warn: null }, 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.includes('import { computed } from \'bar\';')
|
||||
expect(await transform('import { computed as ref } from "foo"; const a = ref(0)')).to.include('import { computed } from \'bar\';')
|
||||
})
|
||||
|
||||
it('should ignore existing imported', async () => {
|
||||
|
Loading…
Reference in New Issue
Block a user