增加HMR支持

master 1.5.0
yutent 2023-04-17 11:33:48 +08:00
parent 9c2b9020e7
commit b3f73a0fae
7 changed files with 144 additions and 38 deletions

9
.vscode/launch.json vendored
View File

@ -10,19 +10,14 @@
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
]
"args": ["--extensionDevelopmentPath=${workspaceFolder}"]
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/test/suite/index"
]
"args": ["--extensionTestsPath=${workspaceFolder}"]
}
]
}

View File

@ -1,10 +1,7 @@
.vscode/**
.vscode-test/**
node_modules/**
test/**
test/*
package-lock.json
.travis.yml
.gitignore
index.js
test.js

View File

@ -10,6 +10,14 @@
> 这插件只是因为`Live Server插件`有bug,应一个基友需求临时写的。如果你有其他的功能需求, 请在插件商店中`搜索其他插件`。
## 插件特色
- 支持指定子目录为根目录
- 支持前端`history`路由
- 自动指定端口(端口不可用时自动寻找其他可用端口)
- 支持注入`headers`,满足某些测试场景下的测试需求
- 支持简单的热更新
## 插件使用
> 在项目根目录中放置 `.httpserver`文件, 并配置好之后, 重启或重新加载窗口, 以启动服务。

View File

@ -9,6 +9,7 @@ const { join } = require('path')
const { parse } = require('url')
const http = require('http')
const fs = require('iofs')
const { WebSocketServer } = require('ws')
const std = vsc.window.createOutputChannel('http.server')
const decode = decodeURIComponent
@ -56,9 +57,60 @@ let root
let enabled = false
let port = 23333
let baseUrl = 'http://127.0.0.1:' + port
let ws = null
std.out = function (...args) {
std.appendLine('[simple.http]: ' + args.join(' '))
// console.log('[simple.http]: ' + args.join(' '))
}
const HMR_SCRIPT = `
<script>
!(function http_hmr(){
var ws = new WebSocket(\`ws://\${location.host}/ws-http-hmr\`)
ws.addEventListener('open', function (r) {
if(http_hmr.closed){
delete http_hmr.closed
location.reload()
}
console.log('http.server hmr ready...')
})
ws.addEventListener('close', function(){
http_hmr.closed = true
setTimeout(http_hmr, 2000)
})
ws.addEventListener('message', function (ev) {
location.reload()
})
})()
</script>
`
class WebSocket {
#ws = null // ws实例
#queue = [] // 消息队列
constructor(server) {
if (server.listening) {
let conn = new WebSocketServer({ server, path: '/ws-http-hmr' })
conn.on('connection', ws => {
this.#ws = ws
if (this.#queue.length) {
this.#queue = []
this.send()
}
})
}
}
send(msg = {}) {
if (this.#ws) {
this.#ws.send(JSON.stringify(msg))
} else {
this.#queue.push(msg)
}
}
}
function createServer() {
@ -68,7 +120,7 @@ function createServer() {
return
}
std.out(`尝试使用${port}端口...`)
http
const server = http
.createServer(function (req, res) {
let pathname = parse(req.url).pathname.slice(1)
let paths = pathname.split('/')
@ -82,6 +134,7 @@ function createServer() {
let file = join(root, pathname)
let stat = fs.stat(file)
let ext = pathname.split('.').pop()
let code
for (let k in COMMON_HEADERS) {
res.setHeader(k, COMMON_HEADERS[k])
@ -90,18 +143,40 @@ function createServer() {
if (stat.isFile()) {
res.setHeader('accept-ranges', 'bytes')
res.setHeader('content-type', MIME_TYPES[ext] || MIME_TYPES.other)
if (ext === 'html') {
code = fs
.cat(file)
.toString()
.replace('</head>', HMR_SCRIPT + '</head>')
res.setHeader('content-length', code.length)
} else {
res.setHeader('content-length', stat.size)
}
res.writeHead(200, 'OK')
if (ext === 'html') {
res.end(code)
} else {
fs.origin.createReadStream(file).pipe(res)
}
} else {
if (paths.length > 1 && paths[0].endsWith('.html')) {
let rootFile = join(root, paths[0])
if (fs.isfile(rootFile)) {
code = fs
.cat(rootFile)
.toString()
.replace('</head>', HMR_SCRIPT + '</head>')
res.setHeader('accept-ranges', 'bytes')
res.setHeader('content-type', MIME_TYPES.html)
res.setHeader('content-length', fs.stat(rootFile).size)
res.setHeader('content-length', code.length)
res.writeHead(200, 'OK')
fs.origin.createReadStream(rootFile).pipe(res)
res.end(code)
return
}
}
@ -117,8 +192,10 @@ function createServer() {
port++
createServer()
})
.on('listening', _ => {
server.on('listening', _ => {
baseUrl = 'http://127.0.0.1:' + port
ws = new WebSocket(server)
std.out('启动成功, 请访问', baseUrl)
})
}
@ -166,6 +243,15 @@ function deactivate() {}
exports.activate = function (ctx) {
__init__()
vsc.workspace.onDidSaveTextDocument(doc => {
if (enabled) {
let file = doc.fileName
if (file.startsWith(root)) {
ws.send()
}
}
})
let cmd = vsc.commands.registerCommand('HttpServer.open', _ => {
if (enabled) {
let editor = vsc.window.activeTextEditor

View File

@ -2,7 +2,7 @@
"name": "simple-http",
"displayName": "simple http",
"description": "🔥 简单的http服务器, 方便临时调试html",
"version": "1.4.0",
"version": "1.5.0",
"publisher": "yutent",
"author": "Yutent [@yutent]",
"icon": "logo.png",
@ -44,8 +44,9 @@
"yutent"
],
"scripts": {
"start": "esbuild index.js --bundle --outfile=out.js --external:vscode --format=cjs --platform=node",
"pack": "npm start -- --minify"
"bundle": "esbuild index.js --bundle --outfile=out.js --external:vscode --format=cjs --platform=node",
"start": "npm run bundle -- --watch",
"pack": "npm run bundle -- --minify"
},
"license": "MIT",
"dependencies": {},

3
test/.httpserver Normal file
View File

@ -0,0 +1,3 @@
{
"enabled": true
}

16
test/index.html Normal file
View File

@ -0,0 +1,16 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title></title>
<meta name="keywords" content="">
<meta name="description" content="">
<link href="" rel="stylesheet">
</head>
<body>
sdsdsdsdsssdsdsd
dsdsd
</body>
</html>