]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(server-renderer): respect compilerOptions during runtime template compilation...
authorskirtle <65301168+skirtles-code@users.noreply.github.com>
Sat, 25 Sep 2021 18:40:32 +0000 (19:40 +0100)
committerGitHub <noreply@github.com>
Sat, 25 Sep 2021 18:40:32 +0000 (14:40 -0400)
packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts [new file with mode: 0644]
packages/server-renderer/src/helpers/ssrCompile.ts

diff --git a/packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts b/packages/server-renderer/__tests__/ssrCompilerOptions.spec.ts
new file mode 100644 (file)
index 0000000..2ff588a
--- /dev/null
@@ -0,0 +1,148 @@
+/**
+ * @jest-environment node
+ */
+
+import { createApp } from 'vue'
+import { renderToString } from '../src/renderToString'
+
+describe('ssr: compiler options', () => {
+  test('config.isCustomElement (deprecated)', async () => {
+    const app = createApp({
+      template: `<div><x-button/></div>`
+    })
+    app.config.isCustomElement = tag => tag.startsWith('x-')
+    expect(await renderToString(app)).toBe(`<div><x-button></x-button></div>`)
+  })
+
+  test('config.compilerOptions.isCustomElement', async () => {
+    const app = createApp({
+      template: `<div><x-panel/></div>`
+    })
+    app.config.compilerOptions.isCustomElement = tag => tag.startsWith('x-')
+    expect(await renderToString(app)).toBe(`<div><x-panel></x-panel></div>`)
+  })
+
+  test('component.compilerOptions.isCustomElement', async () => {
+    const app = createApp({
+      template: `<div><x-card/><y-child/></div>`,
+      compilerOptions: {
+        isCustomElement: (tag: string) => tag.startsWith('x-')
+      },
+      components: {
+        YChild: {
+          template: `<div><y-button/></div>`
+        }
+      }
+    })
+    app.config.compilerOptions.isCustomElement = tag => tag.startsWith('y-')
+    expect(await renderToString(app)).toBe(
+      `<div><x-card></x-card><div><y-button></y-button></div></div>`
+    )
+  })
+
+  test('component.delimiters (deprecated)', async () => {
+    const app = createApp({
+      template: `<div>[[ 1 + 1 ]]</div>`,
+      delimiters: ['[[', ']]']
+    })
+    expect(await renderToString(app)).toBe(`<div>2</div>`)
+  })
+
+  test('config.compilerOptions.delimiters', async () => {
+    const app = createApp({
+      template: `<div>[( 1 + 1 )]</div>`
+    })
+    app.config.compilerOptions.delimiters = ['[(', ')]']
+    expect(await renderToString(app)).toBe(`<div>2</div>`)
+  })
+
+  test('component.compilerOptions.delimiters', async () => {
+    const app = createApp({
+      template: `<div>[[ 1 + 1 ]]<ChildComponent/></div>`,
+      compilerOptions: {
+        delimiters: ['[[', ']]']
+      },
+      components: {
+        ChildComponent: {
+          template: `<div>(( 2 + 2 ))</div>`
+        }
+      }
+    })
+    app.config.compilerOptions.delimiters = ['((', '))']
+    expect(await renderToString(app)).toBe(`<div>2<div>4</div></div>`)
+  })
+
+  test('compilerOptions.whitespace', async () => {
+    const app = createApp({
+      template: `<div><span>Hello   world</span><ChildComponent/></div>`,
+      compilerOptions: {
+        whitespace: 'condense'
+      },
+      components: {
+        ChildComponent: {
+          template: `<span>Hello   world</span>`
+        }
+      }
+    })
+    app.config.compilerOptions.whitespace = 'preserve'
+    expect(await renderToString(app)).toBe(
+      `<div><span>Hello world</span><span>Hello   world</span></div>`
+    )
+  })
+
+  test('caching with compilerOptions', async () => {
+    const template = `<div>{{1 + 1}}   [[1 + 1]]</div>`
+
+    const app = createApp({
+      template: `<div><ChildOne/><ChildTwo/><ChildThree/></div>`,
+      components: {
+        ChildOne: {
+          template
+        },
+        ChildTwo: {
+          template,
+          compilerOptions: {
+            whitespace: 'preserve'
+          }
+        },
+        ChildThree: {
+          template,
+          compilerOptions: {
+            delimiters: ['[[', ']]']
+          }
+        }
+      }
+    })
+    expect(await renderToString(app)).toBe(
+      `<div><div>2 [[1 + 1]]</div><div>2   [[1 + 1]]</div><div>{{1 + 1}} 2</div></div>`
+    )
+  })
+
+  test('caching with isCustomElement', async () => {
+    const template = `<div><MyChild/></div>`
+
+    const app = createApp({
+      template,
+      // No compilerOptions on the root
+      components: {
+        MyChild: {
+          template,
+          compilerOptions: {
+            isCustomElement: tag => tag.startsWith('x-')
+          },
+          components: {
+            MyChild: {
+              template,
+              compilerOptions: {
+                isCustomElement: tag => tag.startsWith('My')
+              }
+            }
+          }
+        }
+      }
+    })
+    expect(await renderToString(app)).toBe(
+      `<div><div><div><MyChild></MyChild></div></div></div>`
+    )
+  })
+})
index 39fd6c09ba6a43f2a8de5706054291d4b4adf9dc..a44feb5fc0dc6622ef6018a9f83b4dfa011d05b8 100644 (file)
@@ -1,7 +1,7 @@
-import { ComponentInternalInstance, warn } from 'vue'
+import { ComponentInternalInstance, ComponentOptions, warn } from 'vue'
 import { compile } from '@vue/compiler-ssr'
-import { generateCodeFrame, NO } from '@vue/shared'
-import { CompilerError } from '@vue/compiler-core'
+import { extend, generateCodeFrame, isFunction, NO } from '@vue/shared'
+import { CompilerError, CompilerOptions } from '@vue/compiler-core'
 import { PushFn } from '../render'
 
 type SSRRenderFunction = (
@@ -24,29 +24,57 @@ export function ssrCompile(
     )
   }
 
-  const cached = compileCache[template]
+  // TODO: This is copied from runtime-core/src/component.ts and should probably be refactored
+  const Component = instance.type as ComponentOptions
+  const { isCustomElement, compilerOptions } = instance.appContext.config
+  const { delimiters, compilerOptions: componentCompilerOptions } = Component
+
+  const finalCompilerOptions: CompilerOptions = extend(
+    extend(
+      {
+        isCustomElement,
+        delimiters
+      },
+      compilerOptions
+    ),
+    componentCompilerOptions
+  )
+
+  finalCompilerOptions.isCustomElement =
+    finalCompilerOptions.isCustomElement || NO
+  finalCompilerOptions.isNativeTag = finalCompilerOptions.isNativeTag || NO
+
+  const cacheKey = JSON.stringify(
+    {
+      template,
+      compilerOptions: finalCompilerOptions
+    },
+    (key, value) => {
+      return isFunction(value) ? value.toString() : value
+    }
+  )
+
+  const cached = compileCache[cacheKey]
   if (cached) {
     return cached
   }
 
-  const { code } = compile(template, {
-    isCustomElement: instance.appContext.config.isCustomElement || NO,
-    isNativeTag: instance.appContext.config.isNativeTag || NO,
-    onError(err: CompilerError) {
-      if (__DEV__) {
-        const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
-        const codeFrame =
-          err.loc &&
-          generateCodeFrame(
-            template as string,
-            err.loc.start.offset,
-            err.loc.end.offset
-          )
-        warn(codeFrame ? `${message}\n${codeFrame}` : message)
-      } else {
-        throw err
-      }
+  finalCompilerOptions.onError = (err: CompilerError) => {
+    if (__DEV__) {
+      const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
+      const codeFrame =
+        err.loc &&
+        generateCodeFrame(
+          template as string,
+          err.loc.start.offset,
+          err.loc.end.offset
+        )
+      warn(codeFrame ? `${message}\n${codeFrame}` : message)
+    } else {
+      throw err
     }
-  })
-  return (compileCache[template] = Function('require', code)(require))
+  }
+
+  const { code } = compile(template, finalCompilerOptions)
+  return (compileCache[cacheKey] = Function('require', code)(require))
 }