mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 00:23:53 +00:00
feat: auto global imports (#410)
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
parent
50acd3dda5
commit
b2b4c64807
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -51,6 +51,9 @@ jobs:
|
||||
- name: Stub
|
||||
run: yarn stub
|
||||
|
||||
- name: Test (unit)
|
||||
run: yarn test:unit
|
||||
|
||||
- name: Test (presets)
|
||||
run: yarn test:presets
|
||||
|
||||
|
@ -4,7 +4,7 @@ Nuxt provides `asyncData` to handle data fetching within you application.
|
||||
|
||||
## `asyncData`
|
||||
|
||||
Within your pages and components you can use `asyncData` to get access to data that resolves asynchronously.
|
||||
Within your pages and components you can use `asyncData` to get access to data that resolves asynchronously.
|
||||
|
||||
### Usage
|
||||
|
||||
@ -32,8 +32,6 @@ This helper only works with:
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { asyncData, defineNuxtComponent } from '@nuxt/app'
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup () {
|
||||
const { data } = asyncData('time', () => $fetch('/api/count'))
|
||||
@ -47,8 +45,6 @@ When using with the `<script setup>` syntax, an addition attribute `nuxt` is req
|
||||
|
||||
```vue
|
||||
<script setup nuxt>
|
||||
import { asyncData } from '@nuxt/app'
|
||||
|
||||
const { data } = asyncData('time', () => $fetch('/api/count'))
|
||||
</script>
|
||||
|
||||
|
@ -8,9 +8,6 @@ Within your `setup()` function you can call `useMeta` with an object of meta pro
|
||||
|
||||
For example:
|
||||
```ts
|
||||
import { ref } from 'vue'
|
||||
import { useMeta } from '@nuxt/app'
|
||||
|
||||
export default {
|
||||
setup () {
|
||||
useMeta({
|
||||
|
@ -1,6 +1,4 @@
|
||||
<script nuxt setup lang="ts">
|
||||
import { asyncData } from '@nuxt/app'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { data } = asyncData('time', () => $fetch('/api/count'))
|
||||
</script>
|
||||
|
@ -5,8 +5,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineNuxtComponent, asyncData } from '@nuxt/app'
|
||||
|
||||
export default defineNuxtComponent({
|
||||
setup () {
|
||||
const { data } = asyncData('time', () => $fetch('/api/count'))
|
||||
|
@ -9,9 +9,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineNuxtComponent } from '@nuxt/app'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default defineNuxtComponent({
|
||||
fetchKey: 'custom',
|
||||
asyncData ({ route }) {
|
||||
|
@ -15,8 +15,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useMeta } from '@nuxt/app'
|
||||
|
||||
export default {
|
||||
setup () {
|
||||
|
@ -12,8 +12,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineNuxtComponent } from '@nuxt/app'
|
||||
|
||||
export default defineNuxtComponent({
|
||||
layout: 'custom'
|
||||
})
|
||||
|
@ -32,8 +32,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { ContentLoader } from 'vue-content-loader'
|
||||
import { defineNuxtComponent, asyncData } from '@nuxt/app'
|
||||
|
||||
export default defineNuxtComponent({
|
||||
components: { ContentLoader },
|
||||
setup () {
|
||||
|
@ -19,6 +19,7 @@
|
||||
"test": "yarn lint && yarn test:presets",
|
||||
"test:presets": "mocha test/presets/*.mjs",
|
||||
"test:compat": "TEST_COMPAT=1 yarn test:presets",
|
||||
"test:unit": "mocha -r jiti/register packages/**/test/*.test.*",
|
||||
"version": "yarn && git add yarn.lock"
|
||||
},
|
||||
"resolutions": {
|
||||
|
8
packages/global-imports/build.config.ts
Normal file
8
packages/global-imports/build.config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
declaration: true,
|
||||
entries: [
|
||||
'src/module'
|
||||
]
|
||||
})
|
1
packages/global-imports/module.js
Normal file
1
packages/global-imports/module.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/module')
|
28
packages/global-imports/package.json
Normal file
28
packages/global-imports/package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@nuxt/global-imports",
|
||||
"version": "0.1.0",
|
||||
"repository": "nuxt/framework",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
"./module": {
|
||||
"import": "./dist/module.mjs",
|
||||
"require": "./dist/module.js"
|
||||
}
|
||||
},
|
||||
"types": "./dist/module.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"prepack": "unbuild"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^0.6.4",
|
||||
"ufo": "^0.7.7",
|
||||
"unplugin": "^0.0.5",
|
||||
"upath": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"unbuild": "^0.4.2"
|
||||
}
|
||||
}
|
57
packages/global-imports/src/identifiers.ts
Normal file
57
packages/global-imports/src/identifiers.ts
Normal file
@ -0,0 +1,57 @@
|
||||
const VueAPIs = [
|
||||
// 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 nuxtComposition = [
|
||||
'useAsyncData',
|
||||
'asyncData',
|
||||
'defineNuxtComponent',
|
||||
'useNuxt',
|
||||
'defineNuxtPlugin',
|
||||
'useMeta'
|
||||
]
|
||||
|
||||
export const defaultIdentifiers = Object.fromEntries([
|
||||
...VueAPIs.map(name => [name, 'vue']),
|
||||
...nuxtComposition.map(name => [name, '@nuxt/app'])
|
||||
])
|
57
packages/global-imports/src/module.ts
Normal file
57
packages/global-imports/src/module.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { addVitePlugin, addWebpackPlugin, defineNuxtModule, addTemplate, addPluginTemplate } from '@nuxt/kit'
|
||||
import { resolve } from 'upath'
|
||||
import type { Identifiers, GlobalImportsOptions } from './types'
|
||||
import { TrsnsformPlugin } from './transform'
|
||||
import { defaultIdentifiers } from './identifiers'
|
||||
|
||||
export default defineNuxtModule<GlobalImportsOptions>({
|
||||
name: 'global-imports',
|
||||
configKey: 'globalImports',
|
||||
defaults: { identifiers: defaultIdentifiers },
|
||||
setup ({ identifiers }, nuxt) {
|
||||
if (nuxt.options.dev) {
|
||||
// Add all imports to globalThis in development mode
|
||||
addPluginTemplate({
|
||||
filename: 'global-imports.mjs',
|
||||
src: '',
|
||||
getContents: () => {
|
||||
const imports = toImports(Object.entries(identifiers))
|
||||
const globalThisSet = Object.keys(identifiers).map(name => `globalThis.${name} = ${name};`).join('\n')
|
||||
return `${imports}\n\n${globalThisSet}\n\nexport default () => {};`
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Transform to inject imports in production mode
|
||||
addVitePlugin(TrsnsformPlugin.vite(identifiers))
|
||||
addWebpackPlugin(TrsnsformPlugin.webpack(identifiers))
|
||||
}
|
||||
|
||||
// Add types
|
||||
addTemplate({
|
||||
filename: 'global-imports.d.ts',
|
||||
write: true,
|
||||
getContents: () => `// Generated by global imports
|
||||
declare global {
|
||||
${Object.entries(identifiers).map(([api, moduleName]) => ` const ${api}: typeof import('${moduleName}')['${api}']`).join('\n')}
|
||||
}\nexport {}`
|
||||
})
|
||||
nuxt.hook('prepare:types', ({ references }) => {
|
||||
references.push({ path: resolve(nuxt.options.buildDir, 'global-imports.d.ts') })
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function toImports (identifiers: Identifiers) {
|
||||
const map: Record<string, Set<string>> = {}
|
||||
|
||||
identifiers.forEach(([name, moduleName]) => {
|
||||
if (!map[moduleName]) {
|
||||
map[moduleName] = new Set()
|
||||
}
|
||||
map[moduleName].add(name)
|
||||
})
|
||||
|
||||
return Object.entries(map)
|
||||
.map(([name, imports]) => `import { ${Array.from(imports).join(', ')} } from '${name}';`)
|
||||
.join('\n')
|
||||
}
|
68
packages/global-imports/src/transform.ts
Normal file
68
packages/global-imports/src/transform.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { parseQuery, parseURL } from 'ufo'
|
||||
import { IdentifierMap } from './types'
|
||||
|
||||
const excludeRegex = [
|
||||
// imported from other module
|
||||
/\bimport\s*\{([\s\S]*?)\}\s*from\b/g,
|
||||
// defined as function
|
||||
/\bfunction\s*([\s\S]+?)\s*\(/g,
|
||||
// defined as local variable
|
||||
/\b(?:const|let|var)\s*([\w\d_$]+?)\b/g
|
||||
]
|
||||
|
||||
export const TrsnsformPlugin = createUnplugin((map: IdentifierMap) => {
|
||||
const regex = new RegExp('\\b(' + (Object.keys(map).join('|')) + ')\\b', 'g')
|
||||
|
||||
return {
|
||||
name: 'nuxt-global-imports-transform',
|
||||
enforce: 'post',
|
||||
transformInclude (id) {
|
||||
const { pathname, search } = parseURL(id)
|
||||
const query = parseQuery(search)
|
||||
|
||||
if (id.includes('node_modules')) {
|
||||
return false
|
||||
}
|
||||
|
||||
// vue files
|
||||
if (pathname.endsWith('.vue') && (query.type === 'template' || !search)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// js files
|
||||
if (pathname.match(/\.((c|m)?j|t)sx?/g)) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
transform (code) {
|
||||
// find all possible injection
|
||||
const matched = new Set(Array.from(code.matchAll(regex)).map(i => i[1]))
|
||||
|
||||
// remove those already defined
|
||||
for (const regex of excludeRegex) {
|
||||
Array.from(code.matchAll(regex))
|
||||
.flatMap(i => i[1]?.split(',') || [])
|
||||
.forEach(i => matched.delete(i.trim()))
|
||||
}
|
||||
|
||||
const modules: Record<string, string[]> = {}
|
||||
|
||||
// group by module name
|
||||
Array.from(matched).forEach((name) => {
|
||||
const moduleName = map[name]!
|
||||
if (!modules[moduleName]) {
|
||||
modules[moduleName] = []
|
||||
}
|
||||
modules[moduleName].push(name)
|
||||
})
|
||||
|
||||
// stringify import
|
||||
const imports = Object.entries(modules)
|
||||
.map(([moduleName, names]) => `import { ${names.join(',')} } from '${moduleName}';`)
|
||||
.join('')
|
||||
|
||||
return imports + code
|
||||
}
|
||||
}
|
||||
})
|
6
packages/global-imports/src/types.ts
Normal file
6
packages/global-imports/src/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export type IdentifierMap = Record<string, string>
|
||||
export type Identifiers = [string, string][]
|
||||
|
||||
export interface GlobalImportsOptions {
|
||||
identifiers?: IdentifierMap
|
||||
}
|
16
packages/global-imports/test/build.test.ts
Normal file
16
packages/global-imports/test/build.test.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { expect } from 'chai'
|
||||
import { TrsnsformPlugin } from '../src/transform'
|
||||
|
||||
describe('module:global-imports:build', () => {
|
||||
const { transform } = TrsnsformPlugin.raw({ ref: 'vue' })
|
||||
|
||||
it('should correct inject', () => {
|
||||
expect(transform('const a = ref(0)', ''))
|
||||
.to.equal('import { ref } from \'vue\';const a = ref(0)')
|
||||
})
|
||||
|
||||
it('should ignore imported', () => {
|
||||
expect(transform('import { ref } from "foo";const a = ref(0)', ''))
|
||||
.to.equal('import { ref } from "foo";const a = ref(0)')
|
||||
})
|
||||
})
|
@ -36,6 +36,8 @@ export function normalizeTemplate (template: NuxtTemplate | string): NuxtTemplat
|
||||
// Normalize
|
||||
if (typeof template === 'string') {
|
||||
template = { src: template }
|
||||
} else {
|
||||
template = { ...template }
|
||||
}
|
||||
|
||||
// Use src if provided
|
||||
@ -74,7 +76,10 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
|
||||
// Normalize src
|
||||
if (typeof plugin === 'string') {
|
||||
plugin = { src: plugin }
|
||||
} else {
|
||||
plugin = { ...plugin }
|
||||
}
|
||||
|
||||
if (!plugin.src) {
|
||||
throw new Error('Invalid plugin. src option is required: ' + JSON.stringify(plugin))
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
"dependencies": {
|
||||
"@nuxt/app": "^0.5.0",
|
||||
"@nuxt/component-discovery": "^0.2.0",
|
||||
"@nuxt/global-imports": "^0.1.0",
|
||||
"@nuxt/kit": "^0.6.4",
|
||||
"@nuxt/meta": "^0.1.0",
|
||||
"@nuxt/nitro": "^0.9.1",
|
||||
|
@ -57,6 +57,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
|
||||
options.buildModules.push(normalize(require.resolve('@nuxt/pages/module')))
|
||||
options.buildModules.push(normalize(require.resolve('@nuxt/meta/module')))
|
||||
options.buildModules.push(normalize(require.resolve('@nuxt/component-discovery/module')))
|
||||
options.buildModules.push(normalize(require.resolve('@nuxt/global-imports/module')))
|
||||
|
||||
const nuxt = createNuxt(options)
|
||||
|
||||
|
8
playground/nuxt.d.ts
vendored
8
playground/nuxt.d.ts
vendored
@ -1,7 +1,13 @@
|
||||
// Declarations auto generated by `nuxt prepare`. Please do not manually modify this file.
|
||||
// This file is auto generated by `nuxt prepare`
|
||||
// Please do not manually modify this file.
|
||||
|
||||
/// <reference types="@nuxt/kit" />
|
||||
/// <reference types="@nuxt/app" />
|
||||
/// <reference types="@nuxt/nitro" />
|
||||
/// <reference types="@nuxt/pages" />
|
||||
/// <reference types="@nuxt/meta" />
|
||||
/// <reference types="@nuxt/component-discovery" />
|
||||
/// <reference types="@nuxt/global-imports" />
|
||||
/// <reference path=".nuxt/components.d.ts" />
|
||||
/// <reference path=".nuxt/global-imports.d.ts" />
|
||||
export {}
|
||||
|
@ -5,8 +5,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineNuxtComponent } from '@nuxt/app'
|
||||
|
||||
export default defineNuxtComponent({
|
||||
})
|
||||
</script>
|
||||
|
@ -14,7 +14,7 @@
|
||||
"mocha",
|
||||
"chai",
|
||||
"@nuxt/app",
|
||||
"@nuxt/nitro",
|
||||
"@nuxt/nitro"
|
||||
]
|
||||
},
|
||||
"exclude": ["./packages/*/dist/*"]
|
||||
|
13
yarn.lock
13
yarn.lock
@ -1335,6 +1335,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nuxt/global-imports@^0.1.0, @nuxt/global-imports@workspace:packages/global-imports":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@nuxt/global-imports@workspace:packages/global-imports"
|
||||
dependencies:
|
||||
"@nuxt/kit": ^0.6.4
|
||||
ufo: ^0.7.7
|
||||
unbuild: ^0.4.2
|
||||
unplugin: ^0.0.5
|
||||
upath: ^2.0.1
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@nuxt/kit@^0.6.4, @nuxt/kit@workspace:packages/kit":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@nuxt/kit@workspace:packages/kit"
|
||||
@ -8735,6 +8747,7 @@ fsevents@~2.3.2:
|
||||
dependencies:
|
||||
"@nuxt/app": ^0.5.0
|
||||
"@nuxt/component-discovery": ^0.2.0
|
||||
"@nuxt/global-imports": ^0.1.0
|
||||
"@nuxt/kit": ^0.6.4
|
||||
"@nuxt/meta": ^0.1.0
|
||||
"@nuxt/nitro": ^0.9.1
|
||||
|
Loading…
Reference in New Issue
Block a user