mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +00:00
refactor(bridge): provide vue2 compat with a transform plugin (#3886)
This commit is contained in:
parent
e34ed887f2
commit
9e67d58005
@ -1,9 +1,10 @@
|
|||||||
import { useNuxt, resolveModule, addTemplate, resolveAlias, extendWebpackConfig } from '@nuxt/kit'
|
import { useNuxt, addTemplate, resolveAlias, addWebpackPlugin, addVitePlugin } from '@nuxt/kit'
|
||||||
import { NuxtModule } from '@nuxt/schema'
|
import { NuxtModule } from '@nuxt/schema'
|
||||||
import { resolve } from 'pathe'
|
import { resolve } from 'pathe'
|
||||||
import { componentsTypeTemplate } from '../../nuxt3/src/components/templates'
|
import { componentsTypeTemplate } from '../../nuxt3/src/components/templates'
|
||||||
import { schemaTemplate } from '../../nuxt3/src/core/templates'
|
import { schemaTemplate } from '../../nuxt3/src/core/templates'
|
||||||
import { distDir } from './dirs'
|
import { distDir } from './dirs'
|
||||||
|
import { VueCompat } from './vue-compat'
|
||||||
|
|
||||||
export function setupAppBridge (_options: any) {
|
export function setupAppBridge (_options: any) {
|
||||||
const nuxt = useNuxt()
|
const nuxt = useNuxt()
|
||||||
@ -21,24 +22,13 @@ export function setupAppBridge (_options: any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve vue2 builds
|
// Transpile core vue libraries
|
||||||
const vue2ESM = resolveModule('vue/dist/vue.runtime.esm.js', { paths: nuxt.options.modulesDir })
|
// TODO: resolve in vercel/nft
|
||||||
const vue2CJS = resolveModule('vue/dist/vue.runtime.common.js', { paths: nuxt.options.modulesDir })
|
nuxt.options.build.transpile.push('vuex')
|
||||||
nuxt.options.alias.vue2 = vue2ESM
|
|
||||||
nuxt.options.build.transpile.push('vue')
|
|
||||||
|
|
||||||
// Transpile libs with modern syntax
|
// Transpile libs with modern syntax
|
||||||
nuxt.options.build.transpile.push('h3')
|
nuxt.options.build.transpile.push('h3')
|
||||||
|
|
||||||
extendWebpackConfig((config) => {
|
|
||||||
(config.resolve.alias as any).vue2 = vue2CJS
|
|
||||||
}, { client: false })
|
|
||||||
nuxt.hook('vite:extendConfig', (config, { isServer }) => {
|
|
||||||
if (isServer && !nuxt.options.dev) {
|
|
||||||
(config.resolve.alias as any).vue2 = vue2CJS
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Disable legacy fetch polyfills
|
// Disable legacy fetch polyfills
|
||||||
nuxt.options.fetch.server = false
|
nuxt.options.fetch.server = false
|
||||||
nuxt.options.fetch.client = false
|
nuxt.options.fetch.client = false
|
||||||
@ -70,43 +60,18 @@ export function setupAppBridge (_options: any) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Alias vue to have identical vue3 exports
|
// Alias vue to have identical vue3 exports
|
||||||
nuxt.options.alias['vue2-bridge'] = resolve(distDir, 'runtime/vue2-bridge.mjs')
|
addWebpackPlugin(VueCompat.webpack({
|
||||||
for (const alias of [
|
src: resolve(distDir, 'runtime/vue2-bridge.mjs')
|
||||||
// vue
|
}))
|
||||||
'vue',
|
addVitePlugin(VueCompat.vite({
|
||||||
// vue 3 helper packages
|
src: resolve(distDir, 'runtime/vue2-bridge.mjs')
|
||||||
'@vue/shared',
|
}))
|
||||||
'@vue/reactivity',
|
|
||||||
'@vue/runtime-core',
|
|
||||||
'@vue/runtime-dom',
|
|
||||||
// vue-demi
|
|
||||||
'vue-demi',
|
|
||||||
...[
|
|
||||||
// vue 2 dist files
|
|
||||||
'vue/dist/vue.common.dev',
|
|
||||||
'vue/dist/vue.common',
|
|
||||||
'vue/dist/vue.common.prod',
|
|
||||||
'vue/dist/vue.esm.browser',
|
|
||||||
'vue/dist/vue.esm.browser.min',
|
|
||||||
'vue/dist/vue.esm',
|
|
||||||
'vue/dist/vue',
|
|
||||||
'vue/dist/vue.min',
|
|
||||||
'vue/dist/vue.runtime.common.dev',
|
|
||||||
'vue/dist/vue.runtime.common',
|
|
||||||
'vue/dist/vue.runtime.common.prod',
|
|
||||||
'vue/dist/vue.runtime.esm',
|
|
||||||
'vue/dist/vue.runtime',
|
|
||||||
'vue/dist/vue.runtime.min'
|
|
||||||
].flatMap(m => [m, `${m}.js`])
|
|
||||||
]) {
|
|
||||||
nuxt.options.alias[alias] = nuxt.options.alias['vue2-bridge']
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure TS still recognises vue imports
|
nuxt.hook('prepare:types', ({ tsConfig, references }) => {
|
||||||
nuxt.hook('prepare:types', ({ tsConfig }) => {
|
// Type 'vue' module with composition API exports
|
||||||
tsConfig.compilerOptions.paths.vue2 = ['vue']
|
references.push({ path: resolve(distDir, 'runtime/vue2-bridge.d.ts') })
|
||||||
delete tsConfig.compilerOptions.paths.vue
|
|
||||||
|
|
||||||
|
// Enable Volar support with vue 2 compat mode
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
tsConfig.vueCompilerOptions = {
|
tsConfig.vueCompilerOptions = {
|
||||||
experimentalCompatMode: 2
|
experimentalCompatMode: 2
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Vue from 'vue' // eslint-disable-line import/default
|
import Vue from 'vue'
|
||||||
import VueCompositionAPI from '@vue/composition-api'
|
import VueCompositionAPI from '@vue/composition-api'
|
||||||
import { defineNuxtPlugin } from '#app'
|
import { defineNuxtPlugin } from '#app'
|
||||||
|
|
||||||
|
63
packages/bridge/src/runtime/vue2-bridge.d.ts
vendored
63
packages/bridge/src/runtime/vue2-bridge.d.ts
vendored
@ -1,7 +1,60 @@
|
|||||||
import Vue from 'vue'
|
import * as VueCapi from '@vue/composition-api'
|
||||||
|
|
||||||
export * from '@vue/composition-api'
|
declare module 'vue' {
|
||||||
|
export const EffectScope: typeof VueCapi['EffectScope']
|
||||||
|
export const computed: typeof VueCapi['computed']
|
||||||
|
export const createApp: typeof VueCapi['createApp']
|
||||||
|
export const createRef: typeof VueCapi['createRef']
|
||||||
|
export const customRef: typeof VueCapi['customRef']
|
||||||
|
export const defineAsyncComponent: typeof VueCapi['defineAsyncComponent']
|
||||||
|
export const defineComponent: typeof VueCapi['defineComponent']
|
||||||
|
export const del: typeof VueCapi['del']
|
||||||
|
export const effectScope: typeof VueCapi['effectScope']
|
||||||
|
export const getCurrentInstance: typeof VueCapi['getCurrentInstance']
|
||||||
|
export const getCurrentScope: typeof VueCapi['getCurrentScope']
|
||||||
|
export const h: typeof VueCapi['h']
|
||||||
|
export const inject: typeof VueCapi['inject']
|
||||||
|
export const isRaw: typeof VueCapi['isRaw']
|
||||||
|
export const isReactive: typeof VueCapi['isReactive']
|
||||||
|
export const isReadonly: typeof VueCapi['isReadonly']
|
||||||
|
export const isRef: typeof VueCapi['isRef']
|
||||||
|
export const markRaw: typeof VueCapi['markRaw']
|
||||||
|
export const nextTick: typeof VueCapi['nextTick']
|
||||||
|
export const onActivated: typeof VueCapi['onActivated']
|
||||||
|
export const onBeforeMount: typeof VueCapi['onBeforeMount']
|
||||||
|
export const onBeforeUnmount: typeof VueCapi['onBeforeUnmount']
|
||||||
|
export const onBeforeUpdate: typeof VueCapi['onBeforeUpdate']
|
||||||
|
export const onDeactivated: typeof VueCapi['onDeactivated']
|
||||||
|
export const onErrorCaptured: typeof VueCapi['onErrorCaptured']
|
||||||
|
export const onMounted: typeof VueCapi['onMounted']
|
||||||
|
export const onScopeDispose: typeof VueCapi['onScopeDispose']
|
||||||
|
export const onServerPrefetch: typeof VueCapi['onServerPrefetch']
|
||||||
|
export const onUnmounted: typeof VueCapi['onUnmounted']
|
||||||
|
export const onUpdated: typeof VueCapi['onUpdated']
|
||||||
|
export const provide: typeof VueCapi['provide']
|
||||||
|
export const proxyRefs: typeof VueCapi['proxyRefs']
|
||||||
|
export const reactive: typeof VueCapi['reactive']
|
||||||
|
export const readonly: typeof VueCapi['readonly']
|
||||||
|
export const ref: typeof VueCapi['ref']
|
||||||
|
export const set: typeof VueCapi['set']
|
||||||
|
export const shallowReactive: typeof VueCapi['shallowReactive']
|
||||||
|
export const shallowReadonly: typeof VueCapi['shallowReadonly']
|
||||||
|
export const shallowRef: typeof VueCapi['shallowRef']
|
||||||
|
export const toRaw: typeof VueCapi['toRaw']
|
||||||
|
export const toRef: typeof VueCapi['toRef']
|
||||||
|
export const toRefs: typeof VueCapi['toRefs']
|
||||||
|
export const triggerRef: typeof VueCapi['triggerRef']
|
||||||
|
export const unref: typeof VueCapi['unref']
|
||||||
|
export const useAttrs: typeof VueCapi['useAttrs']
|
||||||
|
export const useCSSModule: typeof VueCapi['useCSSModule']
|
||||||
|
export const useCssModule: typeof VueCapi['useCssModule']
|
||||||
|
export const useSlots: typeof VueCapi['useSlots']
|
||||||
|
export const warn: typeof VueCapi['warn']
|
||||||
|
export const watch: typeof VueCapi['watch']
|
||||||
|
export const watchEffect: typeof VueCapi['watchEffect']
|
||||||
|
export const watchPostEffect: typeof VueCapi['watchPostEffect']
|
||||||
|
export const watchSyncEffect: typeof VueCapi['watchSyncEffect']
|
||||||
|
export const isFunction: (fn: unknown) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
export declare const isFunction: (fn: unknown) => boolean
|
export {}
|
||||||
|
|
||||||
export { Vue as default }
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Vue from 'vue2'
|
import Vue from 'vue'
|
||||||
|
|
||||||
export { EffectScope, computed, createApp, createRef, customRef, defineAsyncComponent, defineComponent, del, effectScope, getCurrentInstance, getCurrentScope, h, inject, isRaw, isReactive, isReadonly, isRef, markRaw, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, provide, proxyRefs, reactive, readonly, ref, set, shallowReactive, shallowReadonly, shallowRef, toRaw, toRef, toRefs, triggerRef, unref, useAttrs, useCSSModule, useCssModule, useSlots, warn, watch, watchEffect, watchPostEffect, watchSyncEffect } from '@vue/composition-api'
|
export { EffectScope, computed, createApp, createRef, customRef, defineAsyncComponent, defineComponent, del, effectScope, getCurrentInstance, getCurrentScope, h, inject, isRaw, isReactive, isReadonly, isRef, markRaw, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, provide, proxyRefs, reactive, readonly, ref, set, shallowReactive, shallowReadonly, shallowRef, toRaw, toRef, toRefs, triggerRef, unref, useAttrs, useCSSModule, useCssModule, useSlots, warn, watch, watchEffect, watchPostEffect, watchSyncEffect } from '@vue/composition-api'
|
||||||
|
|
||||||
|
@ -58,9 +58,7 @@ async function bundle (nuxt: Nuxt, builder: any) {
|
|||||||
'ufo',
|
'ufo',
|
||||||
'date-fns',
|
'date-fns',
|
||||||
'nanoid',
|
'nanoid',
|
||||||
'vue',
|
'vue'
|
||||||
'vue2',
|
|
||||||
'vue2-bridge'
|
|
||||||
// TODO(Anthony): waiting for Vite's fix https://github.com/vitejs/vite/issues/5688
|
// TODO(Anthony): waiting for Vite's fix https://github.com/vitejs/vite/issues/5688
|
||||||
// ...nuxt.options.build.transpile.filter(i => typeof i === 'string'),
|
// ...nuxt.options.build.transpile.filter(i => typeof i === 'string'),
|
||||||
// 'vue-demi'
|
// 'vue-demi'
|
||||||
|
74
packages/bridge/src/vue-compat.ts
Normal file
74
packages/bridge/src/vue-compat.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { pathToFileURL } from 'url'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import { findStaticImports } from 'mlly'
|
||||||
|
import { parseQuery, parseURL } from 'ufo'
|
||||||
|
import { createUnplugin } from 'unplugin'
|
||||||
|
|
||||||
|
export const VueCompat = createUnplugin((opts: { src?: string }) => {
|
||||||
|
return {
|
||||||
|
name: 'nuxt-legacy-vue-transform',
|
||||||
|
enforce: 'post',
|
||||||
|
transformInclude (id) {
|
||||||
|
if (id.includes('vue2-bridge')) { return false }
|
||||||
|
|
||||||
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
||||||
|
const query = parseQuery(search)
|
||||||
|
|
||||||
|
// vue files
|
||||||
|
if (pathname.endsWith('.vue') && (query.type === 'script' || !search)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// js files
|
||||||
|
if (pathname.match(/\.((c|m)?j|t)sx?/g)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transform (code, id) {
|
||||||
|
if (id.includes('vue2-bridge')) { return }
|
||||||
|
|
||||||
|
const s = new MagicString(code)
|
||||||
|
const imports = findStaticImports(code).filter(i => i.type === 'static' && vueAliases.includes(i.specifier))
|
||||||
|
|
||||||
|
for (const i of imports) {
|
||||||
|
s.overwrite(i.start, i.end, i.code.replace(`"${i.specifier}"`, `"${opts.src}"`).replace(`'${i.specifier}'`, `'${opts.src}'`))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.hasChanged()) {
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: s.generateMap({ source: id, includeContent: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const vueAliases = [
|
||||||
|
// vue
|
||||||
|
'vue',
|
||||||
|
// vue 3 helper packages
|
||||||
|
'@vue/shared',
|
||||||
|
'@vue/reactivity',
|
||||||
|
'@vue/runtime-core',
|
||||||
|
'@vue/runtime-dom',
|
||||||
|
// vue-demi
|
||||||
|
'vue-demi',
|
||||||
|
...[
|
||||||
|
// vue 2 dist files
|
||||||
|
'vue/dist/vue.common.dev',
|
||||||
|
'vue/dist/vue.common',
|
||||||
|
'vue/dist/vue.common.prod',
|
||||||
|
'vue/dist/vue.esm.browser',
|
||||||
|
'vue/dist/vue.esm.browser.min',
|
||||||
|
'vue/dist/vue.esm',
|
||||||
|
'vue/dist/vue',
|
||||||
|
'vue/dist/vue.min',
|
||||||
|
'vue/dist/vue.runtime.common.dev',
|
||||||
|
'vue/dist/vue.runtime.common',
|
||||||
|
'vue/dist/vue.runtime.common.prod',
|
||||||
|
'vue/dist/vue.runtime.esm',
|
||||||
|
'vue/dist/vue.runtime',
|
||||||
|
'vue/dist/vue.runtime.min'
|
||||||
|
].flatMap(m => [m, `${m}.js`])
|
||||||
|
]
|
@ -168,7 +168,8 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
|
|||||||
'process.env.RUNTIME_CONFIG': devalue(nitroContext._nuxt.runtimeConfig),
|
'process.env.RUNTIME_CONFIG': devalue(nitroContext._nuxt.runtimeConfig),
|
||||||
'process.env.DEBUG': JSON.stringify(nitroContext._nuxt.dev),
|
'process.env.DEBUG': JSON.stringify(nitroContext._nuxt.dev),
|
||||||
// Needed for vue 2 server build
|
// Needed for vue 2 server build
|
||||||
'commonjsGlobal.process.env.VUE_ENV': '"server"'
|
'commonjsGlobal.process.env.VUE_ENV': '"server"',
|
||||||
|
'global["process"].env.VUE_ENV': '"server"'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ describe('fixtures:bridge', async () => {
|
|||||||
it('render hello world', async () => {
|
it('render hello world', async () => {
|
||||||
expect(await $fetch('/')).to.contain('Hello Vue 2!')
|
expect(await $fetch('/')).to.contain('Hello Vue 2!')
|
||||||
})
|
})
|
||||||
|
it('uses server Vue build', async () => {
|
||||||
|
expect(await $fetch('/')).to.contain('Rendered on server: true')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('navigate', () => {
|
describe('navigate', () => {
|
||||||
|
2
test/fixtures/bridge/pages/index.vue
vendored
2
test/fixtures/bridge/pages/index.vue
vendored
@ -6,6 +6,7 @@
|
|||||||
Update
|
Update
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
Rendered on server: {{ serverBuild }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -15,4 +16,5 @@ const version = ref('2')
|
|||||||
const state = useState('test-state')
|
const state = useState('test-state')
|
||||||
state.value = '123'
|
state.value = '123'
|
||||||
const updateState = () => { state.value = '456' }
|
const updateState = () => { state.value = '456' }
|
||||||
|
const serverBuild = useState('server-build', () => getCurrentInstance().proxy.$isServer)
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user