]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(vapor): e2e interop test for vapor
authorEvan You <evan@vuejs.org>
Sat, 8 Feb 2025 12:42:34 +0000 (20:42 +0800)
committerEvan You <evan@vuejs.org>
Sat, 8 Feb 2025 12:42:34 +0000 (20:42 +0800)
31 files changed:
eslint.config.js
package.json
packages-private/benchmark/index.js
packages-private/benchmark/package.json
packages-private/local-playground/package.json
packages-private/tsconfig.json
packages-private/vapor-e2e-test/__tests__/e2eUtils.ts [new file with mode: 0644]
packages-private/vapor-e2e-test/__tests__/todomvc.spec.ts [new file with mode: 0644]
packages-private/vapor-e2e-test/__tests__/vdomInterop.spec.ts [new file with mode: 0644]
packages-private/vapor-e2e-test/index.html [new file with mode: 0644]
packages-private/vapor-e2e-test/interop/App.vue [new file with mode: 0644]
packages-private/vapor-e2e-test/interop/VaporComp.vue [new file with mode: 0644]
packages-private/vapor-e2e-test/interop/VdomComp.vue [new file with mode: 0644]
packages-private/vapor-e2e-test/interop/index.html [new file with mode: 0644]
packages-private/vapor-e2e-test/interop/main.ts [new file with mode: 0644]
packages-private/vapor-e2e-test/package.json [new file with mode: 0644]
packages-private/vapor-e2e-test/todomvc/index.html [new file with mode: 0644]
packages-private/vapor-e2e-test/todomvc/main.ts [new file with mode: 0644]
packages-private/vapor-e2e-test/vite.config.ts [new file with mode: 0644]
packages/vue/__tests__/e2e/e2eUtils.ts
packages/vue/package.json
pnpm-lock.yaml
pnpm-workspace.yaml
rollup.config.js
scripts/build.js
scripts/dev.js
scripts/trim-vapor-exports.js
vitest.config.ts
vitest.e2e.config.ts [deleted file]
vitest.unit.config.ts [deleted file]
vitest.workspace.ts [deleted file]

index 384074d4c32ebcd2745f29fc88b811ae75c5986e..1f5128ec72febc7eb5870445a18e669fff779777 100644 (file)
@@ -154,6 +154,7 @@ export default tseslint.config(
       'packages/*/*.js',
       'packages/vue/*/*.js',
       'packages-private/benchmark/*',
+      'packages-private/e2e-utils/*',
     ],
     rules: {
       'no-restricted-globals': 'off',
index 80a86145a150842f43b39afc39b2b62356998dbf..2967aa4d4d30bcb4a48961ae32bd23fe7d5cf45f 100644 (file)
     "format": "prettier --write --cache .",
     "format-check": "prettier --check --cache .",
     "test": "vitest",
-    "test-unit": "vitest --project unit",
+    "test-unit": "vitest --project unit --project unit-jsdom",
     "test-e2e": "node scripts/build.js vue -f global -d && vitest --project e2e",
+    "test-e2e-vapor": "pnpm run prepare-e2e-vapor && vitest --project e2e-vapor",
+    "prepare-e2e-vapor": "node scripts/build.js -f cjs+esm-bundler+esm-bundler-runtime && pnpm run -C packages-private/vapor-e2e-test build",
     "test-dts": "run-s build-dts test-dts-only",
     "test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
     "test-coverage": "vitest run --project unit --coverage",
     "release": "node scripts/release.js",
     "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
     "dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
-    "dev-prepare-cjs": "node scripts/prepare-cjs.js || npm run build-all-cjs",
+    "dev-prepare-cjs": "node scripts/prepare-cjs.js || node scripts/build.js -f cjs",
     "dev-compiler": "run-p \"dev template-explorer\" serve open",
     "dev-sfc": "run-s dev-prepare-cjs dev-sfc-run",
     "dev-sfc-serve": "vite packages-private/sfc-playground",
-    "dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if vapor\" \"dev vue -ipf vapor\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
-    "dev-vapor": "pnpm -C playground run dev",
+    "dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-browser-vapor\" \"dev vue -ipf esm-browser-vapor\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
+    "dev-vapor": "pnpm -C packages-private/local-playground run dev",
     "serve": "serve",
     "open": "open http://localhost:3000/packages-private/template-explorer/local.html",
-    "build-sfc-playground": "run-s build-all-cjs build-all-esm build-sfc-playground-self",
-    "build-all-cjs": "node scripts/build.js vue runtime compiler reactivity shared -af cjs",
-    "build-all-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler+esm-browser+esm-bundler-runtime+esm-browser-runtime+vapor && node scripts/build.js compiler-sfc server-renderer -f esm-browser",
-    "build-sfc-playground-self": "cd packages-private/sfc-playground && npm run build",
+    "build-sfc-playground": "run-s build-sfc-deps build-sfc-playground-self",
+    "build-sfc-deps": "node scripts/build.js -f ~global+global-runtime",
+    "build-sfc-playground-self": "pnpm run -C packages-private/sfc-playground build",
     "preinstall": "npx only-allow pnpm",
     "postinstall": "simple-git-hooks"
   },
index 7841b0b2998510d615eb7ffecef4f68594bf1168..86a73368743cf7d8ddaf47704d273eaf74d6bffe 100644 (file)
@@ -121,7 +121,7 @@ async function buildLib() {
     ),
     exec(
       'pnpm',
-      `run --silent build vue ${buildOptions} vapor`.split(' '),
+      `run --silent build vue ${buildOptions} esm-browser-vapor`.split(' '),
       options,
     ),
   ])
@@ -146,9 +146,7 @@ async function buildApp(isVapor) {
 
   const runtimePath = path.resolve(
     import.meta.dirname,
-    (isVapor
-      ? '../../packages/vue/dist/vue.runtime-with-vapor.esm-browser'
-      : '../../packages/vue/dist/vue.runtime.esm-browser') + prodSuffix,
+    '../../packages/vue/dist/vue.runtime-with-vapor.esm-browser' + prodSuffix,
   )
 
   const mode = isVapor ? 'vapor' : 'vdom'
@@ -159,7 +157,7 @@ async function buildApp(isVapor) {
       'import.meta.env.IS_VAPOR': String(isVapor),
     },
     build: {
-      minify: !devBuild && 'terser',
+      minify: !devBuild,
       outDir: path.resolve('./client/dist', mode),
       rollupOptions: {
         onwarn(log, handler) {
index b087cfca4bbe2bfec648d8aae6209b8009c804c4..61dab94161ed5a21791dd084f506b288bc64deca 100644 (file)
@@ -5,7 +5,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "dev": "pnpm start --devBuild --noHeadless --skipBench --vdom",
+    "dev": "pnpm start --devBuild --skipBench --vdom",
     "start": "node index.js"
   },
   "dependencies": {
@@ -15,7 +15,6 @@
     "vite": "catalog:"
   },
   "devDependencies": {
-    "@types/connect": "^3.4.38",
-    "terser": "^5.33.0"
+    "@types/connect": "^3.4.38"
   }
 }
index 23903a2ae2858240dc0ba03496f36295699374a0..37c9818a706e2b927fe7587b97929a3799e749c3 100644 (file)
@@ -13,7 +13,7 @@
     "vue": "workspace:*"
   },
   "devDependencies": {
-    "@vitejs/plugin-vue": "https://pkg.pr.new/@vitejs/plugin-vue@c156992",
+    "@vitejs/plugin-vue": "catalog:",
     "@vue/compiler-sfc": "workspace:*",
     "vite": "catalog:",
     "vite-hyper-config": "^0.4.0",
index 1c287a7500c5a9f813b9d6143f460071b134ba91..37a38f53fc63ecda8082c79d1b5a00712ea5b78d 100644 (file)
@@ -3,5 +3,5 @@
   "compilerOptions": {
     "isolatedDeclarations": false
   },
-  "include": ["."]
+  "include": [".", "../packages/vue/__tests__/e2e/e2eUtils.ts"]
 }
diff --git a/packages-private/vapor-e2e-test/__tests__/e2eUtils.ts b/packages-private/vapor-e2e-test/__tests__/e2eUtils.ts
new file mode 100644 (file)
index 0000000..2ffebeb
--- /dev/null
@@ -0,0 +1,223 @@
+import puppeteer, {
+  type Browser,
+  type ClickOptions,
+  type LaunchOptions,
+  type Page,
+} from 'puppeteer'
+
+export const E2E_TIMEOUT: number = 30 * 1000
+
+const puppeteerOptions: LaunchOptions = {
+  args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
+  headless: true,
+}
+
+const maxTries = 30
+export const timeout = (n: number): Promise<any> =>
+  new Promise(r => setTimeout(r, n))
+
+export async function expectByPolling(
+  poll: () => Promise<any>,
+  expected: string,
+): Promise<void> {
+  for (let tries = 0; tries < maxTries; tries++) {
+    const actual = (await poll()) || ''
+    if (actual.indexOf(expected) > -1 || tries === maxTries - 1) {
+      expect(actual).toMatch(expected)
+      break
+    } else {
+      await timeout(50)
+    }
+  }
+}
+
+interface PuppeteerUtils {
+  page: () => Page
+  click(selector: string, options?: ClickOptions): Promise<void>
+  count(selector: string): Promise<number>
+  text(selector: string): Promise<string | null>
+  value(selector: string): Promise<string>
+  html(selector: string): Promise<string>
+  classList(selector: string): Promise<string[]>
+  style(selector: string, property: keyof CSSStyleDeclaration): Promise<any>
+  children(selector: string): Promise<any[]>
+  isVisible(selector: string): Promise<boolean>
+  isChecked(selector: string): Promise<boolean>
+  isFocused(selector: string): Promise<boolean>
+  setValue(selector: string, value: string): Promise<any>
+  typeValue(selector: string, value: string): Promise<any>
+  enterValue(selector: string, value: string): Promise<any>
+  clearValue(selector: string): Promise<any>
+  timeout(time: number): Promise<any>
+  nextFrame(): Promise<any>
+}
+
+export function setupPuppeteer(args?: string[]): PuppeteerUtils {
+  let browser: Browser
+  let page: Page
+
+  const resolvedOptions = args
+    ? {
+        ...puppeteerOptions,
+        args: [...puppeteerOptions.args!, ...args],
+      }
+    : puppeteerOptions
+
+  beforeAll(async () => {
+    browser = await puppeteer.launch(resolvedOptions)
+  }, 20000)
+
+  beforeEach(async () => {
+    page = await browser.newPage()
+
+    await page.evaluateOnNewDocument(() => {
+      localStorage.clear()
+    })
+
+    page.on('console', e => {
+      if (e.type() === 'error') {
+        console.error(`Error from Puppeteer-loaded page:\n`, e.text())
+      }
+    })
+  })
+
+  afterEach(async () => {
+    await page.close()
+  })
+
+  afterAll(async () => {
+    await browser.close()
+  })
+
+  async function click(
+    selector: string,
+    options?: ClickOptions,
+  ): Promise<void> {
+    await page.click(selector, options)
+  }
+
+  async function count(selector: string): Promise<number> {
+    return (await page.$$(selector)).length
+  }
+
+  async function text(selector: string): Promise<string | null> {
+    return page.$eval(selector, node => node.textContent)
+  }
+
+  async function value(selector: string): Promise<string> {
+    return page.$eval(selector, node => (node as HTMLInputElement).value)
+  }
+
+  async function html(selector: string): Promise<string> {
+    return page.$eval(selector, node => node.innerHTML)
+  }
+
+  async function classList(selector: string): Promise<string[]> {
+    return page.$eval(selector, (node: any) => [...node.classList])
+  }
+
+  async function children(selector: string): Promise<any[]> {
+    return page.$eval(selector, (node: any) => [...node.children])
+  }
+
+  async function style(
+    selector: string,
+    property: keyof CSSStyleDeclaration,
+  ): Promise<any> {
+    return await page.$eval(
+      selector,
+      (node, property) => {
+        return window.getComputedStyle(node)[property]
+      },
+      property,
+    )
+  }
+
+  async function isVisible(selector: string): Promise<boolean> {
+    const display = await page.$eval(selector, node => {
+      return window.getComputedStyle(node).display
+    })
+    return display !== 'none'
+  }
+
+  async function isChecked(selector: string) {
+    return await page.$eval(
+      selector,
+      node => (node as HTMLInputElement).checked,
+    )
+  }
+
+  async function isFocused(selector: string) {
+    return await page.$eval(selector, node => node === document.activeElement)
+  }
+
+  async function setValue(selector: string, value: string) {
+    await page.$eval(
+      selector,
+      (node, value) => {
+        ;(node as HTMLInputElement).value = value as string
+        node.dispatchEvent(new Event('input'))
+      },
+      value,
+    )
+  }
+
+  async function typeValue(selector: string, value: string) {
+    const el = (await page.$(selector))!
+    await el.evaluate(node => ((node as HTMLInputElement).value = ''))
+    await el.type(value)
+  }
+
+  async function enterValue(selector: string, value: string) {
+    const el = (await page.$(selector))!
+    await el.evaluate(node => ((node as HTMLInputElement).value = ''))
+    await el.type(value)
+    await el.press('Enter')
+  }
+
+  async function clearValue(selector: string) {
+    return await page.$eval(
+      selector,
+      node => ((node as HTMLInputElement).value = ''),
+    )
+  }
+
+  function timeout(time: number) {
+    return page.evaluate(time => {
+      return new Promise(r => {
+        setTimeout(r, time)
+      })
+    }, time)
+  }
+
+  function nextFrame() {
+    return page.evaluate(() => {
+      return new Promise(resolve => {
+        requestAnimationFrame(() => {
+          requestAnimationFrame(resolve)
+        })
+      })
+    })
+  }
+
+  return {
+    page: () => page,
+    click,
+    count,
+    text,
+    value,
+    html,
+    classList,
+    style,
+    children,
+    isVisible,
+    isChecked,
+    isFocused,
+    setValue,
+    typeValue,
+    enterValue,
+    clearValue,
+    timeout,
+    nextFrame,
+  }
+}
diff --git a/packages-private/vapor-e2e-test/__tests__/todomvc.spec.ts b/packages-private/vapor-e2e-test/__tests__/todomvc.spec.ts
new file mode 100644 (file)
index 0000000..23f9e18
--- /dev/null
@@ -0,0 +1 @@
+test('bar', () => {})
diff --git a/packages-private/vapor-e2e-test/__tests__/vdomInterop.spec.ts b/packages-private/vapor-e2e-test/__tests__/vdomInterop.spec.ts
new file mode 100644 (file)
index 0000000..9a60a26
--- /dev/null
@@ -0,0 +1,84 @@
+import path from 'node:path'
+import {
+  E2E_TIMEOUT,
+  setupPuppeteer,
+} from '../../../packages/vue/__tests__/e2e/e2eUtils'
+import connect from 'connect'
+import sirv from 'sirv'
+
+describe('vdom / vapor interop', () => {
+  const { page, click, text, enterValue } = setupPuppeteer()
+
+  let server: any
+  const port = '8193'
+  beforeAll(() => {
+    server = connect()
+      .use(sirv(path.resolve(import.meta.dirname, '../dist')))
+      .listen(port)
+    process.on('SIGTERM', () => server && server.close())
+  })
+
+  afterAll(() => {
+    server.close()
+  })
+
+  test(
+    'should work',
+    async () => {
+      const baseUrl = `http://localhost:${port}/interop`
+      await page().goto(baseUrl)
+
+      expect(await text('.vapor > h2')).toContain('Vapor component in VDOM')
+
+      expect(await text('.vapor-prop')).toContain('hello')
+
+      const t = await text('.vdom-slot-in-vapor-default')
+      expect(t).toContain('slot prop: slot prop')
+      expect(t).toContain('component prop: hello')
+
+      await click('.change-vdom-slot-in-vapor-prop')
+      expect(await text('.vdom-slot-in-vapor-default')).toContain(
+        'slot prop: changed',
+      )
+
+      expect(await text('.vdom-slot-in-vapor-test')).toContain('A test slot')
+
+      await click('.toggle-vdom-slot-in-vapor')
+      expect(await text('.vdom-slot-in-vapor-test')).toContain(
+        'fallback content',
+      )
+
+      await click('.toggle-vdom-slot-in-vapor')
+      expect(await text('.vdom-slot-in-vapor-test')).toContain('A test slot')
+
+      expect(await text('.vdom > h2')).toContain('VDOM component in Vapor')
+
+      expect(await text('.vdom-prop')).toContain('hello')
+
+      const tt = await text('.vapor-slot-in-vdom-default')
+      expect(tt).toContain('slot prop: slot prop')
+      expect(tt).toContain('component prop: hello')
+
+      await click('.change-vapor-slot-in-vdom-prop')
+      expect(await text('.vapor-slot-in-vdom-default')).toContain(
+        'slot prop: changed',
+      )
+
+      expect(await text('.vapor-slot-in-vdom-test')).toContain('fallback')
+
+      await click('.toggle-vapor-slot-in-vdom-default')
+      expect(await text('.vapor-slot-in-vdom-default')).toContain(
+        'default slot fallback',
+      )
+
+      await click('.toggle-vapor-slot-in-vdom-default')
+
+      await enterValue('input', 'bye')
+      expect(await text('.vapor-prop')).toContain('bye')
+      expect(await text('.vdom-slot-in-vapor-default')).toContain('bye')
+      expect(await text('.vdom-prop')).toContain('bye')
+      expect(await text('.vapor-slot-in-vdom-default')).toContain('bye')
+    },
+    E2E_TIMEOUT,
+  )
+})
diff --git a/packages-private/vapor-e2e-test/index.html b/packages-private/vapor-e2e-test/index.html
new file mode 100644 (file)
index 0000000..7dc205e
--- /dev/null
@@ -0,0 +1,2 @@
+<a href="/interop/">VDOM / Vapor interop</a>
+<a href="/todomvc/">Vapor TodoMVC</a>
diff --git a/packages-private/vapor-e2e-test/interop/App.vue b/packages-private/vapor-e2e-test/interop/App.vue
new file mode 100644 (file)
index 0000000..772a698
--- /dev/null
@@ -0,0 +1,22 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import VaporComp from './VaporComp.vue'
+
+const msg = ref('hello')
+const passSlot = ref(true)
+</script>
+
+<template>
+  <input v-model="msg" />
+  <button class="toggle-vdom-slot-in-vapor" @click="passSlot = !passSlot">
+    toggle #test slot
+  </button>
+  <VaporComp :msg="msg">
+    <template #default="{ foo }">
+      <div>slot prop: {{ foo }}</div>
+      <div>component prop: {{ msg }}</div>
+    </template>
+
+    <template #test v-if="passSlot">A test slot</template>
+  </VaporComp>
+</template>
diff --git a/packages-private/vapor-e2e-test/interop/VaporComp.vue b/packages-private/vapor-e2e-test/interop/VaporComp.vue
new file mode 100644 (file)
index 0000000..88a60c7
--- /dev/null
@@ -0,0 +1,50 @@
+<script setup vapor lang="ts">
+import { ref } from 'vue'
+import VdomComp from './VdomComp.vue'
+
+defineProps<{
+  msg: string
+}>()
+
+const ok = ref(true)
+const passSlot = ref(true)
+const slotProp = ref('slot prop')
+</script>
+
+<template>
+  <div class="vapor" style="border: 2px solid red; padding: 10px">
+    <h2>This is a Vapor component in VDOM</h2>
+    <p class="vapor-prop">props.msg: {{ msg }}</p>
+
+    <button @click="ok = !ok">Toggle slots</button>
+
+    <div v-if="ok" style="border: 2px solid orange; padding: 10px">
+      <h3>vdom slots in vapor component</h3>
+      <button
+        class="change-vdom-slot-in-vapor-prop"
+        @click="slotProp = 'changed'"
+      >
+        change slot prop
+      </button>
+      <div class="vdom-slot-in-vapor-default">
+        #default: <slot :foo="slotProp" />
+      </div>
+      <div class="vdom-slot-in-vapor-test">
+        #test: <slot name="test">fallback content</slot>
+      </div>
+    </div>
+
+    <button
+      class="toggle-vapor-slot-in-vdom-default"
+      @click="passSlot = !passSlot"
+    >
+      Toggle default slot to vdom
+    </button>
+    <VdomComp :msg="msg">
+      <template #default="{ foo }" v-if="passSlot">
+        <div>slot prop: {{ foo }}</div>
+        <div>component prop: {{ msg }}</div>
+      </template>
+    </VdomComp>
+  </div>
+</template>
diff --git a/packages-private/vapor-e2e-test/interop/VdomComp.vue b/packages-private/vapor-e2e-test/interop/VdomComp.vue
new file mode 100644 (file)
index 0000000..30ec1b2
--- /dev/null
@@ -0,0 +1,28 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+defineProps<{
+  msg: string
+}>()
+
+const bar = ref('slot prop')
+</script>
+
+<template>
+  <div class="vdom" style="border: 2px solid blue; padding: 10px">
+    <h2>This is a VDOM component in Vapor</h2>
+    <p class="vdom-prop">props.msg: {{ msg }}</p>
+    <div style="border: 2px solid aquamarine; padding: 10px">
+      <h3>vapor slots in vdom</h3>
+      <button class="change-vapor-slot-in-vdom-prop" @click="bar = 'changed'">
+        Change slot prop
+      </button>
+      <div class="vapor-slot-in-vdom-default">
+        #default: <slot :foo="bar">default slot fallback</slot>
+      </div>
+      <div class="vapor-slot-in-vdom-test">
+        #test <slot name="test">fallback</slot>
+      </div>
+    </div>
+  </div>
+</template>
diff --git a/packages-private/vapor-e2e-test/interop/index.html b/packages-private/vapor-e2e-test/interop/index.html
new file mode 100644 (file)
index 0000000..79052a0
--- /dev/null
@@ -0,0 +1,2 @@
+<script type="module" src="./main.ts"></script>
+<div id="app"></div>
diff --git a/packages-private/vapor-e2e-test/interop/main.ts b/packages-private/vapor-e2e-test/interop/main.ts
new file mode 100644 (file)
index 0000000..d5d6d7d
--- /dev/null
@@ -0,0 +1,4 @@
+import { createApp, vaporInteropPlugin } from 'vue'
+import App from './App.vue'
+
+createApp(App).use(vaporInteropPlugin).mount('#app')
diff --git a/packages-private/vapor-e2e-test/package.json b/packages-private/vapor-e2e-test/package.json
new file mode 100644 (file)
index 0000000..66ea045
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "name": "vapor-e2e-test",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite dev",
+    "build": "vite build"
+  },
+  "devDependencies": {
+    "@types/connect": "^3.4.38",
+    "@vitejs/plugin-vue": "catalog:",
+    "connect": "^3.7.0",
+    "sirv": "^2.0.4",
+    "vite": "catalog:",
+    "vue": "workspace:*"
+  }
+}
diff --git a/packages-private/vapor-e2e-test/todomvc/index.html b/packages-private/vapor-e2e-test/todomvc/index.html
new file mode 100644 (file)
index 0000000..689a5a8
--- /dev/null
@@ -0,0 +1 @@
+<script type="module" src="./main.ts"></script>
diff --git a/packages-private/vapor-e2e-test/todomvc/main.ts b/packages-private/vapor-e2e-test/todomvc/main.ts
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/packages-private/vapor-e2e-test/vite.config.ts b/packages-private/vapor-e2e-test/vite.config.ts
new file mode 100644 (file)
index 0000000..1e29a4d
--- /dev/null
@@ -0,0 +1,20 @@
+import { defineConfig } from 'vite'
+import Vue from '@vitejs/plugin-vue'
+import * as CompilerSFC from 'vue/compiler-sfc'
+import { resolve } from 'node:path'
+
+export default defineConfig({
+  plugins: [
+    Vue({
+      compiler: CompilerSFC,
+    }),
+  ],
+  build: {
+    rollupOptions: {
+      input: {
+        interop: resolve(import.meta.dirname, 'interop/index.html'),
+        todomvc: resolve(import.meta.dirname, 'todomvc/index.html'),
+      },
+    },
+  },
+})
index 1ed5322d1f043a4c9efe5cca256432eefe6fc176..2ffebeb59508ff2d62c02b75ae99d33e8c976243 100644 (file)
@@ -76,8 +76,7 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
 
     page.on('console', e => {
       if (e.type() === 'error') {
-        const err = e.args()[0]
-        console.error(`Error from Puppeteer-loaded page:\n`, err.remoteObject())
+        console.error(`Error from Puppeteer-loaded page:\n`, e.text())
       }
     })
   })
index b434379d01913cecbcf7e4402badaf3a8ea75fcf..afaceec96cecc3bf7600db1b903a0fe3492ba479 100644 (file)
@@ -79,7 +79,7 @@
       "global-runtime",
       "esm-browser",
       "esm-browser-runtime",
-      "vapor"
+      "esm-browser-vapor"
     ]
   },
   "repository": {
index 0f5e574ff066aa7eaabde4b6725f79bff33a6077..05fb94ef2aa8d1fc5cee8ec06fa789fc12f6fc8c 100644 (file)
@@ -13,7 +13,7 @@ catalogs:
       specifier: ^7.25.2
       version: 7.26.0
     '@vitejs/plugin-vue':
-      specifier: ^5.2.1
+      specifier: https://pkg.pr.new/@vitejs/plugin-vue@c156992
       version: 5.2.1
     estree-walker:
       specifier: ^2.0.2
@@ -201,9 +201,6 @@ importers:
       '@types/connect':
         specifier: ^3.4.38
         version: 3.4.38
-      terser:
-        specifier: ^5.33.0
-        version: 5.33.0
 
   packages-private/dts-built-test:
     dependencies:
@@ -226,6 +223,8 @@ importers:
         specifier: workspace:*
         version: link:../../packages/vue
 
+  packages-private/e2e-utils: {}
+
   packages-private/local-playground:
     dependencies:
       '@vueuse/core':
@@ -236,7 +235,7 @@ importers:
         version: link:../../packages/vue
     devDependencies:
       '@vitejs/plugin-vue':
-        specifier: https://pkg.pr.new/@vitejs/plugin-vue@c156992
+        specifier: 'catalog:'
         version: https://pkg.pr.new/@vitejs/plugin-vue@c156992(vite@6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1))(vue@packages+vue)
       '@vue/compiler-sfc':
         specifier: workspace:*
@@ -268,7 +267,7 @@ importers:
     devDependencies:
       '@vitejs/plugin-vue':
         specifier: 'catalog:'
-        version: 5.2.1(vite@6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1))(vue@packages+vue)
+        version: https://pkg.pr.new/@vitejs/plugin-vue@c156992(vite@6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1))(vue@packages+vue)
       vite:
         specifier: 'catalog:'
         version: 6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1)
@@ -285,11 +284,32 @@ importers:
         specifier: ^1.2.1
         version: 1.2.1
 
+  packages-private/vapor-e2e-test:
+    devDependencies:
+      '@types/connect':
+        specifier: ^3.4.38
+        version: 3.4.38
+      '@vitejs/plugin-vue':
+        specifier: 'catalog:'
+        version: https://pkg.pr.new/@vitejs/plugin-vue@c156992(vite@6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1))(vue@packages+vue)
+      connect:
+        specifier: ^3.7.0
+        version: 3.7.0
+      sirv:
+        specifier: ^2.0.4
+        version: 2.0.4
+      vite:
+        specifier: 'catalog:'
+        version: 6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1)
+      vue:
+        specifier: workspace:*
+        version: link:../../packages/vue
+
   packages-private/vite-debug:
     devDependencies:
       '@vitejs/plugin-vue':
         specifier: 'catalog:'
-        version: 5.2.1(vite@6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1))(vue@packages+vue)
+        version: https://pkg.pr.new/@vitejs/plugin-vue@c156992(vite@6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1))(vue@packages+vue)
       vite:
         specifier: 'catalog:'
         version: 6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1)
@@ -1427,13 +1447,6 @@ packages:
     resolution: {integrity: sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@vitejs/plugin-vue@5.2.1':
-    resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==}
-    engines: {node: ^18.0.0 || >=20.0.0}
-    peerDependencies:
-      vite: ^5.0.0 || ^6.0.0
-      vue: ^3.2.25
-
   '@vitejs/plugin-vue@https://pkg.pr.new/@vitejs/plugin-vue@c156992':
     resolution: {tarball: https://pkg.pr.new/@vitejs/plugin-vue@c156992}
     version: 5.2.1
@@ -4196,6 +4209,7 @@ snapshots:
     dependencies:
       '@jridgewell/gen-mapping': 0.3.5
       '@jridgewell/trace-mapping': 0.3.25
+    optional: true
 
   '@jridgewell/sourcemap-codec@1.5.0': {}
 
@@ -4608,11 +4622,6 @@ snapshots:
       '@typescript-eslint/types': 8.20.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-vue@5.2.1(vite@6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1))(vue@packages+vue)':
-    dependencies:
-      vite: 6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1)
-      vue: link:packages/vue
-
   '@vitejs/plugin-vue@https://pkg.pr.new/@vitejs/plugin-vue@c156992(vite@6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1))(vue@3.5.13(typescript@5.6.2))':
     dependencies:
       vite: 6.1.0(@types/node@22.10.7)(sass@1.83.4)(terser@5.33.0)(yaml@2.6.1)
@@ -4925,7 +4934,8 @@ snapshots:
 
   buffer-crc32@0.2.13: {}
 
-  buffer-from@1.1.2: {}
+  buffer-from@1.1.2:
+    optional: true
 
   buffer@5.7.1:
     dependencies:
@@ -5046,7 +5056,8 @@ snapshots:
 
   commander@12.1.0: {}
 
-  commander@2.20.3: {}
+  commander@2.20.3:
+    optional: true
 
   commondir@1.0.1: {}
 
@@ -6747,6 +6758,7 @@ snapshots:
     dependencies:
       buffer-from: 1.1.2
       source-map: 0.6.1
+    optional: true
 
   source-map@0.6.1: {}
 
@@ -6864,6 +6876,7 @@ snapshots:
       acorn: 8.14.0
       commander: 2.20.3
       source-map-support: 0.5.21
+    optional: true
 
   test-exclude@7.0.1:
     dependencies:
index c33fbb43b72ef6494e486e4fff9b5f7f9888470c..9754a377ae77a2e3e3683a8430b8a26b70a34d07 100644 (file)
@@ -9,4 +9,4 @@ catalog:
   'magic-string': ^0.30.11
   'source-map-js': ^1.2.0
   'vite': ^6.1.0
-  '@vitejs/plugin-vue': ^5.2.1
+  '@vitejs/plugin-vue': https://pkg.pr.new/@vitejs/plugin-vue@c156992
index b97d6593c506d0cd1e6e2ba3ec31e9dc94bbd7eb..7f2ecb8c8642b701152ec386a4a10204cd83e12b 100644 (file)
@@ -22,7 +22,7 @@ import { trimVaporExportsPlugin } from './scripts/trim-vapor-exports.js'
  * @template {keyof T} K
  * @typedef { Omit<T, K> & Required<Pick<T, K>> } MarkRequired
  */
-/** @typedef {'cjs' | 'esm-bundler' | 'global' | 'global-runtime' | 'esm-browser' | 'esm-bundler-runtime' | 'esm-browser-runtime' | 'vapor'} PackageFormat */
+/** @typedef {'cjs' | 'esm-bundler' | 'global' | 'global-runtime' | 'esm-browser' | 'esm-bundler-runtime' | 'esm-browser-runtime' | 'esm-browser-vapor'} PackageFormat */
 /** @typedef {MarkRequired<import('rollup').OutputOptions, 'file' | 'format'>} OutputOptions */
 
 if (!process.env.TARGET) {
@@ -88,7 +88,7 @@ const outputConfigs = {
   },
   // The vapor format is a esm-browser + runtime only build that is meant for
   // the SFC playground only.
-  vapor: {
+  'esm-browser-vapor': {
     file: resolve(`dist/${name}.runtime-with-vapor.esm-browser.js`),
     format: 'es',
   },
@@ -114,7 +114,10 @@ if (process.env.NODE_ENV === 'production') {
     if (format === 'cjs') {
       packageConfigs.push(createProductionConfig(format))
     }
-    if (format === 'vapor' || /^(global|esm-browser)(-runtime)?/.test(format)) {
+    if (
+      format === 'esm-browser-vapor' ||
+      /^(global|esm-browser)(-runtime)?/.test(format)
+    ) {
       packageConfigs.push(createMinifiedConfig(format))
     }
   })
@@ -138,7 +141,7 @@ function createConfig(format, output, plugins = []) {
   const isProductionBuild =
     process.env.__DEV__ === 'false' || /\.prod\.js$/.test(output.file)
   const isBundlerESMBuild = /esm-bundler/.test(format)
-  const isBrowserESMBuild = /esm-browser/.test(format) || format === 'vapor'
+  const isBrowserESMBuild = /esm-browser/.test(format)
   const isServerRenderer = name === 'server-renderer'
   const isCJSBuild = format === 'cjs'
   const isGlobalBuild = /global/.test(format)
@@ -164,13 +167,16 @@ function createConfig(format, output, plugins = []) {
     output.name = packageOptions.name
   }
 
-  let entryFile =
-    pkg.name === 'vue' &&
-    (format === 'vapor' || format.startsWith('esm-bundler'))
-      ? 'runtime-with-vapor.ts'
-      : /\bruntime\b/.test(format)
-        ? `runtime.ts`
-        : `index.ts`
+  let entryFile = 'index.ts'
+  if (pkg.name === 'vue') {
+    if (format === 'esm-browser-vapor' || format === 'esm-bundler-runtime') {
+      entryFile = 'runtime-with-vapor.ts'
+    } else if (format === 'esm-bundler') {
+      entryFile = 'index-with-vapor.ts'
+    } else if (format.includes('runtime')) {
+      entryFile = 'runtime.ts'
+    }
+  }
 
   // the compat build needs both default AND named exports. This will cause
   // Rollup to complain for non-ESM targets, so we use separate entries for
index 01d1cba52e4340da79112a2a5fd0aab721a226ca..76e6612d5c40029fc986d2c48381f38def40c78c 100644 (file)
@@ -173,11 +173,17 @@ async function build(target) {
     return
   }
 
+  let resolvedFormats
   if (formats) {
-    let resolvedFormats = formats.split('+')
+    const isNegation = formats.startsWith('~')
+    resolvedFormats = (isNegation ? formats.slice(1) : formats).split('+')
     const pkgFormats = pkg.buildOptions?.formats
     if (pkgFormats) {
-      resolvedFormats = resolvedFormats.filter(f => pkgFormats.includes(f))
+      if (isNegation) {
+        resolvedFormats = pkgFormats.filter(f => !resolvedFormats.includes(f))
+      } else {
+        resolvedFormats = resolvedFormats.filter(f => pkgFormats.includes(f))
+      }
     }
     if (!resolvedFormats.length) {
       return
@@ -202,7 +208,7 @@ async function build(target) {
         `COMMIT:${commit}`,
         `NODE_ENV:${env}`,
         `TARGET:${target}`,
-        formats ? `FORMATS:${formats}` : ``,
+        resolvedFormats ? `FORMATS:${resolvedFormats.join('+')}` : ``,
         prodOnly ? `PROD_ONLY:true` : ``,
         sourceMap ? `SOURCE_MAP:true` : ``,
       ]
@@ -219,7 +225,10 @@ async function build(target) {
  * @returns {Promise<void>}
  */
 async function checkAllSizes(targets) {
-  if (devOnly || (formats && !formats.includes('global'))) {
+  if (
+    devOnly ||
+    (formats && (formats.startsWith('~') || !formats.includes('global')))
+  ) {
     return
   }
   console.log()
index 06bf7de93ed3bee4ee286128d6b72e7e35230665..f8690749221e94aa34e225694d3d900a61f3c30d 100644 (file)
@@ -50,7 +50,7 @@ const outputFormat = format.startsWith('global')
     : 'esm'
 
 const postfix =
-  format === 'vapor'
+  format === 'esm-browser-vapor'
     ? 'runtime-with-vapor.esm-browser'
     : format.endsWith('-runtime')
       ? `runtime.${format.replace(/-runtime$/, '')}`
@@ -131,7 +131,7 @@ for (const target of targets) {
   }
 
   const entry =
-    format === 'vapor'
+    format === 'esm-browser-vapor'
       ? 'runtime-with-vapor.ts'
       : format.endsWith('-runtime')
         ? 'runtime.ts'
index 672192fc6c4a5a624d3ca41ca345c150dfb9642e..313a1a9b8ea6c67f313d222a2875d9a4e6cfde41 100644 (file)
@@ -12,7 +12,7 @@
  */
 export function trimVaporExportsPlugin(format, pkgName) {
   if (
-    format === 'vapor' ||
+    format.includes('vapor') ||
     format.startsWith('esm-bundler') ||
     pkgName === '@vue/runtime-vapor'
   ) {
index 425b509ed92b6e6a072a3a38bc1186818c652e47..8daa34f7ed36ba0ec301d69022b4eee7c54afa9c 100644 (file)
@@ -1,4 +1,4 @@
-import { defineConfig } from 'vitest/config'
+import { configDefaults, defineConfig } from 'vitest/config'
 import { entries } from './scripts/aliases.js'
 
 export default defineConfig({
@@ -31,9 +31,6 @@ export default defineConfig({
       },
     },
     setupFiles: 'scripts/setup-vitest.ts',
-    environmentMatchGlobs: [
-      ['packages/{vue,vue-compat,runtime-dom,runtime-vapor}/**', 'jsdom'],
-    ],
     sequence: {
       hooks: 'list',
     },
@@ -55,5 +52,54 @@ export default defineConfig({
         'packages/runtime-dom/src/components/Transition*',
       ],
     },
+    workspace: [
+      {
+        extends: true,
+        test: {
+          name: 'unit',
+          exclude: [
+            ...configDefaults.exclude,
+            '**/e2e/**',
+            '**/vapor-e2e-test/**',
+            'packages/{vue,vue-compat,runtime-dom,runtime-vapor}/**',
+          ],
+        },
+      },
+      {
+        extends: true,
+        test: {
+          name: 'unit-jsdom',
+          environment: 'jsdom',
+          include: [
+            'packages/{vue,vue-compat,runtime-dom,runtime-vapor}/**/*.spec.ts',
+          ],
+          exclude: [...configDefaults.exclude, '**/e2e/**'],
+        },
+      },
+      {
+        extends: true,
+        test: {
+          name: 'e2e',
+          poolOptions: {
+            threads: {
+              singleThread: !!process.env.CI,
+            },
+          },
+          include: ['packages/vue/__tests__/e2e/*.spec.ts'],
+        },
+      },
+      {
+        extends: true,
+        test: {
+          name: 'e2e-vapor',
+          poolOptions: {
+            threads: {
+              singleThread: !!process.env.CI,
+            },
+          },
+          include: ['packages-private/vapor-e2e-test/__tests__/*.spec.ts'],
+        },
+      },
+    ],
   },
 })
diff --git a/vitest.e2e.config.ts b/vitest.e2e.config.ts
deleted file mode 100644 (file)
index 622bda0..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-import { mergeConfig } from 'vitest/config'
-import config from './vitest.config'
-
-export default mergeConfig(config, {
-  test: {
-    name: 'e2e',
-    poolOptions: {
-      threads: {
-        singleThread: !!process.env.CI,
-      },
-    },
-    include: ['packages/vue/__tests__/e2e/*.spec.ts'],
-  },
-})
diff --git a/vitest.unit.config.ts b/vitest.unit.config.ts
deleted file mode 100644 (file)
index 0082997..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-import { configDefaults, mergeConfig } from 'vitest/config'
-import config from './vitest.config'
-
-export default mergeConfig(config, {
-  test: {
-    name: 'unit',
-    exclude: [...configDefaults.exclude, '**/e2e/**'],
-  },
-})
diff --git a/vitest.workspace.ts b/vitest.workspace.ts
deleted file mode 100644 (file)
index a20586e..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-import { defineWorkspace } from 'vitest/config'
-
-export default defineWorkspace([
-  './vitest.unit.config.ts',
-  './vitest.e2e.config.ts',
-])