@tsed/vike 8.0.1 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -8
- package/src/ViteModule.spec.ts +104 -0
- package/src/ViteModule.ts +42 -0
- package/src/decorators/vite.spec.ts +31 -0
- package/src/decorators/vite.ts +5 -0
- package/src/index.ts +11 -0
- package/src/interfaces/ViteConfig.ts +9 -0
- package/src/interfaces/ViteRenderContext.ts +13 -0
- package/src/interfaces/interfaces.ts +9 -0
- package/src/middlewares/ViteRendererMiddleware.spec.ts +65 -0
- package/src/middlewares/ViteRendererMiddleware.ts +17 -0
- package/src/services/ViteServer.spec.ts +138 -0
- package/src/services/ViteServer.ts +50 -0
- package/src/services/ViteService.spec.ts +254 -0
- package/src/services/ViteService.ts +74 -0
package/package.json
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
"name": "@tsed/vike",
|
|
3
3
|
"description": "Vike package for Ts.ED framework",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "8.0.
|
|
5
|
+
"version": "8.0.2",
|
|
6
6
|
"source": "./src/index.ts",
|
|
7
7
|
"main": "./lib/esm/index.js",
|
|
8
8
|
"module": "./lib/esm/index.js",
|
|
9
9
|
"typings": "./lib/types/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
+
"@tsed/source": "./src/index.ts",
|
|
12
13
|
"types": "./lib/types/index.d.ts",
|
|
13
14
|
"import": "./lib/esm/index.js",
|
|
14
15
|
"default": "./lib/esm/index.js"
|
|
@@ -25,12 +26,12 @@
|
|
|
25
26
|
"sirv": "^2.0.4"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
|
-
"@tsed/barrels": "8.0.
|
|
29
|
-
"@tsed/core": "8.0.
|
|
30
|
-
"@tsed/di": "8.0.
|
|
31
|
-
"@tsed/exceptions": "8.0.
|
|
32
|
-
"@tsed/platform-http": "8.0.
|
|
33
|
-
"@tsed/typescript": "8.0.
|
|
29
|
+
"@tsed/barrels": "8.0.2",
|
|
30
|
+
"@tsed/core": "8.0.2",
|
|
31
|
+
"@tsed/di": "8.0.2",
|
|
32
|
+
"@tsed/exceptions": "8.0.2",
|
|
33
|
+
"@tsed/platform-http": "8.0.2",
|
|
34
|
+
"@tsed/typescript": "8.0.2",
|
|
34
35
|
"eslint": "9.12.0",
|
|
35
36
|
"typescript": "5.4.5",
|
|
36
37
|
"vike": "0.4.198",
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"vitest": "2.1.2"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
|
-
"@tsed/platform-http": "8.0.
|
|
42
|
+
"@tsed/platform-http": "8.0.2",
|
|
42
43
|
"vike": ">=0.4.160",
|
|
43
44
|
"vite": ">=4"
|
|
44
45
|
},
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {PlatformApplication} from "@tsed/platform-http";
|
|
2
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
3
|
+
import {PlatformViews} from "@tsed/platform-views";
|
|
4
|
+
|
|
5
|
+
import {ViteRendererMiddleware} from "./middlewares/ViteRendererMiddleware.js";
|
|
6
|
+
import {VITE_SERVER} from "./services/ViteServer.js";
|
|
7
|
+
import {ViteService} from "./services/ViteService.js";
|
|
8
|
+
import {ViteModule} from "./ViteModule.js";
|
|
9
|
+
|
|
10
|
+
async function getModuleFixture() {
|
|
11
|
+
const platformViews = {
|
|
12
|
+
registerEngine: vi.fn()
|
|
13
|
+
};
|
|
14
|
+
const viteService = {
|
|
15
|
+
render: vi.fn()
|
|
16
|
+
};
|
|
17
|
+
const platformApplication = {
|
|
18
|
+
use: vi.fn(),
|
|
19
|
+
get: vi.fn()
|
|
20
|
+
};
|
|
21
|
+
const viteServer = {
|
|
22
|
+
middlewares: "middlewares"
|
|
23
|
+
};
|
|
24
|
+
const service = await PlatformTest.invoke<ViteModule>(ViteModule, [
|
|
25
|
+
{
|
|
26
|
+
token: ViteService,
|
|
27
|
+
use: viteService
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
token: PlatformApplication,
|
|
31
|
+
use: platformApplication
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
token: PlatformViews,
|
|
35
|
+
use: platformViews
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
token: VITE_SERVER,
|
|
39
|
+
use: viteServer
|
|
40
|
+
}
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
return {service, viteService, platformApplication, platformViews, viteServer};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("ViteModule", () => {
|
|
47
|
+
beforeEach(() =>
|
|
48
|
+
PlatformTest.create({
|
|
49
|
+
imports: [
|
|
50
|
+
{
|
|
51
|
+
token: VITE_SERVER,
|
|
52
|
+
use: {
|
|
53
|
+
middlewares: vi.fn()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
afterEach(() => PlatformTest.reset());
|
|
60
|
+
|
|
61
|
+
describe("$onInit()", () => {
|
|
62
|
+
it("should render html", async () => {
|
|
63
|
+
const {viteService, platformViews} = await getModuleFixture();
|
|
64
|
+
viteService.render.mockResolvedValue("html");
|
|
65
|
+
expect(platformViews.registerEngine).toHaveBeenCalledWith("vite", {options: {}, render: expect.any(Function)});
|
|
66
|
+
|
|
67
|
+
const result = await platformViews.registerEngine.mock.calls[0][1].render("path.vite", {$ctx: {}});
|
|
68
|
+
|
|
69
|
+
expect(result).toEqual("html");
|
|
70
|
+
expect(viteService.render).toHaveBeenCalledWith("path.vite", {$ctx: {}});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should render empty content", async () => {
|
|
74
|
+
const {viteService, platformViews} = await getModuleFixture();
|
|
75
|
+
|
|
76
|
+
viteService.render.mockResolvedValue(undefined);
|
|
77
|
+
|
|
78
|
+
expect(platformViews.registerEngine).toHaveBeenCalledWith("vite", {options: {}, render: expect.any(Function)});
|
|
79
|
+
|
|
80
|
+
const result = await platformViews.registerEngine.mock.calls[0][1].render("path.vite", {$ctx: {}});
|
|
81
|
+
|
|
82
|
+
expect(result).toEqual("");
|
|
83
|
+
expect(viteService.render).toHaveBeenCalledWith("path.vite", {$ctx: {}});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe("$afterInit()", () => {
|
|
87
|
+
it("should add the middlewares", async () => {
|
|
88
|
+
const {service, platformApplication} = await getModuleFixture();
|
|
89
|
+
|
|
90
|
+
service.$afterInit();
|
|
91
|
+
|
|
92
|
+
expect(platformApplication.use).toHaveBeenCalledWith(expect.any(Function), "middlewares");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe("$afterRoutesInit()", () => {
|
|
96
|
+
it("should handle all GET routes", async () => {
|
|
97
|
+
const {service, platformApplication} = await getModuleFixture();
|
|
98
|
+
|
|
99
|
+
service.$afterRoutesInit();
|
|
100
|
+
|
|
101
|
+
expect(platformApplication.get).toHaveBeenCalledWith("*", ViteRendererMiddleware);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {Inject, Module} from "@tsed/di";
|
|
2
|
+
import {PlatformApplication} from "@tsed/platform-http";
|
|
3
|
+
import {PlatformRenderOptions, PlatformViews} from "@tsed/platform-views";
|
|
4
|
+
import compress from "compression";
|
|
5
|
+
|
|
6
|
+
import {ViteRendererMiddleware} from "./middlewares/ViteRendererMiddleware.js";
|
|
7
|
+
import {VITE_SERVER} from "./services/ViteServer.js";
|
|
8
|
+
import {ViteService} from "./services/ViteService.js";
|
|
9
|
+
|
|
10
|
+
@Module({
|
|
11
|
+
imports: []
|
|
12
|
+
})
|
|
13
|
+
export class ViteModule {
|
|
14
|
+
@Inject()
|
|
15
|
+
protected app: PlatformApplication;
|
|
16
|
+
|
|
17
|
+
@Inject()
|
|
18
|
+
protected platformView: PlatformViews;
|
|
19
|
+
|
|
20
|
+
@Inject()
|
|
21
|
+
protected viteService: ViteService;
|
|
22
|
+
|
|
23
|
+
@Inject(VITE_SERVER)
|
|
24
|
+
protected viteServer: VITE_SERVER;
|
|
25
|
+
|
|
26
|
+
$onInit() {
|
|
27
|
+
this.platformView.registerEngine("vite", {
|
|
28
|
+
options: {},
|
|
29
|
+
render: async (path: string, options: PlatformRenderOptions) => {
|
|
30
|
+
return (await this.viteService.render(path, options)) || "";
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
$afterInit() {
|
|
36
|
+
this.app.use(compress(), this.viteServer.middlewares);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
$afterRoutesInit() {
|
|
40
|
+
return this.app.get("*", ViteRendererMiddleware);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {Store} from "@tsed/core";
|
|
2
|
+
import {Controller} from "@tsed/di";
|
|
3
|
+
|
|
4
|
+
import {Vite} from "./vite.js";
|
|
5
|
+
|
|
6
|
+
@Controller("/")
|
|
7
|
+
class ControllerTest {
|
|
8
|
+
@Vite()
|
|
9
|
+
post() {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Vite("nice-view")
|
|
14
|
+
postWithView() {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("ViteDecorator", () => {
|
|
20
|
+
it("should return attached View with *.vite with empty path", () => {
|
|
21
|
+
const metadata = Store.fromMethod(ControllerTest, "post").get("view");
|
|
22
|
+
|
|
23
|
+
expect(metadata.path).toEqual("*.vite");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should return attached View with {path}.vite", () => {
|
|
27
|
+
const metadata = Store.fromMethod(ControllerTest, "postWithView").get("view");
|
|
28
|
+
|
|
29
|
+
expect(metadata.path).toEqual("nice-view.vite");
|
|
30
|
+
});
|
|
31
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Automatically generated by @tsed/barrels.
|
|
3
|
+
*/
|
|
4
|
+
export * from "./decorators/vite.js";
|
|
5
|
+
export * from "./interfaces/interfaces.js";
|
|
6
|
+
export * from "./interfaces/ViteConfig.js";
|
|
7
|
+
export * from "./interfaces/ViteRenderContext.js";
|
|
8
|
+
export * from "./middlewares/ViteRendererMiddleware.js";
|
|
9
|
+
export * from "./services/ViteServer.js";
|
|
10
|
+
export * from "./services/ViteService.js";
|
|
11
|
+
export * from "./ViteModule.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {IncomingHttpHeaders} from "http";
|
|
2
|
+
|
|
3
|
+
export type VitePageProps<Data = Record<string, unknown>> = Data & {view: string};
|
|
4
|
+
|
|
5
|
+
export interface ViteRenderContext<Data = Record<string, unknown>, Session = Record<string, unknown>> extends Record<string, unknown> {
|
|
6
|
+
host: string;
|
|
7
|
+
protocol: string;
|
|
8
|
+
method: string;
|
|
9
|
+
url: string;
|
|
10
|
+
secure: boolean;
|
|
11
|
+
headers: IncomingHttpHeaders;
|
|
12
|
+
session?: Session;
|
|
13
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
2
|
+
|
|
3
|
+
import {ViteService} from "../services/ViteService.js";
|
|
4
|
+
import {ViteRendererMiddleware} from "./ViteRendererMiddleware.js";
|
|
5
|
+
|
|
6
|
+
const rootDir = import.meta.dirname; // automatically replaced by import.meta.dirname on build
|
|
7
|
+
|
|
8
|
+
describe("ViteRenderMiddleware", () => {
|
|
9
|
+
describe("use()", () => {
|
|
10
|
+
beforeEach(() =>
|
|
11
|
+
PlatformTest.create({
|
|
12
|
+
vite: {
|
|
13
|
+
root: rootDir
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
afterEach(() => PlatformTest.reset());
|
|
18
|
+
it("should return the response", async () => {
|
|
19
|
+
const viteService = {
|
|
20
|
+
render: vi.fn()
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
24
|
+
vi.spyOn($ctx.response, "body");
|
|
25
|
+
|
|
26
|
+
viteService.render.mockResolvedValue("result");
|
|
27
|
+
vi.spyOn($ctx.response, "isDone").mockReturnValue(false);
|
|
28
|
+
|
|
29
|
+
const middleware = await PlatformTest.invoke<ViteRendererMiddleware>(ViteRendererMiddleware, [
|
|
30
|
+
{
|
|
31
|
+
token: ViteService,
|
|
32
|
+
use: viteService
|
|
33
|
+
}
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
await middleware.use($ctx);
|
|
37
|
+
|
|
38
|
+
expect(viteService.render).toHaveBeenCalledWith("*", {$ctx});
|
|
39
|
+
expect($ctx.response.body).toHaveBeenCalledWith("result");
|
|
40
|
+
});
|
|
41
|
+
it("should not return the response", async () => {
|
|
42
|
+
const viteService = {
|
|
43
|
+
render: vi.fn()
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
47
|
+
vi.spyOn($ctx.response, "body");
|
|
48
|
+
vi.spyOn($ctx.response, "isDone").mockReturnValue(true);
|
|
49
|
+
|
|
50
|
+
viteService.render.mockResolvedValue("result");
|
|
51
|
+
|
|
52
|
+
const middleware = await PlatformTest.invoke<ViteRendererMiddleware>(ViteRendererMiddleware, [
|
|
53
|
+
{
|
|
54
|
+
token: ViteService,
|
|
55
|
+
use: viteService
|
|
56
|
+
}
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
await middleware.use($ctx);
|
|
60
|
+
|
|
61
|
+
expect(viteService.render).toHaveBeenCalledWith("*", {$ctx});
|
|
62
|
+
expect($ctx.response.body).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {Inject} from "@tsed/di";
|
|
2
|
+
import {Middleware} from "@tsed/platform-middlewares";
|
|
3
|
+
import {Context} from "@tsed/platform-params";
|
|
4
|
+
|
|
5
|
+
import {ViteService} from "../services/ViteService.js";
|
|
6
|
+
|
|
7
|
+
@Middleware()
|
|
8
|
+
export class ViteRendererMiddleware {
|
|
9
|
+
@Inject()
|
|
10
|
+
protected viteService: ViteService;
|
|
11
|
+
|
|
12
|
+
async use(@Context() $ctx: Context) {
|
|
13
|
+
const response = await this.viteService.render("*", {$ctx});
|
|
14
|
+
|
|
15
|
+
response && !$ctx.response.isDone() && $ctx.response.body(response);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {Env} from "@tsed/core";
|
|
2
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
3
|
+
import sirv from "sirv";
|
|
4
|
+
|
|
5
|
+
import {VITE_SERVER} from "./ViteServer.js";
|
|
6
|
+
|
|
7
|
+
vi.mock("sirv", () => {
|
|
8
|
+
return {default: vi.fn().mockReturnValue("sirv")};
|
|
9
|
+
});
|
|
10
|
+
vi.mock("vite", () => {
|
|
11
|
+
return {
|
|
12
|
+
createServer: vi.fn().mockResolvedValue({
|
|
13
|
+
middlewares: "middlewares",
|
|
14
|
+
close: vi.fn()
|
|
15
|
+
})
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
async function createTestFixture() {
|
|
20
|
+
const mod = await import("vite");
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
createServer: mod.createServer
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("ViteServer", () => {
|
|
28
|
+
describe("with default options - dev mode", () => {
|
|
29
|
+
beforeEach(() =>
|
|
30
|
+
PlatformTest.create({
|
|
31
|
+
vite: {}
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
afterEach(() => PlatformTest.reset());
|
|
35
|
+
|
|
36
|
+
it("should load vite dev server", async () => {
|
|
37
|
+
const {createServer} = await createTestFixture();
|
|
38
|
+
const viteDevServer = PlatformTest.get<VITE_SERVER>(VITE_SERVER);
|
|
39
|
+
|
|
40
|
+
expect(viteDevServer.middlewares).toEqual("middlewares");
|
|
41
|
+
expect(createServer).toHaveBeenCalledWith({
|
|
42
|
+
logLevel: "off",
|
|
43
|
+
server: {
|
|
44
|
+
middlewareMode: true
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
it("should load and close server", async () => {
|
|
49
|
+
const {createServer} = await createTestFixture();
|
|
50
|
+
const viteDevServer = PlatformTest.get<VITE_SERVER>(VITE_SERVER);
|
|
51
|
+
viteDevServer.close = vi.fn();
|
|
52
|
+
|
|
53
|
+
expect(viteDevServer.middlewares).toEqual("middlewares");
|
|
54
|
+
expect(createServer).toHaveBeenCalledWith({
|
|
55
|
+
logLevel: "off",
|
|
56
|
+
server: {
|
|
57
|
+
middlewareMode: true
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await PlatformTest.injector.destroy();
|
|
62
|
+
|
|
63
|
+
expect(viteDevServer.close).toHaveBeenCalledWith();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe("with defined options - dev mode", () => {
|
|
67
|
+
beforeEach(() =>
|
|
68
|
+
PlatformTest.create({
|
|
69
|
+
vite: {
|
|
70
|
+
logLevel: "error",
|
|
71
|
+
root: "/root",
|
|
72
|
+
server: {
|
|
73
|
+
middlewareMode: false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
afterEach(() => PlatformTest.reset());
|
|
79
|
+
|
|
80
|
+
it("should load vite dev server", async () => {
|
|
81
|
+
const {createServer} = await createTestFixture();
|
|
82
|
+
const viteDevServer = PlatformTest.get<VITE_SERVER>(VITE_SERVER);
|
|
83
|
+
|
|
84
|
+
expect(viteDevServer.middlewares).toEqual("middlewares");
|
|
85
|
+
expect(createServer).toHaveBeenCalledWith({
|
|
86
|
+
logLevel: "error",
|
|
87
|
+
root: "/root",
|
|
88
|
+
server: {
|
|
89
|
+
middlewareMode: true
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("with default options - prod mode", () => {
|
|
95
|
+
beforeEach(() =>
|
|
96
|
+
PlatformTest.create({
|
|
97
|
+
env: Env.PROD,
|
|
98
|
+
vite: {},
|
|
99
|
+
logger: {
|
|
100
|
+
level: "off"
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
afterEach(() => PlatformTest.reset());
|
|
105
|
+
|
|
106
|
+
it("should load sirv to expose statics files", async () => {
|
|
107
|
+
await createTestFixture();
|
|
108
|
+
const viteDevServer = PlatformTest.get<VITE_SERVER>(VITE_SERVER);
|
|
109
|
+
|
|
110
|
+
expect(viteDevServer.middlewares).toEqual("sirv");
|
|
111
|
+
expect(sirv).toHaveBeenCalledWith("/dist/client", {
|
|
112
|
+
dev: false,
|
|
113
|
+
dotfiles: false
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe("with defined options - prod mode", () => {
|
|
118
|
+
beforeEach(() =>
|
|
119
|
+
PlatformTest.create({
|
|
120
|
+
env: Env.PROD,
|
|
121
|
+
logger: {
|
|
122
|
+
level: "off"
|
|
123
|
+
},
|
|
124
|
+
vite: {
|
|
125
|
+
root: "/root"
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
afterEach(() => PlatformTest.reset());
|
|
130
|
+
|
|
131
|
+
it("should load sirv to expose statics files", () => {
|
|
132
|
+
const viteDevServer = PlatformTest.get<VITE_SERVER>(VITE_SERVER);
|
|
133
|
+
|
|
134
|
+
expect(viteDevServer.middlewares).toEqual("sirv");
|
|
135
|
+
expect(sirv).toHaveBeenCalledWith("/root/dist/client", {dev: false, dotfiles: false});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {Env} from "@tsed/core";
|
|
2
|
+
import {Configuration, registerProvider} from "@tsed/di";
|
|
3
|
+
import {Logger} from "@tsed/logger";
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import type {InlineConfig, ViteDevServer} from "vite";
|
|
6
|
+
|
|
7
|
+
import {ViteConfig} from "../interfaces/ViteConfig.js";
|
|
8
|
+
|
|
9
|
+
export const VITE_SERVER = Symbol.for("VITE_DEV_SERVER");
|
|
10
|
+
export type VITE_SERVER = ViteDevServer;
|
|
11
|
+
|
|
12
|
+
registerProvider({
|
|
13
|
+
token: VITE_SERVER,
|
|
14
|
+
deps: [Configuration, Logger],
|
|
15
|
+
async useAsyncFactory(settings: Configuration, logger: Logger) {
|
|
16
|
+
const {enableStream, statics: staticsOpts = {}, ...config} = settings.get<ViteConfig & InlineConfig>("vite", {});
|
|
17
|
+
const level = settings.logger.level;
|
|
18
|
+
|
|
19
|
+
if (settings.env !== Env.PROD) {
|
|
20
|
+
logger.info("Start Vite Dev Server 🚀...");
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
const {createServer} = await import("vite");
|
|
23
|
+
|
|
24
|
+
return createServer({
|
|
25
|
+
...config,
|
|
26
|
+
server: {
|
|
27
|
+
...(config?.server || {}),
|
|
28
|
+
middlewareMode: true
|
|
29
|
+
},
|
|
30
|
+
logLevel: String(config.logLevel || level) as any
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
logger.info("Initialize statics vite application");
|
|
35
|
+
const {default: sirv} = await import("sirv");
|
|
36
|
+
|
|
37
|
+
return Promise.resolve({
|
|
38
|
+
middlewares: sirv(`${config.root || ""}/dist/client`, {
|
|
39
|
+
dotfiles: false,
|
|
40
|
+
...staticsOpts,
|
|
41
|
+
dev: false
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
hooks: {
|
|
46
|
+
$onDestroy(viteDevServer: Partial<ViteDevServer>): Promise<any> | void {
|
|
47
|
+
return viteDevServer.close && viteDevServer.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
2
|
+
|
|
3
|
+
import {ViteService} from "./ViteService.js";
|
|
4
|
+
|
|
5
|
+
vi.mock("vike/server", () => {
|
|
6
|
+
return {
|
|
7
|
+
renderPage: vi.fn()
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
async function getServiceFixture(httpResponse: any) {
|
|
12
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
13
|
+
|
|
14
|
+
const service = await PlatformTest.invoke<ViteService>(ViteService, []);
|
|
15
|
+
|
|
16
|
+
$ctx.request.req.method = "GET";
|
|
17
|
+
$ctx.request.req.url = "/";
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
$ctx.request.req.session = {};
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
$ctx.request.req.secure = true;
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
$ctx.request.req.protocol = "https";
|
|
24
|
+
$ctx.request.req.headers = {
|
|
25
|
+
host: "host",
|
|
26
|
+
"x-header": "x-header",
|
|
27
|
+
"user-agent": "ua"
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const pageContext = {
|
|
31
|
+
httpResponse
|
|
32
|
+
};
|
|
33
|
+
const mod = await import("vike/server");
|
|
34
|
+
|
|
35
|
+
vi.mocked(mod.renderPage).mockResolvedValue(pageContext as never);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
renderPage: mod.renderPage,
|
|
39
|
+
service,
|
|
40
|
+
$ctx,
|
|
41
|
+
pageContext,
|
|
42
|
+
stateSnapshot: PlatformTest.injector.settings.get("stateSnapshot")
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("ViteService", () => {
|
|
47
|
+
describe("render()", () => {
|
|
48
|
+
describe("when the enableStream: false", () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
vi.resetAllMocks();
|
|
51
|
+
return PlatformTest.create({
|
|
52
|
+
vite: {root: "./path/to/client", stateSnapshot: vi.fn().mockReturnValue({state: "state"})}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
afterEach(() => PlatformTest.reset());
|
|
56
|
+
|
|
57
|
+
it("should render the page", async () => {
|
|
58
|
+
const {$ctx, service, renderPage} = await getServiceFixture({
|
|
59
|
+
statusCode: 200,
|
|
60
|
+
headers: [["content-type", "text/html"]],
|
|
61
|
+
body: "html"
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
vi.spyOn($ctx.response, "status").mockReturnThis();
|
|
65
|
+
vi.spyOn($ctx.response, "setHeader").mockReturnThis();
|
|
66
|
+
|
|
67
|
+
const result = await service.render("*", {$ctx});
|
|
68
|
+
|
|
69
|
+
expect(result).toEqual("html");
|
|
70
|
+
expect(renderPage).toHaveBeenCalledWith({
|
|
71
|
+
view: "*",
|
|
72
|
+
pageProps: {
|
|
73
|
+
view: "*"
|
|
74
|
+
},
|
|
75
|
+
contextProps: {
|
|
76
|
+
headers: {
|
|
77
|
+
host: "host",
|
|
78
|
+
"x-header": "x-header",
|
|
79
|
+
"user-agent": "ua"
|
|
80
|
+
},
|
|
81
|
+
host: "host",
|
|
82
|
+
method: "GET",
|
|
83
|
+
protocol: "https",
|
|
84
|
+
secure: true,
|
|
85
|
+
session: {},
|
|
86
|
+
stateSnapshot: {state: "state"},
|
|
87
|
+
url: "/"
|
|
88
|
+
},
|
|
89
|
+
urlOriginal: "/",
|
|
90
|
+
userAgent: "ua"
|
|
91
|
+
});
|
|
92
|
+
expect($ctx.response.status).toHaveBeenCalledWith(200);
|
|
93
|
+
expect($ctx.response.setHeader).toHaveBeenCalledWith("content-type", "text/html");
|
|
94
|
+
});
|
|
95
|
+
it("should return empty content if the page doesn't contains jsx content", async () => {
|
|
96
|
+
const {$ctx, service, renderPage} = await getServiceFixture(null);
|
|
97
|
+
|
|
98
|
+
vi.spyOn($ctx.response, "status").mockReturnThis();
|
|
99
|
+
vi.spyOn($ctx.response, "body").mockReturnThis();
|
|
100
|
+
vi.spyOn($ctx.response, "contentType").mockReturnThis();
|
|
101
|
+
|
|
102
|
+
const result = await service.render("vue.vite", {$ctx, valueOpt: "valueOpt"});
|
|
103
|
+
|
|
104
|
+
expect(result).toEqual(undefined);
|
|
105
|
+
expect(renderPage).toHaveBeenCalledWith(
|
|
106
|
+
expect.objectContaining({
|
|
107
|
+
view: "vue",
|
|
108
|
+
pageProps: {
|
|
109
|
+
view: "vue",
|
|
110
|
+
valueOpt: "valueOpt"
|
|
111
|
+
},
|
|
112
|
+
contextProps: {
|
|
113
|
+
headers: {
|
|
114
|
+
host: "host",
|
|
115
|
+
"x-header": "x-header",
|
|
116
|
+
"user-agent": "ua"
|
|
117
|
+
},
|
|
118
|
+
host: "host",
|
|
119
|
+
method: "GET",
|
|
120
|
+
protocol: "https",
|
|
121
|
+
secure: true,
|
|
122
|
+
session: {},
|
|
123
|
+
stateSnapshot: {state: "state"},
|
|
124
|
+
url: "/"
|
|
125
|
+
},
|
|
126
|
+
urlOriginal: "/",
|
|
127
|
+
userAgent: "ua"
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
expect($ctx.response.status).not.toHaveBeenCalled();
|
|
131
|
+
expect($ctx.response.contentType).not.toHaveBeenCalled();
|
|
132
|
+
expect($ctx.response.body).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
it("should log render error", async () => {
|
|
135
|
+
const {$ctx, service, renderPage} = await getServiceFixture(null);
|
|
136
|
+
|
|
137
|
+
vi.spyOn($ctx.logger, "error").mockReturnThis();
|
|
138
|
+
|
|
139
|
+
(renderPage as any).mockResolvedValue({
|
|
140
|
+
httpResponse: {},
|
|
141
|
+
errorWhileRendering: new Error("")
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
vi.spyOn($ctx.response, "status").mockReturnThis();
|
|
145
|
+
vi.spyOn($ctx.response, "body").mockReturnThis();
|
|
146
|
+
vi.spyOn($ctx.response, "setHeader").mockReturnThis();
|
|
147
|
+
|
|
148
|
+
await service.render("vue.vite", {$ctx});
|
|
149
|
+
|
|
150
|
+
expect($ctx.logger.error).toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("when the enableStream: true", () => {
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
vi.resetAllMocks();
|
|
157
|
+
return PlatformTest.create({
|
|
158
|
+
vite: {
|
|
159
|
+
enableStream: true,
|
|
160
|
+
root: "./path/to/client",
|
|
161
|
+
stateSnapshot: vi.fn().mockReturnValue({state: "state"})
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
afterEach(() => PlatformTest.reset());
|
|
166
|
+
|
|
167
|
+
it("should render the page", async () => {
|
|
168
|
+
const {$ctx, service, renderPage} = await getServiceFixture({
|
|
169
|
+
statusCode: 200,
|
|
170
|
+
headers: [["content-type", "text/html"]],
|
|
171
|
+
body: "html"
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
vi.spyOn($ctx.response, "status").mockReturnThis();
|
|
175
|
+
vi.spyOn($ctx.response, "setHeader").mockReturnThis();
|
|
176
|
+
|
|
177
|
+
const result = await service.render("*", {$ctx});
|
|
178
|
+
|
|
179
|
+
expect(result).toEqual({
|
|
180
|
+
body: "html",
|
|
181
|
+
headers: [["content-type", "text/html"]],
|
|
182
|
+
statusCode: 200
|
|
183
|
+
});
|
|
184
|
+
expect(renderPage).toHaveBeenCalledWith({
|
|
185
|
+
view: "*",
|
|
186
|
+
pageProps: {
|
|
187
|
+
view: "*"
|
|
188
|
+
},
|
|
189
|
+
contextProps: {
|
|
190
|
+
headers: {
|
|
191
|
+
host: "host",
|
|
192
|
+
"x-header": "x-header",
|
|
193
|
+
"user-agent": "ua"
|
|
194
|
+
},
|
|
195
|
+
host: "host",
|
|
196
|
+
method: "GET",
|
|
197
|
+
protocol: "https",
|
|
198
|
+
secure: true,
|
|
199
|
+
session: {},
|
|
200
|
+
stateSnapshot: {state: "state"},
|
|
201
|
+
url: "/"
|
|
202
|
+
},
|
|
203
|
+
urlOriginal: "/",
|
|
204
|
+
userAgent: "ua"
|
|
205
|
+
});
|
|
206
|
+
expect($ctx.response.status).toHaveBeenCalledWith(200);
|
|
207
|
+
expect($ctx.response.setHeader).toHaveBeenCalledWith("content-type", "text/html");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should render the page with statusCode from context.response if superior to httpResponse.statusCode", async () => {
|
|
211
|
+
const {$ctx, service, renderPage} = await getServiceFixture({
|
|
212
|
+
statusCode: 200,
|
|
213
|
+
headers: [["content-type", "text/html"]],
|
|
214
|
+
body: "html"
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
$ctx.response.status(404);
|
|
218
|
+
vi.spyOn($ctx.response, "status").mockReturnThis();
|
|
219
|
+
vi.spyOn($ctx.response, "setHeader").mockReturnThis();
|
|
220
|
+
const result = await service.render("*", {$ctx});
|
|
221
|
+
|
|
222
|
+
expect(result).toEqual({
|
|
223
|
+
body: "html",
|
|
224
|
+
headers: [["content-type", "text/html"]],
|
|
225
|
+
statusCode: 200
|
|
226
|
+
});
|
|
227
|
+
expect(renderPage).toHaveBeenCalledWith({
|
|
228
|
+
view: "*",
|
|
229
|
+
pageProps: {
|
|
230
|
+
view: "*"
|
|
231
|
+
},
|
|
232
|
+
contextProps: {
|
|
233
|
+
headers: {
|
|
234
|
+
host: "host",
|
|
235
|
+
"x-header": "x-header",
|
|
236
|
+
"user-agent": "ua"
|
|
237
|
+
},
|
|
238
|
+
host: "host",
|
|
239
|
+
method: "GET",
|
|
240
|
+
protocol: "https",
|
|
241
|
+
secure: true,
|
|
242
|
+
session: {},
|
|
243
|
+
stateSnapshot: {state: "state"},
|
|
244
|
+
url: "/"
|
|
245
|
+
},
|
|
246
|
+
urlOriginal: "/",
|
|
247
|
+
userAgent: "ua"
|
|
248
|
+
});
|
|
249
|
+
expect($ctx.response.status).toHaveBeenCalledWith(404);
|
|
250
|
+
expect($ctx.response.setHeader).toHaveBeenCalledWith("content-type", "text/html");
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {Constant, Injectable} from "@tsed/di";
|
|
2
|
+
import {PlatformContext} from "@tsed/platform-http";
|
|
3
|
+
import {Writable} from "stream";
|
|
4
|
+
|
|
5
|
+
import {ViteConfig} from "../interfaces/ViteConfig.js";
|
|
6
|
+
import {ViteRenderContext} from "../interfaces/ViteRenderContext.js";
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class ViteService {
|
|
10
|
+
static moduleName = "vike/server";
|
|
11
|
+
|
|
12
|
+
@Constant("vite", {})
|
|
13
|
+
protected config: ViteConfig;
|
|
14
|
+
|
|
15
|
+
@Constant("vite.enableStream", false)
|
|
16
|
+
private enableStream: boolean;
|
|
17
|
+
|
|
18
|
+
async render(
|
|
19
|
+
viewPath: string,
|
|
20
|
+
{$ctx, ...opts}: {$ctx: PlatformContext} & Record<string, any>
|
|
21
|
+
): Promise<string | {pipe(stream: Writable): void} | undefined> {
|
|
22
|
+
const urlOriginal = $ctx.request.url;
|
|
23
|
+
|
|
24
|
+
const {data} = $ctx;
|
|
25
|
+
const view = viewPath.replace(".vite", "");
|
|
26
|
+
|
|
27
|
+
const pageProps = {
|
|
28
|
+
view,
|
|
29
|
+
...$ctx.response.locals,
|
|
30
|
+
...opts,
|
|
31
|
+
...data
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const contextProps: ViteRenderContext = {
|
|
35
|
+
host: $ctx.request.host,
|
|
36
|
+
protocol: $ctx.request.protocol,
|
|
37
|
+
method: $ctx.request.method,
|
|
38
|
+
url: urlOriginal,
|
|
39
|
+
secure: $ctx.request.secure,
|
|
40
|
+
headers: $ctx.request.headers,
|
|
41
|
+
session: $ctx.request.session,
|
|
42
|
+
stateSnapshot: this.config.stateSnapshot && this.config.stateSnapshot()
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const {renderPage} = await import("vike/server");
|
|
46
|
+
|
|
47
|
+
const pageContext = await renderPage({
|
|
48
|
+
view,
|
|
49
|
+
urlOriginal,
|
|
50
|
+
pageProps,
|
|
51
|
+
contextProps,
|
|
52
|
+
userAgent: $ctx.request.headers["user-agent"]
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (pageContext.errorWhileRendering) {
|
|
56
|
+
$ctx.logger.error({
|
|
57
|
+
event: "VITE_RENDER_ERROR",
|
|
58
|
+
error: pageContext.errorWhileRendering
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (pageContext.httpResponse) {
|
|
63
|
+
const {httpResponse} = pageContext;
|
|
64
|
+
httpResponse.headers?.forEach(([name, value]: [string, string]) => $ctx.response.setHeader(name, value));
|
|
65
|
+
$ctx.response.status(Math.max(httpResponse.statusCode, $ctx.response.statusCode));
|
|
66
|
+
|
|
67
|
+
if (this.enableStream) {
|
|
68
|
+
return httpResponse;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return httpResponse.body;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|