mirror of
https://github.com/wahyd4/vuepress.git
synced 2026-08-09 05:16:23 +10:00
feat: refine node api (#1395)
- fix($core): markdown slot doesn‘t work. (regression of c85f62d)
- docs: fresh Node.JS API.
- test: official plugins.
This commit is contained in:
@@ -3,6 +3,9 @@ module.exports = {
|
||||
'test': {
|
||||
'presets': [
|
||||
['@babel/preset-env', { 'targets': { 'node': 'current' }}]
|
||||
],
|
||||
'plugins': [
|
||||
'@babel/plugin-syntax-dynamic-import'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"remote-version": "node scripts/remote-version.js",
|
||||
"dev": "yarn tsc && yarn workspace docs dev",
|
||||
"build": "yarn tsc && yarn workspace docs build",
|
||||
"view-info": "yarn tsc && yarn workspace docs view-info",
|
||||
"show-help": "yarn workspace docs show-help",
|
||||
"dev:blog": "yarn tsc && yarn workspace blog dev",
|
||||
"build:blog": "yarn tsc && yarn workspace blog build",
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = async function build (sourceDir, cliOptions = {}) {
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
const webpack = require('webpack')
|
||||
const readline = require('readline')
|
||||
const escape = require('escape-html')
|
||||
|
||||
const { chalk, fs, path, logger, env, performance } = require('@vuepress/shared-utils')
|
||||
const prepare = require('./prepare/index')
|
||||
const createClientConfig = require('./webpack/createClientConfig')
|
||||
const createServerConfig = require('./webpack/createServerConfig')
|
||||
const { createBundleRenderer } = require('vue-server-renderer')
|
||||
const { normalizeHeadTag, applyUserWebpackConfig } = require('./util/index')
|
||||
|
||||
logger.wait('Extracting site metadata...')
|
||||
const ctx = await prepare(sourceDir, cliOptions, true /* isProd */)
|
||||
|
||||
const { outDir, cwd } = ctx
|
||||
if (cwd === outDir) {
|
||||
return console.error(logger.error(chalk.red('Unexpected option: outDir cannot be set to the current working directory.\n'), false))
|
||||
}
|
||||
|
||||
await fs.emptyDir(outDir)
|
||||
logger.debug('Dist directory: ' + chalk.gray(outDir))
|
||||
|
||||
let clientConfig = createClientConfig(ctx, cliOptions).toConfig()
|
||||
let serverConfig = createServerConfig(ctx, cliOptions).toConfig()
|
||||
|
||||
// apply user config...
|
||||
const userConfig = ctx.siteConfig.configureWebpack
|
||||
if (userConfig) {
|
||||
clientConfig = applyUserWebpackConfig(userConfig, clientConfig, false)
|
||||
serverConfig = applyUserWebpackConfig(userConfig, serverConfig, true)
|
||||
}
|
||||
|
||||
// compile!
|
||||
const stats = await compile([clientConfig, serverConfig])
|
||||
|
||||
const serverBundle = require(path.resolve(outDir, 'manifest/server.json'))
|
||||
const clientManifest = require(path.resolve(outDir, 'manifest/client.json'))
|
||||
|
||||
// remove manifests after loading them.
|
||||
await fs.remove(path.resolve(outDir, 'manifest'))
|
||||
|
||||
// find and remove empty style chunk caused by
|
||||
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
|
||||
// TODO remove when it's fixed
|
||||
if (!clientConfig.devtool && (!clientConfig.plugins ||
|
||||
!clientConfig.plugins.some(p =>
|
||||
p instanceof webpack.SourceMapDevToolPlugin ||
|
||||
p instanceof webpack.EvalSourceMapDevToolPlugin
|
||||
))) {
|
||||
await workaroundEmptyStyleChunk()
|
||||
}
|
||||
|
||||
// create server renderer using built manifests
|
||||
const renderer = createBundleRenderer(serverBundle, {
|
||||
clientManifest,
|
||||
runInNewContext: false,
|
||||
inject: false,
|
||||
shouldPrefetch: ctx.siteConfig.shouldPrefetch || (() => true),
|
||||
template: await fs.readFile(ctx.ssrTemplate, 'utf-8')
|
||||
})
|
||||
|
||||
// pre-render head tags from user config
|
||||
const userHeadTags = (ctx.siteConfig.head || [])
|
||||
.map(renderHeadTag)
|
||||
.join('\n ')
|
||||
|
||||
// if the user does not have a custom 404.md, generate the theme's default
|
||||
if (!ctx.pages.some(p => p.path === '/404.html')) {
|
||||
ctx.addPage({ path: '/404.html' })
|
||||
}
|
||||
|
||||
// render pages
|
||||
logger.wait('Rendering static HTML...')
|
||||
|
||||
const pagePaths = []
|
||||
for (const page of ctx.pages) {
|
||||
pagePaths.push(await renderPage(page))
|
||||
}
|
||||
|
||||
readline.clearLine(process.stdout, 0)
|
||||
readline.cursorTo(process.stdout, 0)
|
||||
|
||||
await ctx.pluginAPI.options.generated.apply(pagePaths)
|
||||
|
||||
// DONE.
|
||||
const relativeDir = path.relative(cwd, outDir)
|
||||
logger.success(`Generated static files in ${chalk.cyan(relativeDir)}.`)
|
||||
const { duration } = performance.stop()
|
||||
logger.developer(`It took a total of ${chalk.cyan(`${duration}ms`)} to run the ${chalk.cyan('vuepress build')}.`)
|
||||
console.log()
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
function compile (config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
webpack(config, (err, stats) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
if (stats.hasErrors()) {
|
||||
stats.toJson().errors.forEach(err => {
|
||||
console.error(err)
|
||||
})
|
||||
reject(new Error(`Failed to compile with errors.`))
|
||||
return
|
||||
}
|
||||
if (env.isDebug && stats.hasWarnings()) {
|
||||
stats.toJson().warnings.forEach(warning => {
|
||||
console.warn(warning)
|
||||
})
|
||||
}
|
||||
resolve(stats.toJson({ modules: false }))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function renderHeadTag (tag) {
|
||||
const { tagName, attributes, innerHTML, closeTag } = normalizeHeadTag(tag)
|
||||
return `<${tagName}${renderAttrs(attributes)}>${innerHTML}${closeTag ? `</${tagName}>` : ``}`
|
||||
}
|
||||
|
||||
function renderAttrs (attrs = {}) {
|
||||
const keys = Object.keys(attrs)
|
||||
if (keys.length) {
|
||||
return ' ' + keys.map(name => `${name}="${escape(attrs[name])}"`).join(' ')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
async function renderPage (page) {
|
||||
const pagePath = page.path
|
||||
readline.clearLine(process.stdout, 0)
|
||||
readline.cursorTo(process.stdout, 0)
|
||||
process.stdout.write(`Rendering page: ${pagePath}`)
|
||||
|
||||
// #565 Avoid duplicate description meta at SSR.
|
||||
const meta = (page.frontmatter && page.frontmatter.meta || []).filter(item => item.name !== 'description')
|
||||
const pageMeta = renderPageMeta(meta)
|
||||
|
||||
const context = {
|
||||
url: pagePath,
|
||||
userHeadTags,
|
||||
pageMeta,
|
||||
title: 'VuePress',
|
||||
lang: 'en',
|
||||
description: ''
|
||||
}
|
||||
|
||||
let html
|
||||
try {
|
||||
html = await renderer.renderToString(context)
|
||||
} catch (e) {
|
||||
console.error(logger.error(chalk.red(`Error rendering ${pagePath}:`), false))
|
||||
throw e
|
||||
}
|
||||
const filename = decodeURIComponent(pagePath.replace(/\/$/, '/index.html').replace(/^\//, ''))
|
||||
const filePath = path.resolve(outDir, filename)
|
||||
await fs.ensureDir(path.dirname(filePath))
|
||||
await fs.writeFile(filePath, html)
|
||||
return filePath
|
||||
}
|
||||
|
||||
function renderPageMeta (meta) {
|
||||
if (!meta) return ''
|
||||
return meta.map(m => {
|
||||
let res = `<meta`
|
||||
Object.keys(m).forEach(key => {
|
||||
res += ` ${key}="${escape(m[key])}"`
|
||||
})
|
||||
return res + `>`
|
||||
}).join('')
|
||||
}
|
||||
|
||||
async function workaroundEmptyStyleChunk () {
|
||||
const styleChunk = stats.children[0].assets.find(a => {
|
||||
return /styles\.\w{8}\.js$/.test(a.name)
|
||||
})
|
||||
if (!styleChunk) return
|
||||
const styleChunkPath = path.resolve(outDir, styleChunk.name)
|
||||
const styleChunkContent = await fs.readFile(styleChunkPath, 'utf-8')
|
||||
await fs.remove(styleChunkPath)
|
||||
// prepend it to app.js.
|
||||
// this is necessary for the webpack runtime to work properly.
|
||||
const appChunk = stats.children[0].assets.find(a => {
|
||||
return /app\.\w{8}\.js$/.test(a.name)
|
||||
})
|
||||
const appChunkPath = path.resolve(outDir, appChunk.name)
|
||||
const appChunkContent = await fs.readFile(appChunkPath, 'utf-8')
|
||||
await fs.writeFile(appChunkPath, styleChunkContent + appChunkContent)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/* global VUEPRESS_TEMP_PATH */
|
||||
|
||||
import Vue from 'vue'
|
||||
import GLobalVue from 'vue'
|
||||
|
||||
export default function dataMixin (I18n, siteData) {
|
||||
export default function dataMixin (I18n, siteData, Vue = GLobalVue) {
|
||||
prepare(siteData)
|
||||
Vue.$vuepress.$set('siteData', siteData)
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = async (sourceDir, cliOptions = {}, ctx) => {
|
||||
const { server, host, port } = await prepareServer(sourceDir, cliOptions, ctx)
|
||||
server.listen(port, host, err => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.prepare = prepareServer
|
||||
|
||||
async function prepareServer (sourceDir, cliOptions = {}, context) {
|
||||
const WebpackDevServer = require('webpack-dev-server')
|
||||
const { path } = require('@vuepress/shared-utils')
|
||||
const webpack = require('webpack')
|
||||
const chokidar = require('chokidar')
|
||||
|
||||
const prepare = require('./prepare/index')
|
||||
const { chalk, fs, logger } = require('@vuepress/shared-utils')
|
||||
const HeadPlugin = require('./webpack/HeadPlugin')
|
||||
const DevLogPlugin = require('./webpack/DevLogPlugin')
|
||||
const createClientConfig = require('./webpack/createClientConfig')
|
||||
const { applyUserWebpackConfig } = require('./util/index')
|
||||
const { frontmatterEmitter } = require('@vuepress/markdown-loader')
|
||||
|
||||
const ctx = context || await prepare(sourceDir, cliOptions, false /* isProd */)
|
||||
|
||||
// setup watchers to update options and dynamically generated files
|
||||
const update = (reason) => {
|
||||
console.log(`Reload due to ${reason}`)
|
||||
ctx.pluginAPI.options.updated.syncApply()
|
||||
prepare(sourceDir, cliOptions, false /* isProd */).catch(err => {
|
||||
console.error(logger.error(chalk.red(err.stack), false))
|
||||
})
|
||||
}
|
||||
|
||||
// Curry update handler by update type
|
||||
const spawnUpdate = updateType => file => {
|
||||
const target = path.join(sourceDir, file)
|
||||
// Bust cache.
|
||||
delete require.cache[target]
|
||||
update(`${chalk.red(updateType)} ${chalk.cyan(file)}`)
|
||||
}
|
||||
|
||||
// watch add/remove of files
|
||||
const pagesWatcher = chokidar.watch([
|
||||
'**/*.md',
|
||||
'.vuepress/components/**/*.vue'
|
||||
], {
|
||||
cwd: sourceDir,
|
||||
ignored: ['.vuepress/**/*.md', 'node_modules'],
|
||||
ignoreInitial: true
|
||||
})
|
||||
pagesWatcher.on('add', spawnUpdate('add'))
|
||||
pagesWatcher.on('unlink', spawnUpdate('unlink'))
|
||||
pagesWatcher.on('addDir', spawnUpdate('addDir'))
|
||||
pagesWatcher.on('unlinkDir', spawnUpdate('unlinkDir'))
|
||||
|
||||
const watchFiles = [
|
||||
'.vuepress/config.js',
|
||||
'.vuepress/config.yml',
|
||||
'.vuepress/config.toml'
|
||||
].concat(
|
||||
(
|
||||
ctx.siteConfig.extraWatchFiles || []
|
||||
).map(file => normalizeWatchFilePath(file, ctx.sourceDir))
|
||||
)
|
||||
|
||||
logger.debug('watchFiles', watchFiles)
|
||||
|
||||
// watch config file
|
||||
const configWatcher = chokidar.watch(watchFiles, {
|
||||
cwd: sourceDir,
|
||||
ignoreInitial: true
|
||||
})
|
||||
configWatcher.on('change', spawnUpdate('change'))
|
||||
|
||||
// also listen for frontmatter changes from markdown files
|
||||
frontmatterEmitter.on('update', () => update('frontmatter or headers change'))
|
||||
|
||||
// resolve webpack config
|
||||
let config = createClientConfig(ctx)
|
||||
|
||||
config
|
||||
.plugin('html')
|
||||
// using a fork of html-webpack-plugin to avoid it requiring webpack
|
||||
// internals from an incompatible version.
|
||||
.use(require('vuepress-html-webpack-plugin'), [{
|
||||
template: ctx.devTemplate
|
||||
}])
|
||||
|
||||
config
|
||||
.plugin('site-data')
|
||||
.use(HeadPlugin, [{
|
||||
tags: ctx.siteConfig.head || []
|
||||
}])
|
||||
|
||||
const port = await resolvePort(cliOptions.port || ctx.siteConfig.port)
|
||||
const { host, displayHost } = await resolveHost(cliOptions.host || ctx.siteConfig.host)
|
||||
|
||||
// debug in a running dev process.
|
||||
process.stdin
|
||||
&& process.stdin.on('data', chunk => {
|
||||
const parsed = chunk.toString('utf-8').trim()
|
||||
if (parsed === '*') {
|
||||
console.log(Object.keys(ctx))
|
||||
}
|
||||
if (ctx[parsed]) {
|
||||
console.log(ctx[parsed])
|
||||
}
|
||||
})
|
||||
|
||||
config
|
||||
.plugin('vuepress-log')
|
||||
.use(DevLogPlugin, [{
|
||||
port,
|
||||
displayHost,
|
||||
publicPath: ctx.base
|
||||
}])
|
||||
|
||||
config = config.toConfig()
|
||||
const userConfig = ctx.siteConfig.configureWebpack
|
||||
if (userConfig) {
|
||||
config = applyUserWebpackConfig(userConfig, config, false /* isServer */)
|
||||
}
|
||||
|
||||
const contentBase = path.resolve(sourceDir, '.vuepress/public')
|
||||
|
||||
const serverConfig = Object.assign({
|
||||
disableHostCheck: true,
|
||||
compress: true,
|
||||
clientLogLevel: 'error',
|
||||
hot: true,
|
||||
quiet: true,
|
||||
headers: {
|
||||
'access-control-allow-origin': '*'
|
||||
},
|
||||
open: cliOptions.open,
|
||||
publicPath: ctx.base,
|
||||
watchOptions: {
|
||||
ignored: [
|
||||
/node_modules/,
|
||||
`!${ctx.tempPath}/**`
|
||||
]
|
||||
},
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
rewrites: [
|
||||
{ from: /./, to: path.posix.join(ctx.base, 'index.html') }
|
||||
]
|
||||
},
|
||||
overlay: false,
|
||||
host,
|
||||
contentBase,
|
||||
before (app, server) {
|
||||
if (fs.existsSync(contentBase)) {
|
||||
app.use(ctx.base, require('express').static(contentBase))
|
||||
}
|
||||
|
||||
ctx.pluginAPI.options.beforeDevServer.syncApply(app, server)
|
||||
},
|
||||
after (app, server) {
|
||||
ctx.pluginAPI.options.afterDevServer.syncApply(app, server)
|
||||
}
|
||||
}, ctx.siteConfig.devServer || {})
|
||||
|
||||
WebpackDevServer.addDevServerEntrypoints(config, serverConfig)
|
||||
|
||||
const compiler = webpack(config)
|
||||
const server = new WebpackDevServer(compiler, serverConfig)
|
||||
|
||||
return {
|
||||
server,
|
||||
host,
|
||||
port,
|
||||
ctx
|
||||
}
|
||||
}
|
||||
|
||||
function resolveHost (host) {
|
||||
const defaultHost = 'localhost'
|
||||
host = host || defaultHost
|
||||
const displayHost = host === defaultHost
|
||||
? 'localhost'
|
||||
: host
|
||||
return {
|
||||
displayHost,
|
||||
host
|
||||
}
|
||||
}
|
||||
|
||||
async function resolvePort (port) {
|
||||
const portfinder = require('portfinder')
|
||||
portfinder.basePort = parseInt(port) || 8080
|
||||
port = await portfinder.getPortPromise()
|
||||
return port
|
||||
}
|
||||
|
||||
function normalizeWatchFilePath (filepath, baseDir) {
|
||||
const { isAbsolute, relative } = require('path')
|
||||
if (isAbsolute(filepath)) {
|
||||
return relative(baseDir, filepath)
|
||||
}
|
||||
return filepath
|
||||
}
|
||||
@@ -1,5 +1,26 @@
|
||||
'use strict'
|
||||
|
||||
exports.dev = require('./dev')
|
||||
exports.build = require('./build')
|
||||
const App = require('./node/App')
|
||||
const { logger } = require('@vuepress/shared-utils')
|
||||
|
||||
function createApp (options) {
|
||||
logger.wait('Extracting site metadata...')
|
||||
return new App(options)
|
||||
}
|
||||
|
||||
async function dev (options) {
|
||||
const app = createApp(options)
|
||||
await app.process()
|
||||
await app.dev()
|
||||
}
|
||||
|
||||
async function build (options) {
|
||||
const app = createApp(options)
|
||||
await app.process()
|
||||
await app.build()
|
||||
}
|
||||
|
||||
exports.createApp = createApp
|
||||
exports.dev = dev
|
||||
exports.build = build
|
||||
exports.eject = require('./eject')
|
||||
|
||||
+96
-83
@@ -16,18 +16,21 @@ const {
|
||||
|
||||
const Page = require('./Page')
|
||||
const ClientComputedMixin = require('./ClientComputedMixin')
|
||||
const PluginAPI = require('../plugin-api/index')
|
||||
const PluginAPI = require('./plugin-api')
|
||||
const DevProcess = require('./dev')
|
||||
const BuildProcess = require('./build')
|
||||
const createTemp = require('./createTemp')
|
||||
|
||||
/**
|
||||
* Expose AppContext.
|
||||
* Expose VuePressApp.
|
||||
*/
|
||||
|
||||
module.exports = class AppContext {
|
||||
module.exports = class App {
|
||||
static getInstance (...args) {
|
||||
if (!AppContext._instance) {
|
||||
AppContext._instance = new AppContext(...args)
|
||||
if (!App._instance) {
|
||||
App._instance = new App(...args)
|
||||
}
|
||||
return AppContext._instance
|
||||
return App._instance
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,24 +38,23 @@ module.exports = class AppContext {
|
||||
*
|
||||
* @param {string} sourceDir
|
||||
* @param {{
|
||||
* isProd: boolean,
|
||||
* plugins: pluginsConfig,
|
||||
* theme: themeNameConfig
|
||||
* temp: string
|
||||
* }} options
|
||||
*/
|
||||
|
||||
constructor (sourceDir, cliOptions = {}, isProd) {
|
||||
logger.debug('sourceDir', sourceDir)
|
||||
this.sourceDir = sourceDir
|
||||
this.cliOptions = cliOptions
|
||||
this.isProd = isProd
|
||||
constructor (options = {}) {
|
||||
this.options = options
|
||||
this.sourceDir = this.options.sourceDir || path.join(__dirname, 'docs.fallback')
|
||||
logger.debug('sourceDir', this.sourceDir)
|
||||
|
||||
const { tempPath, writeTemp } = createTemp(cliOptions.temp)
|
||||
const { tempPath, writeTemp } = createTemp(options.temp)
|
||||
this.tempPath = tempPath
|
||||
this.writeTemp = writeTemp
|
||||
|
||||
this.vuepressDir = path.resolve(sourceDir, '.vuepress')
|
||||
this.vuepressDir = path.resolve(this.sourceDir, '.vuepress')
|
||||
this.libDir = path.join(__dirname, '../')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,9 +65,14 @@ module.exports = class AppContext {
|
||||
*/
|
||||
|
||||
resolveConfigAndInitialize () {
|
||||
this.siteConfig = loadConfig(this.vuepressDir)
|
||||
if (isFunction(this.siteConfig)) {
|
||||
this.siteConfig = this.siteConfig(this)
|
||||
if (this.options.siteConfig) {
|
||||
this.siteConfig = this.options.siteConfig
|
||||
} else {
|
||||
let siteConfig = loadConfig(this.vuepressDir)
|
||||
if (isFunction(siteConfig)) {
|
||||
siteConfig = siteConfig(this)
|
||||
}
|
||||
this.siteConfig = siteConfig
|
||||
}
|
||||
|
||||
// TODO custom cwd.
|
||||
@@ -74,7 +81,7 @@ module.exports = class AppContext {
|
||||
this.base = this.siteConfig.base || '/'
|
||||
this.themeConfig = this.siteConfig.themeConfig || {}
|
||||
|
||||
const rawOutDir = this.cliOptions.dest || this.siteConfig.dest
|
||||
const rawOutDir = this.options.dest || this.siteConfig.dest
|
||||
this.outDir = rawOutDir
|
||||
? require('path').resolve(this.cwd, rawOutDir)
|
||||
: require('path').resolve(this.sourceDir, '.vuepress/dist')
|
||||
@@ -92,7 +99,6 @@ module.exports = class AppContext {
|
||||
|
||||
async process () {
|
||||
this.resolveConfigAndInitialize()
|
||||
this.resolveCacheLoaderOptions()
|
||||
this.normalizeHeadTagUrls()
|
||||
this.themeAPI = loadTheme(this)
|
||||
this.resolveTemplates()
|
||||
@@ -105,18 +111,18 @@ module.exports = class AppContext {
|
||||
this.markdown = createMarkdown(this)
|
||||
|
||||
await this.resolvePages()
|
||||
await this.pluginAPI.options.additionalPages.apply(this)
|
||||
|
||||
await this.pluginAPI.applyAsyncOption('additionalPages', this)
|
||||
await Promise.all(
|
||||
this.pluginAPI.options.additionalPages.appliedValues.map(async (options) => {
|
||||
this.pluginAPI.getOption('additionalPages').appliedValues.map(async (options) => {
|
||||
await this.addPage(options)
|
||||
})
|
||||
)
|
||||
|
||||
await this.pluginAPI.options.ready.apply()
|
||||
await this.pluginAPI.applyAsyncOption('ready')
|
||||
await Promise.all([
|
||||
this.pluginAPI.options.clientDynamicModules.apply(this),
|
||||
this.pluginAPI.options.enhanceAppFiles.apply(this),
|
||||
this.pluginAPI.options.globalUIComponents.apply(this)
|
||||
this.pluginAPI.applyAsyncOption('clientDynamicModules', this),
|
||||
this.pluginAPI.applyAsyncOption('enhanceAppFiles', this),
|
||||
this.pluginAPI.applyAsyncOption('globalUIComponents', this)
|
||||
])
|
||||
}
|
||||
|
||||
@@ -138,17 +144,17 @@ module.exports = class AppContext {
|
||||
|
||||
this.pluginAPI
|
||||
// internl core plugins
|
||||
.use(require('../internal-plugins/siteData'))
|
||||
.use(require('../internal-plugins/routes'))
|
||||
.use(require('../internal-plugins/rootMixins'))
|
||||
.use(require('../internal-plugins/enhanceApp'))
|
||||
.use(require('../internal-plugins/palette'))
|
||||
.use(require('../internal-plugins/style'))
|
||||
.use(require('../internal-plugins/layoutComponents'))
|
||||
.use(require('../internal-plugins/pageComponents'))
|
||||
.use(require('../internal-plugins/transformModule'))
|
||||
.use(require('../internal-plugins/dataBlock'))
|
||||
.use(require('../internal-plugins/frontmatterBlock'))
|
||||
.use(require('./internal-plugins/siteData'))
|
||||
.use(require('./internal-plugins/routes'))
|
||||
.use(require('./internal-plugins/rootMixins'))
|
||||
.use(require('./internal-plugins/enhanceApp'))
|
||||
.use(require('./internal-plugins/palette'))
|
||||
.use(require('./internal-plugins/style'))
|
||||
.use(require('./internal-plugins/layoutComponents'))
|
||||
.use(require('./internal-plugins/pageComponents'))
|
||||
.use(require('./internal-plugins/transformModule'))
|
||||
.use(require('./internal-plugins/dataBlock'))
|
||||
.use(require('./internal-plugins/frontmatterBlock'))
|
||||
.use('@vuepress/container', {
|
||||
type: 'slot',
|
||||
before: info => `<template slot="${info}">`,
|
||||
@@ -176,7 +182,7 @@ module.exports = class AppContext {
|
||||
*/
|
||||
|
||||
applyUserPlugins () {
|
||||
this.pluginAPI.useByPluginsConfig(this.cliOptions.plugins)
|
||||
this.pluginAPI.useByPluginsConfig(this.options.plugins)
|
||||
if (this.themeAPI.existsParentTheme) {
|
||||
this.pluginAPI.use(this.themeAPI.parentTheme.entry)
|
||||
}
|
||||
@@ -215,7 +221,7 @@ module.exports = class AppContext {
|
||||
*/
|
||||
|
||||
resolveCacheLoaderOptions () {
|
||||
Object.assign(this, (getCacheLoaderOptions(this.siteConfig, this.cliOptions, this.cwd, this.isProd)))
|
||||
Object.assign(this, (getCacheLoaderOptions(this.siteConfig, this.options, this.cwd, this.isProd)))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +241,7 @@ module.exports = class AppContext {
|
||||
this.devTemplate = this.resolveCommonAgreementFilePath(
|
||||
'devTemplate',
|
||||
{
|
||||
defaultValue: path.resolve(__dirname, '../app/index.dev.html'),
|
||||
defaultValue: this.getLibFilePath('client/index.dev.html'),
|
||||
siteAgreement: 'templates/dev.html',
|
||||
themeAgreement: 'templates/dev.html'
|
||||
}
|
||||
@@ -244,7 +250,7 @@ module.exports = class AppContext {
|
||||
this.ssrTemplate = this.resolveCommonAgreementFilePath(
|
||||
'ssrTemplate',
|
||||
{
|
||||
defaultValue: path.resolve(__dirname, '../app/index.ssr.html'),
|
||||
defaultValue: this.getLibFilePath('client/index.ssr.html'),
|
||||
siteAgreement: 'templates/ssr.html',
|
||||
themeAgreement: 'templates/ssr.html'
|
||||
}
|
||||
@@ -265,7 +271,7 @@ module.exports = class AppContext {
|
||||
this.globalLayout = this.resolveCommonAgreementFilePath(
|
||||
'globalLayout',
|
||||
{
|
||||
defaultValue: path.resolve(__dirname, `../app/components/GlobalLayout.vue`),
|
||||
defaultValue: this.getLibFilePath('client/components/GlobalLayout.vue'),
|
||||
siteAgreement: `components/GlobalLayout.vue`,
|
||||
themeAgreement: `layouts/GlobalLayout.vue`
|
||||
}
|
||||
@@ -343,7 +349,7 @@ module.exports = class AppContext {
|
||||
await page.process({
|
||||
markdown: this.markdown,
|
||||
computed: new this.ClientComputedMixinConstructor(),
|
||||
enhancers: this.pluginAPI.options.extendPageData.items
|
||||
enhancers: this.pluginAPI.getOption('extendPageData').items
|
||||
})
|
||||
this.pages.push(page)
|
||||
}
|
||||
@@ -425,45 +431,52 @@ module.exports = class AppContext {
|
||||
locales
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file path in core lib
|
||||
*
|
||||
* @param relative
|
||||
* @returns {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
getLibFilePath (relative) {
|
||||
return path.join(this.libDir, relative)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a dev process with correct app context
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
async dev () {
|
||||
this.isProd = false
|
||||
this.devProcess = new DevProcess(this)
|
||||
await this.devProcess.process()
|
||||
|
||||
this.devProcess
|
||||
.on('fileChanged', ({ type, target }) => {
|
||||
console.log(`Reload due to ${chalk.red(type)} ${chalk.cyan(target)}`)
|
||||
this.process()
|
||||
})
|
||||
.createServer()
|
||||
.listen()
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a build process with correct app context
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
async build () {
|
||||
this.isProd = true
|
||||
this.buildProcess = new BuildProcess(this)
|
||||
await this.buildProcess.process()
|
||||
await this.buildProcess.render()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dynamic temp utility context that allow to lanuch
|
||||
* multiple apps with isolated context at the same time.
|
||||
* @param tempPath
|
||||
* @returns {{
|
||||
* writeTemp: (function(file: string, content: string): string),
|
||||
* tempPath: string
|
||||
* }}
|
||||
*/
|
||||
|
||||
function createTemp (tempPath) {
|
||||
if (!tempPath) {
|
||||
tempPath = path.resolve(__dirname, '../../.temp')
|
||||
} else {
|
||||
tempPath = path.resolve(tempPath)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(tempPath)) {
|
||||
fs.ensureDirSync(tempPath)
|
||||
} else {
|
||||
fs.emptyDirSync(tempPath)
|
||||
}
|
||||
|
||||
logger.debug(`Temp directory: ${chalk.gray(tempPath)}`)
|
||||
const tempCache = new Map()
|
||||
|
||||
async function writeTemp (file, content) {
|
||||
const destPath = path.join(tempPath, file)
|
||||
await fs.ensureDir(path.parse(destPath).dir)
|
||||
// cache write to avoid hitting the dist if it didn't change
|
||||
const cached = tempCache.get(file)
|
||||
if (cached !== content) {
|
||||
await fs.writeFile(destPath, content)
|
||||
tempCache.set(file, content)
|
||||
}
|
||||
return destPath
|
||||
}
|
||||
|
||||
return { writeTemp, tempPath }
|
||||
}
|
||||
+3
-3
@@ -12,12 +12,12 @@ const {
|
||||
/**
|
||||
* Get cache directory and cache identifier via config.
|
||||
* @param {object} siteConfig
|
||||
* @param {object} cliOptions
|
||||
* @param {object} options
|
||||
*/
|
||||
|
||||
exports.getCacheLoaderOptions = function (siteConfig, cliOptions, cwd, isProd) {
|
||||
exports.getCacheLoaderOptions = function (siteConfig, options, cwd, isProd) {
|
||||
const defaultCacheDirectory = path.resolve(__dirname, '../../node_modules/.cache/vuepress')
|
||||
let cache = cliOptions.cache || siteConfig.cache || defaultCacheDirectory
|
||||
let cache = options.cache || siteConfig.cache || defaultCacheDirectory
|
||||
|
||||
if (isBoolean(cache)) {
|
||||
if (cache === true) {
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const { inferDate, DATE_RE } = require('../util/index')
|
||||
const { inferDate, DATE_RE } = require('./util/index')
|
||||
const {
|
||||
fs,
|
||||
path,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const AsyncOption = require('../../lib/plugin-api/abstract/AsyncOption')
|
||||
const AsyncOption = require('../../plugin-api/abstract/AsyncOption')
|
||||
|
||||
describe('AsyncOption', () => {
|
||||
test('parallelApply', async () => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import Option from '../../lib/plugin-api/abstract/Option'
|
||||
import Option from '../../plugin-api/abstract/Option'
|
||||
|
||||
describe('Option', () => {
|
||||
test('key', () => {
|
||||
+2
-2
@@ -2,8 +2,8 @@ jest.mock('vuepress-plugin-a')
|
||||
jest.mock('vuepress-plugin-b')
|
||||
jest.mock('@org/vuepress-plugin-a')
|
||||
|
||||
import PluginAPI from '../../lib/plugin-api/index'
|
||||
import { PLUGIN_OPTION_MAP } from '../../lib/plugin-api/constants'
|
||||
import PluginAPI from '../../plugin-api/index'
|
||||
import { PLUGIN_OPTION_MAP } from '../../plugin-api/constants'
|
||||
|
||||
describe('Plugin', () => {
|
||||
test('registerOption', () => {
|
||||
+8
-8
@@ -1,31 +1,31 @@
|
||||
import { flattenPlugin } from '../../lib/plugin-api/util'
|
||||
import { flattenPlugin } from '../../plugin-api/util'
|
||||
|
||||
describe('flattenPlugin', () => {
|
||||
test('should hydrate plugin correctly', () => {
|
||||
const plugin = { name: 'a', shortcut: 'a', module: { enhanceAppFiles: 'file' }}
|
||||
const plugin = { name: 'a', shortcut: 'a', entry: { enhanceAppFiles: 'file' }}
|
||||
const hydratedPlugin = flattenPlugin(plugin, {}, {})
|
||||
expect(hydratedPlugin.name).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe(null)
|
||||
expect(hydratedPlugin.enabled).toBe(true)
|
||||
expect(hydratedPlugin.enhanceAppFiles).toBe('file')
|
||||
})
|
||||
|
||||
test('should set \'enabled\' to false when \'pluginOptions\' is set to false.', () => {
|
||||
const plugin = { name: 'a', shortcut: 'a', module: {}}
|
||||
const plugin = { name: 'a', shortcut: 'a', entry: {}}
|
||||
const hydratedPlugin = flattenPlugin(plugin, false, {})
|
||||
expect(hydratedPlugin.name).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe(null)
|
||||
expect(hydratedPlugin.enabled).toBe(false)
|
||||
})
|
||||
|
||||
test('should flatten functional plugin correctly.', () => {
|
||||
const config = jest.fn(() => ({ enhanceAppFiles: 'file' }))
|
||||
const plugin = { name: 'a', shortcut: 'a', module: config }
|
||||
const plugin = { name: 'a', shortcut: 'a', entry: config }
|
||||
const pluginOptions = {}
|
||||
const pluginContext = {}
|
||||
const hydratedPlugin = flattenPlugin(plugin, pluginOptions, pluginContext)
|
||||
expect(hydratedPlugin.name).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe('a')
|
||||
expect(hydratedPlugin.shortcut).toBe(null)
|
||||
expect(hydratedPlugin.enabled).toBe(true)
|
||||
expect(hydratedPlugin.enhanceAppFiles).toBe('file')
|
||||
expect(config.mock.calls).toHaveLength(1)
|
||||
@@ -35,7 +35,7 @@ describe('flattenPlugin', () => {
|
||||
|
||||
test('should flatten functional plugin correctly - options defaults to \'{}\'.', () => {
|
||||
const config = jest.fn(() => ({ enhanceAppFiles: 'file' }))
|
||||
const plugin = { name: 'a', shortcut: 'a', module: config }
|
||||
const plugin = { name: 'a', shortcut: 'a', entry: config }
|
||||
const pluginOptions = undefined
|
||||
const pluginContext = {}
|
||||
flattenPlugin(plugin, pluginOptions, pluginContext)
|
||||
+8
-4
@@ -1,5 +1,5 @@
|
||||
const { fs, path } = require('@vuepress/shared-utils')
|
||||
const prepare = require('../../lib/prepare')
|
||||
const App = require('../../App')
|
||||
|
||||
const docsBaseDir = path.resolve(__dirname, 'fixtures')
|
||||
const docsModeNames = fs.readdirSync(docsBaseDir)
|
||||
@@ -9,12 +9,16 @@ const docsModes = docsModeNames.map(name => {
|
||||
return { name, docsPath, docsTempPath }
|
||||
})
|
||||
|
||||
describe('prepare', () => {
|
||||
describe('App', () => {
|
||||
test('should not throw error', async () => {
|
||||
await Promise.all(docsModes.map(async ({ name, docsPath, docsTempPath }) => {
|
||||
await fs.ensureDir(docsTempPath)
|
||||
const context = await prepare(docsPath, { theme: '@vuepress/default', temp: docsTempPath })
|
||||
expect(context.sourceDir).toBe(docsPath)
|
||||
const app = new App({
|
||||
sourceDir: docsPath,
|
||||
theme: '@vuepress/default',
|
||||
emp: docsTempPath
|
||||
})
|
||||
expect(app.sourceDir).toBe(docsPath)
|
||||
}))
|
||||
})
|
||||
})
|
||||
+21
-13
@@ -1,19 +1,28 @@
|
||||
const Page = require('../../lib/prepare/Page')
|
||||
const Page = require('../../Page')
|
||||
const App = require('../../App')
|
||||
|
||||
const {
|
||||
getComputed,
|
||||
getMarkdown,
|
||||
getDocument,
|
||||
readFile
|
||||
} = require('./util')
|
||||
|
||||
describe('Page', () => {
|
||||
let app
|
||||
let computed
|
||||
|
||||
beforeAll(async () => {
|
||||
app = new App()
|
||||
await app.process()
|
||||
computed = new app.ClientComputedMixinConstructor()
|
||||
})
|
||||
|
||||
test('pure route', async () => {
|
||||
const page = new Page({ path: '/' })
|
||||
const page = new Page({ path: '/' }, app)
|
||||
|
||||
expect(page.path).toBe('/')
|
||||
expect(page.regularPath).toBe('/')
|
||||
|
||||
const computed = getComputed()
|
||||
await page.process({ computed })
|
||||
|
||||
expect(page.path).toBe('/')
|
||||
@@ -22,7 +31,7 @@ describe('Page', () => {
|
||||
|
||||
test('pure route - encodeURI', async () => {
|
||||
const path = '/尤/'
|
||||
const page = new Page({ path })
|
||||
const page = new Page({ path }, app)
|
||||
|
||||
expect(page.path).toBe(encodeURI(path))
|
||||
expect(page.regularPath).toBe(encodeURI(path))
|
||||
@@ -33,7 +42,7 @@ describe('Page', () => {
|
||||
const page = new Page({
|
||||
path: '/',
|
||||
frontmatter
|
||||
})
|
||||
}, app)
|
||||
expect(page.frontmatter).toBe(frontmatter)
|
||||
})
|
||||
|
||||
@@ -42,15 +51,16 @@ describe('Page', () => {
|
||||
const page = new Page({
|
||||
path: '/',
|
||||
frontmatter
|
||||
})
|
||||
}, app)
|
||||
|
||||
expect(page.frontmatter.title).toBe('alpha')
|
||||
|
||||
const computed = getComputed()
|
||||
const enhancers = [
|
||||
{
|
||||
name: 'plugin-a',
|
||||
value: page => { page.frontmatter.title = 'beta' }
|
||||
value: page => {
|
||||
page.frontmatter.title = 'beta'
|
||||
}
|
||||
}
|
||||
]
|
||||
await page.process({ computed, enhancers })
|
||||
@@ -60,14 +70,13 @@ describe('Page', () => {
|
||||
|
||||
test('markdown page - pointing to a markdown file', async () => {
|
||||
const { relative, filePath } = getDocument('README.md')
|
||||
const page = new Page({ filePath, relative })
|
||||
const page = new Page({ filePath, relative }, app)
|
||||
|
||||
expect(page._filePath).toBe(filePath)
|
||||
expect(page.regularPath).toBe('/')
|
||||
expect(page.path).toBe('/')
|
||||
expect(page.frontmatter).toEqual({})
|
||||
|
||||
const computed = getComputed()
|
||||
const markdown = getMarkdown()
|
||||
await page.process({ computed, markdown })
|
||||
|
||||
@@ -79,14 +88,13 @@ describe('Page', () => {
|
||||
|
||||
test('markdown page - pointing to a markdown file with frontmatter', async () => {
|
||||
const { relative, filePath } = getDocument('alpha.md')
|
||||
const page = new Page({ filePath, relative })
|
||||
const page = new Page({ filePath, relative }, app)
|
||||
|
||||
expect(page._filePath).toBe(filePath)
|
||||
expect(page.regularPath).toBe('/alpha.html')
|
||||
expect(page.path).toBe('/alpha.html')
|
||||
expect(page.frontmatter).toEqual({})
|
||||
|
||||
const computed = getComputed()
|
||||
const markdown = getMarkdown()
|
||||
await page.process({ computed, markdown })
|
||||
|
||||
+1
-13
@@ -1,15 +1,5 @@
|
||||
const { fs, path } = require('@vuepress/shared-utils')
|
||||
const AppContext = require('../../lib/prepare/AppContext')
|
||||
const createMarkdown = require('../../../markdown/index')
|
||||
|
||||
function getAppContext () {
|
||||
return new AppContext('.')
|
||||
}
|
||||
|
||||
function getComputed () {
|
||||
const context = getAppContext()
|
||||
return new context.ClientComputedMixinConstructor()
|
||||
}
|
||||
const createMarkdown = require('../../../../../markdown/index')
|
||||
|
||||
const docsBaseDir = path.resolve(__dirname, 'fixtures/docs')
|
||||
|
||||
@@ -25,8 +15,6 @@ const getMarkdown = createMarkdown
|
||||
const readFile = async filePath => await fs.readFile(filePath, 'utf-8')
|
||||
|
||||
module.exports = {
|
||||
getAppContext,
|
||||
getComputed,
|
||||
getMarkdown,
|
||||
getDocument,
|
||||
readFile
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
jest.mock('vuepress-theme-parent')
|
||||
jest.mock('vuepress-theme-child')
|
||||
|
||||
import ThemeAPI from '../../lib/theme-api'
|
||||
import ThemeAPI from '../../theme-api'
|
||||
import { resolve } from 'path'
|
||||
|
||||
const theme = {
|
||||
@@ -0,0 +1,273 @@
|
||||
'use strict'
|
||||
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const webpack = require('webpack')
|
||||
const readline = require('readline')
|
||||
const escape = require('escape-html')
|
||||
|
||||
const { chalk, fs, path, logger, env, performance } = require('@vuepress/shared-utils')
|
||||
const createClientConfig = require('../webpack/createClientConfig')
|
||||
const createServerConfig = require('../webpack/createServerConfig')
|
||||
const { createBundleRenderer } = require('vue-server-renderer')
|
||||
const { normalizeHeadTag, applyUserWebpackConfig } = require('../util/index')
|
||||
|
||||
/**
|
||||
* Expose Build Process Class.
|
||||
*/
|
||||
|
||||
module.exports = class Build extends EventEmitter {
|
||||
constructor (context) {
|
||||
super()
|
||||
process.env.NODE_ENV = 'production'
|
||||
this.context = context
|
||||
this.outDir = this.context.outDir
|
||||
}
|
||||
|
||||
/**
|
||||
* Doing somthing before render pages, e.g. validate and empty output directory,
|
||||
* prepare webpack config.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
async process () {
|
||||
if (this.context.cwd === this.outDir) {
|
||||
throw new Error('Unexpected option: "outDir" cannot be set to the current working directory')
|
||||
}
|
||||
|
||||
this.context.resolveCacheLoaderOptions()
|
||||
await fs.emptyDir(this.outDir)
|
||||
logger.debug('Dist directory: ' + chalk.gray(this.outDir))
|
||||
this.prepareWebpackConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and render pages.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
async render () {
|
||||
logger.wait('Extracting site metadata...')
|
||||
|
||||
// compile!
|
||||
const stats = await compile([this.clientConfig, this.serverConfig])
|
||||
const serverBundle = require(path.resolve(this.outDir, 'manifest/server.json'))
|
||||
const clientManifest = require(path.resolve(this.outDir, 'manifest/client.json'))
|
||||
|
||||
// remove manifests after loading them.
|
||||
await fs.remove(path.resolve(this.outDir, 'manifest'))
|
||||
|
||||
// ref: https://github.com/vuejs/vuepress/issues/1367
|
||||
if (!this.clientConfig.devtool && (!this.clientConfig.plugins
|
||||
|| !this.clientConfig.plugins.some(p =>
|
||||
p instanceof webpack.SourceMapDevToolPlugin
|
||||
|| p instanceof webpack.EvalSourceMapDevToolPlugin
|
||||
))) {
|
||||
await workaroundEmptyStyleChunk(stats, this.outDir)
|
||||
}
|
||||
|
||||
// create server renderer using built manifests
|
||||
this.renderer = createBundleRenderer(serverBundle, {
|
||||
clientManifest,
|
||||
runInNewContext: false,
|
||||
inject: false,
|
||||
shouldPrefetch: this.context.siteConfig.shouldPrefetch || (() => true),
|
||||
template: await fs.readFile(this.context.ssrTemplate, 'utf-8')
|
||||
})
|
||||
|
||||
// pre-render head tags from user config
|
||||
this.userHeadTags = (this.context.siteConfig.head || [])
|
||||
.map(renderHeadTag)
|
||||
.join('\n ')
|
||||
|
||||
// if the user does not have a custom 404.md, generate the theme's default
|
||||
if (!this.context.pages.some(p => p.path === '/404.html')) {
|
||||
this.context.addPage({ path: '/404.html' })
|
||||
}
|
||||
|
||||
// render pages
|
||||
logger.wait('Rendering static HTML...')
|
||||
|
||||
const pagePaths = []
|
||||
for (const page of this.context.pages) {
|
||||
pagePaths.push(await this.renderPage(page))
|
||||
}
|
||||
|
||||
readline.clearLine(process.stdout, 0)
|
||||
readline.cursorTo(process.stdout, 0)
|
||||
|
||||
await this.context.pluginAPI.applyAsyncOption('generated', pagePaths)
|
||||
|
||||
// DONE.
|
||||
const relativeDir = path.relative(this.context.cwd, this.outDir)
|
||||
logger.success(`Generated static files in ${chalk.cyan(relativeDir)}.`)
|
||||
const { duration } = performance.stop()
|
||||
logger.developer(`It took a total of ${chalk.cyan(`${duration}ms`)} to run the ${chalk.cyan('vuepress build')}.`)
|
||||
console.log()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare webpack config under build.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
prepareWebpackConfig () {
|
||||
this.clientConfig = createClientConfig(this.context).toConfig()
|
||||
this.serverConfig = createServerConfig(this.context).toConfig()
|
||||
|
||||
const userConfig = this.context.siteConfig.configureWebpack
|
||||
if (userConfig) {
|
||||
this.clientConfig = applyUserWebpackConfig(userConfig, this.clientConfig, false)
|
||||
this.serverConfig = applyUserWebpackConfig(userConfig, this.serverConfig, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render page
|
||||
*
|
||||
* @param {Page} page
|
||||
* @returns {Promise<string>}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
async renderPage (page) {
|
||||
const pagePath = page.path
|
||||
readline.clearLine(process.stdout, 0)
|
||||
readline.cursorTo(process.stdout, 0)
|
||||
process.stdout.write(`Rendering page: ${pagePath}`)
|
||||
|
||||
// #565 Avoid duplicate description meta at SSR.
|
||||
const meta = (page.frontmatter && page.frontmatter.meta || []).filter(item => item.name !== 'description')
|
||||
const pageMeta = renderPageMeta(meta)
|
||||
|
||||
const context = {
|
||||
url: pagePath,
|
||||
userHeadTags: this.userHeadTags,
|
||||
pageMeta,
|
||||
title: 'VuePress',
|
||||
lang: 'en',
|
||||
description: ''
|
||||
}
|
||||
|
||||
let html
|
||||
try {
|
||||
html = await this.renderer.renderToString(context)
|
||||
} catch (e) {
|
||||
console.error(logger.error(chalk.red(`Error rendering ${pagePath}:`), false))
|
||||
throw e
|
||||
}
|
||||
const filename = decodeURIComponent(pagePath.replace(/\/$/, '/index.html').replace(/^\//, ''))
|
||||
const filePath = path.resolve(this.outDir, filename)
|
||||
await fs.ensureDir(path.dirname(filePath))
|
||||
await fs.writeFile(filePath, html)
|
||||
return filePath
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a webpack application and return stats json.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
|
||||
function compile (config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
webpack(config, (err, stats) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
if (stats.hasErrors()) {
|
||||
stats.toJson().errors.forEach(err => {
|
||||
console.error(err)
|
||||
})
|
||||
reject(new Error(`Failed to compile with errors.`))
|
||||
return
|
||||
}
|
||||
if (env.isDebug && stats.hasWarnings()) {
|
||||
stats.toJson().warnings.forEach(warning => {
|
||||
console.warn(warning)
|
||||
})
|
||||
}
|
||||
resolve(stats.toJson({ modules: false }))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Render head tag
|
||||
*
|
||||
* @param {Object} tag
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
function renderHeadTag (tag) {
|
||||
const { tagName, attributes, innerHTML, closeTag } = normalizeHeadTag(tag)
|
||||
return `<${tagName}${renderAttrs(attributes)}>${innerHTML}${closeTag ? `</${tagName}>` : ``}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Render html attributes
|
||||
*
|
||||
* @param {Object} attrs
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
function renderAttrs (attrs = {}) {
|
||||
const keys = Object.keys(attrs)
|
||||
if (keys.length) {
|
||||
return ' ' + keys.map(name => `${name}="${escape(attrs[name])}"`).join(' ')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render meta tags
|
||||
*
|
||||
* @param {Array} meta
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
|
||||
function renderPageMeta (meta) {
|
||||
if (!meta) return ''
|
||||
return meta.map(m => {
|
||||
let res = `<meta`
|
||||
Object.keys(m).forEach(key => {
|
||||
res += ` ${key}="${escape(m[key])}"`
|
||||
})
|
||||
return res + `>`
|
||||
}).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* find and remove empty style chunk caused by
|
||||
* https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
|
||||
* TODO remove when it's fixed
|
||||
*
|
||||
* @param {Object} stats
|
||||
* @param {String} outDir
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
async function workaroundEmptyStyleChunk (stats, outDir) {
|
||||
const styleChunk = stats.children[0].assets.find(a => {
|
||||
return /styles\.\w{8}\.js$/.test(a.name)
|
||||
})
|
||||
if (!styleChunk) return
|
||||
const styleChunkPath = path.resolve(outDir, styleChunk.name)
|
||||
const styleChunkContent = await fs.readFile(styleChunkPath, 'utf-8')
|
||||
await fs.remove(styleChunkPath)
|
||||
// prepend it to app.js.
|
||||
// this is necessary for the webpack runtime to work properly.
|
||||
const appChunk = stats.children[0].assets.find(a => {
|
||||
return /app\.\w{8}\.js$/.test(a.name)
|
||||
})
|
||||
const appChunkPath = path.resolve(outDir, appChunk.name)
|
||||
const appChunkContent = await fs.readFile(appChunkPath, 'utf-8')
|
||||
await fs.writeFile(appChunkPath, styleChunkContent + appChunkContent)
|
||||
}
|
||||
+2
-2
@@ -16,12 +16,12 @@ module.exports = function (ctx) {
|
||||
|
||||
const beforeInstantiate = config => {
|
||||
chainMarkdown && chainMarkdown(config)
|
||||
ctx.pluginAPI.options.chainMarkdown.syncApply(config)
|
||||
ctx.pluginAPI.applySyncOption('chainMarkdown', config)
|
||||
}
|
||||
|
||||
const afterInstantiate = md => {
|
||||
extendMarkdown && extendMarkdown(md)
|
||||
ctx.pluginAPI.options.extendMarkdown.syncApply(md)
|
||||
ctx.pluginAPI.applySyncOption('extendMarkdown', md)
|
||||
}
|
||||
|
||||
return createMarkdown(
|
||||
@@ -0,0 +1,42 @@
|
||||
const { fs, path, chalk, logger } = require('@vuepress/shared-utils')
|
||||
|
||||
/**
|
||||
* Create a dynamic temp utility context that allow to lanuch
|
||||
* multiple apps with isolated context at the same time.
|
||||
* @param tempPath
|
||||
* @returns {{
|
||||
* writeTemp: (function(file: string, content: string): string),
|
||||
* tempPath: string
|
||||
* }}
|
||||
*/
|
||||
|
||||
module.exports = function createTemp (tempPath) {
|
||||
if (!tempPath) {
|
||||
tempPath = path.resolve(__dirname, '../../.temp')
|
||||
} else {
|
||||
tempPath = path.resolve(tempPath)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(tempPath)) {
|
||||
fs.ensureDirSync(tempPath)
|
||||
} else {
|
||||
fs.emptyDirSync(tempPath)
|
||||
}
|
||||
|
||||
logger.debug(`Temp directory: ${chalk.gray(tempPath)}`)
|
||||
const tempCache = new Map()
|
||||
|
||||
async function writeTemp (file, content) {
|
||||
const destPath = path.join(tempPath, file)
|
||||
await fs.ensureDir(path.parse(destPath).dir)
|
||||
// cache write to avoid hitting the dist if it didn't change
|
||||
const cached = tempCache.get(file)
|
||||
if (cached !== content) {
|
||||
await fs.writeFile(destPath, content)
|
||||
tempCache.set(file, content)
|
||||
}
|
||||
return destPath
|
||||
}
|
||||
|
||||
return { writeTemp, tempPath }
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const WebpackDevServer = require('webpack-dev-server')
|
||||
const { frontmatterEmitter } = require('@vuepress/markdown-loader')
|
||||
const webpack = require('webpack')
|
||||
const chokidar = require('chokidar')
|
||||
|
||||
const { path, fs, logger } = require('@vuepress/shared-utils')
|
||||
const HeadPlugin = require('../webpack/HeadPlugin')
|
||||
const DevLogPlugin = require('../webpack/DevLogPlugin')
|
||||
const createClientConfig = require('../webpack/createClientConfig')
|
||||
const { applyUserWebpackConfig } = require('../util/index')
|
||||
|
||||
module.exports = class DevProcess extends EventEmitter {
|
||||
constructor (context) {
|
||||
super()
|
||||
this.context = context
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare essential data for launch dev server.
|
||||
*/
|
||||
|
||||
async process () {
|
||||
this.context.resolveCacheLoaderOptions()
|
||||
this.watchSourceFiles()
|
||||
this.watchUserConfig()
|
||||
this.watchFrontmatter()
|
||||
this.setupDebugTip()
|
||||
await this.resolvePort()
|
||||
await this.resolveHost()
|
||||
this.prepareWebpackConfig()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Hande file's update, need to re-prepare app context.
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {string} target
|
||||
*/
|
||||
|
||||
handleUpdate (type, target) {
|
||||
if (!path.isAbsolute(target)) {
|
||||
target = path.join(this.context.sourceDir, target)
|
||||
}
|
||||
if (target.endsWith('.js')) {
|
||||
// Bust cache.
|
||||
delete require.cache[target]
|
||||
}
|
||||
this.emit('fileChanged', {
|
||||
type,
|
||||
target
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch user's source document files.
|
||||
*/
|
||||
|
||||
watchSourceFiles () {
|
||||
// watch add/remove of files
|
||||
this.pagesWatcher = chokidar.watch([
|
||||
'**/*.md',
|
||||
'.vuepress/components/**/*.vue'
|
||||
], {
|
||||
cwd: this.context.sourceDir,
|
||||
ignored: ['.vuepress/**/*.md', 'node_modules'],
|
||||
ignoreInitial: true
|
||||
})
|
||||
this.pagesWatcher.on('add', target => this.handleUpdate('add', target))
|
||||
this.pagesWatcher.on('unlink', target => this.handleUpdate('unlink', target))
|
||||
this.pagesWatcher.on('addDir', target => this.handleUpdate('addDir', target))
|
||||
this.pagesWatcher.on('unlinkDir', target => this.handleUpdate('unlinkDir', target))
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch user's config files and extra files.
|
||||
*/
|
||||
|
||||
watchUserConfig () {
|
||||
this.watchFiles = [
|
||||
'.vuepress/config.js',
|
||||
'.vuepress/config.yml',
|
||||
'.vuepress/config.toml'
|
||||
].concat(
|
||||
(
|
||||
this.context.siteConfig.extraWatchFiles || []
|
||||
).map(file => normalizeWatchFilePath(file, this.context.sourceDir))
|
||||
)
|
||||
|
||||
logger.debug('watchFiles', this.watchFiles)
|
||||
|
||||
this.configWatcher = chokidar.watch(this.watchFiles, {
|
||||
cwd: this.context.sourceDir,
|
||||
ignoreInitial: true
|
||||
})
|
||||
|
||||
this.configWatcher.on('change', target => this.handleUpdate('change', target))
|
||||
}
|
||||
|
||||
/**
|
||||
* Also listen for frontmatter changes from markdown files
|
||||
*/
|
||||
|
||||
watchFrontmatter () {
|
||||
frontmatterEmitter.on('update', target => this.handleUpdate('frontmatter', target))
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve used port
|
||||
*/
|
||||
|
||||
async resolvePort () {
|
||||
this.port = await resolvePort(this.context.options.port || this.context.siteConfig.port)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve used host
|
||||
*/
|
||||
|
||||
async resolveHost () {
|
||||
const { host, displayHost } = await resolveHost(this.context.options.host || this.context.siteConfig.host)
|
||||
this.host = host
|
||||
this.displayHost = displayHost
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a shortcut to debug context under dev.
|
||||
*/
|
||||
|
||||
setupDebugTip () {
|
||||
// debug in a running dev process.
|
||||
process.stdin
|
||||
&& process.stdin.on('data', chunk => {
|
||||
const parsed = chunk.toString('utf-8').trim()
|
||||
if (parsed === '*') {
|
||||
console.log(Object.keys(this.context))
|
||||
}
|
||||
if (this.context[parsed]) {
|
||||
console.log(this.context[parsed])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare webpack for dev process.
|
||||
*/
|
||||
|
||||
prepareWebpackConfig () {
|
||||
// resolve webpack config
|
||||
let config = createClientConfig(this.context)
|
||||
|
||||
config
|
||||
.plugin('html')
|
||||
// using a fork of html-webpack-plugin to avoid it requiring webpack
|
||||
// internals from an incompatible version.
|
||||
.use(require('vuepress-html-webpack-plugin'), [{
|
||||
template: this.context.devTemplate
|
||||
}])
|
||||
|
||||
config
|
||||
.plugin('site-data')
|
||||
.use(HeadPlugin, [{
|
||||
tags: this.context.siteConfig.head || []
|
||||
}])
|
||||
|
||||
config
|
||||
.plugin('vuepress-log')
|
||||
.use(DevLogPlugin, [{
|
||||
port: this.port,
|
||||
displayHost: this.displayHost,
|
||||
publicPath: this.context.base
|
||||
}])
|
||||
|
||||
config = config.toConfig()
|
||||
const userConfig = this.context.siteConfig.configureWebpack
|
||||
if (userConfig) {
|
||||
config = applyUserWebpackConfig(userConfig, config, false /* isServer */)
|
||||
}
|
||||
this.webpackConfig = config
|
||||
}
|
||||
|
||||
createServer () {
|
||||
const contentBase = path.resolve(this.context.sourceDir, '.vuepress/public')
|
||||
|
||||
const serverConfig = Object.assign({
|
||||
disableHostCheck: true,
|
||||
compress: true,
|
||||
clientLogLevel: 'error',
|
||||
hot: true,
|
||||
quiet: true,
|
||||
headers: {
|
||||
'access-control-allow-origin': '*'
|
||||
},
|
||||
open: this.context.options.open,
|
||||
publicPath: this.context.base,
|
||||
watchOptions: {
|
||||
ignored: [
|
||||
/node_modules/,
|
||||
`!${this.context.tempPath}/**`
|
||||
]
|
||||
},
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
rewrites: [
|
||||
{ from: /./, to: path.posix.join(this.context.base, 'index.html') }
|
||||
]
|
||||
},
|
||||
overlay: false,
|
||||
host: this.host,
|
||||
contentBase,
|
||||
before: (app, server) => {
|
||||
if (fs.existsSync(contentBase)) {
|
||||
app.use(this.context.base, require('express').static(contentBase))
|
||||
}
|
||||
|
||||
this.context.pluginAPI.applySyncOption('beforeDevServer', app, server)
|
||||
},
|
||||
after: (app, server) => {
|
||||
this.context.pluginAPI.applySyncOption('afterDevServer', app, server)
|
||||
}
|
||||
}, this.context.siteConfig.devServer || {})
|
||||
|
||||
WebpackDevServer.addDevServerEntrypoints(this.webpackConfig, serverConfig)
|
||||
|
||||
const compiler = webpack(this.webpackConfig)
|
||||
this.server = new WebpackDevServer(compiler, serverConfig)
|
||||
return this
|
||||
}
|
||||
|
||||
listen () {
|
||||
this.server.listen(this.port, this.host, err => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
function resolveHost (host) {
|
||||
const defaultHost = 'localhost'
|
||||
host = host || defaultHost
|
||||
const displayHost = host === defaultHost
|
||||
? 'localhost'
|
||||
: host
|
||||
return {
|
||||
displayHost,
|
||||
host
|
||||
}
|
||||
}
|
||||
|
||||
async function resolvePort (port) {
|
||||
const portfinder = require('portfinder')
|
||||
portfinder.basePort = parseInt(port) || 8080
|
||||
port = await portfinder.getPortPromise()
|
||||
return port
|
||||
}
|
||||
|
||||
function normalizeWatchFilePath (filepath, baseDir) {
|
||||
const { isAbsolute, relative } = require('path')
|
||||
if (isAbsolute(filepath)) {
|
||||
return relative(baseDir, filepath)
|
||||
}
|
||||
return filepath
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# VuePress
|
||||
|
||||
> `Prompts`: You are running VuePress without setting sourceDir!
|
||||
+1
-1
@@ -18,7 +18,7 @@ module.exports = function (source, map) {
|
||||
&& parsed.data
|
||||
&& JSON.stringify(cached.data) !== JSON.stringify(parsed.data)
|
||||
) {
|
||||
frontmatterEmitter.emit('update')
|
||||
frontmatterEmitter.emit('update', file)
|
||||
}
|
||||
|
||||
cache.set(file, parsed)
|
||||
+1
-1
@@ -8,7 +8,7 @@ module.exports = (options, ctx) => ({
|
||||
|
||||
async ready () {
|
||||
// 1. enable config.styl globally.
|
||||
const configFile = path.resolve(__dirname, '../../app/style/config.styl')
|
||||
const configFile = ctx.getLibFilePath('client/style/config.styl')
|
||||
if (!ctx.siteConfig.stylus) {
|
||||
ctx.siteConfig.stylus = {
|
||||
import: [configFile]
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
const { path } = require('@vuepress/shared-utils')
|
||||
const { codegen: { pathsToModuleCode }} = require('@vuepress/shared-utils')
|
||||
|
||||
module.exports = (options, context, api) => ({
|
||||
@@ -7,7 +6,7 @@ module.exports = (options, context, api) => ({
|
||||
// @internal/root-mixins
|
||||
async clientDynamicModules () {
|
||||
const builtInRootMixins = [
|
||||
path.resolve(__dirname, '../app/root-mixins/updateMeta.js')
|
||||
context.getLibFilePath('client/root-mixins/updateMeta.js')
|
||||
]
|
||||
|
||||
const rootMixins = [
|
||||
+1
-1
@@ -11,7 +11,7 @@ module.exports = (options, ctx) => ({
|
||||
|
||||
async clientDynamicModules () {
|
||||
const files = [
|
||||
path.resolve(__dirname, '../prepare/ClientComputedMixin.js')
|
||||
path.resolve(__dirname, '../ClientComputedMixin.js')
|
||||
]
|
||||
|
||||
const modules = await Promise.all(files.map(async file => {
|
||||
+6
-4
@@ -11,7 +11,7 @@ const {
|
||||
datatypes: { isString },
|
||||
logger, chalk
|
||||
} = require('@vuepress/shared-utils')
|
||||
const ThemeAPI = require('../theme-api')
|
||||
const ThemeAPI = require('./theme-api')
|
||||
|
||||
/**
|
||||
* Resolve theme.
|
||||
@@ -37,14 +37,16 @@ module.exports = function loadTheme (ctx) {
|
||||
if (!theme.path) {
|
||||
throw new Error(`[vuepress] You must specify a theme, or create a local custom theme. \n For more details, refer to https://vuepress.vuejs.org/guide/custom-themes.html#custom-themes. \n`)
|
||||
}
|
||||
logger.tip(`Apply theme ${chalk.gray(theme.name)}`)
|
||||
let applyTip = `Apply theme ${chalk.magenta(theme.name)}`
|
||||
theme.entry.name = '@vuepress/internal-theme-entry-file'
|
||||
|
||||
let parentTheme = {}
|
||||
if (theme.entry.extend) {
|
||||
parentTheme = resolveTheme(ctx, themeResolver, true, theme.entry.extend)
|
||||
parentTheme.entry.name = '@vuepress/internal-parent-theme-entry-file'
|
||||
applyTip += chalk.gray(` (extends ${chalk.magenta(parentTheme.name)})`)
|
||||
}
|
||||
logger.tip(applyTip + ' ...')
|
||||
|
||||
logger.debug('theme', theme.name, theme.path)
|
||||
logger.debug('parentTheme', parentTheme.name, parentTheme.path)
|
||||
@@ -73,9 +75,9 @@ function normalizeThemePath (resolved) {
|
||||
}
|
||||
|
||||
function resolveTheme (ctx, resolver, ignoreLocal, theme) {
|
||||
const { siteConfig, cliOptions, sourceDir, vuepressDir, pluginAPI } = ctx
|
||||
const { siteConfig, options, sourceDir, vuepressDir, pluginAPI } = ctx
|
||||
const localThemePath = resolve(vuepressDir, 'theme')
|
||||
theme = theme || siteConfig.theme || cliOptions.theme
|
||||
theme = theme || siteConfig.theme || options.theme
|
||||
|
||||
let path
|
||||
let name
|
||||
+44
@@ -238,6 +238,50 @@ module.exports = class PluginAPI {
|
||||
.registerOption(PLUGIN_OPTION_MAP.BEFORE_DEV_SERVER.key, beforeDevServer, pluginName)
|
||||
.registerOption(PLUGIN_OPTION_MAP.AFTER_DEV_SERVER.key, afterDevServer, pluginName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply synchronous option.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Array<any>} args
|
||||
* @returns {void}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
applySyncOption (name, ...args) {
|
||||
logger.debug('applySyncOption: ' + name)
|
||||
this.getOption(name).apply(...args)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply asynchronous option.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Array<any>} args
|
||||
* @returns {Promise<void>}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
async applyAsyncOption (name, ...args) {
|
||||
logger.debug('applyAsyncOption: ' + name)
|
||||
await this.getOption(name).apply(...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exisiting option
|
||||
*
|
||||
* @param name
|
||||
* @returns {Option}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
getOption (name) {
|
||||
if (!this.options[name]) {
|
||||
throw new Error(`Unknown option ${name}`)
|
||||
}
|
||||
return this.options[name]
|
||||
}
|
||||
}
|
||||
|
||||
function pluginLog (name, shortcut) {
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<Content/>
|
||||
</template>
|
||||
+1
-1
@@ -88,7 +88,7 @@ module.exports = class ThemeAPI {
|
||||
layoutComponentMap.NotFound = {
|
||||
filename: 'NotFound.vue',
|
||||
componentName: 'NotFound',
|
||||
path: resolve(__dirname, '../app/components/NotFound.vue'),
|
||||
path: resolve(__dirname, '../../client/components/NotFound.vue'),
|
||||
isInternal: true
|
||||
}
|
||||
}
|
||||
+26
-22
@@ -10,20 +10,22 @@ const { fs, path, logger, env } = require('@vuepress/shared-utils')
|
||||
* Expose createBaseConfig method.
|
||||
*/
|
||||
|
||||
module.exports = function createBaseConfig ({
|
||||
siteConfig,
|
||||
sourceDir,
|
||||
outDir,
|
||||
base: publicPath,
|
||||
markdown,
|
||||
tempPath,
|
||||
cacheDirectory,
|
||||
cacheIdentifier,
|
||||
cliOptions: {
|
||||
cache
|
||||
},
|
||||
pluginAPI
|
||||
}, isServer) {
|
||||
module.exports = function createBaseConfig (context, isServer) {
|
||||
const {
|
||||
siteConfig,
|
||||
sourceDir,
|
||||
outDir,
|
||||
base: publicPath,
|
||||
markdown,
|
||||
tempPath,
|
||||
cacheDirectory,
|
||||
cacheIdentifier,
|
||||
options: {
|
||||
cache
|
||||
},
|
||||
pluginAPI
|
||||
} = context
|
||||
|
||||
const Config = require('webpack-chain')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const CSSExtractPlugin = require('mini-css-extract-plugin')
|
||||
@@ -47,12 +49,14 @@ module.exports = function createBaseConfig ({
|
||||
}
|
||||
|
||||
const modulePaths = getModulePaths()
|
||||
const clientDir = context.getLibFilePath('client')
|
||||
|
||||
config.resolve
|
||||
.set('symlinks', true)
|
||||
.alias
|
||||
.set('@source', sourceDir)
|
||||
.set('@app', path.resolve(__dirname, '../app'))
|
||||
.set('@client', clientDir)
|
||||
.set('@app', clientDir)
|
||||
.set('@temp', tempPath)
|
||||
.set('@dynamic', path.resolve(tempPath, 'dynamic'))
|
||||
.set('@internal', path.resolve(tempPath, 'internal'))
|
||||
@@ -76,7 +80,7 @@ module.exports = function createBaseConfig ({
|
||||
fs.emptyDirSync(cacheDirectory)
|
||||
}
|
||||
|
||||
cacheIdentifier += `isServer:${isServer}`
|
||||
const finalCacheIdentifier = cacheIdentifier + `isServer:${isServer}`
|
||||
|
||||
function applyVuePipeline (rule) {
|
||||
rule
|
||||
@@ -84,7 +88,7 @@ module.exports = function createBaseConfig ({
|
||||
.loader('cache-loader')
|
||||
.options({
|
||||
cacheDirectory,
|
||||
cacheIdentifier
|
||||
cacheIdentifier: finalCacheIdentifier
|
||||
})
|
||||
|
||||
rule
|
||||
@@ -95,7 +99,7 @@ module.exports = function createBaseConfig ({
|
||||
preserveWhitespace: true
|
||||
},
|
||||
cacheDirectory,
|
||||
cacheIdentifier
|
||||
cacheIdentifier: finalCacheIdentifier
|
||||
})
|
||||
}
|
||||
|
||||
@@ -144,7 +148,7 @@ module.exports = function createBaseConfig ({
|
||||
.loader('cache-loader')
|
||||
.options({
|
||||
cacheDirectory,
|
||||
cacheIdentifier
|
||||
cacheIdentifier: finalCacheIdentifier
|
||||
})
|
||||
.end()
|
||||
.use('babel-loader')
|
||||
@@ -281,13 +285,13 @@ module.exports = function createBaseConfig ({
|
||||
config
|
||||
.plugin('injections')
|
||||
.use(require('webpack/lib/DefinePlugin'), [{
|
||||
VUEPRESS_VERSION: JSON.stringify(require('../../package.json').version),
|
||||
VUEPRESS_VERSION: JSON.stringify(require('../../../package.json').version),
|
||||
VUEPRESS_TEMP_PATH: JSON.stringify(tempPath),
|
||||
LAST_COMMIT_HASH: JSON.stringify(getLastCommitHash())
|
||||
}])
|
||||
|
||||
pluginAPI.options.define.apply(config)
|
||||
pluginAPI.options.alias.apply(config)
|
||||
pluginAPI.applySyncOption('define', config)
|
||||
pluginAPI.applySyncOption('alias', config)
|
||||
|
||||
return config
|
||||
}
|
||||
+3
-3
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
|
||||
module.exports = function createClientConfig (ctx) {
|
||||
const { path, env } = require('@vuepress/shared-utils')
|
||||
const { env } = require('@vuepress/shared-utils')
|
||||
const createBaseConfig = require('./createBaseConfig')
|
||||
|
||||
const config = createBaseConfig(ctx)
|
||||
|
||||
config
|
||||
.entry('app')
|
||||
.add(path.resolve(__dirname, '../app/clientEntry.js'))
|
||||
.add(ctx.getLibFilePath('client/clientEntry.js'))
|
||||
|
||||
config.node
|
||||
.merge({
|
||||
@@ -71,7 +71,7 @@ module.exports = function createClientConfig (ctx) {
|
||||
}])
|
||||
}
|
||||
|
||||
ctx.pluginAPI.options.chainWebpack.syncApply(config, false /* isServer */)
|
||||
ctx.pluginAPI.applySyncOption('chainWebpack', config, false /* isServer */)
|
||||
|
||||
return config
|
||||
}
|
||||
+2
-2
@@ -24,7 +24,7 @@ module.exports = function createServerConfig (ctx) {
|
||||
|
||||
config
|
||||
.entry('app')
|
||||
.add(path.resolve(__dirname, '../app/serverEntry.js'))
|
||||
.add(ctx.getLibFilePath('client/serverEntry.js'))
|
||||
|
||||
config.output
|
||||
.filename('server-bundle.js')
|
||||
@@ -56,7 +56,7 @@ module.exports = function createServerConfig (ctx) {
|
||||
}])
|
||||
}
|
||||
|
||||
ctx.pluginAPI.options.chainWebpack.syncApply(config, true /* isServer */)
|
||||
ctx.pluginAPI.applySyncOption('chainWebpack', config, true /* isServer */)
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const AppContext = require('./AppContext')
|
||||
const { logger } = require('@vuepress/shared-utils')
|
||||
|
||||
/**
|
||||
* Expose prepare.
|
||||
*/
|
||||
|
||||
module.exports = async function prepare (sourceDir, cliOptions, isProd) {
|
||||
logger.wait('Extracting site metadata...')
|
||||
const appContext = AppContext.getInstance(sourceDir, cliOptions, isProd)
|
||||
await appContext.process()
|
||||
return appContext
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`containers danger 1`] = `
|
||||
<div class="danger custom-block">
|
||||
<p class="custom-block-title">WARNING</p>
|
||||
<p>I am a danger</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`containers tip 1`] = `
|
||||
<div class="tip custom-block">
|
||||
<p class="custom-block-title">TIP</p>
|
||||
<p>I am a tip</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`containers tip-override 1`] = `
|
||||
<div class="tip custom-block">
|
||||
<p class="custom-block-title">提示</p>
|
||||
<p>I am a tip</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`containers v-pre 1`] = `
|
||||
<div v-pre>
|
||||
<p>I am a v-pre</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`containers warning 1`] = `
|
||||
<div class="warning custom-block">
|
||||
<p class="custom-block-title">WARNING</p>
|
||||
<p>I am a warning</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -3,18 +3,19 @@
|
||||
exports[`highlightLines highlight multiple lines 1`] = `
|
||||
<div class="highlight-lines">
|
||||
<div class="highlighted"> </div>
|
||||
<div class="highlighted"> </div><br>
|
||||
<div class="highlighted"> </div>
|
||||
<br>
|
||||
<div class="highlighted"> </div>
|
||||
<div class="highlighted"> </div>
|
||||
<br>
|
||||
<br>
|
||||
</div>const app = new Vue({ render, router }) app.$mount('#app')
|
||||
<div class="highlighted"> </div><br><br>
|
||||
</div>const app = new Vue({
|
||||
render,
|
||||
router
|
||||
})
|
||||
|
||||
app.$mount('#app')
|
||||
`;
|
||||
|
||||
exports[`highlightLines highlight single line 1`] = `
|
||||
<div class="highlight-lines">
|
||||
<div class="highlighted"> </div>
|
||||
<br>
|
||||
<div class="highlighted"> </div><br>
|
||||
</div>new Vue()
|
||||
`;
|
||||
|
||||
@@ -18,6 +18,7 @@ exports[`hoist Should miss script and style when using hoist 1`] = `
|
||||
|
||||
exports[`hoist Should miss script and style when using hoist 2`] = `
|
||||
Object {
|
||||
"__data_block": Object {},
|
||||
"hoistedTags": Array [
|
||||
<script src="vue.js"></script>,
|
||||
<style>
|
||||
|
||||
@@ -5,8 +5,7 @@ exports[`lineNumbers should lineNumbers work with highlightLines 1`] = `
|
||||
<div class="language-js line-numbers-mode">
|
||||
<!--afterbegin-->
|
||||
<div class="highlight-lines">
|
||||
<div class="highlighted"> </div>
|
||||
<br>
|
||||
<div class="highlighted"> </div><br>
|
||||
</div>new Vue()
|
||||
<div class="line-numbers-wrapper"></div>
|
||||
<!--beforeend-->
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`link should render external links correctly 1`] = `
|
||||
<p>
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener noreferrer">vue
|
||||
<OutboundLink/>
|
||||
</a>
|
||||
</p>
|
||||
<p><a href="https://vuejs.org/" target="_blank" rel="noopener noreferrer">vue
|
||||
<OutboundLink /></a></p>
|
||||
`;
|
||||
|
||||
exports[`link should render external links correctly 2`] = `
|
||||
<p>
|
||||
<a href="http://vuejs.org/" target="_blank" rel="noopener noreferrer">vue
|
||||
<OutboundLink/>
|
||||
</a>
|
||||
</p>
|
||||
<p><a href="http://vuejs.org/" target="_blank" rel="noopener noreferrer">vue
|
||||
<OutboundLink /></a></p>
|
||||
`;
|
||||
|
||||
exports[`link should render external links correctly 3`] = `
|
||||
<p>
|
||||
<a href="https://google.com" target="_blank" rel="noopener noreferrer">some <strong>link</strong> with <code>code</code>
|
||||
<OutboundLink/>
|
||||
</a>
|
||||
</p>
|
||||
<p><a href="https://google.com" target="_blank" rel="noopener noreferrer">some <strong>link</strong> with <code>code</code>
|
||||
<OutboundLink /></a></p>
|
||||
`;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user