aboutsummaryrefslogtreecommitdiff
path: root/node_modules/vuepress/lib/build.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/vuepress/lib/build.js')
-rw-r--r--node_modules/vuepress/lib/build.js200
1 files changed, 200 insertions, 0 deletions
diff --git a/node_modules/vuepress/lib/build.js b/node_modules/vuepress/lib/build.js
new file mode 100644
index 00000000..f7efb49b
--- /dev/null
+++ b/node_modules/vuepress/lib/build.js
@@ -0,0 +1,200 @@
+module.exports = async function build (sourceDir, cliOptions = {}) {
+ process.env.NODE_ENV = 'production'
+
+ const fs = require('fs-extra')
+ const path = require('path')
+ const chalk = require('chalk')
+ const webpack = require('webpack')
+ const readline = require('readline')
+ const escape = require('escape-html')
+
+ const logger = require('./util/logger')
+ const prepare = require('./prepare')
+ const createClientConfig = require('./webpack/createClientConfig')
+ const createServerConfig = require('./webpack/createServerConfig')
+ const { createBundleRenderer } = require('vue-server-renderer')
+ const { normalizeHeadTag, applyUserWebpackConfig } = require('./util')
+
+ logger.wait('\nExtracting site metadata...')
+ const options = await prepare(sourceDir)
+ if (cliOptions.outDir) {
+ options.outDir = cliOptions.outDir
+ }
+
+ const { outDir } = options
+ if (path.resolve() === outDir) {
+ return console.error(logger.error(chalk.red('Unexpected option: outDir cannot be set to the current working directory.\n'), false))
+ }
+ await fs.remove(outDir)
+
+ let clientConfig = createClientConfig(options, cliOptions).toConfig()
+ let serverConfig = createServerConfig(options, cliOptions).toConfig()
+
+ // apply user config...
+ const userConfig = options.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
+ await workaroundEmptyStyleChunk()
+
+ // create server renderer using built manifests
+ const renderer = createBundleRenderer(serverBundle, {
+ clientManifest,
+ runInNewContext: false,
+ inject: false,
+ shouldPrefetch: options.siteConfig.shouldPrefetch || (() => true),
+ template: await fs.readFile(path.resolve(__dirname, 'app/index.ssr.html'), 'utf-8')
+ })
+
+ // pre-render head tags from user config
+ const userHeadTags = (options.siteConfig.head || [])
+ .map(renderHeadTag)
+ .join('\n ')
+
+ // render pages
+ logger.wait('Rendering static HTML...')
+ for (const page of options.siteData.pages) {
+ await renderPage(page)
+ }
+
+ // if the user does not have a custom 404.md, generate the theme's default
+ if (!options.siteData.pages.some(p => p.path === '/404.html')) {
+ await renderPage({ path: '/404.html' })
+ }
+
+ readline.clearLine(process.stdout, 0)
+ readline.cursorTo(process.stdout, 0)
+
+ if (options.siteConfig.serviceWorker) {
+ logger.wait('\nGenerating service worker...')
+ const wbb = require('workbox-build')
+ await wbb.generateSW({
+ swDest: path.resolve(outDir, 'service-worker.js'),
+ globDirectory: outDir,
+ globPatterns: ['**\/*.{js,css,html,png,jpg,jpeg,gif,svg,woff,woff2,eot,ttf,otf}']
+ })
+ await fs.writeFile(
+ path.resolve(outDir, 'service-worker.js'),
+ await fs.readFile(path.resolve(__dirname, 'service-worker/skip-waiting.js'), 'utf8'),
+ { flag: 'a' }
+ )
+ }
+
+ // DONE.
+ const relativeDir = path.relative(process.cwd(), outDir)
+ logger.success(`\n${chalk.green('Success!')} Generated static files in ${chalk.cyan(relativeDir)}.\n`)
+
+ // --- 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 (cliOptions.debug && 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)
+ }
+
+ 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)
+ }
+}