Skip to content

Commit

Permalink
feat: dev server
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Apr 4, 2018
1 parent 4cbb6b6 commit 890f929
Show file tree
Hide file tree
Showing 15 changed files with 1,000 additions and 1,074 deletions.
12 changes: 9 additions & 3 deletions bin/vuepress.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
const path = require('path')
const { build } = require('../lib')
const { serve, build } = require('../lib')

build(path.resolve(__dirname, '../docs')).catch(err => {
console.log(err)
const sourceDir = path.resolve(__dirname, '../docs')

serve(sourceDir).catch(err => {
console.error(err)
})

// build(sourceDir).catch(err => {
// console.log(err)
// })
1 change: 1 addition & 0 deletions docs/kitchen/sink.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
foo: 123
bar: 234
---
Expand Down
12 changes: 5 additions & 7 deletions lib/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

// layout is resolved dynamically and set as an alias
import Layout from '~layout'

// dynamically generated files:

// register-commponents.js registers all *.vue files found in _components
// as global async components
import './.temp/register-components'
Expand All @@ -21,10 +16,13 @@ const router = new Router({

// expose Vue Press data
const g = typeof window !== 'undefined' ? window : global
const $site = Vue.prototype.$site = g.VUEPRESS_DATA
const $site = g.VUEPRESS_DATA

Vue.mixin({
computed: {
$site () {
return $site
},
$page () {
return $site.pages[this.$route.path]
}
Expand All @@ -34,7 +32,7 @@ Vue.mixin({
Vue.component('Content', {
functional: true,
render (h, { parent }) {
return h('page-' + parent.$page.name)
return h(parent.$page.componentName)
}
})

Expand Down
42 changes: 42 additions & 0 deletions lib/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module.exports = async function build (sourceDir) {
process.env.NODE_ENV = 'production'

const prepare = require('./prepare')
const path = require('path')
const webpack = require('webpack')
const { promisify } = require('util')
const rimraf = promisify(require('rimraf'))
const createClientConfig = require('./webpack/clientConfig')
const createServerConfig = require('./webpack/serverConfig')

const options = await prepare(sourceDir)

const targetDir = path.resolve(sourceDir, '_dist')
await rimraf(targetDir)

const clientConfig = createClientConfig(options).toConfig()
const serverConfig = createServerConfig(options).toConfig()

await Promise.all([
compile(clientConfig),
compile(serverConfig)
])

function compile (config) {
return new Promise((resolve, reject) => {
webpack(config, (err, stats) => {
if (err) {
return reject(err)
}
if (stats.hasErrors()) {
reject(`Failed to compile with errors.`)
stats.toJson().errors.forEach(err => {
console.error(err)
})
return
}
resolve()
})
})
}
}
2 changes: 1 addition & 1 deletion lib/default-theme/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<router-link :to="page.path">{{ page.name }}</router-link>
</li>
</ul>
<Index v-if="$page.isIndex" />
<Index v-if="$page.path === '/index'" />
<Page v-else />
</div>
</template>
Expand Down
10 changes: 10 additions & 0 deletions lib/default-theme/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
81 changes: 2 additions & 79 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,2 @@
const fs = require('fs')
const path = require('path')
const rimraf = require('rimraf')
const globby = require('globby')
const webpack = require('webpack')
const tempPath = path.resolve(__dirname, 'app/.temp')
const createClientConfig = require('./webpack/clientConfig')
const createServerConfig = require('./webpack/serverConfig')

exports.build = async function build (sourceDir) {
// 1. loadConfig
// const config = await resolveConfig(sourceDir)

// 2. generate dynamic component registration file
await genComponentRegistrationFile(sourceDir)

// 3. generate routes
await genRoutesFile(sourceDir)

// 4. client build
const clientConfig = createClientConfig({ sourceDir }).toConfig()
return new Promise((resolve, reject) => {
rimraf.sync(clientConfig.output.path)
webpack(clientConfig, (err, stats) => {
if (err) {
return reject(err)
}
if (stats.hasErrors()) {
return reject(stats.toJson().errors)
}
resolve()
})
})
}

async function genComponentRegistrationFile (sourceDir) {
const pages = await globby(['**/*.md'], { cwd: sourceDir })
const components = (await resolveComponents(sourceDir)) || []

function genImport (file) {
const isPage = /\.md$/.test(file)
const name = (isPage ? `page-` : ``) + file.replace(/\.(vue|md)$/, '').replace(/\/|\\/, '-')
const baseDir = isPage ? sourceDir : path.resolve(sourceDir, '_components')
const absolutePath = path.resolve(baseDir, file)
const code = `Vue.component(${JSON.stringify(name)}, () => import(${JSON.stringify(absolutePath)}))`
return code
}

const all = [...pages, ...components]
const file = `import Vue from 'vue'\n` + all.map(genImport).join('\n')
fs.writeFileSync(path.join(tempPath, 'register-components.js'), file)
}

async function resolveComponents (sourceDir) {
const componentDir = path.resolve(sourceDir, '_components')
if (!fs.existsSync(componentDir)) {
return
}
return await globby(['*.vue'], { cwd: componentDir })
}

async function genRoutesFile (sourceDir) {
const pages = await globby(['**/*.md'], { cwd: sourceDir })

function genRoute (file) {
const name = file.replace(/\.md$/, '')
const code = `
{
path: ${JSON.stringify('/' + (name === 'index' ? '' : name))},
component: Layout
}`
return code
}

const file =
`import Layout from '~layout'\n` +
`export default [${pages.map(genRoute).join(',')}\n]`
fs.writeFileSync(path.join(tempPath, 'routes.js'), file)
}
exports.build = require('./build')
exports.serve = require('./serve')
120 changes: 120 additions & 0 deletions lib/prepare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
const fs = require('fs')
const path = require('path')
const globby = require('globby')
const yaml = require('yaml-front-matter')
const tempPath = path.resolve(__dirname, 'app/.temp')

module.exports = async function prepare (sourceDir) {
// 1. load options
const options = await resolveOptions(sourceDir)

// 2. generate dynamic component registration file
await genComponentRegistrationFile(options)

// 3. generate routes
await genRoutesFile(options)

return options
}

async function resolveOptions (sourceDir) {
const configPath = path.resolve(sourceDir, 'vuepress.config.js')
const siteConfig = fs.existsSync(configPath) ? require(configPath) : {}

const options = {
siteConfig,
sourceDir,
publicPath: siteConfig.baseUrl || '/',
themePath: path.resolve(__dirname, 'default-theme/App.vue'),
templatePath: path.resolve(__dirname, 'default-theme/index.html'),
pages: await globby(['**/*.md'], { cwd: sourceDir })
}

// resolve theme & index template
const themeDir = path.resolve(sourceDir, '_theme')
if (fs.existsSync(themeDir)) {
const template = path.resolve(themeDir, 'index.html')
if (fs.existsSync(template)) {
options.templatePath = template
}

const app = path.resolve(themeDir, 'App.vue')
if (fs.existsSync(app)) {
options.themePath = app
}
}

const pagesData = {}
options.pages.forEach(file => {
const name = file.replace(/\.md$/, '')
const urlPath = '/' + (name === 'index' ? '' : name)
const componentName = toComponentName(file)
const content = fs.readFileSync(path.resolve(sourceDir, file), 'utf-8')
const frontmatter = yaml.loadFront(content)
delete frontmatter.__content
pagesData[urlPath] = {
name,
path: urlPath,
componentName,
frontmatter
}
})

options.siteData = Object.assign({}, siteConfig.data, {
pages: pagesData
})

return options
}

function toComponentName (file) {
const isPage = /\.md$/.test(file)
return (
(isPage ? `page-` : ``) +
file
.replace(/\.(vue|md)$/, '')
.replace(/\/|\\/, '-')
)
}

async function genComponentRegistrationFile ({ sourceDir, pages }) {
function genImport (file) {
const name = toComponentName(file)
const baseDir = /\.md$/.test(file)
? sourceDir
: path.resolve(sourceDir, '_components')
const absolutePath = path.resolve(baseDir, file)
const code = `Vue.component(${JSON.stringify(name)}, () => import(${JSON.stringify(absolutePath)}))`
return code
}

const components = (await resolveComponents(sourceDir)) || []
const all = [...pages, ...components]
const file = `import Vue from 'vue'\n` + all.map(genImport).join('\n')
fs.writeFileSync(path.join(tempPath, 'register-components.js'), file)
}

async function resolveComponents (sourceDir) {
const componentDir = path.resolve(sourceDir, '_components')
if (!fs.existsSync(componentDir)) {
return
}
return await globby(['*.vue'], { cwd: componentDir })
}

async function genRoutesFile ({ sourceDir, pages }) {
function genRoute (file) {
const name = file.replace(/\.md$/, '')
const code = `
{
path: ${JSON.stringify('/' + (name === 'index' ? '' : name))},
component: Theme
}`
return code
}

const file =
`import Theme from '~theme'\n` +
`export default [${pages.map(genRoute).join(',')}\n]`
fs.writeFileSync(path.join(tempPath, 'routes.js'), file)
}
48 changes: 48 additions & 0 deletions lib/serve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module.exports = async function serve (sourceDir) {
const chalk = require('chalk')
const prepare = require('./prepare')
const webpack = require('webpack')
const serve = require('webpack-serve')
const HTMLPlugin = require('html-webpack-plugin')
const convert = require('koa-connect')
const history = require('connect-history-api-fallback')
const createClientConfig = require('./webpack/clientConfig')
const SiteDataPlugin = require('./webpack/SiteDataPlugin')

const options = await prepare(sourceDir)

const _config = createClientConfig(options)

_config
.plugin('html')
.use(HTMLPlugin, [{ template: options.templatePath }])

_config
.plugin('site-data')
.use(SiteDataPlugin, [options.siteData])

const config = _config.toConfig()
const compiler = webpack(config)
const port = options.siteConfig.port || 8080

let isFirst = true
compiler.hooks.done.tap('vuepress', () => {
if (isFirst) {
isFirst = false
console.log(
`\n VuePress dev server listening at ${
chalk.cyan(`http://localhost:${port}`)
}\n`
)
}
})

await serve({
compiler,
dev: { logLevel: 'error' },
hot: { logLevel: 'error' },
logLevel: 'error',
port,
add: app => app.use(convert(history()))
})
}
22 changes: 22 additions & 0 deletions lib/webpack/SiteDataPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = class SiteDataPlugin {
constructor (data) {
this.data = data
}

apply (compiler) {
compiler.hooks.compilation.tap('vuepress-site-data', compilation => {
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('vuepress-site-data', (data, cb) => {
try {
data.head.push({
tagName: 'script',
closeTag: true,
innerHTML: `window.VUEPRESS_DATA = ${JSON.stringify(this.data)}`
})
} catch (e) {
return cb(e)
}
cb(null, data)
})
})
}
}
Loading

0 comments on commit 890f929

Please sign in to comment.