Skip to content

Commit

Permalink
feat: use customized overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework committed Feb 6, 2022
1 parent 13e97be commit 132cd4e
Show file tree
Hide file tree
Showing 11 changed files with 1,171 additions and 18 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"jest-serializer-path": "^0.1.15",
"lint-staged": "^11.0.0",
"minimist": "^1.2.5",
"nodemon": "^2.0.15",
"playwright-chromium": "^1.12.3",
"prettier": "^2.3.2",
"prompts": "^2.4.1",
Expand Down
8 changes: 6 additions & 2 deletions packages/vite-plugin-checker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
"lib"
],
"scripts": {
"dev": "tsc -p tsconfig.build.json --watch",
"build": "tsc -p tsconfig.build.json",
"watch:node": "tsc -p tsconfig.build.json --watch",
"watch:runtime": "esbuild src/@runtime/main.ts --outfile=./lib/@runtime/main.js --bundle --format=esm --watch",
"dev": "run-p watch:node watch:runtime",
"build": "tsc -p tsconfig.build.json && esbuild src/@runtime/main.ts --outfile=./lib/@runtime/main.js --bundle --format=esm",
"build:test": "tsc -p tsconfig.test.json",
"prepublishOnly": "rimraf lib && npm run build",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . --lerna-package vite-plugin-checker",
Expand Down Expand Up @@ -55,6 +57,8 @@
"@types/eslint": "^7.2.14",
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.pick": "^4.4.6",
"esbuild": "^0.14.13",
"npm-run-all": "^4.1.5",
"optionator": "^0.9.1",
"vls": "^0.7.2"
}
Expand Down
27 changes: 27 additions & 0 deletions packages/vite-plugin-checker/src/@runtime/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ErrorPayload } from 'vite'
import { ErrorOverlay, overlayId } from './overlay'

let enableOverlay = true

export function inject() {
const socketProtocol = null || (location.protocol === 'https:' ? 'wss' : 'ws')
const socketHost = `${null || location.hostname}:${'3000'}`
const socket = new WebSocket(`${socketProtocol}://${socketHost}`, 'vite-hmr')
socket.addEventListener('message', async ({ data: dataStr }) => {
const data = JSON.parse(dataStr)
if (data.type === 'checker-error') {
createErrorOverlay(data.err)
}
})
}

function createErrorOverlay(err: ErrorPayload['err']) {
if (!enableOverlay) return
clearErrorOverlay()
console.log('error! 标记!')
document.body.appendChild(new ErrorOverlay(err))
}

function clearErrorOverlay() {
document.querySelectorAll(overlayId).forEach((n) => (n as ErrorOverlay).close())
}
187 changes: 187 additions & 0 deletions packages/vite-plugin-checker/src/@runtime/overlay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import type { ErrorPayload } from 'vite'

const template = `
<style>
:host {
position: fixed;
z-index: 99999;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow-y: scroll;
margin: 0;
background: rgba(0, 0, 0, 0.66);
--monospace: 'SFM√ono-Regular', Consolas,
'Liberation Mono', Menlo, Courier, monospace;
--red: #ff5555;
--yellow: #e2aa53;
--purple: #cfa4ff;
--cyan: #2dd9da;
--dim: #c9c9c9;√
}
.window {
font-family: var(--monospace);
line-height: 1.5;
width: 800px;
color: #d8d8d8;
margin: 30px auto;
padding: 25px 40px;
position: relative;
background: #181818;
border-radius: 6px 6px 8px 8px;
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
overflow: hidden;
border-top: 8px solid var(--red);
direction: ltr;
text-align: left;
}
pre {
font-family: var(--monospace);
font-size: 16px;
margin-top: 0;
margin-bottom: 1em;
overflow-x: scroll;
scrollbar-width: none;
}
pre::-webkit-scrollbar {
display: none;
}
.message {
line-height: 1.3;
font-weight: 600;
white-space: pre-wrap;
}
.message-body {
color: var(--red);
}
.plugin {
color: var(--purple);
}
.file {
color: var(--cyan);
margin-bottom: 0;
white-space: pre-wrap;
word-break: break-all;
}
.frame {
color: var(--yellow);
}
.stack {
font-size: 13px;
color: var(--dim);
}
.tip {
font-size: 13px;
color: #999;
border-top: 1px dotted #999;
padding-top: 13px;
}
code {
font-size: 13px;
font-family: var(--monospace);
color: var(--yellow);
}
.file-link {
text-decoration: underline;
cursor: pointer;
}
</style>
<div class="window">
<pre class="message"><span class="plugin"></span><span class="message-body"></span></pre>
<pre class="file"></pre>
<pre class="frame"></pre>
<pre class="stack"></pre>
<div class="tip">
Click outside or fix the code to dismiss.<br>
You can also disable this overlay by setting
<code>server.hmr.overlay</code> to <code>false</code> in <code>vite.config.js.</code>
</div>
</div>
`

const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g
const codeframeRE = /^(?:>?\s+\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm

export class ErrorOverlay extends HTMLElement {
public root: ShadowRoot

public constructor(err: ErrorPayload['err']) {
super()
this.root = this.attachShadow({ mode: 'open' })
this.root.innerHTML = template

codeframeRE.lastIndex = 0
const hasFrame = err.frame && codeframeRE.test(err.frame)
const message = hasFrame ? err.message.replace(codeframeRE, '') : err.message
if (err.plugin) {
this.text('.plugin', `[plugin:${err.plugin}] `)
}
this.text('.message-body', message.trim())

const [file] = (err.loc?.file || err.id || 'unknown file').split(`?`)
if (err.loc) {
this.text('.file', `${file}:${err.loc.line}:${err.loc.column}`, true)
} else if (err.id) {
this.text('.file', file)
}

if (hasFrame) {
this.text('.frame', err.frame!.trim())
}
this.text('.stack', err.stack, true)

this.root.querySelector('.window')!.addEventListener('click', (e) => {
e.stopPropagation()
})
this.addEventListener('click', () => {
this.close()
})
}

public text(selector: string, text: string, linkFiles = false): void {
const el = this.root.querySelector(selector)!
if (!linkFiles) {
el.textContent = text
} else {
let curIndex = 0
let match: RegExpExecArray | null
while ((match = fileRE.exec(text))) {
const { 0: file, index } = match
if (index !== null) {
const frag = text.slice(curIndex, index)
el.appendChild(document.createTextNode(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
}
}
}
}

public close(): void {
this.parentNode?.removeChild(this)
}
}

export const overlayId = 'vite-plugin-checker-error-overlay'
if (customElements && !customElements.get(overlayId)) {
customElements.define(overlayId, ErrorOverlay)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

// Copied from /eslint@7.28.0/node_modules/eslint/lib/cli.js

// @ts-expect-error
function quietFixPredicate(message) {
return message.severity === 2
}
Expand Down Expand Up @@ -32,7 +33,7 @@ export function translateOptions({
resolvePluginsRelativeTo,
rule,
rulesdir,
}) {
}: any) {
return {
allowInlineConfig: inlineConfig,
cache,
Expand All @@ -47,12 +48,14 @@ export function translateOptions({
overrideConfig: {
env:
env &&
// @ts-expect-error
env.reduce((obj, name) => {
obj[name] = true
return obj
}, {}),
globals:
global &&
// @ts-expect-error
global.reduce((obj, name) => {
if (name.endsWith(':true')) {
obj[name.slice(0, -5)] = 'writable'
Expand Down
10 changes: 10 additions & 0 deletions packages/vite-plugin-checker/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const RUNTIME_PUBLIC_PATH = '/@vite-plugin-checker/runtime'
import fs from 'fs'

const runtimeFilePath = require.resolve('../@runtime/main.js')

const runtimeCode = `
${fs.readFileSync(runtimeFilePath, 'utf-8')};
`

export { runtimeCode, RUNTIME_PUBLIC_PATH }
55 changes: 46 additions & 9 deletions packages/vite-plugin-checker/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { spawn } from 'child_process'
import pick from 'lodash.pick'
import npmRunPath from 'npm-run-path'
import os from 'os'
import { ConfigEnv, Plugin } from 'vite'
import { Checker } from './Checker'
import { runtimeCode, RUNTIME_PUBLIC_PATH } from './client/index'

import {
OverlayErrorAction,
Expand Down Expand Up @@ -71,6 +71,41 @@ export default function Plugin(userConfig: UserPluginConfig): Plugin {
})
}
},
resolveId(id) {
if (id === RUNTIME_PUBLIC_PATH) {
return id
}
},
load(id) {
if (id === RUNTIME_PUBLIC_PATH) {
return runtimeCode
}
},
transformIndexHtml() {
return [
// {
// tag: 'script',
// attrs: { type: 'module' },
// children: `
// const socketProtocol = null || (location.protocol === 'https:' ? 'wss' : 'ws');
// const socketHost = \`\${null || location.hostname}:\${'3000'}\`;
// const socket = new WebSocket(\`\${socketProtocol}://\${socketHost}\`, 'vite-hmr');
// socket.addEventListener('message', async ({ data }) => {
// console.log(data)
// });
// `,
// },
{
tag: 'script',
attrs: { type: 'module' },
children: `import { inject } from "${RUNTIME_PUBLIC_PATH}"; inject();`,
},
// {
// tag: 'script',
// attrs: { type: 'module', src: `/vue-template/vite-plugin-checker/lib/client/index.js` },
// },
]
},
buildStart: () => {
// for build mode
// run a bin command in a separated process
Expand Down Expand Up @@ -106,6 +141,8 @@ export default function Plugin(userConfig: UserPluginConfig): Plugin {
if (action.type === ACTION_TYPES.overlayError) {
latestOverlayErrors[index] = action.payload
if (action.payload) {
// @ts-ignore
action.payload.type = 'checker-error'
server.ws.send(action.payload)
}
} else if (action.type === ACTION_TYPES.console) {
Expand All @@ -118,14 +155,14 @@ export default function Plugin(userConfig: UserPluginConfig): Plugin {
// sometimes Vite will trigger a full-reload instead of HMR, but the checker
// may update the overlay before full-reload fired. So we make sure the overlay
// will be displayed again after full-reload.
server.ws.on('connection', () => {
connectedTimes++
// if connectedCount !== 1, means Vite is doing a full-reload, so we don't need to send overlay again
const latestOverlayError = latestOverlayErrors.filter(Boolean).slice(-1)[0]
if (connectedTimes > 1 && latestOverlayError) {
server.ws.send(latestOverlayError)
}
})
// server.ws.on('connection', () => {
// connectedTimes++
// // if connectedCount !== 1, means Vite is doing a full-reload, so we don't need to send overlay again
// const latestOverlayError = latestOverlayErrors.filter(Boolean).slice(-1)[0]
// if (connectedTimes > 1 && latestOverlayError) {
// server.ws.send(latestOverlayError)
// }
// })

server.middlewares.use((req, res, next) => {
next()
Expand Down
3 changes: 2 additions & 1 deletion packages/vite-plugin-checker/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"outDir": "./lib",
"rootDir": "src"
},
"include": ["src"]
"include": ["src"],
"exclude": ["src/@runtime"]
}
6 changes: 2 additions & 4 deletions packages/vite-plugin-checker/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"compilerOptions": {
"allowJs": true
},
"extends": "../../tsconfig.json",
"include": ["src", "__tests__"]
"include": ["src", "__tests__"],
"exclude": ["src/@runtime"]
}
1 change: 1 addition & 0 deletions playground/react-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"version": "0.0.0",
"scripts": {
"dev": "vite",
"dev:watch": "nodemon --watch ../../packages/vite-plugin-checker/lib --exec 'vite'",
"build": "vite build",
"serve": "vite preview"
},
Expand Down
Loading

0 comments on commit 132cd4e

Please sign in to comment.