diff --git a/package.json b/package.json index 0e1f9ace..612bf1e3 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,8 @@ "dependencies": { "@vue/composition-api": "1.0.0-rc.3", "defu": "^3.2.2", + "estree-walker": "^2.0.1", + "magic-string": "^0.25.7", "ufo": "^0.6.7", "upath": "^2.0.1" }, @@ -122,6 +124,6 @@ } }, "volta": { - "node": "12.21.0" + "node": "14.16.0" } } diff --git a/src/index.ts b/src/index.ts index 35e02497..9d88f665 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,11 @@ import { resolve, join } from 'upath' import { withTrailingSlash } from 'ufo' import { readdirSync, copyFileSync, existsSync, mkdirpSync } from 'fs-extra' +import type { Module, NuxtConfig } from '@nuxt/types' + import { name, version } from '../package.json' -import type { Module, NuxtConfig } from '@nuxt/types' +import { compositionApiPlugin } from './vite' function isFullStatic(options: NuxtConfig) { return ( @@ -95,6 +97,20 @@ const compositionApiModule: Module = function compositionApiModule() { }, }) + const entryFile = resolve(this.options.buildDir || '', entryDst) + + this.options.build.transpile = this.options.build.transpile || [] + this.options.build.transpile.push('@nuxtjs/composition-api') + + // Fake alias to prevent shadowing actual node_module + this.options.alias['@nuxtjs/composition-api'] = entryFile + + this.nuxt.hook('vite:extend', (ctx: any) => { + ctx.config.plugins.push(compositionApiPlugin()) + ctx.config.optimizeDeps.exclude.push('@vue/composition-api') + ctx.config.resolve.alias['~nuxtjs-composition-api'] = entryFile + }) + this.options.build = this.options.build || {} this.options.build.babel = this.options.build.babel || {} @@ -107,9 +123,6 @@ const compositionApiModule: Module = function compositionApiModule() { this.options.build.babel.plugins.push(join(__dirname, 'babel')) } - this.options.build.transpile = this.options.build.transpile || [] - this.options.build.transpile.push(/@nuxtjs[\\/]composition-api/) - const actualPresets = this.options.build.babel.presets this.options.build.babel.presets = ( @@ -131,11 +144,6 @@ const compositionApiModule: Module = function compositionApiModule() { return [[defaultPreset, newOptions]] } - this.options.alias['@nuxtjs/composition-api'] = resolve( - this.options.buildDir || '', - entryDst - ) - this.options.plugins = this.options.plugins || [] this.options.plugins.unshift(resolve(this.options.buildDir || '', pluginDst)) if ( diff --git a/src/vite.ts b/src/vite.ts new file mode 100644 index 00000000..c8864505 --- /dev/null +++ b/src/vite.ts @@ -0,0 +1,92 @@ +import crypto from 'crypto' +import MagicString from 'magic-string' +import { walk } from 'estree-walker' +import type { Plugin } from 'rollup' + +function createKey( + source: string, + method: crypto.BinaryToTextEncoding = 'base64' +) { + const hash = crypto.createHash('md5') + hash.update(source) + return hash.digest(method).toString() +} + +export function compositionApiPlugin(): Plugin & { enforce: 'pre' } { + return { + name: 'nuxt:composition-api', + enforce: 'pre', + transform(code: string, filename: string) { + code = code.replace( + /@nuxtjs[\\/]composition-api/g, + '~nuxtjs-composition-api' + ) + const keyedFunctions = /(useStatic|shallowSsrRef|ssrPromise|ssrRef|reqSsrRef|useAsync)/ + if (!keyedFunctions.test(code)) { + return { + code, + map: null, + } + } + + try { + const { 0: script = code, index: codeIndex = 0 } = + code.match(/(?<=]*>)[\S\s.]*?(?=<\/script>)/) || [] + const ast = this.parse(script) + const s = new MagicString(code) + + walk(ast, { + enter(node) { + const { end } = (node as unknown) as { + end: number + } + const { callee, arguments: args = [] } = node as { + callee?: { + type?: string + name?: string + property?: { type: string; name: string } + } + arguments?: any[] + } + if ( + callee?.type === 'Identifier' || + callee?.property?.type === 'Identifier' + ) { + let method: crypto.BinaryToTextEncoding = 'base64' + + switch (callee.name || callee.property?.name) { + case 'useStatic': + if (args.length > 2) return + if (args.length === 2) { + s.prependLeft(codeIndex + end - 1, ', undefined') + } + method = 'hex' + break + + case 'shallowSsrRef': + case 'ssrPromise': + case 'ssrRef': + case 'reqSsrRef': + case 'useAsync': + if (args.length > 1) return + break + + default: + return + } + s.appendLeft( + codeIndex + end - 1, + ", '" + createKey(`${filename}-${end}`, method) + "'" + ) + } + }, + }) + + return { + code: s.toString(), + map: s.generateMap().toString(), + } + } catch {} + }, + } +}