Skip to content

Commit f0ff0a6

Browse files
authored
test: add testsuite for vite-ssr-esm (#196)
* test: refactor vite-ssr testsuite to use subprocess, add vite-ssr-esm suite * test: load esm-only dependency in vite-ssr testsuites * test: more strict esm only package, additional imports and assertions * fix: lint issues and dynamic import path on windows * fix: use node to write package.json in dist/server as package.json echo script is not cross platform
1 parent 250b804 commit f0ff0a6

File tree

24 files changed

+406
-106
lines changed

24 files changed

+406
-106
lines changed
File renamed without changes.

packages/e2e-tests/_test_dependencies/esm-only/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
"version": "1.0.0",
33
"private": true,
44
"name": "e2e-test-dep-esm-only",
5-
"main": "index.js",
6-
"module": "index.js",
5+
"module": "esm.js",
76
"files": [
87
"package.json",
9-
"index.js"
8+
"esm.js"
109
],
1110
"exports":{
1211
".": {
13-
"import": "./index.js"
12+
"import": "./esm.js"
1413
},
1514
"./package.json": "./package.json"
1615
},

packages/e2e-tests/e2e-server.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ exports.serve = async function serve(root, isBuild, port) {
126126
if (e.stderr) {
127127
err.push(e.stderr);
128128
}
129-
if (!isWin) {
129+
if (!!process.env.DEBUG && !isWin) {
130130
// treekill on windows uses taskkill and that ends up here always
131-
console.error(`server process did not exit gracefully. dir: ${root}`, e);
131+
console.debug(`e2e server process did not exit gracefully. dir: ${root}`, e);
132132
}
133133
}
134134
}

packages/e2e-tests/testEnv.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Page } from 'playwright-core';
2+
import { E2EServer } from '../../scripts/jestPerTestSetup';
23

34
declare global {
45
// injected by the custom jest env in scripts/jestEnv.js
@@ -7,4 +8,5 @@ declare global {
78
// injected in scripts/jestPerTestSetup.ts
89
const browserLogs: string[];
910
const viteTestUrl: string;
11+
const e2eServer: E2EServer;
1012
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {
2+
editFileAndWaitForHmrComplete,
3+
getColor,
4+
getEl,
5+
getText,
6+
isBuild,
7+
untilUpdated
8+
} from '../../testUtils';
9+
10+
import fetch from 'node-fetch';
11+
12+
test('/', async () => {
13+
expect(await page.textContent('h1')).toMatch('Hello svelte world'); // after hydration
14+
15+
const html = await (await fetch(page.url())).text();
16+
expect(html).toMatch('Hello world'); // before hydration
17+
if (isBuild) {
18+
// TODO expect preload links
19+
}
20+
});
21+
22+
test('css', async () => {
23+
if (isBuild) {
24+
expect(await getColor('h1')).toBe('green');
25+
} else {
26+
// During dev, the CSS is loaded from async chunk and we may have to wait
27+
// when the test runs concurrently.
28+
await untilUpdated(() => getColor('h1'), 'green');
29+
}
30+
});
31+
32+
test('loaded esm only package', async () => {
33+
expect(await page.textContent('#esm')).toMatch('esm');
34+
expect(browserLogs).toContain('esm');
35+
expect(e2eServer.logs.server.out).toContain('esm\n');
36+
});
37+
38+
test('asset', async () => {
39+
// should have no 404s
40+
browserLogs.forEach((msg) => {
41+
expect(msg).not.toMatch('404');
42+
});
43+
const img = await page.$('img');
44+
expect(await img.getAttribute('src')).toMatch(
45+
isBuild ? /\/assets\/logo\.\w{8}\.png/ : '/src/assets/logo.png'
46+
);
47+
});
48+
49+
if (!isBuild) {
50+
describe('hmr', () => {
51+
const updateApp = editFileAndWaitForHmrComplete.bind(null, 'src/App.svelte');
52+
test('should render additional html', async () => {
53+
expect(await getEl('#hmr-test')).toBe(null);
54+
await updateApp((content) =>
55+
content.replace(
56+
'<!-- HMR-TEMPLATE-INJECT -->',
57+
'<div id="hmr-test">foo</div>\n<!-- HMR-TEMPLATE-INJECT -->'
58+
)
59+
);
60+
await expect(await getText(`#hmr-test`)).toBe('foo');
61+
});
62+
test('should apply style update', async () => {
63+
expect(await getColor(`h1`)).toBe('green');
64+
await updateApp((content) => content.replace('color: green', 'color: red'));
65+
expect(await getColor(`h1`)).toBe('red');
66+
});
67+
test('should not preserve state of updated props', async () => {
68+
await expect(await getText(`#foo`)).toBe('foo');
69+
await updateApp((content) => content.replace("foo = 'foo'", "foo = 'bar'"));
70+
await expect(await getText(`#foo`)).toBe('bar');
71+
});
72+
});
73+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Svelte App</title>
7+
<!-- head outlet-->
8+
</head>
9+
<body>
10+
<div id="svelte"><!--app-outlet--></div>
11+
<script type="module" src="/src/entry-client.js"></script>
12+
</body>
13+
</html>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "e2e-tests-vite-ssr-esm",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "node server",
8+
"build": "run-s build:client build:server",
9+
"build:client": "vite build --ssrManifest --outDir dist/client",
10+
"build:server": "vite build --ssr src/entry-server.js --outDir dist/server",
11+
"preview": "cross-env NODE_ENV=production node server",
12+
"debug": "node --inspect-brk server"
13+
},
14+
"devDependencies": {
15+
"@sveltejs/vite-plugin-svelte": "workspace:*",
16+
"compression": "^1.7.4",
17+
"cross-env": "^7.0.3",
18+
"e2e-test-dep-esm-only": "workspace:*",
19+
"express": "^4.17.1",
20+
"npm-run-all": "^4.1.5",
21+
"serve-static": "^1.14.1",
22+
"svelte": "^3.43.2",
23+
"vite": "^2.6.7"
24+
}
25+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// @ts-check
2+
import fs from 'fs';
3+
import path from 'path';
4+
import { pathToFileURL } from 'url';
5+
import express from 'express';
6+
import compression from 'compression';
7+
import serveStatic from 'serve-static';
8+
9+
let port = 3000;
10+
const args = process.argv.slice(2);
11+
const portArgPos = args.indexOf('--port') + 1;
12+
if (portArgPos > 0) {
13+
port = parseInt(args[portArgPos], 10);
14+
}
15+
16+
async function createServer(root = process.cwd(), isProd = process.env.NODE_ENV === 'production') {
17+
const resolve = (p) => path.resolve(root, p);
18+
19+
const indexProd = isProd ? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8') : '';
20+
21+
const manifest = isProd
22+
? // @ts-ignore
23+
// eslint-disable-next-line node/no-missing-require
24+
JSON.parse(fs.readFileSync(resolve('dist/client/ssr-manifest.json'), 'utf-8'))
25+
: {};
26+
27+
const app = express();
28+
29+
/**
30+
* @type {import('vite').ViteDevServer}
31+
*/
32+
let vite;
33+
if (!isProd) {
34+
const inlineCfg = {
35+
root,
36+
server: {
37+
middlewareMode: true,
38+
port
39+
}
40+
};
41+
// @ts-ignore
42+
vite = await (await import('vite')).createServer(inlineCfg);
43+
// use vite's connect instance as middleware
44+
app.use(vite.middlewares);
45+
} else {
46+
app.use(compression());
47+
app.use(
48+
serveStatic(resolve('dist/client'), {
49+
index: false
50+
})
51+
);
52+
53+
// workaround, dist/server/entry-server.js will be cjs, add a package.json as hint
54+
const serverPkg = resolve('dist/server/package.json');
55+
if (!fs.existsSync(serverPkg)) {
56+
fs.writeFileSync(serverPkg, JSON.stringify({ type: 'commonjs' }), 'utf-8');
57+
}
58+
}
59+
60+
app.use('*', async (req, res) => {
61+
try {
62+
const url = req.originalUrl;
63+
64+
let template, render;
65+
if (!isProd) {
66+
// always read fresh template in dev
67+
template = fs.readFileSync(resolve('index.html'), 'utf-8');
68+
template = await vite.transformIndexHtml(url, template);
69+
render = (await vite.ssrLoadModule('/src/entry-server.js')).render;
70+
} else {
71+
template = indexProd;
72+
// @ts-ignore
73+
render = (await import(pathToFileURL(resolve('dist/server/entry-server.js')).href)).render;
74+
}
75+
const rendered = await render(req.originalUrl, manifest);
76+
const appHtml = rendered.html;
77+
const headElements = rendered.head || '';
78+
// TODO what do we do with rendered.css here. find out if emitCss was used and vite took care of it
79+
const html = template
80+
.replace(`<!--head-outlet-->`, headElements)
81+
.replace(`<!--app-outlet-->`, appHtml);
82+
83+
res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
84+
} catch (e) {
85+
vite && vite.ssrFixStacktrace(e);
86+
console.log(e.stack);
87+
res.status(500).end(e.stack);
88+
}
89+
});
90+
91+
return { app, vite };
92+
}
93+
94+
createServer().then(({ app }) => {
95+
const server = app.listen(port, () => {
96+
console.log('http://localhost:' + port);
97+
});
98+
const exitProcess = async () => {
99+
process.off('SIGTERM', exitProcess);
100+
process.off('SIGINT', exitProcess);
101+
process.stdin.off('end', exitProcess);
102+
try {
103+
await server.close(() => {
104+
console.log('ssr server closed');
105+
});
106+
} finally {
107+
process.exit(0);
108+
}
109+
};
110+
process.once('SIGTERM', exitProcess);
111+
process.once('SIGINT', exitProcess);
112+
process.stdin.on('end', exitProcess);
113+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script>
2+
import Foo from './components/Foo.svelte';
3+
import logo from './assets/logo.png';
4+
// eslint-disable-next-line node/no-missing-import
5+
import { esm } from 'e2e-test-dep-esm-only';
6+
export let world = 'world'; // edit world and save to see hmr update
7+
export let foo = 'foo';
8+
</script>
9+
10+
<h1>Hello {world}</h1>
11+
<p>Open App.svelte in your editor and change something to see HMR in action</p>
12+
<Foo />
13+
<img src={logo} alt="logo" width="24" />
14+
<div id="foo">{foo}</div>
15+
<div id="esm">{esm()}</div>
16+
<!-- HMR-TEMPLATE-INJECT -->
17+
18+
<svelte:head>
19+
<style>
20+
body {
21+
background: lightblue;
22+
}
23+
</style>
24+
</svelte:head>
25+
26+
<style>
27+
h1 {
28+
color: green; /* change color an save to see hmr update */
29+
}
30+
</style>
3.05 KB
Loading

0 commit comments

Comments
 (0)