Skip to content

Commit 820c2cf

Browse files
committed
init
0 parents  commit 820c2cf

File tree

11 files changed

+654
-0
lines changed

11 files changed

+654
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
node_modules
3+
TODOs.md
4+
playground

.prettierrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
semi: false
2+
singleQuote: true
3+
printWidth: 80
4+
trailingComma: none

bin/vds.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
require('../lib/server')

lib/fileWatcher.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
const chokidar = require('chokidar')
4+
const { parseSFC } = require('./parseSFC')
5+
6+
exports.createFileWatcher = (notify) => {
7+
const fileWatcher = chokidar.watch(process.cwd(), {
8+
ignored: [/node_modules/]
9+
})
10+
11+
fileWatcher.on('change', (file) => {
12+
if (file.endsWith('.vue')) {
13+
// check which part of the file changed
14+
const [descriptor, prevDescriptor] = parseSFC(file)
15+
const resourcePath = '/' + path.relative(process.cwd(), file)
16+
17+
if (!prevDescriptor) {
18+
// the file has never been accessed yet
19+
return
20+
}
21+
22+
if (
23+
(descriptor.script && descriptor.script.content) !==
24+
(prevDescriptor.script && prevDescriptor.script.content)
25+
) {
26+
console.log(`[hmr] <script> for ${resourcePath} changed. Triggering component reload.`)
27+
notify({
28+
type: 'reload',
29+
path: resourcePath
30+
})
31+
return
32+
}
33+
34+
if (
35+
(descriptor.template && descriptor.template.content) !==
36+
(prevDescriptor.template && prevDescriptor.template.content)
37+
) {
38+
console.log(`[hmr] <template> for ${resourcePath} changed. Triggering component re-render.`)
39+
notify({
40+
type: 'rerender',
41+
path: resourcePath
42+
})
43+
return
44+
}
45+
46+
// TODO styles
47+
} else {
48+
console.log(`[hmr] script file ${resourcePath} changed. Triggering full page reload.`)
49+
notify({
50+
type: 'full-reload'
51+
})
52+
}
53+
})
54+
}

lib/hmrProxy.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// This file runs in the browser.
2+
3+
const socket = new WebSocket(`ws://${location.host}`)
4+
5+
// Listen for messages
6+
socket.addEventListener('message', ({ data }) => {
7+
const { type, path, index } = JSON.parse(data)
8+
switch (type) {
9+
case 'connected':
10+
console.log(`[vds] connected.`)
11+
break
12+
case 'reload':
13+
import(`${path}?t=${Date.now()}`).then(m => {
14+
__VUE_HMR_RUNTIME__.reload(path, m.default)
15+
console.log(`[vds][hmr] ${path} reloaded.`)
16+
})
17+
break
18+
case 'rerender':
19+
import(`${path}?type=template&t=${Date.now()}`).then(m => {
20+
__VUE_HMR_RUNTIME__.rerender(path, m.render)
21+
console.log(`[vds][hmr] ${path} template updated.`)
22+
})
23+
break
24+
case 'update-style':
25+
import(`${path}?type=style&index=${index}&t=${Date.now()}`).then(m => {
26+
// TODO style hmr
27+
})
28+
break
29+
case 'full-reload':
30+
location.reload()
31+
}
32+
})
33+
34+
// ping server
35+
socket.addEventListener('close', () => {
36+
console.log(`[vds] server connection lost. polling for restart...`)
37+
setInterval(() => {
38+
new WebSocket(`ws://${location.host}`).addEventListener('open', () => {
39+
location.reload()
40+
})
41+
}, 1000)
42+
})

lib/parseSFC.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const fs = require('fs')
2+
const { parse } = require('@vue/compiler-sfc')
3+
4+
const cache = new Map()
5+
6+
exports.parseSFC = filename => {
7+
const content = fs.readFileSync(filename, 'utf-8')
8+
const { descriptor, errors } = parse(content, {
9+
filename
10+
})
11+
12+
if (errors) {
13+
// TODO
14+
}
15+
16+
const prev = cache.get(filename)
17+
cache.set(filename, descriptor)
18+
return [descriptor, prev]
19+
}

lib/server.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
const http = require('http')
4+
const url = require('url')
5+
const ws = require('ws')
6+
const serve = require('serve-handler')
7+
const vue = require('./vueMiddleware')
8+
const { createFileWatcher } = require('./fileWatcher')
9+
const { sendJS } = require('./utils')
10+
11+
const hmrProxy = fs.readFileSync(path.resolve(__dirname, './hmrProxy.js'))
12+
13+
const server = http.createServer((req, res) => {
14+
const pathname = url.parse(req.url).pathname
15+
if (pathname === '/__hmrProxy') {
16+
sendJS(res, hmrProxy)
17+
} else if (pathname.endsWith('.vue')) {
18+
vue(req, res)
19+
} else {
20+
serve(req, res)
21+
}
22+
})
23+
24+
const wss = new ws.Server({ server })
25+
const sockets = new Set()
26+
wss.on('connection', (socket) => {
27+
sockets.add(socket)
28+
socket.send(JSON.stringify({ type: 'connected'}))
29+
socket.on('close', () => {
30+
sockets.delete(socket)
31+
})
32+
})
33+
34+
createFileWatcher((payload) =>
35+
sockets.forEach((s) => s.send(JSON.stringify(payload)))
36+
)
37+
38+
server.listen(3000, () => {
39+
console.log('Running at http://localhost:3000')
40+
})

lib/utils.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function send(res, source, mime) {
2+
res.setHeader('Content-Type', mime)
3+
res.end(source)
4+
}
5+
6+
function sendJS(res, source) {
7+
send(res, source, 'application/javascript')
8+
}
9+
10+
exports.send = send
11+
exports.sendJS = sendJS

lib/vueMiddleware.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const fs = require('fs')
2+
const url = require('url')
3+
const path = require('path')
4+
const qs = require('querystring')
5+
const { parseSFC } = require('./parseSFC')
6+
const { compileTemplate } = require('@vue/compiler-sfc')
7+
const { sendJS } = require('./utils')
8+
9+
module.exports = (req, res) => {
10+
const parsed = url.parse(req.url, true)
11+
const query = parsed.query
12+
const filename = path.join(process.cwd(), parsed.pathname.slice(1))
13+
const [descriptor] = parseSFC(filename)
14+
if (!query.type) {
15+
let code = ``
16+
// TODO use more robust rewrite
17+
if (descriptor.script) {
18+
code += descriptor.script.content.replace(
19+
`export default`,
20+
'const script ='
21+
)
22+
code += `\nexport default script`
23+
}
24+
if (descriptor.template) {
25+
code += `\nimport { render } from ${JSON.stringify(
26+
parsed.pathname + `?type=template${query.t ? `&t=${query.t}` : ``}`
27+
)}`
28+
code += `\nscript.render = render`
29+
}
30+
if (descriptor.style) {
31+
// TODO
32+
}
33+
code += `\nscript.__hmrId = ${JSON.stringify(parsed.pathname)}`
34+
return sendJS(res, code)
35+
}
36+
37+
if (query.type === 'template') {
38+
const { code, errors } = compileTemplate({
39+
source: descriptor.template.content,
40+
filename,
41+
compilerOptions: {
42+
runtimeModuleName: '/vue.js'
43+
}
44+
})
45+
46+
if (errors) {
47+
// TODO
48+
}
49+
return sendJS(res, code)
50+
}
51+
52+
if (query.type === 'style') {
53+
// TODO
54+
return
55+
}
56+
57+
// TODO custom blocks
58+
}

package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "@vue/dev-server",
3+
"version": "0.2.0",
4+
"bin": {
5+
"vds": "bin/vds.js"
6+
},
7+
"dependencies": {
8+
"@vue/compiler-sfc": "^3.0.0-beta.2",
9+
"chokidar": "^3.3.1",
10+
"serve-handler": "^6.1.2",
11+
"ws": "^7.2.3"
12+
}
13+
}

0 commit comments

Comments
 (0)