Skip to content

Commit de24877

Browse files
committed
fix(router dev): don't let vite mess with head
1 parent 809aade commit de24877

File tree

1 file changed

+60
-24
lines changed

1 file changed

+60
-24
lines changed

packages/qwik-router/src/buildtime/vite/html-transform-wrapper.ts

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ enum State {
2020
class HtmlTransformPatcher {
2121
private state: State = State.BUFFERING;
2222
private buffer = '';
23-
private bodyStartIndex = -1;
24-
private bodyTagEndIndex = -1;
23+
private headInnerIndex = -1;
24+
private bodyInnerIndex = -1;
2525
private isHtmlResponse = false;
2626

27-
private appendToBody = '';
27+
private bodyPostContent = '';
2828
private response: ServerResponse;
2929
private server: ViteDevServer;
3030
private request: IncomingMessage;
@@ -114,12 +114,22 @@ class HtmlTransformPatcher {
114114

115115
switch (this.state) {
116116
case State.BUFFERING:
117-
const bodyMatch = this.buffer.match(/<body[^>]*>/i);
118-
if (bodyMatch) {
119-
this.state = State.PROCESSING_HEAD;
120-
this.bodyStartIndex = this.buffer.indexOf(bodyMatch[0]);
121-
this.bodyTagEndIndex = this.bodyStartIndex + bodyMatch[0].length;
122-
this.processingPromise = this.processHead();
117+
// Note that we scan the entire buffer every time, in case the <head> or <body> tags are split across chunks
118+
if (this.headInnerIndex === -1) {
119+
const headMatch = this.buffer.match(/<head[^>]*>/i);
120+
if (headMatch) {
121+
const headOuterIndex = this.buffer.indexOf(headMatch[0]);
122+
this.headInnerIndex = headOuterIndex + headMatch[0].length;
123+
}
124+
}
125+
if (this.headInnerIndex !== -1) {
126+
const bodyMatch = this.buffer.slice(this.headInnerIndex).match(/<body[^>]*>/i);
127+
if (bodyMatch) {
128+
this.state = State.PROCESSING_HEAD;
129+
const bodyOuterIndex = this.buffer.indexOf(bodyMatch[0]);
130+
this.bodyInnerIndex = bodyOuterIndex + bodyMatch[0].length;
131+
this.processingPromise = this.processHead();
132+
}
123133
}
124134
break;
125135

@@ -139,31 +149,57 @@ class HtmlTransformPatcher {
139149

140150
private async processHead() {
141151
try {
142-
const headPortion = this.buffer.slice(0, this.bodyTagEndIndex);
143-
const fakeHtml = headPortion + '[FAKE_BODY]</body></html>';
144-
152+
// We can't pass the actual head to vite because it strips some scripts and then the DOM doesn't match the vdom positions
153+
const fakeHtml = '<html><head>[FAKE_HEAD]</head><body>[FAKE_BODY]</body></html>';
145154
// Let Vite transform the HTML
146155
const transformedHtml = await this.server.transformIndexHtml(
147156
this.request.url || '/',
148157
fakeHtml
149158
);
150159

151-
// Find the [FAKE_BODY] marker in the transformed result
152-
const fakeBodyIndex = transformedHtml.indexOf('[FAKE_BODY]');
153-
const bodyEndIndex = transformedHtml.indexOf('</body>', fakeBodyIndex);
154-
if (fakeBodyIndex === -1 || bodyEndIndex === -1) {
160+
// Extract the pre and post head and body content. For now, ignore attributes added to the tags
161+
// If attributes are needed later, put them after the : attribute on qwik's tags
162+
const fakeHeadIndex = transformedHtml.indexOf('[FAKE_HEAD]');
163+
const fakeHeadCloseIndex = transformedHtml.indexOf('</head>', fakeHeadIndex);
164+
if (fakeHeadIndex === -1 || fakeHeadCloseIndex === -1) {
165+
throw new Error('Transformed HTML does not contain [FAKE_HEAD]...</head>');
166+
}
167+
const headPreContent = transformedHtml.slice('<html><head>'.length, fakeHeadIndex);
168+
const headPostContent = transformedHtml.slice(
169+
fakeHeadIndex + '[FAKE_HEAD]'.length,
170+
fakeHeadCloseIndex
171+
);
172+
const fakeBodyStartIndex = transformedHtml.indexOf('<body>', fakeHeadCloseIndex);
173+
const fakeBodyIndex = transformedHtml.indexOf('[FAKE_BODY]', fakeBodyStartIndex);
174+
const fakeBodyEndIndex = transformedHtml.indexOf('</body>', fakeBodyIndex);
175+
if (fakeBodyIndex === -1 || fakeBodyEndIndex === -1) {
155176
throw new Error('Transformed HTML does not contain [FAKE_BODY]...</body>');
156177
}
157-
158-
// Extract the transformed head and body tags
159-
const transformedHead = transformedHtml.substring(0, fakeBodyIndex);
160-
this.appendToBody = transformedHtml.substring(
178+
const bodyPreContent = transformedHtml.slice(
179+
fakeBodyStartIndex + '<body>'.length,
180+
fakeBodyIndex
181+
);
182+
this.bodyPostContent = transformedHtml.slice(
161183
fakeBodyIndex + '[FAKE_BODY]'.length,
162-
bodyEndIndex
184+
fakeBodyEndIndex
163185
);
164-
this.buffer = transformedHead + this.buffer.slice(this.bodyTagEndIndex);
186+
// Now inject the head pre and post content and the body pre into the buffered content
187+
// Note that the head tag has attributes
188+
const headCloseIndex = this.buffer.indexOf('</head>', this.headInnerIndex);
189+
if (headCloseIndex === -1) {
190+
throw new Error('Buffered HTML does not contain </head>');
191+
}
165192

166-
if (this.appendToBody.length > 0) {
193+
this.buffer =
194+
this.buffer.slice(0, this.headInnerIndex) +
195+
headPreContent +
196+
this.buffer.slice(this.headInnerIndex, headCloseIndex) +
197+
headPostContent +
198+
this.buffer.slice(headCloseIndex, this.bodyInnerIndex) +
199+
bodyPreContent +
200+
this.buffer.slice(this.bodyInnerIndex);
201+
202+
if (this.bodyPostContent.length > 0) {
167203
this.state = State.STREAMING_BODY;
168204
this.handleStreamingBodyState();
169205
return;
@@ -184,7 +220,7 @@ class HtmlTransformPatcher {
184220
if (bodyEndMatch) {
185221
const bodyEndPos = this.buffer.indexOf(bodyEndMatch[0]);
186222
this.buffer =
187-
this.buffer.slice(0, bodyEndPos) + this.appendToBody + this.buffer.slice(bodyEndPos);
223+
this.buffer.slice(0, bodyEndPos) + this.bodyPostContent + this.buffer.slice(bodyEndPos);
188224

189225
this.transitionToPassthrough();
190226
return;

0 commit comments

Comments
 (0)