From 3798694919433254eb8b82d762dcbee0aa6af8fe Mon Sep 17 00:00:00 2001 From: maoger Date: Tue, 29 Sep 2026 13:43:58 +0800 Subject: [PATCH] fix(bundler-webpack): externalize vue subpaths in ssr bundle (close #1718) The server bundle only externalized `vue` itself. The array form of webpack `externals` matches requests exactly, so `vue/server-renderer`, which SFCs compiled for SSR import from, was resolved to `@vue/server-renderer` and bundled together with its own copies of `@vue/runtime-core` and `@vue/shared`, while `vue` stayed external. With two vue runtimes in one server bundle, components are created by the bundled runtime, and `instance.refs` is the bundled `EMPTY_OBJ`, but `useTemplateRef` comes from the external `vue` and compares it with its own `EMPTY_OBJ`. With the production build of vue, the check never matches, so the ref is defined as a non-configurable property on the shared `EMPTY_OBJ`, and rendering another page that uses the same ref key throws `Cannot redefine property`, leaving the page content empty in the generated HTML. Externalize `vue` and all of its subpaths. `@vue/*` packages are not externalized, because they are not guaranteed to be resolvable from the temp directory with pnpm, e.g. `@vue/devtools-api`. --- e2e/docs/imports/use-template-ref-1.md | 7 ++++++ e2e/docs/imports/use-template-ref-2.md | 7 ++++++ e2e/tests/imports/use-template-ref.spec.ts | 22 +++++++++++++++++++ .../src/build/createServerConfig.ts | 4 +++- 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 e2e/docs/imports/use-template-ref-1.md create mode 100644 e2e/docs/imports/use-template-ref-2.md create mode 100644 e2e/tests/imports/use-template-ref.spec.ts diff --git a/e2e/docs/imports/use-template-ref-1.md b/e2e/docs/imports/use-template-ref-1.md new file mode 100644 index 0000000000..0570be594a --- /dev/null +++ b/e2e/docs/imports/use-template-ref-1.md @@ -0,0 +1,7 @@ +
use-template-ref-1
+ + diff --git a/e2e/docs/imports/use-template-ref-2.md b/e2e/docs/imports/use-template-ref-2.md new file mode 100644 index 0000000000..77928af55b --- /dev/null +++ b/e2e/docs/imports/use-template-ref-2.md @@ -0,0 +1,7 @@ +
use-template-ref-2
+ + diff --git a/e2e/tests/imports/use-template-ref.spec.ts b/e2e/tests/imports/use-template-ref.spec.ts new file mode 100644 index 0000000000..4651760324 --- /dev/null +++ b/e2e/tests/imports/use-template-ref.spec.ts @@ -0,0 +1,22 @@ +import { expect, test } from '@playwright/test' + +import { IS_PROD } from '../../utils/env' + +// pages using the same template ref key should all be rendered correctly, +// which requires a single vue runtime in the server bundle +for (const name of ['use-template-ref-1', 'use-template-ref-2']) { + test(`should render ${name} correctly`, async ({ page }) => { + await page.goto(`imports/${name}.html`) + + await expect(page.locator('#use-template-ref')).toHaveText(name) + + if (IS_PROD) { + expect( + await page.evaluate( + async (url) => fetch(url).then(async (res) => res.text()), + `./${name}.html`, + ), + ).toContain(`>${name}`) + } + }) +} diff --git a/packages/bundler-webpack/src/build/createServerConfig.ts b/packages/bundler-webpack/src/build/createServerConfig.ts index e446644e2e..c94fbdc636 100644 --- a/packages/bundler-webpack/src/build/createServerConfig.ts +++ b/packages/bundler-webpack/src/build/createServerConfig.ts @@ -33,7 +33,9 @@ export const createServerConfig = async ( // set externals // externalize vue in ssr mode, because we need to import `'vue/server-renderer'` in node side // for ssr usage, then we also need vue as peer-dependency when using pnpm - config.externals(['vue']) + // subpaths like `'vue/server-renderer'` should also be externalized, otherwise + // a second vue runtime would be bundled into the server bundle + config.externals([/^vue($|\/)/]) // devtool config.devtool('source-map')