Skip to content

Commit

Permalink
fix: overlay renders invalid file path (#4280)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan authored Dec 27, 2024
1 parent 7ae6c2a commit 540eaca
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 199 deletions.
16 changes: 4 additions & 12 deletions packages/core/src/client/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ function clearOutdatedErrors() {
}
}

let createOverlay: undefined | ((overlay: string, errors: string[]) => void);
let createOverlay: undefined | ((html: string) => void);
let clearOverlay: undefined | (() => void);

export const registerOverlay = (
createFn: (overlay: string, errors: string[]) => void,
createFn: (html: string) => void,
clearFn: () => void,
): void => {
createOverlay = createFn;
Expand Down Expand Up @@ -91,15 +91,7 @@ function handleWarnings({ text }: { text: string[] }) {
}

// Compilation with errors (e.g. syntax error or missing modules).
function handleErrors({
text,
html,
overlay,
}: {
text: string[];
html: string[];
overlay: string;
}) {
function handleErrors({ text, html }: { text: string[]; html: string }) {
clearOutdatedErrors();

isFirstCompilation = false;
Expand All @@ -111,7 +103,7 @@ function handleErrors({
}

if (createOverlay) {
createOverlay(overlay, html);
createOverlay(html);
}
}

Expand Down
45 changes: 11 additions & 34 deletions packages/core/src/client/overlay.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,12 @@
import { registerOverlay } from './hmr';

function linkedText(root: ShadowRoot, selector: string, text: string): void {
const el = root.querySelector(selector)!;
const fileRegex = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g;

let curIndex = 0;
let match = fileRegex.exec(text);
while (match !== null) {
const { 0: file, index } = match;
if (index != null) {
const frag = text.slice(curIndex, index);
el.insertAdjacentHTML('beforeend', frag);
const link = document.createElement('a');
link.textContent = file;
link.className = 'file-link';

link.onclick = () => {
fetch(`/__open-in-editor?file=${encodeURIComponent(file)}`);
};
el.appendChild(link);
curIndex += frag.length + file.length;
}
match = fileRegex.exec(text);
}

const frag = text.slice(curIndex);
el.insertAdjacentHTML('beforeend', frag);
}

const {
HTMLElement = class {} as typeof globalThis.HTMLElement,
customElements,
} = typeof window !== 'undefined' ? window : globalThis;

class ErrorOverlay extends HTMLElement {
constructor(overlay: string, errors: string[]) {
constructor(html: string) {
super();

if (!this.attachShadow) {
Expand All @@ -45,13 +17,18 @@ class ErrorOverlay extends HTMLElement {
}

const root = this.attachShadow({ mode: 'open' });
root.innerHTML = overlay;
root.innerHTML = html;

linkedText(root, '.content', errors.join('\n\n').trim());
root.querySelector('.close')?.addEventListener('click', this.close);
root.querySelector('.close')!.addEventListener('click', this.close);
// close overlay when click outside
this.addEventListener('click', this.close);
root.querySelector('.container')!.addEventListener('click', (e) => {
if (e.target) {
const { file } = (e.target as HTMLLinkElement).dataset;
if (file) {
fetch(`/__open-in-editor?file=${encodeURIComponent(file)}`);
}
}
e.stopPropagation();
});

Expand Down Expand Up @@ -84,9 +61,9 @@ if (customElements && !customElements.get(overlayId)) {
customElements.define(overlayId, ErrorOverlay);
}

function createOverlay(overlay: string, errors: string[]) {
function createOverlay(html: string) {
clearOverlay();
document.body.appendChild(new ErrorOverlay(overlay, errors));
document.body.appendChild(new ErrorOverlay(html));
}

function clearOverlay() {
Expand Down
144 changes: 144 additions & 0 deletions packages/core/src/server/overlay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import ansiHTML from './ansiHTML';
import { escapeHtml } from './helper';

export function convertLinksInHtml(text: string): string {
const fileRegex = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g;

return text.replace(fileRegex, (file) => {
// If the file contains `</span>`, it means the file path contains ANSI codes.
// We need to move the `</span>` to the end of the file path.
const hasClosingSpan = file.includes('</span>') && !file.includes('<span');
const filePath = hasClosingSpan ? file.replace('</span>', '') : file;

return `<a class="file-link" data-file="${filePath}">${filePath}</a>${
hasClosingSpan ? '</span>' : ''
}`;
});
}

// HTML template for error overlay
export function genOverlayHTML(errors: string[]) {
const htmlItems = errors.map((item) =>
convertLinksInHtml(ansiHTML(escapeHtml(item))),
);
return `
<style>
.root {
position: fixed;
z-index: 9999;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow-y: scroll;
margin: 0;
background: rgba(0, 0, 0, 0.66);
cursor: pointer;
}
.container {
font-family: Menlo, Consolas, monospace;
line-height: 1.6;
width: 960px;
max-width: 85%;
color: #d8d8d8;
margin: 32px auto;
padding: 32px 40px;
position: relative;
background: #181818;
border-radius: 24px;
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
overflow: hidden;
direction: ltr;
text-align: left;
box-sizing: border-box;
cursor: default;
}
.title {
margin: 0 0 20px;
padding-bottom: 12px;
font-size: 17px;
font-weight: 600;
color: #fb6a6a;
border-bottom: 2px solid rgba(252,94,94,.66);
}
.content {
margin: 0;
font-size: 14px;
font-family: inherit;
overflow-x: scroll;
scrollbar-width: none;
}
.content::-webkit-scrollbar {
display: none;
}
.file-link {
cursor: pointer;
color: #6eecf7;
text-decoration: underline;
text-underline-offset: 3px;
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
.close {
position: absolute;
top: 27px;
right: 32px;
width: 32px;
height: 32px;
cursor: pointer;
}
.close:hover {
opacity: 0.8;
}
.close:active {
opacity: 0.6;
}
.close:before,
.close:after {
position: absolute;
left: 16px;
top: 8px;
content: ' ';
height: 18px;
width: 2px;
border-radius: 4px;
background-color: #b8b8b8;
}
.close:before {
transform: rotate(45deg);
}
.close:after {
transform: rotate(-45deg);
}
.footer {
font-size: 12px;
color: #7e6a92;
margin-top: 20px;
padding-top: 12px;
border-top: 2px solid rgba(126,106,146,.6);
}
.footer p {
margin: 4px 0 0;
}
.footer span {
color: #a88dc3;
}
</style>
<div class="root">
<div class="container">
<div class="close"></div>
<p class="title">Build failed</p>
<pre class="content">${htmlItems.join('\n\n').trim()}</pre>
<footer class="footer">
<p><span>Fix error</span>, click outside, or press Esc to close the overlay.</p>
<p>Disable overlay by setting Rsbuild's <span>dev.client.overlay</span> config to false.<p>
</footer>
</div>
</div>
`;
}
Loading

0 comments on commit 540eaca

Please sign in to comment.