@tsed/vike 8.0.0 → 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 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.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.0",
29
- "@tsed/core": "8.0.0",
30
- "@tsed/di": "8.0.0",
31
- "@tsed/exceptions": "8.0.0",
32
- "@tsed/platform-http": "8.0.0",
33
- "@tsed/typescript": "8.0.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.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
+ });
@@ -0,0 +1,5 @@
1
+ import {View} from "@tsed/schema";
2
+
3
+ export function Vite(path?: string) {
4
+ return View(`${path || "*"}.vite`);
5
+ }
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,9 @@
1
+ import {Options} from "sirv";
2
+ // @ts-ignore
3
+ import type {InlineConfig} from "vite";
4
+
5
+ export type ViteConfig = InlineConfig & {
6
+ enableStream?: boolean;
7
+ statics?: Omit<Options, "dev">;
8
+ stateSnapshot?(): unknown;
9
+ };
@@ -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,9 @@
1
+ import {ViteConfig} from "./ViteConfig.js";
2
+
3
+ declare global {
4
+ namespace TsED {
5
+ interface Configuration {
6
+ vite: ViteConfig;
7
+ }
8
+ }
9
+ }
@@ -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
+ }