mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +00:00
feat(nuxt): handle nuxt route injection for this.$route
(#27313)
This commit is contained in:
parent
b8fdbedfe1
commit
faa5178d32
@ -1,10 +1,13 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import MagicString from 'magic-string'
|
||||
import type { Nuxt } from '@nuxt/schema'
|
||||
import { stripLiteral } from 'strip-literal'
|
||||
import { isVue } from '../../core/utils'
|
||||
|
||||
const INJECTION_RE = /\b_ctx\.\$route\b/g
|
||||
const INJECTION_SINGLE_RE = /\b_ctx\.\$route\b/
|
||||
const INJECTION_RE_TEMPLATE = /\b_ctx\.\$route\b/g
|
||||
const INJECTION_RE_SCRIPT = /\bthis\.\$route\b/g
|
||||
|
||||
const INJECTION_SINGLE_RE = /\bthis\.\$route\b|\b_ctx\.\$route\b/
|
||||
|
||||
export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => {
|
||||
return {
|
||||
@ -14,14 +17,30 @@ export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => {
|
||||
return isVue(id, { type: ['template', 'script'] })
|
||||
},
|
||||
transform (code) {
|
||||
if (!INJECTION_SINGLE_RE.test(code) || code.includes('_ctx._.provides[__nuxt_route_symbol')) { return }
|
||||
if (!INJECTION_SINGLE_RE.test(code) || code.includes('_ctx._.provides[__nuxt_route_symbol') || code.includes('this._.provides[__nuxt_route_symbol')) { return }
|
||||
|
||||
let replaced = false
|
||||
const s = new MagicString(code)
|
||||
s.replace(INJECTION_RE, () => {
|
||||
const strippedCode = stripLiteral(code)
|
||||
|
||||
// Local helper function for regex-based replacements using `strippedCode`
|
||||
const replaceMatches = (regExp: RegExp, replacement: string) => {
|
||||
for (const match of strippedCode.matchAll(regExp)) {
|
||||
const start = match.index!
|
||||
const end = start + match[0].length
|
||||
s.overwrite(start, end, replacement)
|
||||
if (!replaced) {
|
||||
replaced = true
|
||||
return '(_ctx._.provides[__nuxt_route_symbol] || _ctx.$route)'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handles `$route` in template
|
||||
replaceMatches(INJECTION_RE_TEMPLATE, '(_ctx._.provides[__nuxt_route_symbol] || _ctx.$route)')
|
||||
|
||||
// handles `this.$route` in script
|
||||
replaceMatches(INJECTION_RE_SCRIPT, '(this._.provides[__nuxt_route_symbol] || this.$route)')
|
||||
|
||||
if (replaced) {
|
||||
s.prepend('import { PageRouteSymbol as __nuxt_route_symbol } from \'#app/components/injections\';\n')
|
||||
}
|
||||
|
73
packages/nuxt/test/route-injection.test.ts
Normal file
73
packages/nuxt/test/route-injection.test.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { compileScript, compileTemplate, parse } from '@vue/compiler-sfc'
|
||||
import type { Plugin } from 'vite'
|
||||
import type { Nuxt } from '@nuxt/schema'
|
||||
|
||||
import { RouteInjectionPlugin } from '../src/pages/plugins/route-injection'
|
||||
|
||||
describe('route-injection:transform', () => {
|
||||
const injectionPlugin = RouteInjectionPlugin({ options: { sourcemap: { client: false, server: false } } } as Nuxt).raw({}, { framework: 'rollup' }) as Plugin
|
||||
|
||||
const transform = async (source: string) => {
|
||||
const result = await (injectionPlugin.transform! as Function).call({ error: null, warn: null } as any, source, 'test.vue')
|
||||
const code: string = typeof result === 'string' ? result : result?.code
|
||||
let depth = 0
|
||||
return code.split('\n').map((l) => {
|
||||
l = l.trim()
|
||||
if (l.match(/^[}\]]/)) { depth-- }
|
||||
const res = ''.padStart(depth * 2, ' ') + l
|
||||
if (l.match(/[{[]$/)) { depth++ }
|
||||
return res
|
||||
}).join('\n')
|
||||
}
|
||||
|
||||
it('should correctly inject route in template', async () => {
|
||||
const sfc = `<template>{{ $route.path }}</template>`
|
||||
const res = compileTemplate({
|
||||
filename: 'test.vue',
|
||||
id: 'test.vue',
|
||||
source: sfc,
|
||||
})
|
||||
const transformResult = await transform(res.code)
|
||||
expect(transformResult).toMatchInlineSnapshot(`
|
||||
"import { PageRouteSymbol as __nuxt_route_symbol } from '#app/components/injections';
|
||||
import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("template", null, [
|
||||
_createTextVNode(_toDisplayString((_ctx._.provides[__nuxt_route_symbol] || _ctx.$route).path), 1 /* TEXT */)
|
||||
]))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should correctly inject route in options api', async () => {
|
||||
const sfc = `
|
||||
<template>{{ thing }}</template>
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
thing () {
|
||||
return this.$route.path
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`
|
||||
|
||||
const res = compileScript(parse(sfc).descriptor, { id: 'test.vue' })
|
||||
const transformResult = await transform(res.content)
|
||||
expect(transformResult).toMatchInlineSnapshot(`
|
||||
"import { PageRouteSymbol as __nuxt_route_symbol } from '#app/components/injections';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
thing () {
|
||||
return (this._.provides[__nuxt_route_symbol] || this.$route).path
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user