From 7a6aa2ad2689bf8221389924a608876866db7b0a Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 9 Feb 2021 09:56:13 -0500 Subject: [PATCH] feat(plugin-vue-jsx): register jsx module during ssr --- .../ssr-vue/__tests__/ssr-vue.spec.ts | 9 ++ .../playground/ssr-vue/src/components/Foo.jsx | 1 + .../playground/ssr-vue/src/components/foo.css | 3 + .../playground/ssr-vue/src/pages/Home.vue | 6 +- packages/plugin-vue-jsx/index.js | 87 ++++++++++++++----- 5 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 packages/playground/ssr-vue/src/components/foo.css diff --git a/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts b/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts index f4a490582f2cd1..24223b96c4967d 100644 --- a/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts +++ b/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts @@ -50,6 +50,13 @@ test('/', async () => { expect(html).toMatch( /link rel="stylesheet".*?href="\/assets\/Home\.\w{8}\.css"/ ) + // JSX component preload registration + expect(html).toMatch( + /link rel="modulepreload".*?href="\/assets\/Foo\.\w{8}\.js"/ + ) + expect(html).toMatch( + /link rel="stylesheet".*?href="\/assets\/Foo\.\w{8}\.css"/ + ) expect(html).not.toMatch( /link rel="modulepreload".*?href="\/assets\/About\.\w{8}\.js"/ ) @@ -62,10 +69,12 @@ test('/', async () => { test('css', async () => { if (isBuild) { expect(await getColor('h1')).toBe('green') + expect(await getColor('.jsx')).toBe('blue') } else { // During dev, the CSS is loaded from async chunk and we may have to wait // when the test runs concurrently. await untilUpdated(() => getColor('h1'), 'green') + await untilUpdated(() => getColor('.jsx'), 'blue') } }) diff --git a/packages/playground/ssr-vue/src/components/Foo.jsx b/packages/playground/ssr-vue/src/components/Foo.jsx index d810b40886c596..c1670c6a13758f 100644 --- a/packages/playground/ssr-vue/src/components/Foo.jsx +++ b/packages/playground/ssr-vue/src/components/Foo.jsx @@ -1,4 +1,5 @@ import { defineComponent } from 'vue' +import './foo.css' // named exports w/ variable declaration: ok export const Foo = defineComponent({ diff --git a/packages/playground/ssr-vue/src/components/foo.css b/packages/playground/ssr-vue/src/components/foo.css new file mode 100644 index 00000000000000..bfaacd33a2f805 --- /dev/null +++ b/packages/playground/ssr-vue/src/components/foo.css @@ -0,0 +1,3 @@ +.jsx { + color: blue; +} \ No newline at end of file diff --git a/packages/playground/ssr-vue/src/pages/Home.vue b/packages/playground/ssr-vue/src/pages/Home.vue index bec719062d6805..29e93f3e3cde16 100644 --- a/packages/playground/ssr-vue/src/pages/Home.vue +++ b/packages/playground/ssr-vue/src/pages/Home.vue @@ -4,12 +4,12 @@ logo

- + diff --git a/packages/plugin-vue-jsx/index.js b/packages/plugin-vue-jsx/index.js index cdacfe1ae542d2..5a3479a91e5295 100644 --- a/packages/plugin-vue-jsx/index.js +++ b/packages/plugin-vue-jsx/index.js @@ -4,6 +4,29 @@ const jsx = require('@vue/babel-plugin-jsx') const importMeta = require('@babel/plugin-syntax-import-meta') const hash = require('hash-sum') +const ssrRegisterHelperId = '/__vue-jsx-ssr-register-helper' +const ssrRegisterHelperCode = + `import { useSSRContext } from "vue"\n` + + `export ${ssrRegisterHelper.toString()}` + +/** + * This function is serialized with toString() and evaluated as a virtual + * module during SSR + * @param {import('vue').ComponentOptions} comp + * @param {string} filename + */ +function ssrRegisterHelper(comp, filename) { + const setup = comp.setup + comp.setup = (props, ctx) => { + // @ts-ignore + const ssrContext = useSSRContext() + ;(ssrContext.modules || (ssrContext.modules = new Set())).add(filename) + if (setup) { + return setup(props, ctx) + } + } +} + /** * @param {import('@vue/babel-plugin-jsx').VueJSXPluginOptions} options * @returns {import('vite').Plugin} @@ -35,6 +58,18 @@ function vueJsxPlugin(options = {}) { needSourceMap = config.command === 'serve' || !!config.build.sourcemap }, + resolveId(id) { + if (id === ssrRegisterHelperId) { + return id + } + }, + + load(id) { + if (id === ssrRegisterHelperId) { + return ssrRegisterHelperCode + } + }, + transform(code, id, ssr) { if (/\.[jt]sx$/.test(id)) { const plugins = [importMeta, [jsx, options]] @@ -53,7 +88,7 @@ function vueJsxPlugin(options = {}) { sourceFileName: id }) - if (ssr || !needHmr) { + if (!ssr && !needHmr) { return { code: result.code, map: result.map @@ -143,28 +178,40 @@ function vueJsxPlugin(options = {}) { } if (hotComponents.length) { - let code = result.code - if (hasDefault) { - code = - code.replace( - /export default defineComponent/g, - `const __default__ = defineComponent` - ) + `\nexport default __default__` - } + if (needHmr && !ssr) { + let code = result.code + if (hasDefault) { + code = + code.replace( + /export default defineComponent/g, + `const __default__ = defineComponent` + ) + `\nexport default __default__` + } - let callbackCode = `` - for (const { local, exported, id } of hotComponents) { - code += - `\n${local}.__hmrId = "${id}"` + - `\n__VUE_HMR_RUNTIME__.createRecord("${id}", ${local})` - callbackCode += `\n__VUE_HMR_RUNTIME__.reload("${id}", __${exported})` - } + let callbackCode = `` + for (const { local, exported, id } of hotComponents) { + code += + `\n${local}.__hmrId = "${id}"` + + `\n__VUE_HMR_RUNTIME__.createRecord("${id}", ${local})` + callbackCode += `\n__VUE_HMR_RUNTIME__.reload("${id}", __${exported})` + } + + code += `\nimport.meta.hot.accept(({${hotComponents + .map((c) => `${c.exported}: __${c.exported}`) + .join(',')}}) => {${callbackCode}\n})` - code += `\nimport.meta.hot.accept(({${hotComponents - .map((c) => `${c.exported}: __${c.exported}`) - .join(',')}}) => {${callbackCode}\n})` + result.code = code + } - result.code = code + if (ssr) { + let ssrInjectCode = + `\nimport { ssrRegisterHelper } from "${ssrRegisterHelperId}"` + + `\nconst __moduleId = ${JSON.stringify(id)}` + for (const { local } of hotComponents) { + ssrInjectCode += `\nssrRegisterHelper(${local}, __moduleId)` + } + result.code += ssrInjectCode + } } return {