diff --git a/__tests__/plugin.spec.ts b/__tests__/plugin.spec.ts index be055c4..4931251 100644 --- a/__tests__/plugin.spec.ts +++ b/__tests__/plugin.spec.ts @@ -34,7 +34,8 @@ async function mockBuild(taskName: string, opts: BuildOptions = {}) { ...viteOptions?.build, write: false }, - plugins: [stylexPlugin(stylex)] + plugins: [stylexPlugin(stylex)], + logLevel: 'silent' }, viteOptions)) let css = '' let js = '' diff --git a/e2e/e2e.spec.ts b/e2e/e2e.spec.ts index cfcd8f2..e730740 100644 --- a/e2e/e2e.spec.ts +++ b/e2e/e2e.spec.ts @@ -1,5 +1,25 @@ +import path from 'path' import test from 'ava' import { createE2EServer } from './helper' +import { createSSRServer } from './fixtures/vue-ssr/server' + +// FIXME +// test('fixture remix', async (t) => { +// const { page } = await createE2EServer('remix') +// await page.waitForSelector('div[role="button"]') +// const elementHandle = await page.$('div[role="button"]') +// const windowHandle = await page.evaluateHandle(() => Promise.resolve(window)) +// const red = await page.evaluate(([window, el]) => { +// return (window as Window).getComputedStyle(el as Element).color +// }, [windowHandle, elementHandle]) +// t.is(red, 'rgb(255, 0, 0)', 'first load spa button text color should be red') +// await elementHandle.click() +// await new Promise((resolve) => setTimeout(resolve, 2000)) +// const blue = await page.evaluate(([window, el]) => { +// return (window as Window).getComputedStyle(el as Element).color +// }, [windowHandle, elementHandle]) +// t.is(blue, 'rgb(0, 0, 255)', 'tap button and text color should be blue') +// }) test('fixture spa', async (t) => { const { page } = await createE2EServer('spa') @@ -35,20 +55,19 @@ test('fixture qwik', async (t) => { t.is(blue, 'rgb(0, 0, 255)', 'tap button and text color should be blue') }) -// FIXME -// test('fixture remix', async (t) => { -// const { page } = await createE2EServer('remix') -// await page.waitForSelector('div[role="button"]') -// const elementHandle = await page.$('div[role="button"]') -// const windowHandle = await page.evaluateHandle(() => Promise.resolve(window)) -// const red = await page.evaluate(([window, el]) => { -// return (window as Window).getComputedStyle(el as Element).color -// }, [windowHandle, elementHandle]) -// t.is(red, 'rgb(255, 0, 0)', 'first load spa button text color should be red') -// await elementHandle.click() -// await new Promise((resolve) => setTimeout(resolve, 2000)) -// const blue = await page.evaluate(([window, el]) => { -// return (window as Window).getComputedStyle(el as Element).color -// }, [windowHandle, elementHandle]) -// t.is(blue, 'rgb(0, 0, 255)', 'tap button and text color should be blue') -// }) +test('fixture vue ssr', async (t) => { + const { page } = await createSSRServer(path.join(__dirname, 'fixtures', 'vue-ssr', 'vite.config.mts')) + await page.waitForSelector('div[role="button"]') + const elementHandle = await page.$('div[role="button"]') + const windowHandle = await page.evaluateHandle(() => Promise.resolve(window)) + const red = await page.evaluate(([window, el]) => { + return (window as Window).getComputedStyle(el as Element).color + }, [windowHandle, elementHandle]) + t.is(red, 'rgb(255, 0, 0)', 'first load spa button text color should be red') + await elementHandle.click() + await new Promise((resolve) => setTimeout(resolve, 2000)) + const blue = await page.evaluate(([window, el]) => { + return (window as Window).getComputedStyle(el as Element).color + }, [windowHandle, elementHandle]) + t.is(blue, 'rgb(0, 0, 255)', 'tap button and text color should be blue') +}) diff --git a/e2e/fixtures/vue-ssr/index.html b/e2e/fixtures/vue-ssr/index.html new file mode 100644 index 0000000..516b913 --- /dev/null +++ b/e2e/fixtures/vue-ssr/index.html @@ -0,0 +1,14 @@ + + + + + + + Vite + Vue + + + +
+ + + \ No newline at end of file diff --git a/e2e/fixtures/vue-ssr/package.json b/e2e/fixtures/vue-ssr/package.json new file mode 100644 index 0000000..9a07e47 --- /dev/null +++ b/e2e/fixtures/vue-ssr/package.json @@ -0,0 +1,17 @@ +{ + "name": "vite-plugin-stylex-e2e-vue-ssr", + "scripts": { + "dev": "tsx ./server.ts" + }, + "dependencies": { + "@vitejs/plugin-vue": "4.5.1", + "express": "^4.18.2", + "vite": "^5.0.6", + "vite-plugin-stylex": "workspace:*", + "vue": "^3.3.10" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "tsx": "^4.6.2" + } +} diff --git a/e2e/fixtures/vue-ssr/server.ts b/e2e/fixtures/vue-ssr/server.ts new file mode 100644 index 0000000..dbe2051 --- /dev/null +++ b/e2e/fixtures/vue-ssr/server.ts @@ -0,0 +1,36 @@ +import fs from 'fs/promises' +import path from 'path' +import express from 'express' +import { createChromeBrowser, genRandomPort } from '../../helper' + +export async function createSSRServer(configFile = path.join(__dirname, 'vite.config.mts')) { + const app = express() + const { createServer } = await import('vite') + + const viteServer = await createServer({ configFile, server: { middlewareMode: true }, appType: 'custom', base: '/', root: __dirname }) + app.use(viteServer.middlewares) + app.use('*', async (req, res) => { + try { + const url = req.originalUrl.replace('/', '') + let template = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8') + template = await viteServer.transformIndexHtml(url, template) + const { render } = await viteServer.ssrLoadModule('/src/entry-server.ts') + const rendered = await render(url) + const html = template + .replace('', rendered.head ?? '') + .replace('', rendered.html ?? '') + res.status(200).set({ 'Content-Type': 'text/html' }).end(html) + } catch (error) { + viteServer.ssrFixStacktrace(error) + res.status(500).end(error.stack) + } + }) + const port = genRandomPort() + app.listen(port) + const { page } = await createChromeBrowser(port) + return { app, page } +} + +if (require.main === module) { + createSSRServer() +} diff --git a/e2e/fixtures/vue-ssr/src/app.vue b/e2e/fixtures/vue-ssr/src/app.vue new file mode 100644 index 0000000..089228a --- /dev/null +++ b/e2e/fixtures/vue-ssr/src/app.vue @@ -0,0 +1,33 @@ + + + diff --git a/e2e/fixtures/vue-ssr/src/entry-client.ts b/e2e/fixtures/vue-ssr/src/entry-client.ts new file mode 100644 index 0000000..cb1e4a8 --- /dev/null +++ b/e2e/fixtures/vue-ssr/src/entry-client.ts @@ -0,0 +1,5 @@ +import { createApp } from './main' + +const { app } = createApp() + +app.mount('#app') diff --git a/e2e/fixtures/vue-ssr/src/entry-server.ts b/e2e/fixtures/vue-ssr/src/entry-server.ts new file mode 100644 index 0000000..3ef128a --- /dev/null +++ b/e2e/fixtures/vue-ssr/src/entry-server.ts @@ -0,0 +1,15 @@ +import { renderToString } from 'vue/server-renderer' +import { createApp } from './main' + +export async function render() { + const { app } = createApp() + + // passing SSR context object which will be available via useSSRContext() + // @vitejs/plugin-vue injects code into a component's setup() that registers + // itself on ctx.modules. After the render, ctx.modules would contain all the + // components that have been instantiated during this render call. + const ctx = {} + const html = await renderToString(app, ctx) + + return { html } +} diff --git a/e2e/fixtures/vue-ssr/src/main.ts b/e2e/fixtures/vue-ssr/src/main.ts new file mode 100644 index 0000000..41a2643 --- /dev/null +++ b/e2e/fixtures/vue-ssr/src/main.ts @@ -0,0 +1,10 @@ +import { createSSRApp } from 'vue' +import App from './app.vue' + +// SSR requires a fresh app instance per request, therefore we export a function +// that creates a fresh app instance. If using Vuex, we'd also be creating a +// fresh store here. +export function createApp() { + const app = createSSRApp(App) + return { app } +} diff --git a/e2e/fixtures/vue-ssr/vite.config.mts b/e2e/fixtures/vue-ssr/vite.config.mts new file mode 100644 index 0000000..4da8378 --- /dev/null +++ b/e2e/fixtures/vue-ssr/vite.config.mts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { stylexPlugin } from 'vite-plugin-stylex' + +export default defineConfig({ + plugins: [vue(), stylexPlugin()] +}) diff --git a/e2e/helper.ts b/e2e/helper.ts index a7bc6f9..7b6b678 100644 --- a/e2e/helper.ts +++ b/e2e/helper.ts @@ -1,17 +1,16 @@ import path from 'path' import { chromium } from 'playwright' -import type { ViteDevServer } from 'vite' -export async function createChromeBrowser(server: ViteDevServer) { +export async function createChromeBrowser(port: number) { const browser = await chromium.launch() const page = await browser.newPage() - const localURL = `http://localhost:${server.config.server.port}` + const localURL = `http://localhost:${port}` page.goto(localURL) return { page } } -// I don't know thy vite don't accept port 0 -function genRandomPort() { +// I don't know why vite don't accept port 0 +export function genRandomPort() { const minPort = 5173 const maxPort = 49151 return Math.floor(Math.random() * (maxPort - minPort + 1)) + minPort @@ -28,6 +27,6 @@ export async function createE2EServer(taskName: string) { root: path.join(__dirname, 'fixtures', taskName) }) await server.listen() - const { page } = await createChromeBrowser(server) + const { page } = await createChromeBrowser(server.config.server.port) return { page, server } } diff --git a/yarn.lock b/yarn.lock index 7655d4d..b19afb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1812,7 +1812,17 @@ __metadata: languageName: node linkType: hard -"@types/connect@npm:^3.4.38": +"@types/body-parser@npm:*": + version: 1.19.5 + resolution: "@types/body-parser@npm:1.19.5" + dependencies: + "@types/connect": "*" + "@types/node": "*" + checksum: 1e251118c4b2f61029cc43b0dc028495f2d1957fe8ee49a707fb940f86a9bd2f9754230805598278fe99958b49e9b7e66eec8ef6a50ab5c1f6b93e1ba2aaba82 + languageName: node + linkType: hard + +"@types/connect@npm:*, @types/connect@npm:^3.4.38": version: 3.4.38 resolution: "@types/connect@npm:3.4.38" dependencies: @@ -1862,6 +1872,30 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.17.41 + resolution: "@types/express-serve-static-core@npm:4.17.41" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + "@types/send": "*" + checksum: 12750f6511dd870bbaccfb8208ad1e79361cf197b147f62a3bedc19ec642f3a0f9926ace96705f4bc88ec2ae56f61f7ca8c2438e6b22f5540842b5569c28a121 + languageName: node + linkType: hard + +"@types/express@npm:^4.17.21": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.33 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: fb238298630370a7392c7abdc80f495ae6c716723e114705d7e3fb67e3850b3859bbfd29391463a3fb8c0b32051847935933d99e719c0478710f8098ee7091c5 + languageName: node + linkType: hard + "@types/hast@npm:^2.0.0": version: 2.3.8 resolution: "@types/hast@npm:2.3.8" @@ -1871,6 +1905,13 @@ __metadata: languageName: node linkType: hard +"@types/http-errors@npm:*": + version: 2.0.4 + resolution: "@types/http-errors@npm:2.0.4" + checksum: 1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" @@ -1901,6 +1942,20 @@ __metadata: languageName: node linkType: hard +"@types/mime@npm:*": + version: 3.0.4 + resolution: "@types/mime@npm:3.0.4" + checksum: a6139c8e1f705ef2b064d072f6edc01f3c099023ad7c4fce2afc6c2bf0231888202adadbdb48643e8e20da0ce409481a49922e737eca52871b3dc08017455843 + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: e29a5f9c4776f5229d84e525b7cd7dd960b51c30a0fb9a028c0821790b82fca9f672dab56561e2acd9e8eed51d431bde52eafdfef30f643586c4162f1aecfc78 + languageName: node + linkType: hard + "@types/ms@npm:*": version: 0.7.34 resolution: "@types/ms@npm:0.7.34" @@ -1924,6 +1979,20 @@ __metadata: languageName: node linkType: hard +"@types/qs@npm:*": + version: 6.9.10 + resolution: "@types/qs@npm:6.9.10" + checksum: 3e479ee056bd2b60894baa119d12ecd33f20a25231b836af04654e784c886f28a356477630430152a86fba253da65d7ecd18acffbc2a8877a336e75aa0272c67 + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 95640233b689dfbd85b8c6ee268812a732cf36d5affead89e806fe30da9a430767af8ef2cd661024fd97e19d61f3dec75af2df5e80ec3bea000019ab7028629a + languageName: node + linkType: hard + "@types/react-dom@npm:^18.2.7": version: 18.2.17 resolution: "@types/react-dom@npm:18.2.17" @@ -1958,6 +2027,27 @@ __metadata: languageName: node linkType: hard +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": ^1 + "@types/node": "*" + checksum: cf4db48251bbb03cd6452b4de6e8e09e2d75390a92fd798eca4a803df06444adc94ed050246c94c7ed46fb97be1f63607f0e1f13c3ce83d71788b3e08640e5e0 + languageName: node + linkType: hard + +"@types/serve-static@npm:*": + version: 1.15.5 + resolution: "@types/serve-static@npm:1.15.5" + dependencies: + "@types/http-errors": "*" + "@types/mime": "*" + "@types/node": "*" + checksum: 0ff4b3703cf20ba89c9f9e345bc38417860a88e85863c8d6fe274a543220ab7f5f647d307c60a71bb57dc9559f0890a661e8dc771a6ec5ef195d91c8afc4a893 + languageName: node + linkType: hard + "@types/unist@npm:^2, @types/unist@npm:^2.0.0": version: 2.0.10 resolution: "@types/unist@npm:2.0.10" @@ -4698,7 +4788,7 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.17.1": +"express@npm:^4.17.1, express@npm:^4.18.2": version: 4.18.2 resolution: "express@npm:4.18.2" dependencies: @@ -10143,6 +10233,20 @@ __metadata: languageName: unknown linkType: soft +"vite-plugin-stylex-e2e-vue-ssr@workspace:e2e/fixtures/vue-ssr": + version: 0.0.0-use.local + resolution: "vite-plugin-stylex-e2e-vue-ssr@workspace:e2e/fixtures/vue-ssr" + dependencies: + "@types/express": ^4.17.21 + "@vitejs/plugin-vue": 4.5.1 + express: ^4.18.2 + tsx: ^4.6.2 + vite: ^5.0.6 + vite-plugin-stylex: "workspace:*" + vue: ^3.3.10 + languageName: unknown + linkType: soft + "vite-plugin-stylex@workspace:*, vite-plugin-stylex@workspace:.": version: 0.0.0-use.local resolution: "vite-plugin-stylex@workspace:."