Skip to content

Commit 890f929

Browse files
committed
feat: dev server
1 parent 4cbb6b6 commit 890f929

15 files changed

+1000
-1074
lines changed

bin/vuepress.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
const path = require('path')
2-
const { build } = require('../lib')
2+
const { serve, build } = require('../lib')
33

4-
build(path.resolve(__dirname, '../docs')).catch(err => {
5-
console.log(err)
4+
const sourceDir = path.resolve(__dirname, '../docs')
5+
6+
serve(sourceDir).catch(err => {
7+
console.error(err)
68
})
9+
10+
// build(sourceDir).catch(err => {
11+
// console.log(err)
12+
// })

docs/kitchen/sink.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---
12
foo: 123
23
bar: 234
34
---

lib/app/app.js

+5-7
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import Vue from 'vue'
22
import Router from 'vue-router'
33
Vue.use(Router)
44

5-
// layout is resolved dynamically and set as an alias
6-
import Layout from '~layout'
7-
8-
// dynamically generated files:
9-
105
// register-commponents.js registers all *.vue files found in _components
116
// as global async components
127
import './.temp/register-components'
@@ -21,10 +16,13 @@ const router = new Router({
2116

2217
// expose Vue Press data
2318
const g = typeof window !== 'undefined' ? window : global
24-
const $site = Vue.prototype.$site = g.VUEPRESS_DATA
19+
const $site = g.VUEPRESS_DATA
2520

2621
Vue.mixin({
2722
computed: {
23+
$site () {
24+
return $site
25+
},
2826
$page () {
2927
return $site.pages[this.$route.path]
3028
}
@@ -34,7 +32,7 @@ Vue.mixin({
3432
Vue.component('Content', {
3533
functional: true,
3634
render (h, { parent }) {
37-
return h('page-' + parent.$page.name)
35+
return h(parent.$page.componentName)
3836
}
3937
})
4038

lib/build.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
module.exports = async function build (sourceDir) {
2+
process.env.NODE_ENV = 'production'
3+
4+
const prepare = require('./prepare')
5+
const path = require('path')
6+
const webpack = require('webpack')
7+
const { promisify } = require('util')
8+
const rimraf = promisify(require('rimraf'))
9+
const createClientConfig = require('./webpack/clientConfig')
10+
const createServerConfig = require('./webpack/serverConfig')
11+
12+
const options = await prepare(sourceDir)
13+
14+
const targetDir = path.resolve(sourceDir, '_dist')
15+
await rimraf(targetDir)
16+
17+
const clientConfig = createClientConfig(options).toConfig()
18+
const serverConfig = createServerConfig(options).toConfig()
19+
20+
await Promise.all([
21+
compile(clientConfig),
22+
compile(serverConfig)
23+
])
24+
25+
function compile (config) {
26+
return new Promise((resolve, reject) => {
27+
webpack(config, (err, stats) => {
28+
if (err) {
29+
return reject(err)
30+
}
31+
if (stats.hasErrors()) {
32+
reject(`Failed to compile with errors.`)
33+
stats.toJson().errors.forEach(err => {
34+
console.error(err)
35+
})
36+
return
37+
}
38+
resolve()
39+
})
40+
})
41+
}
42+
}

lib/default-theme/App.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<router-link :to="page.path">{{ page.name }}</router-link>
66
</li>
77
</ul>
8-
<Index v-if="$page.isIndex" />
8+
<Index v-if="$page.path === '/index'" />
99
<Page v-else />
1010
</div>
1111
</template>

lib/default-theme/index.html

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title></title>
6+
</head>
7+
<body>
8+
<div id="app"></div>
9+
</body>
10+
</html>

lib/index.js

+2-79
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,2 @@
1-
const fs = require('fs')
2-
const path = require('path')
3-
const rimraf = require('rimraf')
4-
const globby = require('globby')
5-
const webpack = require('webpack')
6-
const tempPath = path.resolve(__dirname, 'app/.temp')
7-
const createClientConfig = require('./webpack/clientConfig')
8-
const createServerConfig = require('./webpack/serverConfig')
9-
10-
exports.build = async function build (sourceDir) {
11-
// 1. loadConfig
12-
// const config = await resolveConfig(sourceDir)
13-
14-
// 2. generate dynamic component registration file
15-
await genComponentRegistrationFile(sourceDir)
16-
17-
// 3. generate routes
18-
await genRoutesFile(sourceDir)
19-
20-
// 4. client build
21-
const clientConfig = createClientConfig({ sourceDir }).toConfig()
22-
return new Promise((resolve, reject) => {
23-
rimraf.sync(clientConfig.output.path)
24-
webpack(clientConfig, (err, stats) => {
25-
if (err) {
26-
return reject(err)
27-
}
28-
if (stats.hasErrors()) {
29-
return reject(stats.toJson().errors)
30-
}
31-
resolve()
32-
})
33-
})
34-
}
35-
36-
async function genComponentRegistrationFile (sourceDir) {
37-
const pages = await globby(['**/*.md'], { cwd: sourceDir })
38-
const components = (await resolveComponents(sourceDir)) || []
39-
40-
function genImport (file) {
41-
const isPage = /\.md$/.test(file)
42-
const name = (isPage ? `page-` : ``) + file.replace(/\.(vue|md)$/, '').replace(/\/|\\/, '-')
43-
const baseDir = isPage ? sourceDir : path.resolve(sourceDir, '_components')
44-
const absolutePath = path.resolve(baseDir, file)
45-
const code = `Vue.component(${JSON.stringify(name)}, () => import(${JSON.stringify(absolutePath)}))`
46-
return code
47-
}
48-
49-
const all = [...pages, ...components]
50-
const file = `import Vue from 'vue'\n` + all.map(genImport).join('\n')
51-
fs.writeFileSync(path.join(tempPath, 'register-components.js'), file)
52-
}
53-
54-
async function resolveComponents (sourceDir) {
55-
const componentDir = path.resolve(sourceDir, '_components')
56-
if (!fs.existsSync(componentDir)) {
57-
return
58-
}
59-
return await globby(['*.vue'], { cwd: componentDir })
60-
}
61-
62-
async function genRoutesFile (sourceDir) {
63-
const pages = await globby(['**/*.md'], { cwd: sourceDir })
64-
65-
function genRoute (file) {
66-
const name = file.replace(/\.md$/, '')
67-
const code = `
68-
{
69-
path: ${JSON.stringify('/' + (name === 'index' ? '' : name))},
70-
component: Layout
71-
}`
72-
return code
73-
}
74-
75-
const file =
76-
`import Layout from '~layout'\n` +
77-
`export default [${pages.map(genRoute).join(',')}\n]`
78-
fs.writeFileSync(path.join(tempPath, 'routes.js'), file)
79-
}
1+
exports.build = require('./build')
2+
exports.serve = require('./serve')

lib/prepare.js

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
const globby = require('globby')
4+
const yaml = require('yaml-front-matter')
5+
const tempPath = path.resolve(__dirname, 'app/.temp')
6+
7+
module.exports = async function prepare (sourceDir) {
8+
// 1. load options
9+
const options = await resolveOptions(sourceDir)
10+
11+
// 2. generate dynamic component registration file
12+
await genComponentRegistrationFile(options)
13+
14+
// 3. generate routes
15+
await genRoutesFile(options)
16+
17+
return options
18+
}
19+
20+
async function resolveOptions (sourceDir) {
21+
const configPath = path.resolve(sourceDir, 'vuepress.config.js')
22+
const siteConfig = fs.existsSync(configPath) ? require(configPath) : {}
23+
24+
const options = {
25+
siteConfig,
26+
sourceDir,
27+
publicPath: siteConfig.baseUrl || '/',
28+
themePath: path.resolve(__dirname, 'default-theme/App.vue'),
29+
templatePath: path.resolve(__dirname, 'default-theme/index.html'),
30+
pages: await globby(['**/*.md'], { cwd: sourceDir })
31+
}
32+
33+
// resolve theme & index template
34+
const themeDir = path.resolve(sourceDir, '_theme')
35+
if (fs.existsSync(themeDir)) {
36+
const template = path.resolve(themeDir, 'index.html')
37+
if (fs.existsSync(template)) {
38+
options.templatePath = template
39+
}
40+
41+
const app = path.resolve(themeDir, 'App.vue')
42+
if (fs.existsSync(app)) {
43+
options.themePath = app
44+
}
45+
}
46+
47+
const pagesData = {}
48+
options.pages.forEach(file => {
49+
const name = file.replace(/\.md$/, '')
50+
const urlPath = '/' + (name === 'index' ? '' : name)
51+
const componentName = toComponentName(file)
52+
const content = fs.readFileSync(path.resolve(sourceDir, file), 'utf-8')
53+
const frontmatter = yaml.loadFront(content)
54+
delete frontmatter.__content
55+
pagesData[urlPath] = {
56+
name,
57+
path: urlPath,
58+
componentName,
59+
frontmatter
60+
}
61+
})
62+
63+
options.siteData = Object.assign({}, siteConfig.data, {
64+
pages: pagesData
65+
})
66+
67+
return options
68+
}
69+
70+
function toComponentName (file) {
71+
const isPage = /\.md$/.test(file)
72+
return (
73+
(isPage ? `page-` : ``) +
74+
file
75+
.replace(/\.(vue|md)$/, '')
76+
.replace(/\/|\\/, '-')
77+
)
78+
}
79+
80+
async function genComponentRegistrationFile ({ sourceDir, pages }) {
81+
function genImport (file) {
82+
const name = toComponentName(file)
83+
const baseDir = /\.md$/.test(file)
84+
? sourceDir
85+
: path.resolve(sourceDir, '_components')
86+
const absolutePath = path.resolve(baseDir, file)
87+
const code = `Vue.component(${JSON.stringify(name)}, () => import(${JSON.stringify(absolutePath)}))`
88+
return code
89+
}
90+
91+
const components = (await resolveComponents(sourceDir)) || []
92+
const all = [...pages, ...components]
93+
const file = `import Vue from 'vue'\n` + all.map(genImport).join('\n')
94+
fs.writeFileSync(path.join(tempPath, 'register-components.js'), file)
95+
}
96+
97+
async function resolveComponents (sourceDir) {
98+
const componentDir = path.resolve(sourceDir, '_components')
99+
if (!fs.existsSync(componentDir)) {
100+
return
101+
}
102+
return await globby(['*.vue'], { cwd: componentDir })
103+
}
104+
105+
async function genRoutesFile ({ sourceDir, pages }) {
106+
function genRoute (file) {
107+
const name = file.replace(/\.md$/, '')
108+
const code = `
109+
{
110+
path: ${JSON.stringify('/' + (name === 'index' ? '' : name))},
111+
component: Theme
112+
}`
113+
return code
114+
}
115+
116+
const file =
117+
`import Theme from '~theme'\n` +
118+
`export default [${pages.map(genRoute).join(',')}\n]`
119+
fs.writeFileSync(path.join(tempPath, 'routes.js'), file)
120+
}

lib/serve.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module.exports = async function serve (sourceDir) {
2+
const chalk = require('chalk')
3+
const prepare = require('./prepare')
4+
const webpack = require('webpack')
5+
const serve = require('webpack-serve')
6+
const HTMLPlugin = require('html-webpack-plugin')
7+
const convert = require('koa-connect')
8+
const history = require('connect-history-api-fallback')
9+
const createClientConfig = require('./webpack/clientConfig')
10+
const SiteDataPlugin = require('./webpack/SiteDataPlugin')
11+
12+
const options = await prepare(sourceDir)
13+
14+
const _config = createClientConfig(options)
15+
16+
_config
17+
.plugin('html')
18+
.use(HTMLPlugin, [{ template: options.templatePath }])
19+
20+
_config
21+
.plugin('site-data')
22+
.use(SiteDataPlugin, [options.siteData])
23+
24+
const config = _config.toConfig()
25+
const compiler = webpack(config)
26+
const port = options.siteConfig.port || 8080
27+
28+
let isFirst = true
29+
compiler.hooks.done.tap('vuepress', () => {
30+
if (isFirst) {
31+
isFirst = false
32+
console.log(
33+
`\n VuePress dev server listening at ${
34+
chalk.cyan(`http://localhost:${port}`)
35+
}\n`
36+
)
37+
}
38+
})
39+
40+
await serve({
41+
compiler,
42+
dev: { logLevel: 'error' },
43+
hot: { logLevel: 'error' },
44+
logLevel: 'error',
45+
port,
46+
add: app => app.use(convert(history()))
47+
})
48+
}

lib/webpack/SiteDataPlugin.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module.exports = class SiteDataPlugin {
2+
constructor (data) {
3+
this.data = data
4+
}
5+
6+
apply (compiler) {
7+
compiler.hooks.compilation.tap('vuepress-site-data', compilation => {
8+
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('vuepress-site-data', (data, cb) => {
9+
try {
10+
data.head.push({
11+
tagName: 'script',
12+
closeTag: true,
13+
innerHTML: `window.VUEPRESS_DATA = ${JSON.stringify(this.data)}`
14+
})
15+
} catch (e) {
16+
return cb(e)
17+
}
18+
cb(null, data)
19+
})
20+
})
21+
}
22+
}

0 commit comments

Comments
 (0)