diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 427e7a4d01..0000000000 --- a/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "env": { - "test": { - "presets": [ - ["@babel/preset-env", { "targets": { "node": 8 }}] - ] - } - } -} diff --git a/.gitignore b/.gitignore index 96930bdd73..2761c32ce8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ node_modules *.log .temp -vuepress TODOs.md +vuepress +packages/blog-example diff --git a/README.md b/README.md index 3fc1522b56..2dedbe4f94 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ License

+> This is the branch for `VuePress 1.0`. + +## Status: alpha + +Certain combinations of plugins may not work properly, and things may change or break until we reach beta phase. Do not use in production yet unless you are adventurous. + # VuePress > Minimalistic docs generator with Vue component based layout system @@ -78,8 +84,11 @@ Websites built with VuePress: VuePress is still a work in progress. There are a few things that it currently does not support but are planned: -- Plugin support -- Blogging support +- Migrate the old test. +- `@vuepress/plugin-test-utils`. +- `once` option for plugin options, which allows the same plugin only to be applied only once. +- `theme` name shortcut. +- `@vuepress/theme-blog` Contributions are welcome! diff --git a/__mocks__/@org/vuepress-plugin-a.js b/__mocks__/@org/vuepress-plugin-a.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/__mocks__/@org/vuepress-plugin-a.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/__mocks__/@org/vuepress-plugin-b.js b/__mocks__/@org/vuepress-plugin-b.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/__mocks__/@org/vuepress-plugin-b.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/__mocks__/@org/vuepress-theme-a/index.js b/__mocks__/@org/vuepress-theme-a/index.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/__mocks__/@org/vuepress-theme-a/index.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/__mocks__/@vuepress/plugin-a.js b/__mocks__/@vuepress/plugin-a.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/__mocks__/@vuepress/plugin-a.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/__mocks__/@vuepress/theme-a/index.js b/__mocks__/@vuepress/theme-a/index.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/__mocks__/@vuepress/theme-a/index.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/__mocks__/vuepress-plugin-a.js b/__mocks__/vuepress-plugin-a.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/__mocks__/vuepress-plugin-a.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/__mocks__/vuepress-plugin-b.js b/__mocks__/vuepress-plugin-b.js new file mode 100644 index 0000000000..0b52ba9543 --- /dev/null +++ b/__mocks__/vuepress-plugin-b.js @@ -0,0 +1,3 @@ +module.exports = { + multiple: true +} diff --git a/__mocks__/vuepress-theme-a/index.js b/__mocks__/vuepress-theme-a/index.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/__mocks__/vuepress-theme-a/index.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000000..87edd47c65 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,9 @@ +module.exports = { + 'env': { + 'test': { + 'presets': [ + ['@babel/preset-env', { 'targets': { 'node': 'current' }}] + ] + } + } +} diff --git a/bin/vuepress.js b/bin/vuepress.js deleted file mode 100755 index 36433168e6..0000000000 --- a/bin/vuepress.js +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env node - -const chalk = require('chalk') -const semver = require('semver') -const requiredVersion = require('../package.json').engines.node - -if (!semver.satisfies(process.version, requiredVersion)) { - console.log(chalk.red( - `\n[vuepress] minimum Node version not met:` + - `\nYou are using Node ${process.version}, but VuePress ` + - `requires Node ${requiredVersion}.\nPlease upgrade your Node version.\n` - )) - process.exit(1) -} - -const path = require('path') -const { dev, build, eject } = require('../lib') - -const program = require('commander') - -program - .version(require('../package.json').version) - .usage(' [options]') - -program - .command('dev [targetDir]') - .description('start development server') - .option('-p, --port ', 'use specified port (default: 8080)') - .option('-h, --host ', 'use specified host (default: 0.0.0.0)') - .option('--debug', 'start development server in debug mode') - .action((dir = '.', { host, port, debug }) => { - wrapCommand(dev)(path.resolve(dir), { host, port, debug }) - }) - -program - .command('build [targetDir]') - .description('build dir as static site') - .option('-d, --dest ', 'specify build output dir (default: .vuepress/dist)') - .option('--debug', 'build in development mode for debugging') - .action((dir = '.', { debug, dest }) => { - const outDir = dest ? path.resolve(dest) : null - wrapCommand(build)(path.resolve(dir), { debug, outDir }) - }) - -program - .command('eject [targetDir]') - .description('copy the default theme into .vuepress/theme for customization.') - .action((dir = '.') => { - wrapCommand(eject)(path.resolve(dir)) - }) - -// output help information on unknown commands -program - .arguments('') - .action((cmd) => { - program.outputHelp() - console.log(` ` + chalk.red(`Unknown command ${chalk.yellow(cmd)}.`)) - console.log() - }) - -// add some useful info on help -program.on('--help', () => { - console.log() - console.log(` Run ${chalk.cyan(`vuepress --help`)} for detailed usage of given command.`) - console.log() -}) - -program.commands.forEach(c => c.on('--help', () => console.log())) - -// enhance common error messages -const enhanceErrorMessages = (methodName, log) => { - program.Command.prototype[methodName] = function (...args) { - if (methodName === 'unknownOption' && this._allowUnknownOption) { - return - } - this.outputHelp() - console.log(` ` + chalk.red(log(...args))) - console.log() - process.exit(1) - } -} - -enhanceErrorMessages('missingArgument', argName => { - return `Missing required argument ${chalk.yellow(`<${argName}>`)}.` -}) - -enhanceErrorMessages('unknownOption', optionName => { - return `Unknown option ${chalk.yellow(optionName)}.` -}) - -enhanceErrorMessages('optionMissingArgument', (option, flag) => { - return `Missing required argument for option ${chalk.yellow(option.flags)}` + ( - flag ? `, got ${chalk.yellow(flag)}` : `` - ) -}) - -program.parse(process.argv) - -if (!process.argv.slice(2).length) { - program.outputHelp() -} - -function wrapCommand (fn) { - return (...args) => { - return fn(...args).catch(err => { - console.error(chalk.red(err.stack)) - process.exitCode = 1 - }) - } -} diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000000..757fcfd570 --- /dev/null +++ b/lerna.json @@ -0,0 +1,6 @@ +{ + "lerna": "2.5.1", + "npmClient": "yarn", + "useWorkspaces": true, + "version": "1.0.0" +} diff --git a/lib/app/clientEntry.js b/lib/app/clientEntry.js deleted file mode 100644 index dc86314a97..0000000000 --- a/lib/app/clientEntry.js +++ /dev/null @@ -1,74 +0,0 @@ -/* global BASE_URL, GA_ID, ga, SW_ENABLED, VUEPRESS_VERSION, LAST_COMMIT_HASH*/ - -import { createApp } from './app' -import SWUpdateEvent from './SWUpdateEvent' -import { register } from 'register-service-worker' - -const { app, router } = createApp() - -window.__VUEPRESS_VERSION__ = { - version: VUEPRESS_VERSION, - hash: LAST_COMMIT_HASH -} - -// Google analytics integration -if (process.env.NODE_ENV === 'production' && GA_ID) { - (function (i, s, o, g, r, a, m) { - i['GoogleAnalyticsObject'] = r - i[r] = i[r] || function () { - (i[r].q = i[r].q || []).push(arguments) - } - i[r].l = 1 * new Date() - a = s.createElement(o) - m = s.getElementsByTagName(o)[0] - a.async = 1 - a.src = g - m.parentNode.insertBefore(a, m) - })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga') - - ga('create', GA_ID, 'auto') - ga('send', 'pageview') - - router.afterEach(function (to) { - ga('set', 'page', app.$withBase(to.fullPath)) - ga('send', 'pageview') - }) -} - -router.onReady(() => { - app.$mount('#app') - - // Register service worker - if (process.env.NODE_ENV === 'production' && - SW_ENABLED && - window.location.protocol === 'https:') { - register(`${BASE_URL}service-worker.js`, { - ready () { - console.log('[vuepress:sw] Service worker is active.') - app.$refs.layout.$emit('sw-ready') - }, - cached (registration) { - console.log('[vuepress:sw] Content has been cached for offline use.') - app.$refs.layout.$emit('sw-cached', new SWUpdateEvent(registration)) - }, - updated (registration) { - console.log('[vuepress:sw] Content updated.') - app.$refs.layout.$emit('sw-updated', new SWUpdateEvent(registration)) - }, - offline () { - console.log('[vuepress:sw] No internet connection found. App is running in offline mode.') - app.$refs.layout.$emit('sw-offline') - }, - error (err) { - console.error('[vuepress:sw] Error during service worker registration:', err) - app.$refs.layout.$emit('sw-error', err) - if (GA_ID) { - ga('send', 'exception', { - exDescription: err.message, - exFatal: false - }) - } - } - }) - } -}) diff --git a/lib/app/components/Content.js b/lib/app/components/Content.js deleted file mode 100644 index 46628195e6..0000000000 --- a/lib/app/components/Content.js +++ /dev/null @@ -1,17 +0,0 @@ -export default { - functional: true, - - props: { - custom: { - type: Boolean, - default: true - } - }, - - render (h, { parent, props, data }) { - return h(parent.$page.key, { - class: [props.custom ? 'custom' : '', data.class, data.staticClass], - style: data.style - }) - } -} diff --git a/lib/app/dataMixin.js b/lib/app/dataMixin.js deleted file mode 100644 index fd003d926f..0000000000 --- a/lib/app/dataMixin.js +++ /dev/null @@ -1,90 +0,0 @@ -import Vue from 'vue' -import { findPageForPath } from './util' - -export default function dataMixin (siteData) { - prepare(siteData) - const store = new Vue({ - data: { siteData } - }) - - if (module.hot) { - module.hot.accept('./.temp/siteData', () => { - prepare(siteData) - store.siteData = siteData - }) - } - - return { - computed: { - $site () { - return store.siteData - }, - $localeConfig () { - const { locales = {}} = this.$site - let targetLang - let defaultLang - for (const path in locales) { - if (path === '/') { - defaultLang = locales[path] - } else if (this.$page.path.indexOf(path) === 0) { - targetLang = locales[path] - } - } - return targetLang || defaultLang || {} - }, - $siteTitle () { - return this.$localeConfig.title || this.$site.title || '' - }, - $title () { - const page = this.$page - const siteTitle = this.$siteTitle - const selfTitle = page.frontmatter.home ? null : ( - page.frontmatter.title || // explicit title - page.title // inferred title - ) - return siteTitle - ? selfTitle - ? (selfTitle + ' | ' + siteTitle) - : siteTitle - : selfTitle || 'VuePress' - }, - $description () { - // #565 hoist description from meta - if (this.$page.frontmatter.meta) { - const descriptionMeta = this.$page.frontmatter.meta.filter(item => item.name === 'description')[0] - if (descriptionMeta) return descriptionMeta.content - } - return this.$page.frontmatter.description || this.$localeConfig.description || this.$site.description || '' - }, - $lang () { - return this.$page.frontmatter.lang || this.$localeConfig.lang || 'en-US' - }, - $localePath () { - return this.$localeConfig.path || '/' - }, - $themeLocaleConfig () { - return (this.$site.themeConfig.locales || {})[this.$localePath] || {} - }, - $page () { - return findPageForPath( - this.$site.pages, - this.$route.path - ) - } - } - } -} - -function prepare (siteData) { - siteData.pages.forEach(page => { - if (!page.frontmatter) { - page.frontmatter = {} - } - }) - if (siteData.locales) { - Object.keys(siteData.locales).forEach(path => { - siteData.locales[path].path = path - }) - } - Object.freeze(siteData) -} diff --git a/lib/app/root-mixins/index.js b/lib/app/root-mixins/index.js deleted file mode 100644 index fd966f396e..0000000000 --- a/lib/app/root-mixins/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import updateMeta from './updateMeta' -import activeHeaderLinks from '@activeHeaderLinks' - -export default [ - updateMeta, // required - activeHeaderLinks // optional -] diff --git a/lib/app/serverEntry.js b/lib/app/serverEntry.js deleted file mode 100644 index 715fc95653..0000000000 --- a/lib/app/serverEntry.js +++ /dev/null @@ -1,14 +0,0 @@ -import { createApp } from './app' - -export default context => new Promise((resolve, reject) => { - const { app, router } = createApp() - const { url } = context - const { fullPath } = router.resolve(url).route - - if (fullPath !== url) { - return reject({ url: fullPath }) - } - - router.push(url) - router.onReady(() => resolve(app)) -}) diff --git a/lib/app/store.js b/lib/app/store.js deleted file mode 100644 index 115fe0b9e6..0000000000 --- a/lib/app/store.js +++ /dev/null @@ -1,7 +0,0 @@ -// It is not yet time to use Vuex to manage the global state -// singleton object as a global store. -const state = { - disableScrollBehavior: false -} - -export default state diff --git a/lib/app/util.js b/lib/app/util.js deleted file mode 100644 index c11172240e..0000000000 --- a/lib/app/util.js +++ /dev/null @@ -1,19 +0,0 @@ -export function injectMixins (options, mixins) { - if (!options.mixins) { - options.mixins = [] - } - options.mixins.push(...mixins) -} - -export function findPageForPath (pages, path) { - for (let i = 0; i < pages.length; i++) { - const page = pages[i] - if (page.path === path) { - return page - } - } - return { - path: '', - frontmatter: {} - } -} diff --git a/lib/default-theme/SWUpdatePopup.vue b/lib/default-theme/SWUpdatePopup.vue deleted file mode 100644 index b224db31ed..0000000000 --- a/lib/default-theme/SWUpdatePopup.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - - - diff --git a/lib/markdown/index.js b/lib/markdown/index.js deleted file mode 100644 index f4942f6ce6..0000000000 --- a/lib/markdown/index.js +++ /dev/null @@ -1,78 +0,0 @@ -const highlight = require('./highlight') -const highlightLines = require('./highlightLines') -const preWrapper = require('./preWrapper') -const lineNumbers = require('./lineNumbers') -const component = require('./component') -const hoistScriptStyle = require('./hoist') -const convertRouterLink = require('./link') -const containers = require('./containers') -const snippet = require('./snippet') -const emoji = require('markdown-it-emoji') -const anchor = require('markdown-it-anchor') -const toc = require('markdown-it-table-of-contents') -const _slugify = require('./slugify') -const { parseHeaders } = require('../util/parseHeaders') - -module.exports = ({ markdown = {}} = {}) => { - // allow user config slugify - const slugify = markdown.slugify || _slugify - - const md = require('markdown-it')({ - html: true, - highlight - }) - // custom plugins - .use(component) - .use(highlightLines) - .use(preWrapper) - .use(snippet) - .use(convertRouterLink, Object.assign({ - target: '_blank', - rel: 'noopener noreferrer' - }, markdown.externalLinks)) - .use(hoistScriptStyle) - .use(containers) - - // 3rd party plugins - .use(emoji) - .use(anchor, Object.assign({ - slugify, - permalink: true, - permalinkBefore: true, - permalinkSymbol: '#' - }, markdown.anchor)) - .use(toc, Object.assign({ - slugify, - includeLevel: [2, 3], - format: parseHeaders - }, markdown.toc)) - - // apply user config - if (markdown.config) { - markdown.config(md) - } - - if (markdown.lineNumbers) { - md.use(lineNumbers) - } - - module.exports.dataReturnable(md) - - // expose slugify - md.slugify = slugify - - return md -} - -module.exports.dataReturnable = function dataReturnable (md) { - // override render to allow custom plugins return data - const render = md.render - md.render = (...args) => { - md.__data = {} - const html = render.call(md, ...args) - return { - html, - data: md.__data - } - } -} diff --git a/lib/prepare/codegen.js b/lib/prepare/codegen.js deleted file mode 100644 index 52e85355a2..0000000000 --- a/lib/prepare/codegen.js +++ /dev/null @@ -1,74 +0,0 @@ -const path = require('path') -const { fileToComponentName, resolveComponents } = require('./util') - -exports.genRoutesFile = async function ({ - siteData: { pages }, - sourceDir, - pageFiles -}) { - function genRoute ({ path: pagePath, key: componentName }, index) { - const file = pageFiles[index] - const filePath = path.resolve(sourceDir, file) - let code = ` - { - name: ${JSON.stringify(componentName)}, - path: ${JSON.stringify(pagePath)}, - component: ThemeLayout, - beforeEnter: (to, from, next) => { - import(${JSON.stringify(filePath)}).then(comp => { - Vue.component(${JSON.stringify(componentName)}, comp.default) - next() - }) - } - }` - - const dncodedPath = decodeURIComponent(pagePath) - if (dncodedPath !== pagePath) { - code += `, - { - path: ${JSON.stringify(dncodedPath)}, - redirect: ${JSON.stringify(pagePath)} - }` - } - - if (/\/$/.test(pagePath)) { - code += `, - { - path: ${JSON.stringify(pagePath + 'index.html')}, - redirect: ${JSON.stringify(pagePath)} - }` - } - - return code - } - - const notFoundRoute = `, - { - path: '*', - component: ThemeNotFound - }` - - return ( - `import ThemeLayout from '@themeLayout'\n` + - `import ThemeNotFound from '@themeNotFound'\n` + - `import { injectMixins } from '@app/util'\n` + - `import rootMixins from '@app/root-mixins'\n\n` + - `injectMixins(ThemeLayout, rootMixins)\n` + - `injectMixins(ThemeNotFound, rootMixins)\n\n` + - `export const routes = [${pages.map(genRoute).join(',')}${notFoundRoute}\n]` - ) -} - -exports.genComponentRegistrationFile = async function ({ sourceDir }) { - function genImport (file) { - const name = fileToComponentName(file) - const baseDir = path.resolve(sourceDir, '.vuepress/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)) || [] - return `import Vue from 'vue'\n` + components.map(genImport).join('\n') -} - diff --git a/lib/prepare/index.js b/lib/prepare/index.js deleted file mode 100644 index 7561f79158..0000000000 --- a/lib/prepare/index.js +++ /dev/null @@ -1,51 +0,0 @@ -const path = require('path') -const fs = require('fs-extra') -const resolveOptions = require('./resolveOptions') -const { genRoutesFile, genComponentRegistrationFile } = require('./codegen') -const { writeTemp, writeEnhanceTemp } = require('./util') -const logger = require('../util/logger') -const chalk = require('chalk') - -module.exports = async function prepare (sourceDir) { - // 1. load options - const options = await resolveOptions(sourceDir) - - // 2. generate routes & user components registration code - const routesCode = await genRoutesFile(options) - const componentCode = await genComponentRegistrationFile(options) - - await writeTemp('routes.js', [ - componentCode, - routesCode - ].join('\n')) - - // 3. generate siteData - const dataCode = `export const siteData = ${JSON.stringify(options.siteData, null, 2)}` - await writeTemp('siteData.js', dataCode) - - // 4. handle user override - const overridePath = path.resolve(sourceDir, '.vuepress/override.styl').replace(/[\\]+/g, '/') - const hasUserOverride = fs.existsSync(overridePath) - await writeTemp('override.styl', hasUserOverride ? `@import(${JSON.stringify(overridePath)})` : ``) - - const stylePath = path.resolve(sourceDir, '.vuepress/style.styl').replace(/[\\]+/g, '/') - const hasUserStyle = fs.existsSync(stylePath) - await writeTemp('style.styl', hasUserStyle ? `@import(${JSON.stringify(stylePath)})` : ``) - - // Temporary tip, will be removed at next release. - if (hasUserOverride && !hasUserStyle) { - logger.tip( - `${chalk.magenta('override.styl')} has been split into 2 APIs, we recommend you upgrade to continue.\n` + - ` See: ${chalk.magenta('https://vuepress.vuejs.org/default-theme-config/#simple-css-override')}` - ) - } - - // 5. handle enhanceApp.js - const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js') - await writeEnhanceTemp('enhanceApp.js', enhanceAppPath) - - // 6. handle the theme enhanceApp.js - await writeEnhanceTemp('themeEnhanceApp.js', options.themeEnhanceAppPath) - - return options -} diff --git a/lib/prepare/resolveOptions.js b/lib/prepare/resolveOptions.js deleted file mode 100644 index 9b97ca8e85..0000000000 --- a/lib/prepare/resolveOptions.js +++ /dev/null @@ -1,194 +0,0 @@ -const fs = require('fs-extra') -const path = require('path') -const globby = require('globby') -const createMarkdown = require('../markdown') -const loadConfig = require('./loadConfig') -const { encodePath, fileToPath, sort, getGitLastUpdatedTimeStamp } = require('./util') -const { - inferTitle, - extractHeaders, - parseFrontmatter -} = require('../util/index') - -module.exports = async function resolveOptions (sourceDir) { - const vuepressDir = path.resolve(sourceDir, '.vuepress') - const siteConfig = loadConfig(vuepressDir) - - // normalize head tag urls for base - const base = siteConfig.base || '/' - if (base !== '/' && siteConfig.head) { - siteConfig.head.forEach(tag => { - const attrs = tag[1] - if (attrs) { - for (const name in attrs) { - if (name === 'src' || name === 'href') { - const value = attrs[name] - if (value.charAt(0) === '/') { - attrs[name] = base + value.slice(1) - } - } - } - } - }) - } - - // resolve outDir - const outDir = siteConfig.dest - ? path.resolve(siteConfig.dest) - : path.resolve(sourceDir, '.vuepress/dist') - - // resolve theme - const useDefaultTheme = ( - !siteConfig.theme && - !fs.existsSync(path.resolve(vuepressDir, 'theme')) - ) - const defaultThemePath = path.resolve(__dirname, '../default-theme') - let themePath = null - let themeLayoutPath = null - let themeNotFoundPath = null - let themeEnhanceAppPath = null - - if (useDefaultTheme) { - // use default theme - themePath = defaultThemePath - themeLayoutPath = path.resolve(defaultThemePath, 'Layout.vue') - themeNotFoundPath = path.resolve(defaultThemePath, 'NotFound.vue') - } else { - // resolve theme Layout - if (siteConfig.theme) { - // use external theme - try { - themeLayoutPath = require.resolve(`vuepress-theme-${siteConfig.theme}/Layout.vue`, { - paths: [ - path.resolve(__dirname, '../../node_modules'), - path.resolve(sourceDir) - ] - }) - themePath = path.dirname(themeLayoutPath) - } catch (e) { - throw new Error(`[vuepress] Failed to load custom theme "${ - siteConfig.theme - }". File vuepress-theme-${siteConfig.theme}/Layout.vue does not exist.`) - } - } else { - // use custom theme - themePath = path.resolve(vuepressDir, 'theme') - themeLayoutPath = path.resolve(themePath, 'Layout.vue') - if (!fs.existsSync(themeLayoutPath)) { - throw new Error(`[vuepress] Cannot resolve Layout.vue file in .vuepress/theme.`) - } - } - - // resolve theme NotFound - themeNotFoundPath = path.resolve(themePath, 'NotFound.vue') - if (!fs.existsSync(themeNotFoundPath)) { - themeNotFoundPath = path.resolve(defaultThemePath, 'NotFound.vue') - } - - // resolve theme enhanceApp - themeEnhanceAppPath = path.resolve(themePath, 'enhanceApp.js') - if (!fs.existsSync(themeEnhanceAppPath)) { - themeEnhanceAppPath = null - } - } - - // resolve theme config - const themeConfig = siteConfig.themeConfig || {} - - // resolve algolia - const isAlgoliaSearch = ( - themeConfig.algolia || - Object.keys(siteConfig.locales && themeConfig.locales || {}) - .some(base => themeConfig.locales[base].algolia) - ) - - // resolve markdown - const markdown = createMarkdown(siteConfig) - - // resolve pageFiles - const patterns = ['**/*.md', '!.vuepress', '!node_modules'] - if (siteConfig.dest) { - // #654 exclude dest folder when dest dir was set in - // sourceDir but not in '.vuepress' - const outDirRelative = path.relative(sourceDir, outDir) - if (!outDirRelative.includes('..')) { - patterns.push('!' + outDirRelative) - } - } - const pageFiles = sort(await globby(patterns, { cwd: sourceDir })) - - // resolve lastUpdated - const shouldResolveLastUpdated = ( - themeConfig.lastUpdated || - Object.keys(siteConfig.locales && themeConfig.locales || {}) - .some(base => themeConfig.locales[base].lastUpdated) - ) - - // resolve pagesData - const pagesData = await Promise.all(pageFiles.map(async (file) => { - const filepath = path.resolve(sourceDir, file) - const key = 'v-' + Math.random().toString(16).slice(2) - const data = { - key, - path: encodePath(fileToPath(file)) - } - - if (shouldResolveLastUpdated) { - data.lastUpdated = getGitLastUpdatedTimeStamp(filepath) - } - - // extract yaml frontmatter - const content = await fs.readFile(filepath, 'utf-8') - const frontmatter = parseFrontmatter(content) - // infer title - const title = inferTitle(frontmatter) - if (title) { - data.title = title - } - const headers = extractHeaders( - frontmatter.content, - ['h2', 'h3'], - markdown - ) - if (headers.length) { - data.headers = headers - } - if (Object.keys(frontmatter.data).length) { - data.frontmatter = frontmatter.data - } - if (frontmatter.excerpt) { - const { html } = markdown.render(frontmatter.excerpt) - data.excerpt = html - } - return data - })) - - // resolve site data - const siteData = { - title: siteConfig.title || '', - description: siteConfig.description || '', - base, - pages: pagesData, - themeConfig, - locales: siteConfig.locales - } - - const options = { - siteConfig, - siteData, - sourceDir, - outDir, - publicPath: base, - pageFiles, - pagesData, - themePath, - themeLayoutPath, - themeNotFoundPath, - themeEnhanceAppPath, - useDefaultTheme, - isAlgoliaSearch, - markdown - } - - return options -} diff --git a/lib/prepare/util.js b/lib/prepare/util.js deleted file mode 100644 index efa96cc9b7..0000000000 --- a/lib/prepare/util.js +++ /dev/null @@ -1,80 +0,0 @@ -const path = require('path') -const spawn = require('cross-spawn') -const fs = require('fs-extra') -const globby = require('globby') - -const tempPath = path.resolve(__dirname, '../app/.temp') -fs.ensureDirSync(tempPath) - -const tempCache = new Map() -exports.writeTemp = async function (file, content) { - // cache write to avoid hitting the dist if it didn't change - const cached = tempCache.get(file) - if (cached !== content) { - await fs.writeFile(path.join(tempPath, file), content) - tempCache.set(file, content) - } -} - -exports.writeEnhanceTemp = async function (destName, srcPath) { - await exports.writeTemp( - destName, - fs.existsSync(srcPath) - ? `export { default } from ${JSON.stringify(srcPath)}` - : `export default function () {}` - ) -} - -const indexRE = /(^|.*\/)(index|readme)\.md$/i -const extRE = /\.(vue|md)$/ - -exports.fileToPath = function (file) { - if (exports.isIndexFile(file)) { - // README.md -> / - // foo/README.md -> /foo/ - return file.replace(indexRE, '/$1') - } else { - // foo.md -> /foo.html - // foo/bar.md -> /foo/bar.html - return `/${file.replace(extRE, '').replace(/\\/g, '/')}.html` - } -} - -exports.fileToComponentName = function (file) { - let normalizedName = file - .replace(/\/|\\/g, '-') - .replace(extRE, '') - if (exports.isIndexFile(file)) { - normalizedName = normalizedName.replace(/readme$/i, 'index') - } - const pagePrefix = /\.md$/.test(file) ? `page-` : `` - return `${pagePrefix}${normalizedName}` -} - -exports.isIndexFile = function (file) { - return indexRE.test(file) -} - -exports.resolveComponents = async function (sourceDir) { - const componentDir = path.resolve(sourceDir, '.vuepress/components') - if (!fs.existsSync(componentDir)) { - return - } - return exports.sort(await globby(['**/*.vue'], { cwd: componentDir })) -} - -exports.sort = function (arr) { - return arr.sort((a, b) => { - if (a < b) return -1 - if (a > b) return 1 - return 0 - }) -} - -exports.encodePath = function (userpath) { - return userpath.split('/').map(item => encodeURIComponent(item)).join('/') -} - -exports.getGitLastUpdatedTimeStamp = function (filepath) { - return parseInt(spawn.sync('git', ['log', '-1', '--format=%ct', filepath]).stdout.toString('utf-8')) * 1000 -} diff --git a/lib/util/index.js b/lib/util/index.js deleted file mode 100644 index 52044a8fcb..0000000000 --- a/lib/util/index.js +++ /dev/null @@ -1,83 +0,0 @@ -const { deeplyParseHeaders } = require('./parseHeaders') - -exports.normalizeHeadTag = function (tag) { - if (typeof tag === 'string') { - tag = [tag] - } - const tagName = tag[0] - return { - tagName, - attributes: tag[1] || {}, - innerHTML: tag[2] || '', - closeTag: !(tagName === 'meta' || tagName === 'link') - } -} - -exports.applyUserWebpackConfig = function (userConfig, config, isServer) { - const merge = require('webpack-merge') - if (typeof userConfig === 'object') { - return merge(config, userConfig) - } - if (typeof userConfig === 'function') { - const res = userConfig(config, isServer) - if (res && typeof res === 'object') { - return merge(config, res) - } - } - return config -} - -exports.inferTitle = function (frontmatter) { - if (frontmatter.data.home) { - return 'Home' - } - if (frontmatter.data.title) { - return deeplyParseHeaders(frontmatter.data.title) - } - const match = frontmatter.content.trim().match(/^#+\s+(.*)/m) - if (match) { - return deeplyParseHeaders(match[1]) - } -} - -exports.parseFrontmatter = function (content) { - const matter = require('gray-matter') - const toml = require('toml') - - return matter(content, { - excerpt_separator: '', - engines: { - toml: toml.parse.bind(toml), - excerpt: false - } - }) -} - -const LRU = require('lru-cache') -const cache = LRU({ max: 1000 }) - -exports.extractHeaders = function (content, include = [], md) { - const key = content + include.join(',') - const hit = cache.get(key) - if (hit) { - return hit - } - - const tokens = md.parse(content, {}) - - const res = [] - tokens.forEach((t, i) => { - if (t.type === 'heading_open' && include.includes(t.tag)) { - const title = tokens[i + 1].content - const slug = t.attrs.find(([name]) => name === 'id')[1] - res.push({ - level: parseInt(t.tag.slice(1), 10), - title: deeplyParseHeaders(title), - slug: slug || md.slugify(title) - }) - } - }) - - cache.set(key, res) - return res -} diff --git a/package.json b/package.json index afdec2156d..5e2bc5b7e9 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,22 @@ { - "name": "vuepress", - "version": "0.14.4", + "private": true, + "workspaces": [ + "packages/@vuepress/*", + "packages/vuepress", + "packages/docs", + "packages/blog-example" + ], "description": "Minimalistic doc generator with Vue component based layout system", - "main": "lib/index.js", - "bin": { - "vuepress": "bin/vuepress.js" - }, "scripts": { - "dev": "node bin/vuepress dev docs", - "build": "node bin/vuepress build docs", - "lint": "eslint --fix --ext .js,.vue bin/ lib/ test/", - "prepublishOnly": "conventional-changelog -p angular -r 2 -i CHANGELOG.md -s", - "release": "/bin/bash scripts/release.sh", - "test": "node test/prepare.js && jest --config test/jest.config.js" + "boot": "node scripts/bootstrap.js", + "dev": "yarn workspace docs dev", + "build": "yarn workspace docs build", + "dev:blog-example": "yarn workspace blog-example dev", + "build:blog-example": "yarn workspace blog-example build", + "lint": "eslint --fix packages/**/*.js packages/**/*.vue packages/**/bin/*", + "release": "yarn --pure-lockfile && node scripts/release.js", + "changelog": "node scripts/genChangelog.js run", + "test": "node scripts/test.js" }, "repository": { "type": "git", @@ -38,78 +42,17 @@ "git add" ] }, - "dependencies": { - "@babel/core": "7.0.0-beta.47", - "@vue/babel-preset-app": "3.0.0-beta.11", - "autoprefixer": "^8.2.0", - "babel-loader": "8.0.0-beta.3", - "cache-loader": "^1.2.2", - "chalk": "^2.3.2", - "chokidar": "^2.0.3", - "commander": "^2.15.1", - "connect-history-api-fallback": "^1.5.0", - "copy-webpack-plugin": "^4.5.1", - "cross-spawn": "^6.0.5", - "css-loader": "^0.28.11", - "diacritics": "^1.3.0", - "docsearch.js": "^2.5.2", - "escape-html": "^1.0.3", - "file-loader": "^1.1.11", - "fs-extra": "^5.0.0", - "globby": "^8.0.1", - "gray-matter": "^4.0.1", - "js-yaml": "^3.11.0", - "koa-connect": "^2.0.1", - "koa-mount": "^3.0.0", - "koa-range": "^0.3.0", - "koa-static": "^4.0.2", - "loader-utils": "^1.1.0", - "lodash.throttle": "^4.1.1", - "lru-cache": "^4.1.2", - "markdown-it": "^8.4.1", - "markdown-it-anchor": "^5.0.2", - "markdown-it-container": "^2.0.0", - "markdown-it-emoji": "^1.4.0", - "markdown-it-table-of-contents": "^0.4.0", - "mini-css-extract-plugin": "^0.4.1", - "nprogress": "^0.2.0", - "optimize-css-assets-webpack-plugin": "^4.0.0", - "portfinder": "^1.0.13", - "postcss-loader": "^2.1.5", - "prismjs": "^1.13.0", - "register-service-worker": "^1.5.1", - "semver": "^5.5.0", - "stylus": "^0.54.5", - "stylus-loader": "^3.0.2", - "toml": "^2.3.3", - "url-loader": "^1.0.1", - "vue": "^2.5.16", - "vue-loader": "^15.2.4", - "vue-router": "^3.0.1", - "vue-server-renderer": "^2.5.16", - "vue-template-compiler": "^2.5.16", - "vuepress-html-webpack-plugin": "^3.2.0", - "webpack": "^4.8.1", - "webpack-chain": "^4.6.0", - "webpack-merge": "^4.1.2", - "webpack-serve": "^1.0.2", - "webpackbar": "^2.6.1", - "workbox-build": "^3.1.0" - }, "devDependencies": { - "@vue/test-utils": "^1.0.0-beta.16", - "babel-core": "^7.0.0-0", - "babel-jest": "^23.0.0", "conventional-changelog-cli": "^1.3.22", "eslint": "^4.19.1", "eslint-plugin-jest": "^21.15.1", "eslint-plugin-vue-libs": "^3.0.0", - "jest": "^23.0.0", - "jest-serializer-vue": "^1.0.0", + "lerna": "^2.11.0", "lint-staged": "^7.0.4", - "vue-jest": "^2.6.0", - "vuepress-theme-vue": "^1.1.0", - "yorkie": "^1.0.3" + "minimist": "^1.2.0", + "yorkie": "^1.0.3", + "inquirer": "^6.2.0", + "@vue/conventional-changelog": "^0.1.1" }, "engines": { "node": ">=8" diff --git a/packages/@vuepress/cli/.npmignore b/packages/@vuepress/cli/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/cli/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/cli/README.md b/packages/@vuepress/cli/README.md new file mode 100644 index 0000000000..e2524b698b --- /dev/null +++ b/packages/@vuepress/cli/README.md @@ -0,0 +1,18 @@ +# @vuepress/cli + +> cli for vuepress + +## APIs + +### program + +Current instance of [commander.js](https://github.com/tj/commander.js) + +### bootstrap(options) + +Launch the cli. + +#### options.plugins + +#### options.theme + \ No newline at end of file diff --git a/packages/@vuepress/cli/index.js b/packages/@vuepress/cli/index.js new file mode 100644 index 0000000000..9c846e4858 --- /dev/null +++ b/packages/@vuepress/cli/index.js @@ -0,0 +1,131 @@ +const { chalk } = require('@vuepress/shared-utils') +const semver = require('semver') + +try { + require.resolve('@vuepress/core') +} catch (err) { + console.log(chalk.red( + `\n[vuepress] @vuepress/cli ` + + `requires @vuepress/core to be installed.\n` + )) + process.exit(1) +} + +const pkg = require('@vuepress/core/package.json') +const requiredVersion = pkg.engines.node + +if (!semver.satisfies(process.version, requiredVersion)) { + console.log(chalk.red( + `\n[vuepress] minimum Node version not met:` + + `\nYou are using Node ${process.version}, but VuePress ` + + `requires Node ${requiredVersion}.\nPlease upgrade your Node version.\n` + )) + process.exit(1) +} + +const program = require('commander') + +exports.program = program +exports.bootstrap = function ({ + plugins, + theme +} = {}) { + const path = require('path') + const { dev, build, eject } = require('@vuepress/core') + + program + .version(pkg.version) + .usage(' [options]') + + program + .command('dev [targetDir]') + .description('start development server') + .option('-p, --port ', 'use specified port (default: 8080)') + .option('-h, --host ', 'use specified host (default: 0.0.0.0)') + .option('-t, --temp ', 'set the directory of the temporary file') + .option('-c, --cache ', 'set the directory of cache') + .option('--no-cache', 'clean the cache before build') + .option('--debug', 'start development server in debug mode') + .action((dir = '.', { host, port, debug, temp, cache }) => { + wrapCommand(dev)(path.resolve(dir), { host, port, debug, temp, cache, plugins, theme }) + }) + + program + .command('build [targetDir]') + .description('build dir as static site') + .option('-d, --dest ', 'specify build output dir (default: .vuepress/dist)') + .option('-t, --temp ', 'set the directory of the temporary file') + .option('-c, --cache ', 'set the directory of cache') + .option('--no-cache', 'clean the cache before build') + .option('--debug', 'build in development mode for debugging') + .action((dir = '.', { debug, dest, temp, cache }) => { + const outDir = dest ? path.resolve(dest) : null + wrapCommand(build)(path.resolve(dir), { debug, outDir, plugins, theme, temp, cache }) + }) + + program + .command('eject [targetDir]') + .description('copy the default theme into .vuepress/theme for customization.') + .action((dir = '.') => { + wrapCommand(eject)(path.resolve(dir)) + }) + + // output help information on unknown commands + program + .arguments('') + .action((cmd) => { + program.outputHelp() + console.log(` ` + chalk.red(`Unknown command ${chalk.yellow(cmd)}.`)) + console.log() + }) + + // add some useful info on help + program.on('--help', () => { + console.log() + console.log(` Run ${chalk.cyan(`vuepress --help`)} for detailed usage of given command.`) + console.log() + }) + + program.commands.forEach(c => c.on('--help', () => console.log())) + + // enhance common error messages + const enhanceErrorMessages = (methodName, log) => { + program.Command.prototype[methodName] = function (...args) { + if (methodName === 'unknownOption' && this._allowUnknownOption) { + return + } + this.outputHelp() + console.log(` ` + chalk.red(log(...args))) + console.log() + process.exit(1) + } + } + + enhanceErrorMessages('missingArgument', argName => { + return `Missing required argument ${chalk.yellow(`<${argName}>`)}.` + }) + + enhanceErrorMessages('unknownOption', optionName => { + return `Unknown option ${chalk.yellow(optionName)}.` + }) + + enhanceErrorMessages('optionMissingArgument', (option, flag) => { + return `Missing required argument for option ${chalk.yellow(option.flags)}` + ( + flag ? `, got ${chalk.yellow(flag)}` : `` + ) + }) + + function wrapCommand (fn) { + return (...args) => { + return fn(...args).catch(err => { + console.error(chalk.red(err.stack)) + process.exitCode = 1 + }) + } + } + + program.parse(process.argv) + if (!process.argv.slice(2).length) { + program.outputHelp() + } +} diff --git a/packages/@vuepress/cli/package.json b/packages/@vuepress/cli/package.json new file mode 100644 index 0000000000..f6105c8a68 --- /dev/null +++ b/packages/@vuepress/cli/package.json @@ -0,0 +1,32 @@ +{ + "name": "@vuepress/cli", + "version": "1.0.0", + "description": "cli for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vue-cli.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "dependencies": { + "chalk": "^2.3.2", + "semver": "^5.5.0" + }, + "peerDependencies": { + "@vuepress/core": "^1.0.0" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/cli#readme" +} diff --git a/packages/@vuepress/core/.npmignore b/packages/@vuepress/core/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/core/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/core/README.md b/packages/@vuepress/core/README.md new file mode 100644 index 0000000000..7829eb5850 --- /dev/null +++ b/packages/@vuepress/core/README.md @@ -0,0 +1,9 @@ +# @vuepress/core + +## APIs + +### dev(sourceDir, options) + +### build(sourceDir, options) + +### eject(targetDir) diff --git a/packages/@vuepress/core/__test__/plugin-api/AsyncOption.spec.js b/packages/@vuepress/core/__test__/plugin-api/AsyncOption.spec.js new file mode 100644 index 0000000000..357f1fb157 --- /dev/null +++ b/packages/@vuepress/core/__test__/plugin-api/AsyncOption.spec.js @@ -0,0 +1,24 @@ +const AsyncOption = require('../../lib/plugin-api/abstract/AsyncOption') + +describe('AsyncOption', () => { + test('parallelApply', async () => { + const option = new AsyncOption('option') + const handler1 = jest.fn() + const handler2 = jest.fn() + + option.add('plugin-a', handler1) + option.add('plugin-b', handler2) + + // TODO for now, if a class extends from another class. + // the original methods in that class will be lost. + + await option.parallelApply(1, 2) + // expect(handler1.mock.calls).toHaveLength(1) + // expect(handler2.mock.calls).toHaveLength(1) + // expect(handler1.mock.calls[0][0]).toBe(1) + // expect(handler1.mock.calls[0][1]).toBe(2) + // expect(handler2.mock.calls[0][0]).toBe(1) + // expect(handler2.mock.calls[0][1]).toBe(2) + }) +}) + diff --git a/packages/@vuepress/core/__test__/plugin-api/Option.spec.js b/packages/@vuepress/core/__test__/plugin-api/Option.spec.js new file mode 100644 index 0000000000..e00a73608c --- /dev/null +++ b/packages/@vuepress/core/__test__/plugin-api/Option.spec.js @@ -0,0 +1,90 @@ +import Option from '../../lib/plugin-api/abstract/Option' + +describe('Option', () => { + test('key', () => { + const option = new Option('option') + expect(option.key).toBe('option') + }) + + test('add', () => { + const option = new Option('option') + option.add('plugin-a', 'a') + option.add('plugin-b', 'b') + + expect(option.items).toEqual([ + { value: 'a', name: 'plugin-a' }, + { value: 'b', name: 'plugin-b' } + ]) + + expect(option.values).toEqual(['a', 'b']) + }) + + test('add - resolve array', () => { + const option = new Option('option') + option.add('plugin-a', ['a-1', 'a-2']) + option.add('plugin-b', 'b') + + expect(option.items).toEqual([ + { value: 'a-1', name: 'plugin-a' }, + { value: 'a-2', name: 'plugin-a' }, + { value: 'b', name: 'plugin-b' } + ]) + }) + + test('delete', () => { + const option = new Option('option') + option.add('plugin-a', ['a-1', 'a-2']) + option.add('plugin-b', 'b') + + option.delete('plugin-a') + + expect(option.items).toEqual([ + { value: 'b', name: 'plugin-b' } + ]) + }) + + test('clear', () => { + const option = new Option('option') + option.add('plugin-a', ['a-1', 'a-2']) + + option.clear() + + expect(option.items).toEqual([]) + }) + + test('syncApply', () => { + const option = new Option('option') + const handler1 = jest.fn() + const handler2 = jest.fn() + + option.add('plugin-a', handler1) + option.add('plugin-b', handler2) + + option.syncApply('p1', 'p2') + expect(handler1.mock.calls).toHaveLength(1) + expect(handler2.mock.calls).toHaveLength(1) + expect(handler1.mock.calls[0][0]).toBe('p1') + expect(handler1.mock.calls[0][1]).toBe('p2') + expect(handler2.mock.calls[0][0]).toBe('p1') + expect(handler2.mock.calls[0][1]).toBe('p2') + }) + + test('appliedItems', () => { + const option = new Option('option') + const fn1 = () => 'fn1' + const fn2 = () => 'fn2' + const handler1 = jest.fn(fn1) + const handler2 = jest.fn(fn2) + + option.add('plugin-a', handler1) + option.add('plugin-b', handler2) + + option.syncApply(1, 2) + + expect(option.appliedItems).toEqual([ + { value: 'fn1', name: 'plugin-a' }, + { value: 'fn2', name: 'plugin-b' } + ]) + }) +}) + diff --git a/packages/@vuepress/core/__test__/plugin-api/PluginAPI.spec.js b/packages/@vuepress/core/__test__/plugin-api/PluginAPI.spec.js new file mode 100644 index 0000000000..7c580aacde --- /dev/null +++ b/packages/@vuepress/core/__test__/plugin-api/PluginAPI.spec.js @@ -0,0 +1,87 @@ +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' + +describe('Plugin', () => { + test('registerOption', () => { + const api = new PluginAPI() + const readyHandler = () => {} + api.registerOption(PLUGIN_OPTION_MAP.READY.key, readyHandler) + expect(api.options.ready.values).toHaveLength(1) + expect(api.options.ready.values[0]).toBe(readyHandler) + }) + + test('useByPluginsConfig', () => { + [ + ['a'], + [['a']], + [['a', true]], + { a: true } + ].forEach(pluginsConfig => { + const api = new PluginAPI() + api.useByPluginsConfig(pluginsConfig) + expect(api.enabledPlugins).toHaveLength(1) + expect(api.enabledPlugins[0].name).toBe('vuepress-plugin-a') + expect(api.disabledPlugins).toHaveLength(0) + }) + }) + + test('useByPluginsConfig - disable plugin', () => { + [ + [['a', false]], + { a: false } + ].forEach(pluginsConfig => { + const api = new PluginAPI() + api.useByPluginsConfig(pluginsConfig) + expect(api.enabledPlugins).toHaveLength(0) + expect(api.disabledPlugins).toHaveLength(1) + expect(api.disabledPlugins[0].name).toBe('vuepress-plugin-a') + }) + }) + + test('useByPluginsConfig - get options', () => { + const pluginOptions = {}; + [ + [['a', pluginOptions]], + { a: pluginOptions } + ].forEach(pluginsConfig => { + const api = new PluginAPI() + api.useByPluginsConfig(pluginsConfig) + expect(api.enabledPlugins[0].$$options).toBe(pluginOptions) + }) + }) + + test('ensure the namesake plugin is only executed once.', () => { + const pluginOptions1 = {} + const pluginOptions2 = {} + const pluginOptions3 = {} + const pluginsConfig = [ + ['a', pluginOptions1], + ['a', pluginOptions2], + ['a', pluginOptions3] + ] + const api = new PluginAPI() + api.useByPluginsConfig(pluginsConfig) + expect(api.enabledPlugins).toHaveLength(1) + // using the last one + expect(api.enabledPlugins[0].$$options).toBe(pluginOptions3) + }) + + test('ensure a "multuple" plugin can be applied multuple times.', () => { + const pluginOptions1 = { a: 1 } + const pluginOptions2 = { b: 1 } + const pluginsConfig = [ + ['b', pluginOptions1], + ['b', pluginOptions2] + ] + const api = new PluginAPI() + api.useByPluginsConfig(pluginsConfig) + expect(api.enabledPlugins).toHaveLength(2) + // using the last one + expect(api.enabledPlugins[0].$$options).toBe(pluginOptions1) + expect(api.enabledPlugins[1].$$options).toBe(pluginOptions2) + }) +}) diff --git a/packages/@vuepress/core/__test__/plugin-api/PluginUtil.spec.js b/packages/@vuepress/core/__test__/plugin-api/PluginUtil.spec.js new file mode 100644 index 0000000000..2bc618c2d3 --- /dev/null +++ b/packages/@vuepress/core/__test__/plugin-api/PluginUtil.spec.js @@ -0,0 +1,45 @@ +import { flattenPlugin } from '../../lib/plugin-api/util' + +describe('flattenPlugin', () => { + test('shoould hydrate plugin correctly', () => { + const plugin = { name: 'a', shortcut: 'a', module: { enhanceAppFiles: 'file' }} + const hydratedPlugin = flattenPlugin(plugin, {}, {}) + expect(hydratedPlugin.name).toBe('a') + expect(hydratedPlugin.shortcut).toBe('a') + expect(hydratedPlugin.enabled).toBe(true) + expect(hydratedPlugin.enhanceAppFiles).toBe('file') + }) + + test('shoould set \'enabled\' to false when \'pluginOptions\' is set to false.', () => { + const plugin = { name: 'a', shortcut: 'a', module: {}} + const hydratedPlugin = flattenPlugin(plugin, false, {}) + expect(hydratedPlugin.name).toBe('a') + expect(hydratedPlugin.shortcut).toBe('a') + expect(hydratedPlugin.enabled).toBe(false) + }) + + test('shoould flatten functional plugin correctly.', () => { + const config = jest.fn(() => ({ enhanceAppFiles: 'file' })) + const plugin = { name: 'a', shortcut: 'a', module: config } + const pluginOptions = {} + const pluginContext = {} + const hydratedPlugin = flattenPlugin(plugin, pluginOptions, pluginContext) + expect(hydratedPlugin.name).toBe('a') + expect(hydratedPlugin.shortcut).toBe('a') + expect(hydratedPlugin.enabled).toBe(true) + expect(hydratedPlugin.enhanceAppFiles).toBe('file') + expect(config.mock.calls).toHaveLength(1) + expect(config.mock.calls[0][0]).toBe(pluginOptions) + expect(Object.getPrototypeOf(config.mock.calls[0][1])).toBe(pluginContext) + }) + + test('shoould flatten functional plugin correctly - options defaults to \'{}\'.', () => { + const config = jest.fn(() => ({ enhanceAppFiles: 'file' })) + const plugin = { name: 'a', shortcut: 'a', module: config } + const pluginOptions = undefined + const pluginContext = {} + flattenPlugin(plugin, pluginOptions, pluginContext) + expect(config.mock.calls[0][0]).toEqual({}) + expect(Object.getPrototypeOf(config.mock.calls[0][1])).toBe(pluginContext) + }) +}) diff --git a/packages/@vuepress/core/__test__/prepare/Page.spec.js b/packages/@vuepress/core/__test__/prepare/Page.spec.js new file mode 100644 index 0000000000..294ae9c790 --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/Page.spec.js @@ -0,0 +1,98 @@ +const Page = require('../../lib/prepare/Page') +const { + getComputed, + getMarkdown, + getDocument, + readFile +} = require('./util') + +describe('Page', () => { + test('pure route', async () => { + const page = new Page({ path: '/' }) + + expect(page.path).toBe('/') + expect(page.regularPath).toBe('/') + + const computed = getComputed() + await page.process({ computed }) + + expect(page.path).toBe('/') + expect(page.regularPath).toBe('/') + }) + + test('pure route - encodeURI', async () => { + const path = '/尤/' + const page = new Page({ path }) + + expect(page.path).toBe(encodeURI(path)) + expect(page.regularPath).toBe(encodeURI(path)) + }) + + test('pure route - custom frontmatter', async () => { + const frontmatter = { title: 'alpha' } + const page = new Page({ + path: '/', + frontmatter + }) + expect(page.frontmatter).toBe(frontmatter) + }) + + test('pure route - enhancers', async () => { + const frontmatter = { title: 'alpha' } + const page = new Page({ + path: '/', + frontmatter + }) + + expect(page.frontmatter.title).toBe('alpha') + + const computed = getComputed() + const enhancers = [ + { + name: 'plugin-a', + value: page => { page.frontmatter.title = 'beta' } + } + ] + await page.process({ computed, enhancers }) + + expect(page.frontmatter.title).toBe('beta') + }) + + test('markdown page - pointing to a markdown file', async () => { + const { relative, filePath } = getDocument('README.md') + const page = new Page({ filePath, relative }) + + 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 }) + + expect(page.title).toBe('Home') + const content = await readFile(filePath) + expect(page._content).toBe(content) + expect(page._strippedContent).toBe(content) + }) + + test('markdown page - pointing to a markdown file with frontmatter', async () => { + const { relative, filePath } = getDocument('alpha.md') + const page = new Page({ filePath, relative }) + + 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 }) + + expect(page.title).toBe(page.frontmatter.title) + expect(page._content.startsWith('---')).toBe(true) + expect(page._strippedContent.startsWith('---')).toBe(false) + }) +}) + diff --git a/test/prepare/fixtures/docs-simple-config/.vuepress/config.js b/packages/@vuepress/core/__test__/prepare/fixtures/docs-config/.vuepress/config.js similarity index 79% rename from test/prepare/fixtures/docs-simple-config/.vuepress/config.js rename to packages/@vuepress/core/__test__/prepare/fixtures/docs-config/.vuepress/config.js index 70f49c13b3..665e1164b3 100644 --- a/test/prepare/fixtures/docs-simple-config/.vuepress/config.js +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs-config/.vuepress/config.js @@ -1,6 +1,6 @@ module.exports = { title: 'Hello VuePress', - description: 'Just playing around', + description: '# Hello, VuePress!', dest: 'vuepress', base: 'vuepress', head: [ diff --git a/packages/@vuepress/core/__test__/prepare/fixtures/docs-config/README.md b/packages/@vuepress/core/__test__/prepare/fixtures/docs-config/README.md new file mode 100644 index 0000000000..aef89369f1 --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs-config/README.md @@ -0,0 +1 @@ +# Hello, VuePress! diff --git a/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/.vuepress/config.js b/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/.vuepress/config.js new file mode 100644 index 0000000000..1fbdc1cec1 --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/.vuepress/config.js @@ -0,0 +1,34 @@ +module.exports = { + dest: 'vuepress', + locales: { + '/': { + lang: 'en-US', + title: 'VuePress', + description: 'Vue-powered Static Site Generator' + }, + '/zh/': { + lang: 'zh-CN', + title: 'VuePress', + description: 'Vue 驱动的静态网站生成器' + } + }, + themeConfig: { + repo: 'vuejs/vuepress', + editLinks: true, + docsDir: 'docs', + locales: { + '/': { + label: 'English', + selectText: 'Languages', + editLinkText: 'Edit this page on GitHub', + lastUpdated: 'Last Updated' + }, + '/zh/': { + label: '简体中文', + selectText: '选择语言', + editLinkText: '在 GitHub 上编辑此页', + lastUpdated: '上次更新', + } + } + } +} diff --git a/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/README.md b/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/README.md new file mode 100644 index 0000000000..aef89369f1 --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/README.md @@ -0,0 +1 @@ +# Hello, VuePress! diff --git a/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/zh/README.md b/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/zh/README.md new file mode 100644 index 0000000000..c526a98742 --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs-i18n/zh/README.md @@ -0,0 +1 @@ +# 你好, VuePress! diff --git a/test/prepare/fixtures/docs-custom-theme/.vuepress/config.js b/packages/@vuepress/core/__test__/prepare/fixtures/docs-local-theme/.vuepress/config.js similarity index 56% rename from test/prepare/fixtures/docs-custom-theme/.vuepress/config.js rename to packages/@vuepress/core/__test__/prepare/fixtures/docs-local-theme/.vuepress/config.js index a5f568cb6d..0a8ba98858 100644 --- a/test/prepare/fixtures/docs-custom-theme/.vuepress/config.js +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs-local-theme/.vuepress/config.js @@ -1,4 +1,4 @@ module.exports = { title: 'Hello VuePress', - description: 'Just playing around' + description: '# Hello, VuePress!' } diff --git a/test/prepare/fixtures/docs-custom-theme/.vuepress/theme/Layout.vue b/packages/@vuepress/core/__test__/prepare/fixtures/docs-local-theme/.vuepress/theme/Layout.vue similarity index 100% rename from test/prepare/fixtures/docs-custom-theme/.vuepress/theme/Layout.vue rename to packages/@vuepress/core/__test__/prepare/fixtures/docs-local-theme/.vuepress/theme/Layout.vue diff --git a/packages/@vuepress/core/__test__/prepare/fixtures/docs-local-theme/README.md b/packages/@vuepress/core/__test__/prepare/fixtures/docs-local-theme/README.md new file mode 100644 index 0000000000..aef89369f1 --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs-local-theme/README.md @@ -0,0 +1 @@ +# Hello, VuePress! diff --git a/packages/@vuepress/core/__test__/prepare/fixtures/docs/README.md b/packages/@vuepress/core/__test__/prepare/fixtures/docs/README.md new file mode 100644 index 0000000000..291ca3867f --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs/README.md @@ -0,0 +1 @@ +# Home diff --git a/packages/@vuepress/core/__test__/prepare/fixtures/docs/alpha.md b/packages/@vuepress/core/__test__/prepare/fixtures/docs/alpha.md new file mode 100644 index 0000000000..67de3e0ace --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/fixtures/docs/alpha.md @@ -0,0 +1,5 @@ +--- +title: VuePress Alpha +--- + +# Alpha diff --git a/packages/@vuepress/core/__test__/prepare/prepare.spec.js b/packages/@vuepress/core/__test__/prepare/prepare.spec.js new file mode 100644 index 0000000000..420ebaaddc --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/prepare.spec.js @@ -0,0 +1,21 @@ +const { fs } = require('@vuepress/shared-utils') +const path = require('path') +const prepare = require('../../lib/prepare') + +const docsBaseDir = path.resolve(__dirname, 'fixtures') +const docsModeNames = fs.readdirSync(docsBaseDir) +const docsModes = docsModeNames.map(name => { + const docsPath = path.resolve(docsBaseDir, name) + const docsTempPath = path.resolve(docsPath, '.vuepress/.temp') + return { name, docsPath, docsTempPath } +}) + +describe('prepare', () => { + 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) + })) + }) +}) diff --git a/packages/@vuepress/core/__test__/prepare/util.js b/packages/@vuepress/core/__test__/prepare/util.js new file mode 100644 index 0000000000..edb2bf37e3 --- /dev/null +++ b/packages/@vuepress/core/__test__/prepare/util.js @@ -0,0 +1,34 @@ +const path = require('path') +const { fs } = require('@vuepress/shared-utils') +const AppContext = require('../../lib/prepare/AppContext') +const createMarkdown = require('../../../markdown/lib/index') + +function getAppContext () { + return new AppContext('.') +} + +function getComputed () { + const context = getAppContext() + return new context.ClientComputedMixinConstructor() +} + +const docsBaseDir = path.resolve(__dirname, 'fixtures/docs') + +function getDocument (relative) { + return { + filePath: path.join(docsBaseDir, relative), + relative + } +} + +const getMarkdown = createMarkdown + +const readFile = async filePath => await fs.readFile(filePath, 'utf-8') + +module.exports = { + getAppContext, + getComputed, + getMarkdown, + getDocument, + readFile +} diff --git a/packages/@vuepress/core/lib/app/Store.js b/packages/@vuepress/core/lib/app/Store.js new file mode 100644 index 0000000000..cf3e5b60b9 --- /dev/null +++ b/packages/@vuepress/core/lib/app/Store.js @@ -0,0 +1,19 @@ +import Vue from 'vue' + +export default class Store { + constructor () { + this.store = new Vue({ + data: { + ob: {} + } + }) + } + + get (key) { + return this.store.ob[key] + } + + set (key, value) { + Vue.set(this.store.ob, key, value) + } +} diff --git a/lib/app/app.js b/packages/@vuepress/core/lib/app/app.js similarity index 68% rename from lib/app/app.js rename to packages/@vuepress/core/lib/app/app.js index 511abbd472..171cfd2929 100644 --- a/lib/app/app.js +++ b/packages/@vuepress/core/lib/app/app.js @@ -1,11 +1,13 @@ +/* global VUEPRESS_TEMP_PATH */ import Vue from 'vue' import Router from 'vue-router' import dataMixin from './dataMixin' -import store from './store' -import { routes } from '@temp/routes' -import { siteData } from '@temp/siteData' -import enhanceApp from '@temp/enhanceApp' -import themeEnhanceApp from '@temp/themeEnhanceApp' +import { routes } from '@internal/routes' +import { siteData } from '@internal/siteData' +import appEnhancers from '@internal/app-enhancers' +import globalUIComponents from '@internal/global-ui' +import ClientComputedMixin from '../prepare/ClientComputedMixin' +import Store from './Store' // generated from user config import('@temp/style.styl') @@ -18,7 +20,7 @@ import ClientOnly from './components/ClientOnly' // suggest dev server restart on base change if (module.hot) { const prevBase = siteData.base - module.hot.accept('./.temp/siteData', () => { + module.hot.accept(VUEPRESS_TEMP_PATH + '/internal/siteData.js', () => { if (siteData.base !== prevBase) { window.alert( `[vuepress] Site base has changed. ` + @@ -29,13 +31,15 @@ if (module.hot) { } Vue.config.productionTip = false + +Vue.$store = new Store() + Vue.use(Router) // mixin for exposing $site and $page -Vue.mixin(dataMixin(siteData)) +Vue.mixin(dataMixin(ClientComputedMixin, siteData)) // component for rendering markdown content and setting title etc. Vue.component('Content', Content) Vue.component('OutboundLink', OutboundLink) -Vue.component('Badge', () => import('./components/Badge.vue')) // component for client-only content Vue.component('ClientOnly', ClientOnly) @@ -49,7 +53,7 @@ Vue.prototype.$withBase = function (path) { } } -export function createApp () { +export function createApp (isServer) { const router = new Router({ base: siteData.base, mode: 'history', @@ -59,7 +63,7 @@ export function createApp () { if (saved) { return saved } else if (to.hash) { - if (store.disableScrollBehavior) { + if (Vue.$store.get('disableScrollBehavior')) { return false } return { @@ -84,15 +88,23 @@ export function createApp () { const options = {} - themeEnhanceApp({ Vue, options, router, siteData }) - enhanceApp({ Vue, options, router, siteData }) + try { + appEnhancers.forEach(enhancer => { + if (typeof enhancer === 'function') { + enhancer({ Vue, options, router, siteData, isServer }) + } + }) + } catch (e) { + console.error(e) + } const app = new Vue( Object.assign(options, { router, render (h) { return h('div', { attrs: { id: 'app' }}, [ - h('router-view', { ref: 'layout' }) + h('router-view', { ref: 'layout' }), + h('div', { class: 'global-ui' }, globalUIComponents.map(component => h(component))) ]) } }) diff --git a/packages/@vuepress/core/lib/app/clientEntry.js b/packages/@vuepress/core/lib/app/clientEntry.js new file mode 100644 index 0000000000..b7f795f613 --- /dev/null +++ b/packages/@vuepress/core/lib/app/clientEntry.js @@ -0,0 +1,14 @@ +/* global VUEPRESS_VERSION, LAST_COMMIT_HASH*/ + +import { createApp } from './app' + +const { app, router } = createApp(false /* isServer */) + +window.__VUEPRESS_VERSION__ = { + version: VUEPRESS_VERSION, + hash: LAST_COMMIT_HASH +} + +router.onReady(() => { + app.$mount('#app') +}) diff --git a/lib/app/components/ClientOnly.js b/packages/@vuepress/core/lib/app/components/ClientOnly.js similarity index 100% rename from lib/app/components/ClientOnly.js rename to packages/@vuepress/core/lib/app/components/ClientOnly.js diff --git a/packages/@vuepress/core/lib/app/components/Content.js b/packages/@vuepress/core/lib/app/components/Content.js new file mode 100644 index 0000000000..c034a47178 --- /dev/null +++ b/packages/@vuepress/core/lib/app/components/Content.js @@ -0,0 +1,34 @@ +import Vue from 'vue' +import components from '@internal/page-components' + +export default { + functional: true, + + props: { + custom: { + type: Boolean, + default: true + }, + pageKey: String, + slot: String + }, + + render (h, { parent, props, data }) { + const pageKey = props.pageKey || parent.$page.key + + if (components[pageKey]) { + // In SSR, if a component is not registered with the component option + // vue-server-renderer will not be able to resovle it. + if (!parent.$ssrContext) { + Vue.component(pageKey, components[pageKey]) + } + + return h(pageKey, { + class: [props.custom ? 'custom' : '', data.class, data.staticClass], + style: data.style, + slot: props.slot || 'default' + }) + } + return h('') + } +} diff --git a/packages/@vuepress/core/lib/app/components/LayoutDistributor.vue b/packages/@vuepress/core/lib/app/components/LayoutDistributor.vue new file mode 100644 index 0000000000..82bc8dcf0c --- /dev/null +++ b/packages/@vuepress/core/lib/app/components/LayoutDistributor.vue @@ -0,0 +1,16 @@ + + + diff --git a/lib/default-theme/NotFound.vue b/packages/@vuepress/core/lib/app/components/NotFound.vue similarity index 100% rename from lib/default-theme/NotFound.vue rename to packages/@vuepress/core/lib/app/components/NotFound.vue diff --git a/lib/app/components/OutboundLink.vue b/packages/@vuepress/core/lib/app/components/OutboundLink.vue similarity index 100% rename from lib/app/components/OutboundLink.vue rename to packages/@vuepress/core/lib/app/components/OutboundLink.vue diff --git a/packages/@vuepress/core/lib/app/dataMixin.js b/packages/@vuepress/core/lib/app/dataMixin.js new file mode 100644 index 0000000000..1f0b2dfa18 --- /dev/null +++ b/packages/@vuepress/core/lib/app/dataMixin.js @@ -0,0 +1,37 @@ +/* global VUEPRESS_TEMP_PATH */ + +import Vue from 'vue' + +export default function dataMixin (I18n, siteData) { + prepare(siteData) + Vue.$store.set('siteData', siteData) + + if (module.hot) { + module.hot.accept(VUEPRESS_TEMP_PATH + '/internal/siteData.js', () => { + prepare(siteData) + Vue.$store.set('siteData', siteData) + }) + } + + const I18nConstructor = I18n(Vue.$store.get('siteData')) + const i18n = new I18nConstructor() + const descriptors = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(i18n)) + const computed = {} + Object.keys(descriptors).reduce((computed, key) => { + if (key.startsWith('$')) { + computed[key] = descriptors[key].get + } + return computed + }, computed) + + return { computed } +} + +function prepare (siteData) { + if (siteData.locales) { + Object.keys(siteData.locales).forEach(path => { + siteData.locales[path].path = path + }) + } + Object.freeze(siteData) +} diff --git a/lib/app/index.dev.html b/packages/@vuepress/core/lib/app/index.dev.html similarity index 100% rename from lib/app/index.dev.html rename to packages/@vuepress/core/lib/app/index.dev.html diff --git a/lib/app/index.ssr.html b/packages/@vuepress/core/lib/app/index.ssr.html similarity index 100% rename from lib/app/index.ssr.html rename to packages/@vuepress/core/lib/app/index.ssr.html diff --git a/lib/app/root-mixins/updateMeta.js b/packages/@vuepress/core/lib/app/root-mixins/updateMeta.js similarity index 100% rename from lib/app/root-mixins/updateMeta.js rename to packages/@vuepress/core/lib/app/root-mixins/updateMeta.js diff --git a/packages/@vuepress/core/lib/app/serverEntry.js b/packages/@vuepress/core/lib/app/serverEntry.js new file mode 100644 index 0000000000..84c707b354 --- /dev/null +++ b/packages/@vuepress/core/lib/app/serverEntry.js @@ -0,0 +1,38 @@ +import Vue from 'vue' +import { createApp } from './app' +import pageComponents from '@internal/page-components' +import layoutComponents from '@internal/layout-components' + +export default context => new Promise((resolve, reject) => { + const { app, router } = createApp(true /* isServer */) + const { url } = context + const { fullPath } = router.resolve(url).route + + if (fullPath !== url) { + return reject({ url: fullPath }) + } + + router.push(url) + + // In SSR, if a component is not registered with the component option, + // vue-server-renderer will not able to resolve it. + // + // Build also works after deleting this, but the content of all pages + // will not appear to the output html, which is not conducive to SEO. + const asyncComponentLoadingPromises = [ + ...getComponentArr(pageComponents), + ...getComponentArr(layoutComponents) + ].map(({ name, loadFn }) => { + return loadFn().then(comp => { + Vue.component(name, comp.default) + }) + }) + + router.onReady(() => { + Promise.all(asyncComponentLoadingPromises).then(() => resolve(app)) + }) +}) + +function getComponentArr (components) { + return Object.keys(components).map(name => ({ name, loadFn: components[name] })) +} diff --git a/lib/default-theme/styles/config.styl b/packages/@vuepress/core/lib/app/style/config.styl similarity index 92% rename from lib/default-theme/styles/config.styl rename to packages/@vuepress/core/lib/app/style/config.styl index a57d4484de..1653b37cfd 100644 --- a/lib/default-theme/styles/config.styl +++ b/packages/@vuepress/core/lib/app/style/config.styl @@ -19,4 +19,4 @@ $MQMobileNarrow = 419px $lineNumbersWrapperWidth = 3.5rem $codeLang = js ts html md vue css sass scss less stylus go java c sh yaml py -@import '~@temp/override.styl' +@import '~@temp/palette.styl' diff --git a/packages/@vuepress/core/lib/app/util.js b/packages/@vuepress/core/lib/app/util.js new file mode 100644 index 0000000000..8b52325409 --- /dev/null +++ b/packages/@vuepress/core/lib/app/util.js @@ -0,0 +1,92 @@ +/** + * Inject option to Vue SFC + * @param {object} options + * @param {string} key + * @param {any} value + */ +export function injectComponentOption (options, key, value) { + const arrayInject = () => { + if (!options[key]) options[key] = [] + options[key].push(...value) + } + const objectInject = () => { + if (!options[key]) options[key] = {} + Object.assign(options[key], value) + } + // const primitiveInject = () => options[key] = value + + switch (key) { + case 'components': objectInject(); break + case 'mixins': arrayInject(); break + default: throw new Error('Unknown option name.') + } +} + +export function findPageForPath (pages, path) { + for (let i = 0; i < pages.length; i++) { + const page = pages[i] + if (page.path === path) { + return page + } + } + return { + path: '', + frontmatter: {} + } +} + +export function findPageByKey (pages, key) { + for (let i = 0; i < pages.length; i++) { + const page = pages[i] + if (page.key === key) { + return page + } + } + return { + path: '', + frontmatter: {} + } +} + +/** + * Normalize config. + * This utility is mainly for plugin developers. For some + * plugins that need internationalize the text. but it's + * not recommenbded to let plugin care about to the internal + * i18n implementation, so this utility was born. + * + * + * Usage: + * + * import { normalizeConfig } from '@app/util' + * export default { + * data () { + * return { config } + * } + * computed: { + * normalizedConfig() { + * return normalizeConfig(this, config) + * } + * } + * } + * + * + * e.g. + * + * Config: : 'Text' + * Normalized Config: 'Text' + * + * Config: : { '/': 'Text', '/zh/': '文本' } + * Normalized Config: 'Text' or '文本' + * + * @param {Vue} component + * @param {any} rawConfig + * @returns {any} + */ +export function normalizeConfig (component, rawConfig) { + const { $localePath } = component + if (typeof rawConfig === 'object' && rawConfig[$localePath]) { + return rawConfig[$localePath] + } + return rawConfig +} diff --git a/lib/build.js b/packages/@vuepress/core/lib/build.js similarity index 86% rename from lib/build.js rename to packages/@vuepress/core/lib/build.js index f7efb49bb0..5c4fe16929 100644 --- a/lib/build.js +++ b/packages/@vuepress/core/lib/build.js @@ -1,22 +1,22 @@ +'use strict' + 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 { chalk, fs, logger } = 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') + const { normalizeHeadTag, applyUserWebpackConfig } = require('./util/index') logger.wait('\nExtracting site metadata...') - const options = await prepare(sourceDir) + const options = await prepare(sourceDir, cliOptions, true /* isProd */) if (cliOptions.outDir) { options.outDir = cliOptions.outDir } @@ -26,6 +26,7 @@ module.exports = async function build (sourceDir, cliOptions = {}) { return console.error(logger.error(chalk.red('Unexpected option: outDir cannot be set to the current working directory.\n'), false)) } await fs.remove(outDir) + logger.debug('Dist directory: ' + chalk.gray(path.resolve(outDir))) let clientConfig = createClientConfig(options, cliOptions).toConfig() let serverConfig = createServerConfig(options, cliOptions).toConfig() @@ -57,7 +58,7 @@ module.exports = async function build (sourceDir, cliOptions = {}) { runInNewContext: false, inject: false, shouldPrefetch: options.siteConfig.shouldPrefetch || (() => true), - template: await fs.readFile(path.resolve(__dirname, 'app/index.ssr.html'), 'utf-8') + template: await fs.readFile(options.ssrTemplate, 'utf-8') }) // pre-render head tags from user config @@ -67,32 +68,19 @@ module.exports = async function build (sourceDir, cliOptions = {}) { // render pages logger.wait('Rendering static HTML...') - for (const page of options.siteData.pages) { + for (const page of options.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')) { + if (!options.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' } - ) - } + await options.pluginAPI.options.generated.apply() // DONE. const relativeDir = path.relative(process.cwd(), outDir) diff --git a/lib/dev.js b/packages/@vuepress/core/lib/dev.js similarity index 83% rename from lib/dev.js rename to packages/@vuepress/core/lib/dev.js index 08f828d06c..a70585bb05 100644 --- a/lib/dev.js +++ b/packages/@vuepress/core/lib/dev.js @@ -1,7 +1,7 @@ +'use strict' + module.exports = async function dev (sourceDir, cliOptions = {}) { - const fs = require('fs') const path = require('path') - const chalk = require('chalk') const webpack = require('webpack') const chokidar = require('chokidar') const serve = require('webpack-serve') @@ -11,20 +11,21 @@ module.exports = async function dev (sourceDir, cliOptions = {}) { const serveStatic = require('koa-static') const history = require('connect-history-api-fallback') - const prepare = require('./prepare') - const logger = require('./util/logger') + 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') - const { frontmatterEmitter } = require('./webpack/markdownLoader') + const { applyUserWebpackConfig } = require('./util/index') + const { frontmatterEmitter } = require('@vuepress/markdown-loader') logger.wait('\nExtracting site metadata...') - const options = await prepare(sourceDir) + const options = await prepare(sourceDir, cliOptions, false /* isProd */) // setup watchers to update options and dynamically generated files const update = () => { - prepare(sourceDir).catch(err => { + options.pluginAPI.options.updated.syncApply() + prepare(sourceDir, cliOptions, false /* isProd */).catch(err => { console.error(logger.error(chalk.red(err.stack), false)) }) } @@ -58,14 +59,14 @@ module.exports = async function dev (sourceDir, cliOptions = {}) { frontmatterEmitter.on('update', update) // resolve webpack config - let config = createClientConfig(options, cliOptions) + let config = createClientConfig(options) 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: path.resolve(__dirname, 'app/index.dev.html') + template: options.devTemplate }]) config @@ -82,7 +83,7 @@ module.exports = async function dev (sourceDir, cliOptions = {}) { .use(DevLogPlugin, [{ port, displayHost, - publicPath: options.publicPath + publicPath: options.base }]) config = config.toConfig() @@ -108,6 +109,10 @@ module.exports = async function dev (sourceDir, cliOptions = {}) { logLevel: 'error', port, add: app => { + // apply plugin options to extend dev server. + const { pluginAPI } = options + pluginAPI.options.enhanceDevServer.syncApply(app) + const userPublic = path.resolve(sourceDir, '.vuepress/public') // enable range request @@ -115,7 +120,7 @@ module.exports = async function dev (sourceDir, cliOptions = {}) { // respect base when serving static files... if (fs.existsSync(userPublic)) { - app.use(mount(options.publicPath, serveStatic(userPublic))) + app.use(mount(options.base, serveStatic(userPublic))) } app.use(convert(history({ diff --git a/lib/eject.js b/packages/@vuepress/core/lib/eject.js similarity index 61% rename from lib/eject.js rename to packages/@vuepress/core/lib/eject.js index e96b00fd84..f3cfa5df6b 100644 --- a/lib/eject.js +++ b/packages/@vuepress/core/lib/eject.js @@ -1,10 +1,16 @@ -const fs = require('fs-extra') +'use strict' + const path = require('path') -const chalk = require('chalk') -const logger = require('./util/logger') +const { chalk, fs, logger } = require('@vuepress/shared-utils') module.exports = async (dir) => { - const source = path.resolve(__dirname, 'default-theme') + try { + require.resolve('@vuepress/theme-default') + } catch (err) { + console.log(chalk.red(`\n[vuepress] cannot find '@vuepress/theme-default'\n`)) + process.exit(1) + } + const source = require.resolve('@vuepress/theme-default') const target = path.resolve(dir, '.vuepress/theme') await fs.copy(source, target) // remove the import to default theme override diff --git a/lib/index.js b/packages/@vuepress/core/lib/index.js similarity index 70% rename from lib/index.js rename to packages/@vuepress/core/lib/index.js index 8e4d4a19ad..43a6d0124b 100644 --- a/lib/index.js +++ b/packages/@vuepress/core/lib/index.js @@ -1,4 +1,5 @@ +'use strict' + exports.dev = require('./dev') exports.build = require('./build') exports.eject = require('./eject') -Object.assign(exports, require('./util')) diff --git a/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js new file mode 100644 index 0000000000..1163d3187b --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/enhanceApp.js @@ -0,0 +1,15 @@ +const path = require('path') + +module.exports = (options, context) => ({ + name: '@vuepress/internal-enhance-app', + + enhanceAppFiles () { + const { sourceDir, themePath } = context + const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js') + const themeEnhanceAppPath = path.resolve(themePath, 'enhanceApp.js') + return [ + enhanceAppPath, + themeEnhanceAppPath + ] + } +}) diff --git a/packages/@vuepress/core/lib/internal-plugins/layoutComponents.js b/packages/@vuepress/core/lib/internal-plugins/layoutComponents.js new file mode 100644 index 0000000000..97f781b933 --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/layoutComponents.js @@ -0,0 +1,22 @@ +module.exports = (options, ctx) => { + const { layoutComponentMap } = ctx + const componentNames = Object.keys(layoutComponentMap) + + return { + name: '@vuepress/internal-layout-components', + + async clientDynamicModules () { + const code = `export default {\n${componentNames + .map(name => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(layoutComponentMap[name].path)})`) + .join(',\n')} \n}` + return { name: 'layout-components.js', content: code, dirname: 'internal' } + }, + + chainWebpack (config, isServer) { + const setAlias = (alias, raw) => config.resolve.alias.set(alias, raw) + componentNames.forEach(name => { + setAlias(`@${name}`, layoutComponentMap[name].path) + }) + } + } +} diff --git a/packages/@vuepress/core/lib/internal-plugins/overrideCSS.js b/packages/@vuepress/core/lib/internal-plugins/overrideCSS.js new file mode 100644 index 0000000000..597e74710e --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/overrideCSS.js @@ -0,0 +1,69 @@ +const path = require('path') +const { + fs, logger, chalk, + datatypes: { + isPlainObject, + assertTypes, + isString + } +} = require('@vuepress/shared-utils') + +module.exports = (options, context) => ({ + name: '@vuepress/internal-override-css', + + async ready () { + const { sourceDir, writeTemp } = context + + const overridePath = path.resolve(sourceDir, '.vuepress/override.styl') + const hasUserOverride = fs.existsSync(overridePath) + + if (hasUserOverride) { + logger.tip(`${chalk.magenta('override.styl')} has been deprecated from v1.0.0, using ${chalk.cyan('config.palette')} instead.\n`) + } + + // palette API. + const themePalette = context.themePalette + const { palette: userPalette } = context.siteConfig + const themePaletteContent = resolvePaletteContent(themePalette) + const userPaletteContent = resolvePaletteContent(userPalette) + // user's palette can override theme's palette. + const paletteContent = themePaletteContent + userPaletteContent + await writeTemp('palette.styl', paletteContent) + + // style.styl API. + const stylePath = path.resolve(sourceDir, '.vuepress/style.styl').replace(/[\\]+/g, '/') + const hasUserStyle = fs.existsSync(stylePath) + await writeTemp('style.styl', hasUserStyle ? `@import(${JSON.stringify(stylePath)})` : ``) + + // Temporary tip, will be removed at next release. + if (hasUserOverride && !hasUserStyle) { + logger.tip( + `${chalk.magenta('override.styl')} has been split into 2 APIs, we recommend you upgrade to continue.\n` + + ` See: ${chalk.magenta('https://vuepress.vuejs.org/default-theme-config/#simple-css-override')}` + ) + } + } +}) + +function resolvePaletteContent (palette) { + const { valid, warnMsg } = assertTypes(palette, [String, Object]) + if (!valid) { + if (palette !== undefined) { + logger.warn( + `[vuepress] Invalid value for "palette": ${warnMsg}` + ) + } + return '' + } + + if (isString(palette)) { + if (fs.existsSync(palette)) { + return `@import(${JSON.stringify(palette)})\n` + } + return '' + } else if (isPlainObject(palette)) { + return Object.keys(palette).map(variableName => { + return `${variableName} = ${palette[variableName]}` + }).join('\n') + '\n' + } +} diff --git a/packages/@vuepress/core/lib/internal-plugins/pageComponents.js b/packages/@vuepress/core/lib/internal-plugins/pageComponents.js new file mode 100644 index 0000000000..e4f1377888 --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/pageComponents.js @@ -0,0 +1,16 @@ +module.exports = (options, ctx) => { + const { pages } = ctx + // const componentNames = Object.keys(layoutComponentMap) + + return { + name: '@vuepress/internal-page-components', + + async clientDynamicModules () { + const code = `export default {\n${pages + .filter(({ _filePath }) => _filePath) + .map(({ key, _filePath }) => ` ${JSON.stringify(key)}: () => import(${JSON.stringify(_filePath)})`) + .join(',\n')} \n}` + return { name: 'page-components.js', content: code, dirname: 'internal' } + } + } +} diff --git a/packages/@vuepress/core/lib/internal-plugins/rootMixins.js b/packages/@vuepress/core/lib/internal-plugins/rootMixins.js new file mode 100644 index 0000000000..38edf4c52a --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/rootMixins.js @@ -0,0 +1,21 @@ +const path = require('path') +const { codegen: { pathsToModuleCode }} = require('@vuepress/shared-utils') + +module.exports = (options, context, api) => ({ + name: '@vuepress/internal-root-mixins', + + // @internal/root-mixins + async clientDynamicModules () { + const builtInRootMixins = [ + path.resolve(__dirname, '../app/root-mixins/updateMeta.js') + ] + + const rootMixins = [ + ...builtInRootMixins, + ...api.options.clientRootMixin.values + ] + + const rootMixinsCode = pathsToModuleCode(rootMixins) + return { name: 'root-mixins.js', content: rootMixinsCode, dirname: 'internal' } + } +}) diff --git a/packages/@vuepress/core/lib/internal-plugins/routes.js b/packages/@vuepress/core/lib/internal-plugins/routes.js new file mode 100644 index 0000000000..799610f079 --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/routes.js @@ -0,0 +1,84 @@ +module.exports = (options, ctx) => ({ + name: '@vuepress/internal-routes', + + // @internal/routes + async clientDynamicModules () { + const code = importCode() + routesCode(ctx.pages) + return { name: 'routes.js', content: code, dirname: 'internal' } + } +}) + +/** + * Import utilities + * @returns {string} + */ +function importCode () { + return ` +import { injectComponentOption } from '@app/util' +import rootMixins from '@internal/root-mixins' +import components from '@internal/layout-components' +import LayoutDistributor from '@app/components/LayoutDistributor.vue' + +injectComponentOption(LayoutDistributor, 'mixins', rootMixins) +injectComponentOption(LayoutDistributor, 'components', components) +` +} + +/** + * Get Vue routes code. + * @param {array} pages + * @returns {string} + */ +function routesCode (pages) { + function genRoute ({ + path: pagePath, + key: componentName, + regularPath, + _meta + }) { + let code = ` + { + name: ${JSON.stringify(componentName)}, + path: ${JSON.stringify(pagePath)}, + component: LayoutDistributor,${_meta ? `\n meta: ${JSON.stringify(_meta)}` : ''} + }` + + const dncodedPath = decodeURIComponent(pagePath) + if (dncodedPath !== pagePath) { + code += `, + { + path: ${JSON.stringify(dncodedPath)}, + redirect: ${JSON.stringify(pagePath)} + }` + } + + if (/\/$/.test(pagePath)) { + code += `, + { + path: ${JSON.stringify(pagePath + 'index.html')}, + redirect: ${JSON.stringify(pagePath)} + }` + } + + if (regularPath !== pagePath) { + code += `, + { + path: ${JSON.stringify(regularPath)}, + redirect: ${JSON.stringify(pagePath)} + }` + } + + return code + } + + const notFoundRoute = `, + { + path: '*', + component: LayoutDistributor + }` + + return ( + `export const routes = [${pages.map(genRoute).join(',')}${notFoundRoute}\n]` + ) +} + diff --git a/packages/@vuepress/core/lib/internal-plugins/siteData.js b/packages/@vuepress/core/lib/internal-plugins/siteData.js new file mode 100644 index 0000000000..91a99baf4f --- /dev/null +++ b/packages/@vuepress/core/lib/internal-plugins/siteData.js @@ -0,0 +1,9 @@ +module.exports = (options, context) => ({ + name: '@vuepress/internal-site-data', + + // @internal/siteData + async clientDynamicModules () { + const code = `export const siteData = ${JSON.stringify(context.getSiteData(), null, 2)}` + return { name: 'siteData.js', content: code, dirname: 'internal' } + } +}) diff --git a/packages/@vuepress/core/lib/plugin-api/abstract/AsyncOption.js b/packages/@vuepress/core/lib/plugin-api/abstract/AsyncOption.js new file mode 100644 index 0000000000..2685ef09af --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/abstract/AsyncOption.js @@ -0,0 +1,94 @@ +'use strict' + +/** + * Module dependencies. + */ + +const { logger, chalk, datatypes: { isFunction }} = require('@vuepress/shared-utils') +const Option = require('./Option') + +/** + * Expose asynchronous option class. + */ + +class AsyncOption extends Option { + /** + * Asynchronous serial running + * + * @param args + * @param {Array} args + * @api public + */ + + async asyncApply (...args) { + const rawItems = this.items + this.items = [] + this.appliedItems = this.items + + for (const { name, value } of rawItems) { + try { + this.add( + name, + isFunction(value) + ? await value(...args) + : value + ) + } catch (error) { + logger.error(`${chalk.cyan(name)} apply ${chalk.cyan(this.key)} failed.`) + throw error + } + } + + this.items = rawItems + } + + /** + * Asynchronous serial running + * + * @param args + * @param {Array} args + * @api public + */ + + async parallelApply (...args) { + const rawItems = this.items + this.items = [] + this.appliedItems = this.items + + await Promise.all(rawItems.map(async ({ name, value }) => { + try { + this.add( + name, + isFunction(value) + ? await value(...args) + : value + ) + } catch (error) { + logger.error(`${chalk.cyan(name)} apply ${chalk.cyan(this.key)} failed.`) + throw error + } + })).catch(error => { + throw error + }) + + this.items = rawItems + } + + /** + * Process a value via a pipeline. + * + * @param input + * @returns {any} + * @api public + */ + + async pipeline (input) { + for (const fn of this.values) { + input = await fn(input) + } + return input + } +} + +AsyncOption.prototype.apply = AsyncOption.prototype.asyncApply +module.exports = AsyncOption diff --git a/packages/@vuepress/core/lib/plugin-api/abstract/Option.js b/packages/@vuepress/core/lib/plugin-api/abstract/Option.js new file mode 100644 index 0000000000..6ffd7d48f6 --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/abstract/Option.js @@ -0,0 +1,135 @@ +'use strict' + +/** + * Module dependencies. + */ + +const { logger, chalk, compose, datatypes: { isFunction }} = require('@vuepress/shared-utils') + +/** + * Expose synchronous option class. + */ + +class Option { + constructor (key) { + this.key = key + this.items = [] + } + + /** + * Set value with name. + * + * @param {string} name + * @param {T} value + * @api public + */ + + add (name, value) { + if (Array.isArray(value)) { + return this.items.push(...value.map(i => ({ value: i, name }))) + } + this.items.push({ value, name }) + } + + /** + * Delete value with name. + * + * @param {string} name + * @api public + */ + + delete (name) { + let index = this.items.findIndex(({ name: _name }) => _name === name) + while (index !== -1) { + this.items.splice(index, 1) + index = this.items.findIndex(({ name: _name }) => _name === name) + } + } + + /** + * Clean option store + * + * @param {string} name + * @api public + */ + + clear (name) { + this.items = [] + } + + /** + * Get values. + * + * @returns {any} + * @api public + */ + + get values () { + return this.items.map(item => item.value) + } + + /** + * Get applied values + * + * @returns {Array|*|any[]} + * @api public + */ + + get appliedValues () { + return this.appliedItems && this.appliedItems.map(item => item.value) + } + + /** + * Get entries. + * + * @returns {any} + * @api public + */ + + get entries () { + return this.items.map(({ name, value }) => ([name, value])) + } + + /** + * Synchronous running + * + * @param {Array} args + * @api public + */ + + syncApply (...args) { + const rawItems = this.items + this.items = [] + this.appliedItems = this.items + + for (const { name, value } of rawItems) { + try { + this.add( + name, + isFunction(value) + ? value(...args) + : value + ) + } catch (error) { + logger.error(`${chalk.cyan(name)} apply ${chalk.cyan(this.key)} failed.`) + throw error + } + } + + this.items = rawItems + } + + /** + * Process a value via a pipeline. + * @param input + * @returns {*} + */ + + pipeline (input) { + const fn = compose(this.values) + return fn(input) + } +} + +Option.prototype.apply = Option.prototype.syncApply +module.exports = Option diff --git a/packages/@vuepress/core/lib/plugin-api/constants.js b/packages/@vuepress/core/lib/plugin-api/constants.js new file mode 100644 index 0000000000..b7c1d66ace --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/constants.js @@ -0,0 +1,33 @@ +'use strict' + +const PLUGIN_OPTION_META_MAP = { + // hooks + READY: { name: 'ready', types: [Function] }, + COMPILED: { name: 'compiled', types: [Function] }, + UPDATED: { name: 'updated', types: [Function] }, + GENERATED: { name: 'generated', types: [Function] }, + // options + CHAIN_WEBPACK: { name: 'chainWebpack', types: [Function] }, + ENHANCE_DEV_SERVER: { name: 'enhanceDevServer', types: [Function] }, + ENHANCE_APP_FILES: { name: 'enhanceAppFiles', types: [Array, Function] }, + OUT_FILES: { name: 'outFiles', types: [Object] }, + EXTEND_PAGE_DATA: { name: 'extendPageData', types: [Function] }, + EXTEND_MARKDOWN: { name: 'extendMarkdown', types: [Function] }, + CHAIN_MARKDOWN: { name: 'chainMarkdown', types: [Function] }, + CLIENT_DYNAMIC_MODULES: { name: 'clientDynamicModules', types: [Function] }, + CLIENT_ROOT_MIXIN: { name: 'clientRootMixin', types: [String] }, + ADDITIONAL_PAGES: { name: 'additionalPages', types: [Function, Array] }, + GLOBAL_UI_COMPONENTS: { name: 'globalUIComponents', types: [String, Array] }, + DEFINE: { name: 'define', types: [Function, Object] }, + ALIAS: { name: 'alias', types: [Function, Object] } +} + +const PLUGIN_OPTION_MAP = {} +Object.keys(PLUGIN_OPTION_META_MAP).forEach(key => { + PLUGIN_OPTION_MAP[key] = Object.assign({ key }, PLUGIN_OPTION_META_MAP[key]) +}) + +const OPTION_NAMES = Object.keys(PLUGIN_OPTION_META_MAP).map(key => PLUGIN_OPTION_META_MAP[key].name) + +exports.PLUGIN_OPTION_MAP = PLUGIN_OPTION_MAP +exports.OPTION_NAMES = OPTION_NAMES diff --git a/packages/@vuepress/core/lib/plugin-api/index.js b/packages/@vuepress/core/lib/plugin-api/index.js new file mode 100644 index 0000000000..bea3dd85ef --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/index.js @@ -0,0 +1,205 @@ +'use strict' + +/** + * Module dependencies. + */ + +const instantiateOption = require('./override/instantiateOption') +const { flattenPlugin, normalizePluginsConfig } = require('./util') +const { PLUGIN_OPTION_MAP } = require('./constants') +const { + shortcutPackageResolver: { resolvePlugin }, + datatypes: { assertTypes }, + env: { isDebug }, + logger, chalk +} = require('@vuepress/shared-utils') + +/** + * Expose PluginAPI class. + */ + +module.exports = class PluginAPI { + constructor (context) { + this.options = {} + this._pluginContext = context + this._pluginQueue = [] + this.initializeOptions(PLUGIN_OPTION_MAP) + } + + /** + * Get enabled plugins + * + * @returns {array} + * @api public + */ + + get enabledPlugins () { + return this._pluginQueue.filter(({ enabled }) => enabled) + } + + /** + * Get disabled plugins + * + * @returns {array} + * @api public + */ + + get disabledPlugins () { + return this._pluginQueue.filter(({ enabled }) => !enabled) + } + + /** + * apply plugin. + * + * @api public + */ + + apply () { + this._pluginQueue.forEach(plugin => { + if (plugin.enabled) { + this.applyPlugin(plugin) + } else { + logger.debug(`\n${chalk.gray(`[${plugin.name}]`)} disabled.`) + } + }) + } + + /** + * Normalize plugin and push it to the plugin queue. + * + * @param {object} pluginRaw + * @param {object} pluginOptions + * @returns {module.PluginAPI} + * @api public + */ + + use (pluginRaw, pluginOptions = {}) { + let plugin = resolvePlugin(pluginRaw) + if (!plugin.module) { + console.warn(`[vuepress] cannot resolve plugin "${pluginRaw}"`) + return this + } + plugin = flattenPlugin(plugin, pluginOptions, this._pluginContext, this) + if (plugin.multiple !== true) { + const duplicateIndex = this._pluginQueue.findIndex(({ name }) => name === plugin.name) + if (duplicateIndex !== -1) { + this._pluginQueue.splice(duplicateIndex, 1) + } + } + this._pluginQueue.push(plugin) + return this + } + + /** + * Use plugin by config. + * + * @param pluginsConfig + * @returns {module.PluginAPI} + * @api public + */ + + useByPluginsConfig (pluginsConfig) { + pluginsConfig = normalizePluginsConfig(pluginsConfig) + pluginsConfig.forEach(([pluginRaw, pluginOptions]) => { + this.use(pluginRaw, pluginOptions) + }) + return this + } + + /** + * initialize plugin options. + * + * @api private + */ + + initializeOptions () { + Object.keys(PLUGIN_OPTION_MAP).forEach(key => { + const option = PLUGIN_OPTION_MAP[key] + this.options[option.name] = instantiateOption(option.name) + }) + } + + /** + * Register plugin option. + * + * @param {string} key + * @param {any} value + * @param {string} pluginName + * @returns {module.PluginAPI} + * @api private + */ + + registerOption (key, value, pluginName) { + const option = PLUGIN_OPTION_MAP[key] + const types = option.types + const { valid, warnMsg } = assertTypes(value, types) + if (valid) { + this.options[option.name].add(pluginName, value) + } else if (value !== undefined) { + logger.warn( + `${chalk.gray(pluginName)} ` + + `Invalid value for "option" ${chalk.cyan(option.name)}: ${warnMsg}` + ) + } + return this + } + + /** + * apply plugin. + * + * @api private + */ + + applyPlugin ({ + // info + name: pluginName, + shortcut, + + // hooks + ready, + compiled, + updated, + generated, + + // options + chainWebpack, + enhanceDevServer, + extendMarkdown, + chainMarkdown, + enhanceAppFiles, + outFiles, + extendPageData, + clientDynamicModules, + clientRootMixin, + additionalPages, + globalUIComponents, + define, + alias + }) { + const isInternalPlugin = pluginName.startsWith('@vuepress/internal-') + if (shortcut) { + logger.tip(`\nApply plugin ${chalk.magenta(shortcut)} ${chalk.gray(`(i.e. "${pluginName}")`)} ...`, !isInternalPlugin) + } else if (!isInternalPlugin || isDebug) { + logger.tip(`\nApply plugin ${chalk.magenta(pluginName)} ...`) + } + + this + .registerOption(PLUGIN_OPTION_MAP.READY.key, ready, pluginName) + .registerOption(PLUGIN_OPTION_MAP.COMPILED.key, compiled, pluginName) + .registerOption(PLUGIN_OPTION_MAP.UPDATED.key, updated, pluginName) + .registerOption(PLUGIN_OPTION_MAP.GENERATED.key, generated, pluginName) + .registerOption(PLUGIN_OPTION_MAP.CHAIN_WEBPACK.key, chainWebpack, pluginName) + .registerOption(PLUGIN_OPTION_MAP.ENHANCE_DEV_SERVER.key, enhanceDevServer, pluginName) + .registerOption(PLUGIN_OPTION_MAP.EXTEND_MARKDOWN.key, extendMarkdown, pluginName) + .registerOption(PLUGIN_OPTION_MAP.CHAIN_MARKDOWN.key, chainMarkdown, pluginName) + .registerOption(PLUGIN_OPTION_MAP.EXTEND_PAGE_DATA.key, extendPageData, pluginName) + .registerOption(PLUGIN_OPTION_MAP.ENHANCE_APP_FILES.key, enhanceAppFiles, pluginName) + .registerOption(PLUGIN_OPTION_MAP.OUT_FILES.key, outFiles, pluginName) + .registerOption(PLUGIN_OPTION_MAP.CLIENT_DYNAMIC_MODULES.key, clientDynamicModules, pluginName) + .registerOption(PLUGIN_OPTION_MAP.CLIENT_ROOT_MIXIN.key, clientRootMixin, pluginName) + .registerOption(PLUGIN_OPTION_MAP.ADDITIONAL_PAGES.key, additionalPages, pluginName) + .registerOption(PLUGIN_OPTION_MAP.GLOBAL_UI_COMPONENTS.key, globalUIComponents, pluginName) + .registerOption(PLUGIN_OPTION_MAP.DEFINE.key, define, pluginName) + .registerOption(PLUGIN_OPTION_MAP.ALIAS.key, alias, pluginName) + } +} diff --git a/packages/@vuepress/core/lib/plugin-api/override/AliasOption.js b/packages/@vuepress/core/lib/plugin-api/override/AliasOption.js new file mode 100644 index 0000000000..fd3c2f555b --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/override/AliasOption.js @@ -0,0 +1,23 @@ +'use strict' + +/** + * Module dependencies. + */ + +const Option = require('../abstract/Option') + +/** + * define option. + */ + +module.exports = class DefineOption extends Option { + apply (config) { + super.syncApply() + const aliases = this.appliedValues + aliases.forEach((alias) => { + Object.keys(alias).forEach(key => { + config.resolve.alias.set(key, alias[key]) + }) + }) + } +} diff --git a/packages/@vuepress/core/lib/plugin-api/override/ClientDynamicModulesOption.js b/packages/@vuepress/core/lib/plugin-api/override/ClientDynamicModulesOption.js new file mode 100644 index 0000000000..7dae23d628 --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/override/ClientDynamicModulesOption.js @@ -0,0 +1,29 @@ +'use strict' + +/** + * Module dependencies. + */ + +const AsyncOption = require('../abstract/AsyncOption') + +/** + * clientDynamicModules option. + */ + +module.exports = class ClientDynamicModulesOption extends AsyncOption { + async apply (ctx) { + await super.asyncApply() + + for (const { value, name: pluginName } of this.appliedItems) { + const { name, content, dirname = 'dynamic' } = value + await ctx.writeTemp( + `${dirname}/${name}`, + ` +/** + * Generated by "${pluginName}" + */ +${content}\n\n + `.trim()) + } + } +} diff --git a/packages/@vuepress/core/lib/plugin-api/override/DefineOption.js b/packages/@vuepress/core/lib/plugin-api/override/DefineOption.js new file mode 100644 index 0000000000..006f0883e5 --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/override/DefineOption.js @@ -0,0 +1,26 @@ +'use strict' + +/** + * Module dependencies. + */ + +const Option = require('../abstract/Option') + +/** + * define option. + */ + +module.exports = class DefineOption extends Option { + apply (config) { + super.syncApply() + const defines = this.appliedValues + defines.forEach(define => { + Object.keys(define).forEach(key => { + define[key] = JSON.stringify(define[key]) + }) + config.plugin('injections').tap(([options]) => [ + Object.assign(options, define) + ]) + }) + } +} diff --git a/packages/@vuepress/core/lib/plugin-api/override/EnhanceAppFilesOption.js b/packages/@vuepress/core/lib/plugin-api/override/EnhanceAppFilesOption.js new file mode 100644 index 0000000000..9e5b3f5ac5 --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/override/EnhanceAppFilesOption.js @@ -0,0 +1,88 @@ +'use strict' + +/** + * Module dependencies. + */ + +const AsyncOption = require('../abstract/AsyncOption') +const { + fs, + chalk, + logger, + codegen: { pathsToModuleCode }, + datatypes: { isPlainObject } +} = require('@vuepress/shared-utils') + +/** + * enhanceAppFiles option. + */ + +module.exports = class EnhanceAppFilesOption extends AsyncOption { + async apply (ctx) { + await super.asyncApply() + + const manifest = [] + let moduleId = 0 + + async function writeEnhancer (name, content, hasDefaultExport = true) { + return await ctx.writeTemp( + `app-enhancers/${name}.js`, + hasDefaultExport + ? content + : content + '\nexport default {}' + ) + } + + // 1. write enhance app files. + for (const { value: enhanceAppFile, name: pluginName } of this.appliedItems) { + let destPath + + // 1.1 dynamic code + if (isPlainObject(enhanceAppFile)) { + const { content } = enhanceAppFile + let { name } = enhanceAppFile + name = name.replace(/.js$/, '') + + if (hasDefaultExport(content)) { + destPath = await writeEnhancer(name, content) + } else { + destPath = await writeEnhancer(name, content, false /* do not contain default export*/) + } + // 1.2 local file + } else { + if (fs.existsSync(enhanceAppFile)) { + const content = await fs.readFile(enhanceAppFile, 'utf-8') + + if (hasDefaultExport(content)) { + destPath = await writeEnhancer( + moduleId++, + `export { default } from ${JSON.stringify(enhanceAppFile)}` + ) + } else { + destPath = await writeEnhancer( + moduleId++, + `import ${JSON.stringify(enhanceAppFile)}`, + false /* do not contain default export*/ + ) + } + } else { + logger.debug( + chalk.gray(`[${pluginName}] `) + + `${chalk.cyan(enhanceAppFile)} Not Found.` + ) + } + } + + if (destPath) { + manifest.push(destPath) + } + } + + // 2. write entry file. + await ctx.writeTemp('internal/app-enhancers.js', pathsToModuleCode(manifest)) + } +} + +function hasDefaultExport (content) { + return content.includes('export default') || content.includes('module.exports') +} diff --git a/packages/@vuepress/core/lib/plugin-api/override/GlobalUIComponentsOption.js b/packages/@vuepress/core/lib/plugin-api/override/GlobalUIComponentsOption.js new file mode 100644 index 0000000000..fb9f18e2c8 --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/override/GlobalUIComponentsOption.js @@ -0,0 +1,20 @@ +'use strict' + +/** + * Module dependencies. + */ + +const Option = require('../abstract/Option') + +/** + * globalUIComponents option. + */ + +module.exports = class GlobalUIComponentsOption extends Option { + async apply (ctx) { + await ctx.writeTemp( + `internal/global-ui.js`, + `export default ${JSON.stringify(this.values, null, 2)}` + ) + } +} diff --git a/packages/@vuepress/core/lib/plugin-api/override/instantiateOption.js b/packages/@vuepress/core/lib/plugin-api/override/instantiateOption.js new file mode 100644 index 0000000000..ad0fac2c7a --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/override/instantiateOption.js @@ -0,0 +1,28 @@ +const EnhanceAppFilesOption = require('./EnhanceAppFilesOption') +const ClientDynamicModulesOption = require('./ClientDynamicModulesOption') +const GlobalUIComponentsOption = require('./GlobalUIComponentsOption') +const DefineOption = require('./DefineOption') +const AliasOption = require('./AliasOption') +const Option = require('../abstract/Option') +const { PLUGIN_OPTION_MAP } = require('../constants') + +module.exports = function instantiateOption (name) { + switch (name) { + case PLUGIN_OPTION_MAP.ENHANCE_APP_FILES.name: + return new EnhanceAppFilesOption(name) + + case PLUGIN_OPTION_MAP.CLIENT_DYNAMIC_MODULES.name: + return new ClientDynamicModulesOption(name) + + case PLUGIN_OPTION_MAP.GLOBAL_UI_COMPONENTS.name: + return new GlobalUIComponentsOption(name) + + case PLUGIN_OPTION_MAP.DEFINE.name: + return new DefineOption(name) + + case PLUGIN_OPTION_MAP.ALIAS.name: + return new AliasOption(name) + + default: return new Option(name) + } +} diff --git a/packages/@vuepress/core/lib/plugin-api/util.js b/packages/@vuepress/core/lib/plugin-api/util.js new file mode 100644 index 0000000000..c7ff8123f0 --- /dev/null +++ b/packages/@vuepress/core/lib/plugin-api/util.js @@ -0,0 +1,85 @@ +'use strict' + +/** + * Module dependencies. + */ + +const { logger, chalk, datatypes: { assertTypes }} = require('@vuepress/shared-utils') + +/** + * flatten your plugin config by passing in name, options and context. + * @param {function|object} module + * @param {string} name + * @param {string} hortcut + * @param {object} pluginOptions + * @param {object} pluginContext + */ + +exports.flattenPlugin = function ( + { module: config, name, shortcut, isLocal }, + pluginOptions, + pluginContext, + self +) { + const { valid, warnMsg } = assertTypes(pluginOptions, [Object, Boolean]) + if (!valid) { + if (pluginOptions !== undefined) { + logger.warn( + `[${chalk.gray(shortcut)}] ` + + `Invalid value for "pluginOptions" ${chalk.cyan(name)}: ${warnMsg}` + ) + } + pluginOptions = {} + } + + let enabled = true + if (typeof pluginOptions === 'boolean') { + enabled = pluginOptions + pluginOptions = {} + } + + if (typeof config === 'function') { + // 'Object.create' here is to give each plugin a separate context, + // but also own the inheritance context. + config = config(pluginOptions, Object.create(pluginContext), self) + } + + // respect name in local plugin config + name = isLocal && config.name || name + return Object.assign({}, config, { + name, + shortcut: isLocal ? null : shortcut, + enabled, + $$options: pluginOptions /* used for test */ + }) +} + +/** + * Normalize plugins config in `.vuepress/config.js` + * @param pluginsConfig + */ + +exports.normalizePluginsConfig = function (pluginsConfig) { + const { valid, warnMsg } = assertTypes(pluginsConfig, [Object, Array]) + if (!valid) { + if (pluginsConfig !== undefined) { + logger.warn( + `[${chalk.gray('config')}] ` + + `Invalid value for "plugin" field : ${warnMsg}` + ) + } + pluginsConfig = [] + return pluginsConfig + } + + if (Array.isArray(pluginsConfig)) { + pluginsConfig = pluginsConfig.map(item => { + return Array.isArray(item) ? item : [item] + }) + } else if (typeof pluginsConfig === 'object') { + pluginsConfig = Object.keys(pluginsConfig).map(item => { + return [item, pluginsConfig[item]] + }) + } + return pluginsConfig +} diff --git a/packages/@vuepress/core/lib/prepare/AppContext.js b/packages/@vuepress/core/lib/prepare/AppContext.js new file mode 100644 index 0000000000..6b1d945899 --- /dev/null +++ b/packages/@vuepress/core/lib/prepare/AppContext.js @@ -0,0 +1,307 @@ +'use strict' + +/** + * Module dependencies. + */ + +const path = require('path') +const createMarkdown = require('./createMarkdown') +const loadConfig = require('./loadConfig') +const loadTheme = require('./loadTheme') +const { fs, logger, chalk, globby, sort, datatypes: { isFunction }} = require('@vuepress/shared-utils') + +const Page = require('./Page') +const ClientComputedMixin = require('./ClientComputedMixin') +const PluginAPI = require('../plugin-api/index') + +/** + * Expose AppContext. + */ + +module.exports = class AppContext { + /** + * Instantiate the app context with a new API + * + * @param {string} sourceDir + * @param {{ + * isProd: boolean, + * plugins: pluginsConfig, + * theme: themeNameConfig + * temp: string + * }} options + */ + + constructor (sourceDir, cliOptions = {}, isProd) { + this.sourceDir = sourceDir + this.cliOptions = cliOptions + this.isProd = isProd + + const { tempPath, writeTemp } = createTemp(cliOptions.temp) + this.tempPath = tempPath + this.writeTemp = writeTemp + + this.vuepressDir = path.resolve(sourceDir, '.vuepress') + this.siteConfig = loadConfig(this.vuepressDir) + if (isFunction(this.siteConfig)) { + this.siteConfig = this.siteConfig(this) + } + + this.base = this.siteConfig.base || '/' + this.themeConfig = this.siteConfig.themeConfig || {} + this.outDir = this.siteConfig.dest + ? path.resolve(this.siteConfig.dest) + : path.resolve(sourceDir, '.vuepress/dist') + + this.pluginAPI = new PluginAPI(this) + this.pages = [] // Array + this.ClientComputedMixinConstructor = ClientComputedMixin(this.getSiteData()) + } + + /** + * Load pages, load plugins, apply plugins / plugin options, etc. + * + * @returns {Promise} + * @api private + */ + + async process () { + this.normalizeHeadTagUrls() + this.resolveTemplates() + await this.resolveTheme() + this.resolvePlugins() + this.markdown = createMarkdown(this) + + await this.resolvePages() + await Promise.all( + this.pluginAPI.options.additionalPages.values.map(async (options) => { + await this.addPage(options) + }) + ) + + await this.pluginAPI.options.ready.apply() + await this.pluginAPI.options.clientDynamicModules.apply(this) + await this.pluginAPI.options.globalUIComponents.apply(this) + await this.pluginAPI.options.enhanceAppFiles.apply(this) + } + + /** + * Apply internal and user plugins + * + * @api private + */ + + resolvePlugins () { + const themeConfig = this.themeConfig + const siteConfig = this.siteConfig + + const shouldUseLastUpdated = ( + themeConfig.lastUpdated || + Object.keys(siteConfig.locales && themeConfig.locales || {}) + .some(base => themeConfig.locales[base].lastUpdated) + ) + + this.pluginAPI + // internl core plugins + .use(Object.assign({}, siteConfig, { name: '@vuepress/internal-site-config' })) + .use(require('../internal-plugins/siteData')) + .use(require('../internal-plugins/routes')) + .use(require('../internal-plugins/rootMixins')) + .use(require('../internal-plugins/enhanceApp')) + .use(require('../internal-plugins/overrideCSS')) + .use(require('../internal-plugins/layoutComponents')) + .use(require('../internal-plugins/pageComponents')) + // user plugin + .useByPluginsConfig(this.cliOptions.plugins) + .useByPluginsConfig(this.siteConfig.plugins) + .useByPluginsConfig(this.themePlugins) + // built-in plugins + .use('@vuepress/last-updated', shouldUseLastUpdated) + .use('@vuepress/register-components', { + componentsDir: [ + path.resolve(this.sourceDir, '.vuepress/components'), + path.resolve(this.themePath, 'components') + ] + }) + .apply() + } + + /** + * normalize head tag urls for base + * + * @api private + */ + + normalizeHeadTagUrls () { + if (this.base !== '/' && this.siteConfig.head) { + this.siteConfig.head.forEach(tag => { + const attrs = tag[1] + if (attrs) { + for (const name in attrs) { + if (name === 'src' || name === 'href') { + const value = attrs[name] + if (value.charAt(0) === '/') { + attrs[name] = this.base + value.slice(1) + } + } + } + } + }) + } + } + + /** + * Make template configurable + * + * @api private + */ + + resolveTemplates () { + let { ssrTemplate, devTemplate } = this.siteConfig + const templateDir = path.resolve(this.vuepressDir, 'templates') + if (!devTemplate) { + devTemplate = path.resolve(templateDir, 'dev.html') + if (!fs.existsSync(devTemplate)) { + devTemplate = path.resolve(__dirname, '../app/index.dev.html') + } + } + if (!ssrTemplate) { + ssrTemplate = path.resolve(templateDir, 'ssr.html') + if (!fs.existsSync(ssrTemplate)) { + ssrTemplate = path.resolve(__dirname, '../app/index.ssr.html') + } + } + logger.debug('SSR Template File: ' + chalk.gray(ssrTemplate)) + logger.debug('DEV Template File: ' + chalk.gray(devTemplate)) + this.devTemplate = devTemplate + this.ssrTemplate = ssrTemplate + } + + /** + * Find all page source files located in sourceDir + * + * @returns {Promise} + * @api private + */ + + async resolvePages () { + // resolve pageFiles + const patterns = ['**/*.md', '!.vuepress', '!node_modules'] + if (this.siteConfig.dest) { + // #654 exclude dest folder when dest dir was set in + // sourceDir but not in '.vuepress' + const outDirRelative = path.relative(this.sourceDir, this.outDir) + if (!outDirRelative.includes('..')) { + patterns.push('!' + outDirRelative) + } + } + const pageFiles = sort(await globby(patterns, { cwd: this.sourceDir })) + + await Promise.all(pageFiles.map(async (relative) => { + const filePath = path.resolve(this.sourceDir, relative) + await this.addPage({ filePath, relative }) + })) + } + + /** + * Add a page + * + * @returns {Promise} + * @api public + */ + + async addPage (options) { + options.permalinkPattern = this.siteConfig.permalink + const page = new Page(options, this) + await page.process({ + markdown: this.markdown, + computed: new this.ClientComputedMixinConstructor(), + enhancers: this.pluginAPI.options.extendPageData.items + }) + this.pages.push(page) + } + + /** + * Resolve theme + * + * @returns {Promise} + * @api private + */ + + async resolveTheme () { + const theme = this.siteConfig.theme || this.cliOptions.theme + Object.assign(this, (await loadTheme(theme, this.sourceDir, this.vuepressDir))) + } + + /** + * Get the data to be delivered to the client. + * + * @returns {{ + * title: string, + * description: string, + * base: string, + * pages: Page[], + * themeConfig: ThemeConfig, + * locales: Locales + * }} + * @api public + */ + + getSiteData () { + const { locales } = this.siteConfig + if (locales) { + Object.keys(locales).forEach(path => { + locales[path].path = path + }) + } + + return { + title: this.siteConfig.title || '', + description: this.siteConfig.description || '', + base: this.base, + pages: this.pages.map(page => page.toJson()), + themeConfig: this.siteConfig.themeConfig || {}, + locales + } + } +} + +/** + * 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.tip(`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 } +} diff --git a/packages/@vuepress/core/lib/prepare/ClientComputedMixin.js b/packages/@vuepress/core/lib/prepare/ClientComputedMixin.js new file mode 100644 index 0000000000..5922819432 --- /dev/null +++ b/packages/@vuepress/core/lib/prepare/ClientComputedMixin.js @@ -0,0 +1,128 @@ +'use strict' + +/** + * Get page data via path (permalink). + * + * @param {array} pages + * @param {string} path + * @returns {object} + */ + +function findPageForPath (pages, path) { + for (let i = 0; i < pages.length; i++) { + const page = pages[i] + if (page.path === path) { + return page + } + } + return { + path: '', + frontmatter: {} + } +} + +/** + * Expose a function to get ClientComputedMixin constructor. + * Note that this file will run in both server and client side. + * + * @param {object} siteData + * @returns {ClientComputedMixin} + */ + +module.exports = siteData => { + // We cannot use class here. webpack will throw error. + function ClientComputedMixin () {} + + ClientComputedMixin.prototype = { + setPage (page) { + this.__page = page + }, + + get $site () { + return siteData + }, + + get $themeConfig () { + return this.$site.themeConfig + }, + + get $localeConfig () { + const { locales = {}} = this.$site + let targetLang + let defaultLang + for (const path in locales) { + if (path === '/') { + defaultLang = locales[path] + } else if (this.$page.path.indexOf(path) === 0) { + targetLang = locales[path] + } + } + return targetLang || defaultLang || {} + }, + + get $siteTitle () { + return this.$localeConfig.title || this.$site.title || '' + }, + + get $title () { + const page = this.$page + const siteTitle = this.$siteTitle + const selfTitle = page.frontmatter.home ? null : ( + page.frontmatter.title || // explicit title + page.title // inferred title + ) + return siteTitle + ? selfTitle + ? (selfTitle + ' | ' + siteTitle) + : siteTitle + : selfTitle || 'VuePress' + }, + + get $description () { + // #565 hoist description from meta + const description = getMetaDescription(this.$page.frontmatter.meta) + if (description) { + return description + } + // if (this.$page.frontmatter.meta) { + // const descriptionMeta = this.$page.frontmatter.meta.filter(item => item.name === 'description')[0] + // if (descriptionMeta) return descriptionMeta.content + // } + return this.$page.frontmatter.description || this.$localeConfig.description || this.$site.description || '' + }, + + get $lang () { + return this.$page.frontmatter.lang || this.$localeConfig.lang || 'en-US' + }, + + get $localePath () { + return this.$localeConfig.path || '/' + }, + + get $themeLocaleConfig () { + return (this.$site.themeConfig.locales || {})[this.$localePath] || {} + }, + + get $page () { + if (this.__page) { + return this.__page + } + return findPageForPath( + this.$site.pages, + this.$route.path + ) + } + } + + return ClientComputedMixin +} + +function getMetaDescription (meta) { + if (meta) { + // Why '(() => 'name')()' ? + // You can use item.name directly and see what happened. + // "How many pits did webpack bury?" + const descriptionMeta = meta.filter(item => item[(() => 'name')()] === 'description')[0] + if (descriptionMeta) return descriptionMeta.content + } +} diff --git a/packages/@vuepress/core/lib/prepare/Page.js b/packages/@vuepress/core/lib/prepare/Page.js new file mode 100644 index 0000000000..af4cdba7f5 --- /dev/null +++ b/packages/@vuepress/core/lib/prepare/Page.js @@ -0,0 +1,223 @@ +'use strict' + +/** + * Module dependencies. + */ + +const path = require('path') +const slugify = require('../../../markdown/lib/slugify') +const { inferDate, DATE_RE } = require('../util/index') +const { extractHeaders, fs, fileToPath, parseFrontmatter, getPermalink, inferTitle } = require('@vuepress/shared-utils') + +/** + * Expose Page class. + */ + +module.exports = class Page { + /** + * @param {string} path the URL (excluding the domain name) for your page/post. + * @param {string} title markdown title + * @param {string} content markdown file content + * @param {string} filePath absolute file path of source markdown file. + * @param {string} relative relative file path of source markdown file. + * @param {string} permalink same to path, the URL (excluding the domain name) for your page/post. + * @param {object} frontmatter + * @param {string} permalinkPattern + */ + + constructor ({ + path, + meta, + title, + content, + filePath, + relative, + permalink, + frontmatter = {}, + permalinkPattern + }) { + this.title = title + this._meta = meta + this._filePath = filePath + this._content = content + this._permalink = permalink + this.frontmatter = frontmatter + this._permalinkPattern = permalinkPattern + + if (relative) { + this.regularPath = encodeURI(fileToPath(relative)) + } else if (path) { + this.regularPath = encodeURI(path) + } else if (permalink) { + this.regularPath = encodeURI(permalink) + } + + this.key = 'v-' + Math.random().toString(16).slice(2) + // Using regularPath first, would be override by permalink later. + this.path = this.regularPath + } + + /** + * Process a page. + * + * 1. If it's a page pointing to a md file, this method will try + * to resolve the page's title / headers from the content. + * 2. If it's a pure route. this method will only enhance it. + * + * @api public + */ + + async process ({ + computed, + markdown, + enhancers = [] + }) { + if (this._filePath) { + this._content = await fs.readFile(this._filePath, 'utf-8') + } + + if (this._content) { + const { excerpt, data, content } = parseFrontmatter(this._content) + this._strippedContent = content + this.frontmatter = data + + // infer title + const title = inferTitle(this.frontmatter, this._strippedContent) + if (title) { + this.title = title + } + + // headers + const headers = extractHeaders( + this._strippedContent, + ['h2', 'h3'], + markdown + ) + if (headers.length) { + this.headers = headers + } + + if (excerpt) { + const { html } = markdown.render(excerpt) + this.excerpt = html + } + } + + // resolve i18n + computed.setPage(this) + this._computed = computed + this._localePath = computed.$localePath + + this.enhance(enhancers) + this.buildPermalink() + } + + /** + * file name of page's source markdown file, or the last cut of regularPath. + * + * @returns {string} + * @api public + */ + + get filename () { + return path.parse(this._filePath || this.regularPath).name + } + + /** + * slugified file name. + * + * @returns {string} + * @api public + */ + + get slug () { + return slugify(this.strippedFilename) + } + + /** + * stripped file name. + * + * If filename was yyyy-MM-dd-[title], the date prefix will be stripped. + * If filename was yyyy-MM-[title], the date prefix will be stripped. + * + * @returns {string} + * @api public + */ + + get strippedFilename () { + const match = this.filename.match(DATE_RE) + return match ? match[3] : this.filename + } + + /** + * get date of a page. + * + * @returns {null|string} + * @api public + */ + + get date () { + return inferDate(this.frontmatter, this.filename) + } + + /** + * Convert page's metadata to JSON, note that all fields beginning + * with an underscore will not be serialized. + * + * @returns {object} + * @api public + */ + + toJson () { + const json = {} + Object.keys(this).reduce((json, key) => { + if (!key.startsWith('_')) { + json[key] = this[key] + } + return json + }, json) + return json + } + + /** + * Build permalink via permalink pattern and page's metadata. + * + * @api private + */ + + buildPermalink () { + if (!this._permalink) { + this._permalink = getPermalink({ + pattern: this.frontmatter.permalink || this._permalinkPattern, + slug: this.slug, + date: this.date, + localePath: this._localePath, + regularPath: this.regularPath + }) + } + + if (this._permalink) { + this.path = this._permalink + } + } + + /** + * Execute the page enhancers. A enhancer could do following things: + * + * 1. Modify page's frontmetter. + * 2. Add extra field to the page. + * + * @api private + */ + + enhance (enhancers) { + for (const { name: pluginName, value: enhancer } of enhancers) { + try { + enhancer(this) + } catch (error) { + console.log(error) + throw new Error(`[${pluginName}] excuete extendPageData failed.`) + } + } + } +} diff --git a/packages/@vuepress/core/lib/prepare/createMarkdown.js b/packages/@vuepress/core/lib/prepare/createMarkdown.js new file mode 100644 index 0000000000..6f399f66d4 --- /dev/null +++ b/packages/@vuepress/core/lib/prepare/createMarkdown.js @@ -0,0 +1,33 @@ +'use strict' + +/** + * Module dependencies. + */ + +const createMarkdown = require('@vuepress/markdown') + +/** + * Expose createMarkdown. + */ + +module.exports = function (ctx) { + const { markdown: markdownConfig = {}} = ctx.siteConfig + const { chainMarkdown, extendMarkdown } = markdownConfig + + const beforeInstantiate = config => { + chainMarkdown && chainMarkdown(config) + ctx.pluginAPI.options.chainMarkdown.syncApply(config) + } + + const afterInstantiate = md => { + extendMarkdown && extendMarkdown(md) + ctx.pluginAPI.options.extendMarkdown.syncApply(md) + } + + return createMarkdown( + Object.assign(markdownConfig, { + beforeInstantiate, + afterInstantiate + }) + ) +} diff --git a/packages/@vuepress/core/lib/prepare/index.js b/packages/@vuepress/core/lib/prepare/index.js new file mode 100644 index 0000000000..06bd77ed83 --- /dev/null +++ b/packages/@vuepress/core/lib/prepare/index.js @@ -0,0 +1,17 @@ +'use strict' + +/** + * Module dependencies. + */ + +const AppContext = require('./AppContext') + +/** + * Expose prepare. + */ + +module.exports = async function prepare (sourceDir, cliOptions, isProd) { + const appContext = new AppContext(sourceDir, cliOptions, isProd) + await appContext.process() + return appContext +} diff --git a/lib/prepare/loadConfig.js b/packages/@vuepress/core/lib/prepare/loadConfig.js similarity index 91% rename from lib/prepare/loadConfig.js rename to packages/@vuepress/core/lib/prepare/loadConfig.js index aa913ba013..a6f264bf7e 100644 --- a/lib/prepare/loadConfig.js +++ b/packages/@vuepress/core/lib/prepare/loadConfig.js @@ -1,8 +1,18 @@ -const fs = require('fs-extra') +'use strict' + +/** + * Module dependencies. + */ + +const { fs } = require('@vuepress/shared-utils') const path = require('path') const yamlParser = require('js-yaml') const tomlParser = require('toml') +/** + * Expose loadConfig. + */ + module.exports = function loadConfig (vuepressDir, bustCache = true) { const configPath = path.resolve(vuepressDir, 'config.js') const configYmlPath = path.resolve(vuepressDir, 'config.yml') diff --git a/packages/@vuepress/core/lib/prepare/loadTheme.js b/packages/@vuepress/core/lib/prepare/loadTheme.js new file mode 100644 index 0000000000..3a8eedec21 --- /dev/null +++ b/packages/@vuepress/core/lib/prepare/loadTheme.js @@ -0,0 +1,126 @@ +'use strict' + +/** + * Module dependencies. + */ + +const path = require('path') +const fs = require('fs') +const { + shortcutPackageResolver: { resolveTheme }, + datatypes: { isString }, + logger, chalk +} = require('@vuepress/shared-utils') + +/** + * Resolve theme. + * + * Resolving Priority: + * + * 1. If the theme was a absolute path and that path exists, respect it + * as the theme directory. + * 2. If 'theme' directory located at vuepressDir exists, respect it as + * the theme directory. + * 3. If 'theme' was a shortcut string, resolve it from deps. + * + * @param {string} theme + * @param {string} sourceDir + * @param {string} vuepressDir + * @returns {Promise} + */ + +module.exports = async function loadTheme (theme, sourceDir, vuepressDir) { + const localThemePath = path.resolve(vuepressDir, 'theme') + const useLocalTheme = + !fs.existsSync(theme) && + fs.existsSync(localThemePath) && + (fs.readdirSync(localThemePath)).length > 0 + + let themePath = null // Mandatory + let themeIndexFile = null // Optional + let themeName + let themeShortcut + + if (useLocalTheme) { + themePath = localThemePath + logger.tip(`\nApply theme located at ${chalk.gray(themePath)}...`) + } else if (isString(theme)) { + const { module: modulePath, name, shortcut } = resolveTheme(theme, sourceDir) + if (modulePath.endsWith('.js') || modulePath.endsWith('.vue')) { + themePath = path.parse(modulePath).dir + } else { + themePath = modulePath + } + themeName = name + themeShortcut = shortcut + logger.tip(`\nApply theme ${chalk.gray(themeName)}`) + } else { + 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`) + } + + try { + themeIndexFile = require(themePath) + } catch (error) { + themeIndexFile = {} + } + + // handle theme api + const { + plugins: themePlugins, + palette: themePalette, + layoutDir = useLocalTheme + ? '.' + : 'layouts' + } = themeIndexFile + + const layoutDirPath = path.resolve(themePath, layoutDir) + + // normalize component name + const getComponentName = filename => { + filename = filename.slice(0, -4) + if (filename === '404') { + filename = 'NotFound' + } + return filename + } + + // built-in named layout or not. + const isInternal = componentName => componentName === 'Layout' || + componentName === 'NotFound' + + const layoutComponentMap = fs.readdirSync(layoutDirPath) + .filter(filename => filename.endsWith('.vue')) + .reduce((map, filename) => { + const componentName = getComponentName(filename) + const componentPath = path.resolve(layoutDirPath, filename) + map[componentName] = { filename, componentName, path: componentPath } + if (isInternal(componentName)) { + map[componentName].isInternal = true + } + return map + }, {}) + + if (!layoutComponentMap.Layout && !fs.existsSync(layoutComponentMap.Layout.path)) { + throw new Error(`[vuepress] Cannot resolve Layout.vue file in \n ${layoutComponentMap.Layout.path}`) + } + + // use default 404 component. + if (!layoutComponentMap.NotFound || !fs.existsSync(layoutComponentMap.NotFound.path)) { + layoutComponentMap['404'] = { + filename: 'Layout.vue', + componentName: 'NotFound', + path: path.resolve(__dirname, '../app/components/NotFound.vue'), + isInternal: true + } + } + + return { + themePath, + layoutComponentMap, + themeIndexFile, + themePlugins, + themePalette, + themeName, + themeShortcut + } +} diff --git a/packages/@vuepress/core/lib/util/index.js b/packages/@vuepress/core/lib/util/index.js new file mode 100644 index 0000000000..20b97a5154 --- /dev/null +++ b/packages/@vuepress/core/lib/util/index.js @@ -0,0 +1,66 @@ +'use strict' + +/** + * Normalize head tag config. + * + * @param {string|array} tag + * @returns {object} + */ + +exports.normalizeHeadTag = function (tag) { + if (typeof tag === 'string') { + tag = [tag] + } + const tagName = tag[0] + return { + tagName, + attributes: tag[1] || {}, + innerHTML: tag[2] || '', + closeTag: !(tagName === 'meta' || tagName === 'link') + } +} + +/** + * Use webpack-merge to merge user's config into default config. + * + * @param {object} userConfig + * @param {object} config + * @param {boolean} isServer + * @returns {object} + */ + +exports.applyUserWebpackConfig = function (userConfig, config, isServer) { + const merge = require('webpack-merge') + if (typeof userConfig === 'object') { + return merge(config, userConfig) + } + if (typeof userConfig === 'function') { + const res = userConfig(config, isServer) + if (res && typeof res === 'object') { + return merge(config, res) + } + } + return config +} + +/** + * Infer date. + * + * @param {object} frontmatter + * @param {string} filename + * @returns {null|string} + */ + +const DATE_RE = /(\d{4}-\d{1,2}(-\d{1,2})?)-(.*)/ +exports.DATE_RE = DATE_RE + +exports.inferDate = function (frontmatter = {}, filename) { + if (frontmatter.date) { + return frontmatter.date + } + const match = filename.match(DATE_RE) + if (match) { + return match[1] + } + return null +} diff --git a/lib/webpack/ClientPlugin.js b/packages/@vuepress/core/lib/webpack/ClientPlugin.js similarity index 99% rename from lib/webpack/ClientPlugin.js rename to packages/@vuepress/core/lib/webpack/ClientPlugin.js index 82028475bb..80aeb8927d 100644 --- a/lib/webpack/ClientPlugin.js +++ b/packages/@vuepress/core/lib/webpack/ClientPlugin.js @@ -1,3 +1,5 @@ +'use strict' + // Temporarily hacked dev build var isJS = function (file) { return /\.js(\?[^.]+)?$/.test(file) } diff --git a/lib/webpack/DevLogPlugin.js b/packages/@vuepress/core/lib/webpack/DevLogPlugin.js similarity index 87% rename from lib/webpack/DevLogPlugin.js rename to packages/@vuepress/core/lib/webpack/DevLogPlugin.js index 8fd18d4a0a..254f0bd4dd 100644 --- a/lib/webpack/DevLogPlugin.js +++ b/packages/@vuepress/core/lib/webpack/DevLogPlugin.js @@ -1,5 +1,14 @@ -const chalk = require('chalk') -const logger = require('../util/logger') +'use strict' + +/** + * Module dependencies. + */ + +const { chalk, logger } = require('@vuepress/shared-utils') + +/** + * Expose DevLogPlugin class. + */ module.exports = class DevLogPlugin { constructor (options) { diff --git a/lib/webpack/HeadPlugin.js b/packages/@vuepress/core/lib/webpack/HeadPlugin.js similarity index 72% rename from lib/webpack/HeadPlugin.js rename to packages/@vuepress/core/lib/webpack/HeadPlugin.js index 2ccb46e07d..828c2f2212 100644 --- a/lib/webpack/HeadPlugin.js +++ b/packages/@vuepress/core/lib/webpack/HeadPlugin.js @@ -1,6 +1,16 @@ -const { normalizeHeadTag } = require('../util') +'use strict' -module.exports = class SiteDataPlugin { +/** + * Module dependencies. + */ + +const { normalizeHeadTag } = require('../util/index') + +/** + * Expose HeadPlugin class. + */ + +module.exports = class HeadPlugin { constructor ({ tags }) { this.tags = tags } diff --git a/lib/webpack/createBaseConfig.js b/packages/@vuepress/core/lib/webpack/createBaseConfig.js similarity index 83% rename from lib/webpack/createBaseConfig.js rename to packages/@vuepress/core/lib/webpack/createBaseConfig.js index dcf7fab328..dd7969203c 100644 --- a/lib/webpack/createBaseConfig.js +++ b/packages/@vuepress/core/lib/webpack/createBaseConfig.js @@ -1,17 +1,30 @@ +'use strict' + +/** + * Module dependencies. + */ + const path = require('path') +const { fs, logger, chalk } = require('@vuepress/shared-utils') + +/** + * Expose createBaseConfig method. + */ module.exports = function createBaseConfig ({ siteConfig, - siteData, sourceDir, outDir, - publicPath, + base: publicPath, themePath, - themeLayoutPath, - themeNotFoundPath, - isAlgoliaSearch, - markdown -}, { debug } = {}, isServer) { + markdown, + tempPath, + cliOptions: { + debug, + cache + }, + pluginAPI +}, isServer) { const Config = require('webpack-chain') const { VueLoaderPlugin } = require('vue-loader') const CSSExtractPlugin = require('mini-css-extract-plugin') @@ -38,15 +51,11 @@ module.exports = function createBaseConfig ({ .set('symlinks', true) .alias .set('@theme', themePath) - .set('@themeLayout', themeLayoutPath) - .set('@themeNotFound', themeNotFoundPath) .set('@source', sourceDir) .set('@app', path.resolve(__dirname, '../app')) - .set('@temp', path.resolve(__dirname, '../app/.temp')) - .set('@default-theme', path.resolve(__dirname, '../default-theme')) - .set('@AlgoliaSearchBox', isAlgoliaSearch - ? path.resolve(__dirname, '../default-theme/AlgoliaSearchBox.vue') - : path.resolve(__dirname, './noopModule.js')) + .set('@temp', tempPath) + .set('@dynamic', path.resolve(tempPath, 'dynamic')) + .set('@internal', path.resolve(tempPath, 'internal')) .end() .extensions .merge(['.js', '.jsx', '.vue', '.json', '.styl']) @@ -57,18 +66,6 @@ module.exports = function createBaseConfig ({ .add(path.resolve(__dirname, '../../../')) .add('node_modules') - // Load extra root mixins on demand. - const { activeHeaderLinks = true } = siteData.themeConfig - const rootMixinsLoadingConfig = { activeHeaderLinks } - for (const mixinName in rootMixinsLoadingConfig) { - const enabled = rootMixinsLoadingConfig[mixinName] - config.resolve - .alias.set(`@${mixinName}`, enabled - ? path.resolve(__dirname, `../app/root-mixins/${mixinName}.js`) - : path.resolve(__dirname, './noopModule.js') - ) - } - config.resolveLoader .set('symlinks', true) .modules @@ -80,7 +77,17 @@ module.exports = function createBaseConfig ({ config.module .noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/) - const cacheDirectory = path.resolve(__dirname, '../../node_modules/.cache/vuepress') + let cacheDirectory + if (cache && typeof cache === 'string') { + cacheDirectory = path.resolve(cache) + } else { + cacheDirectory = path.resolve(__dirname, '../../node_modules/.cache/vuepress') + } + logger.debug('Cache directory: ' + chalk.gray(cacheDirectory)) + if (!cache) { + logger.tip('\nClean cache...\n') + fs.emptyDirSync(cacheDirectory) + } const cacheIdentifier = JSON.stringify({ vuepress: require('../../package.json').version, 'cache-loader': require('cache-loader').version, @@ -129,7 +136,7 @@ module.exports = function createBaseConfig ({ mdRule .use('markdown-loader') - .loader(require.resolve('./markdownLoader')) + .loader(require.resolve('@vuepress/markdown-loader')) .options({ sourceDir, markdown }) config.module @@ -144,17 +151,17 @@ module.exports = function createBaseConfig ({ config.module .rule('js') .test(/\.js$/) - .exclude.add(filepath => { + .exclude.add(filePath => { // Always transpile lib directory - if (filepath.startsWith(libDir)) { + if (filePath.startsWith(libDir)) { return false } // always transpile js in vue files - if (/\.vue\.js$/.test(filepath)) { + if (/\.vue\.js$/.test(filePath)) { return false } // Don't transpile node_modules - return /node_modules/.test(filepath) + return /node_modules/.test(filePath) }).end() .use('cache-loader') .loader('cache-loader') @@ -295,13 +302,14 @@ module.exports = function createBaseConfig ({ config .plugin('injections') .use(require('webpack/lib/DefinePlugin'), [{ - BASE_URL: JSON.stringify(siteConfig.base || '/'), - GA_ID: siteConfig.ga ? JSON.stringify(siteConfig.ga) : false, - SW_ENABLED: !!siteConfig.serviceWorker, 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) + return config } diff --git a/lib/webpack/createClientConfig.js b/packages/@vuepress/core/lib/webpack/createClientConfig.js similarity index 82% rename from lib/webpack/createClientConfig.js rename to packages/@vuepress/core/lib/webpack/createClientConfig.js index 8c27663301..c818c29310 100644 --- a/lib/webpack/createClientConfig.js +++ b/packages/@vuepress/core/lib/webpack/createClientConfig.js @@ -1,9 +1,15 @@ -module.exports = function createClientConfig (options, cliOptions) { +'use strict' + +/** + * Expose createClientConfig method. + */ + +module.exports = function createClientConfig (ctx) { const path = require('path') const WebpackBar = require('webpackbar') const createBaseConfig = require('./createBaseConfig') - const config = createBaseConfig(options, cliOptions) + const config = createBaseConfig(ctx) config .entry('app') @@ -51,7 +57,7 @@ module.exports = function createClientConfig (options, cliOptions) { }]) } - if (!cliOptions.debug) { + if (!ctx.cliOptions.debug) { config .plugin('bar') .use(WebpackBar, [{ @@ -61,9 +67,11 @@ module.exports = function createClientConfig (options, cliOptions) { }]) } - if (options.siteConfig.chainWebpack) { - options.siteConfig.chainWebpack(config, false /* isServer */) + if (ctx.siteConfig.chainWebpack) { + ctx.siteConfig.chainWebpack(config, false /* isServer */) } + ctx.pluginAPI.options.chainWebpack.syncApply(config, false /* isServer */) + return config } diff --git a/lib/webpack/createServerConfig.js b/packages/@vuepress/core/lib/webpack/createServerConfig.js similarity index 73% rename from lib/webpack/createServerConfig.js rename to packages/@vuepress/core/lib/webpack/createServerConfig.js index e9f098354b..b8592a98fa 100644 --- a/lib/webpack/createServerConfig.js +++ b/packages/@vuepress/core/lib/webpack/createServerConfig.js @@ -1,4 +1,10 @@ -module.exports = function createServerConfig (options, cliOptions) { +'use strict' + +/** + * Expose createServerConfig method. + */ + +module.exports = function createServerConfig (ctx) { const fs = require('fs') const path = require('path') const WebpackBar = require('webpackbar') @@ -6,8 +12,8 @@ module.exports = function createServerConfig (options, cliOptions) { const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') const CopyPlugin = require('copy-webpack-plugin') - const config = createBaseConfig(options, cliOptions, true /* isServer */) - const { sourceDir, outDir } = options + const config = createBaseConfig(ctx, true /* isServer */) + const { sourceDir, outDir } = ctx config .target('node') @@ -40,7 +46,7 @@ module.exports = function createServerConfig (options, cliOptions) { ]]) } - if (!cliOptions.debug) { + if (!ctx.cliOptions.debug) { config .plugin('bar') .use(WebpackBar, [{ @@ -50,9 +56,11 @@ module.exports = function createServerConfig (options, cliOptions) { }]) } - if (options.siteConfig.chainWebpack) { - options.siteConfig.chainWebpack(config, true /* isServer */) + if (ctx.siteConfig.chainWebpack) { + ctx.siteConfig.chainWebpack(config, true /* isServer */) } + ctx.pluginAPI.options.chainWebpack.syncApply(config, true /* isServer */) + return config } diff --git a/lib/webpack/noopModule.js b/packages/@vuepress/core/lib/webpack/noopModule.js similarity index 100% rename from lib/webpack/noopModule.js rename to packages/@vuepress/core/lib/webpack/noopModule.js diff --git a/packages/@vuepress/core/package.json b/packages/@vuepress/core/package.json new file mode 100644 index 0000000000..e88364f6fb --- /dev/null +++ b/packages/@vuepress/core/package.json @@ -0,0 +1,71 @@ +{ + "name": "@vuepress/core", + "version": "1.0.0", + "description": "Minimalistic doc generator with Vue component based layout system", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress#readme", + "dependencies": { + "@babel/core": "7.0.0-beta.47", + "@vue/babel-preset-app": "3.0.0-beta.11", + "@vuepress/markdown": "^1.0.0", + "@vuepress/markdown-loader": "^1.0.0", + "@vuepress/shared-utils": "^1.0.0", + "@vuepress/plugin-last-updated": "^1.0.0", + "@vuepress/plugin-register-components": "^1.0.0", + "autoprefixer": "^8.2.0", + "babel-loader": "8.0.0-beta.3", + "cache-loader": "^1.2.2", + "chokidar": "^2.0.3", + "commander": "^2.15.1", + "connect-history-api-fallback": "^1.5.0", + "copy-webpack-plugin": "^4.5.1", + "cross-spawn": "^6.0.5", + "css-loader": "^0.28.11", + "file-loader": "^1.1.11", + "globby": "^8.0.1", + "gray-matter": "^4.0.1", + "js-yaml": "^3.11.0", + "koa-connect": "^2.0.1", + "koa-mount": "^3.0.0", + "koa-range": "^0.3.0", + "koa-static": "^4.0.2", + "lru-cache": "^4.1.2", + "mini-css-extract-plugin": "^0.4.1", + "optimize-css-assets-webpack-plugin": "^4.0.0", + "portfinder": "^1.0.13", + "postcss-loader": "^2.1.5", + "toml": "^2.3.3", + "url-loader": "^1.0.1", + "vue": "^2.5.16", + "vue-loader": "^15.2.4", + "vue-router": "^3.0.1", + "vue-server-renderer": "^2.5.16", + "vue-template-compiler": "^2.5.16", + "vuepress-html-webpack-plugin": "^3.2.0", + "webpack": "^4.8.1", + "webpack-chain": "^4.6.0", + "webpack-merge": "^4.1.2", + "webpack-serve": "^1.0.2", + "webpackbar": "^2.6.1" + }, + "engines": { + "node": ">=8" + }, + "browserslist": [ + ">1%" + ] +} diff --git a/packages/@vuepress/markdown-loader/.npmignore b/packages/@vuepress/markdown-loader/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/markdown-loader/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/markdown-loader/README.md b/packages/@vuepress/markdown-loader/README.md new file mode 100644 index 0000000000..90ad547ca5 --- /dev/null +++ b/packages/@vuepress/markdown-loader/README.md @@ -0,0 +1,24 @@ +# @vuepress/markdown-loader + +> markdown-loader for vuepress + +## Usage + +```js +const rule = config.module + .rule('markdown') + .test(/\.md$/) + +rule + .use('vue-loader') + .loader('vue-loader') + .options({ /* ... */ }) + +rule + .use('markdown-loader') + .loader(require.resolve('@vuepress/markdown-loader')) + .options({ + markdown: /* instance created by @vuepress/markdown */, + sourceDir: /* root source directory of your docs */, + }) +``` diff --git a/lib/webpack/markdownLoader.js b/packages/@vuepress/markdown-loader/index.js similarity index 86% rename from lib/webpack/markdownLoader.js rename to packages/@vuepress/markdown-loader/index.js index 3c49b747f6..fd35030157 100644 --- a/lib/webpack/markdownLoader.js +++ b/packages/@vuepress/markdown-loader/index.js @@ -1,18 +1,32 @@ -const fs = require('fs') +'use strict' + +/** + * Module dependencies. + */ + const path = require('path') -const hash = require('hash-sum') const { EventEmitter } = require('events') const { getOptions } = require('loader-utils') -const { inferTitle, extractHeaders, parseFrontmatter } = require('../util') +const { fs, hash, parseFrontmatter, inferTitle, extractHeaders } = require('@vuepress/shared-utils') const LRU = require('lru-cache') +const md = require('@vuepress/markdown') const cache = LRU({ max: 1000 }) const devCache = LRU({ max: 1000 }) +/** + * Expose markdown loader. + */ + module.exports = function (src) { const isProd = process.env.NODE_ENV === 'production' const isServer = this.target === 'node' - const { markdown, sourceDir } = getOptions(this) + const options = getOptions(this) + const { sourceDir } = options + let { markdown } = options + if (!markdown) { + markdown = md() + } // we implement a manual cache here because this loader is chained before // vue-loader, and will be applied on the same file multiple times when @@ -28,7 +42,7 @@ module.exports = function (src) { const content = frontmatter.content if (!isProd && !isServer) { - const inferredTitle = inferTitle(frontmatter) + const inferredTitle = inferTitle(frontmatter.data, frontmatter.content) const headers = extractHeaders(content, ['h2', 'h3'], markdown) delete frontmatter.content @@ -58,18 +72,23 @@ module.exports = function (src) { // check if relative links are valid links && links.forEach(link => { link = decodeURIComponent(link) + const shortname = link .replace(/#.*$/, '') .replace(/\.html$/, '.md') + const filename = shortname .replace(/\/$/, '/README.md') .replace(/^\//, sourceDir + '/') + const altname = shortname .replace(/\/$/, '/index.md') .replace(/^\//, sourceDir + '/') + const dir = path.dirname(this.resourcePath) const file = path.resolve(dir, filename) const altfile = altname !== filename ? path.resolve(dir, altname) : null + if (!fs.existsSync(file) && (!altfile || !fs.existsSync(altfile))) { this.emitWarning( new Error( diff --git a/packages/@vuepress/markdown-loader/package.json b/packages/@vuepress/markdown-loader/package.json new file mode 100644 index 0000000000..7aaa5bafc8 --- /dev/null +++ b/packages/@vuepress/markdown-loader/package.json @@ -0,0 +1,29 @@ +{ + "name": "@vuepress/markdown-loader", + "version": "1.0.0", + "description": "markdown-loader for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "dependencies": { + "@vuepress/markdown": "1.0.0", + "loader-utils": "^1.1.0" + }, + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/markdown-loader#readme" +} diff --git a/packages/@vuepress/markdown/.npmignore b/packages/@vuepress/markdown/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/markdown/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/markdown/README.md b/packages/@vuepress/markdown/README.md new file mode 100644 index 0000000000..5be9b185d7 --- /dev/null +++ b/packages/@vuepress/markdown/README.md @@ -0,0 +1,3 @@ +# @vuepress/markdown + +> markdown for vuepress \ No newline at end of file diff --git a/test/markdown/__snapshots__/containers.spec.js.snap b/packages/@vuepress/markdown/__tests__/__snapshots__/containers.spec.js.snap similarity index 100% rename from test/markdown/__snapshots__/containers.spec.js.snap rename to packages/@vuepress/markdown/__tests__/__snapshots__/containers.spec.js.snap diff --git a/test/markdown/__snapshots__/highlight.spec.js.snap b/packages/@vuepress/markdown/__tests__/__snapshots__/highlight.spec.js.snap similarity index 100% rename from test/markdown/__snapshots__/highlight.spec.js.snap rename to packages/@vuepress/markdown/__tests__/__snapshots__/highlight.spec.js.snap diff --git a/test/markdown/__snapshots__/highlightLines.spec.js.snap b/packages/@vuepress/markdown/__tests__/__snapshots__/highlightLines.spec.js.snap similarity index 100% rename from test/markdown/__snapshots__/highlightLines.spec.js.snap rename to packages/@vuepress/markdown/__tests__/__snapshots__/highlightLines.spec.js.snap diff --git a/test/markdown/__snapshots__/hoist.spec.js.snap b/packages/@vuepress/markdown/__tests__/__snapshots__/hoist.spec.js.snap similarity index 100% rename from test/markdown/__snapshots__/hoist.spec.js.snap rename to packages/@vuepress/markdown/__tests__/__snapshots__/hoist.spec.js.snap diff --git a/test/markdown/__snapshots__/lineNumers.spec.js.snap b/packages/@vuepress/markdown/__tests__/__snapshots__/lineNumers.spec.js.snap similarity index 100% rename from test/markdown/__snapshots__/lineNumers.spec.js.snap rename to packages/@vuepress/markdown/__tests__/__snapshots__/lineNumers.spec.js.snap diff --git a/test/markdown/__snapshots__/link.spec.js.snap b/packages/@vuepress/markdown/__tests__/__snapshots__/link.spec.js.snap similarity index 100% rename from test/markdown/__snapshots__/link.spec.js.snap rename to packages/@vuepress/markdown/__tests__/__snapshots__/link.spec.js.snap diff --git a/test/markdown/__snapshots__/snippet.spec.js.snap b/packages/@vuepress/markdown/__tests__/__snapshots__/snippet.spec.js.snap similarity index 100% rename from test/markdown/__snapshots__/snippet.spec.js.snap rename to packages/@vuepress/markdown/__tests__/__snapshots__/snippet.spec.js.snap diff --git a/test/markdown/containers.spec.js b/packages/@vuepress/markdown/__tests__/containers.spec.js similarity index 89% rename from test/markdown/containers.spec.js rename to packages/@vuepress/markdown/__tests__/containers.spec.js index dd4fdb4281..e2c0071a31 100644 --- a/test/markdown/containers.spec.js +++ b/packages/@vuepress/markdown/__tests__/containers.spec.js @@ -1,5 +1,5 @@ import { Md, getFragment } from './util' -import containers from '@/markdown/containers.js' +import containers from '../lib/containers.js' const mdC = Md().use(containers) diff --git a/test/markdown/fragments/code-highlightLines-multiple.md b/packages/@vuepress/markdown/__tests__/fragments/code-highlightLines-multiple.md similarity index 100% rename from test/markdown/fragments/code-highlightLines-multiple.md rename to packages/@vuepress/markdown/__tests__/fragments/code-highlightLines-multiple.md diff --git a/test/markdown/fragments/code-highlightLines-single.md b/packages/@vuepress/markdown/__tests__/fragments/code-highlightLines-single.md similarity index 100% rename from test/markdown/fragments/code-highlightLines-single.md rename to packages/@vuepress/markdown/__tests__/fragments/code-highlightLines-single.md diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-multiple.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-multiple.md new file mode 100644 index 0000000000..c06bf83f85 --- /dev/null +++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-multiple.md @@ -0,0 +1 @@ +<<< @/packages/@vuepress/core/__test__/markdown/fragments/snippet.js{1-3} diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-single.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-single.md new file mode 100644 index 0000000000..d98c6ce859 --- /dev/null +++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-single.md @@ -0,0 +1 @@ +<<< @/packages/@vuepress/core/__test__/markdown/fragments/snippet.js{1,3} diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet.md new file mode 100644 index 0000000000..5b87126e7f --- /dev/null +++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet.md @@ -0,0 +1 @@ +<<< @/packages/@vuepress/core/__test__/markdown/fragments/snippet.js \ No newline at end of file diff --git a/test/markdown/fragments/code.md b/packages/@vuepress/markdown/__tests__/fragments/code.md similarity index 100% rename from test/markdown/fragments/code.md rename to packages/@vuepress/markdown/__tests__/fragments/code.md diff --git a/test/markdown/fragments/container-danger.md b/packages/@vuepress/markdown/__tests__/fragments/container-danger.md similarity index 100% rename from test/markdown/fragments/container-danger.md rename to packages/@vuepress/markdown/__tests__/fragments/container-danger.md diff --git a/test/markdown/fragments/container-tip-override.md b/packages/@vuepress/markdown/__tests__/fragments/container-tip-override.md similarity index 100% rename from test/markdown/fragments/container-tip-override.md rename to packages/@vuepress/markdown/__tests__/fragments/container-tip-override.md diff --git a/test/markdown/fragments/container-tip.md b/packages/@vuepress/markdown/__tests__/fragments/container-tip.md similarity index 100% rename from test/markdown/fragments/container-tip.md rename to packages/@vuepress/markdown/__tests__/fragments/container-tip.md diff --git a/test/markdown/fragments/container-v-pre.md b/packages/@vuepress/markdown/__tests__/fragments/container-v-pre.md similarity index 100% rename from test/markdown/fragments/container-v-pre.md rename to packages/@vuepress/markdown/__tests__/fragments/container-v-pre.md diff --git a/test/markdown/fragments/container-warning.md b/packages/@vuepress/markdown/__tests__/fragments/container-warning.md similarity index 100% rename from test/markdown/fragments/container-warning.md rename to packages/@vuepress/markdown/__tests__/fragments/container-warning.md diff --git a/test/markdown/fragments/hoist.md b/packages/@vuepress/markdown/__tests__/fragments/hoist.md similarity index 100% rename from test/markdown/fragments/hoist.md rename to packages/@vuepress/markdown/__tests__/fragments/hoist.md diff --git a/test/markdown/fragments/snippet.js b/packages/@vuepress/markdown/__tests__/fragments/snippet.js similarity index 100% rename from test/markdown/fragments/snippet.js rename to packages/@vuepress/markdown/__tests__/fragments/snippet.js diff --git a/test/markdown/highlight.spec.js b/packages/@vuepress/markdown/__tests__/highlight.spec.js similarity index 88% rename from test/markdown/highlight.spec.js rename to packages/@vuepress/markdown/__tests__/highlight.spec.js index 19522235f8..0ca9cb3cd3 100644 --- a/test/markdown/highlight.spec.js +++ b/packages/@vuepress/markdown/__tests__/highlight.spec.js @@ -1,5 +1,5 @@ import { Md, getFragment } from './util' -import highlight from '@/markdown/highlight.js' +import highlight from '../lib/highlight.js' const md = Md() const mdH = Md().set({ highlight }) diff --git a/test/markdown/highlightLines.spec.js b/packages/@vuepress/markdown/__tests__/highlightLines.spec.js similarity index 92% rename from test/markdown/highlightLines.spec.js rename to packages/@vuepress/markdown/__tests__/highlightLines.spec.js index 49fe7065c4..d242c9a4bb 100644 --- a/test/markdown/highlightLines.spec.js +++ b/packages/@vuepress/markdown/__tests__/highlightLines.spec.js @@ -1,5 +1,5 @@ import { Md, getFragment } from './util' -import highlightLines from '@/markdown/highlightLines.js' +import highlightLines from '../lib/highlightLines.js' const md = Md() const mdH = Md().use(highlightLines) diff --git a/test/markdown/hoist.spec.js b/packages/@vuepress/markdown/__tests__/hoist.spec.js similarity index 86% rename from test/markdown/hoist.spec.js rename to packages/@vuepress/markdown/__tests__/hoist.spec.js index b2c5281b27..ae89a6f452 100644 --- a/test/markdown/hoist.spec.js +++ b/packages/@vuepress/markdown/__tests__/hoist.spec.js @@ -1,6 +1,6 @@ import { Md, getFragment } from './util' -import hoist from '@/markdown/hoist.js' -import { dataReturnable } from '@/markdown/index.js' +import hoist from '../lib/hoist.js' +import { dataReturnable } from '../lib/index.js' const md = Md().set({ html: true }) const mdH = Md().set({ html: true }).use(hoist) diff --git a/test/markdown/lineNumers.spec.js b/packages/@vuepress/markdown/__tests__/lineNumers.spec.js similarity index 81% rename from test/markdown/lineNumers.spec.js rename to packages/@vuepress/markdown/__tests__/lineNumers.spec.js index f7efafb85f..0f3f013525 100644 --- a/test/markdown/lineNumers.spec.js +++ b/packages/@vuepress/markdown/__tests__/lineNumers.spec.js @@ -1,7 +1,7 @@ import { Md, getFragment } from './util' -import preWrapper from '@/markdown/preWrapper.js' -import lineNumbers from '@/markdown/lineNumbers.js' -import highlightLines from '@/markdown/highlightLines.js' +import preWrapper from '../lib/preWrapper.js' +import lineNumbers from '../lib/lineNumbers.js' +import highlightLines from '../lib/highlightLines.js' // lineNumbers must be chained after preWrapper. // since lineNumbers needs to add extra stateful class to its block wrapper. diff --git a/test/markdown/link.spec.js b/packages/@vuepress/markdown/__tests__/link.spec.js similarity index 94% rename from test/markdown/link.spec.js rename to packages/@vuepress/markdown/__tests__/link.spec.js index 620c9fa488..87f56f1029 100644 --- a/test/markdown/link.spec.js +++ b/packages/@vuepress/markdown/__tests__/link.spec.js @@ -1,6 +1,6 @@ import { Md } from './util' -import link from '@/markdown/link.js' -import { dataReturnable } from '@/markdown/index.js' +import link from '../lib/link.js' +import { dataReturnable } from '../lib/index.js' const mdL = Md().use(link, { target: '_blank', diff --git a/test/markdown/slugify.spec.js b/packages/@vuepress/markdown/__tests__/slugify.spec.js similarity index 93% rename from test/markdown/slugify.spec.js rename to packages/@vuepress/markdown/__tests__/slugify.spec.js index 7782eaeea0..2c6a77b6c5 100644 --- a/test/markdown/slugify.spec.js +++ b/packages/@vuepress/markdown/__tests__/slugify.spec.js @@ -1,6 +1,6 @@ import { Md } from './util' import anchor from 'markdown-it-anchor' -import slugify from '@/markdown/slugify.js' +import slugify from '../lib/slugify.js' const mdS = Md().use(anchor, { slugify, diff --git a/test/markdown/snippet.spec.js b/packages/@vuepress/markdown/__tests__/snippet.spec.js similarity index 88% rename from test/markdown/snippet.spec.js rename to packages/@vuepress/markdown/__tests__/snippet.spec.js index 0131132916..5e05b481ed 100644 --- a/test/markdown/snippet.spec.js +++ b/packages/@vuepress/markdown/__tests__/snippet.spec.js @@ -1,6 +1,6 @@ import { Md, getFragment } from './util' -import snippet from '@/markdown/snippet.js' -import highlightLines from '@/markdown/highlightLines.js' +import snippet from '../lib/snippet.js' +import highlightLines from '../lib/highlightLines.js' const md = Md().use(snippet) const mdH = Md().use(snippet).use(highlightLines) diff --git a/test/markdown/util.js b/packages/@vuepress/markdown/__tests__/util.js similarity index 100% rename from test/markdown/util.js rename to packages/@vuepress/markdown/__tests__/util.js diff --git a/lib/markdown/component.js b/packages/@vuepress/markdown/lib/component.js similarity index 100% rename from lib/markdown/component.js rename to packages/@vuepress/markdown/lib/component.js diff --git a/lib/markdown/containers.js b/packages/@vuepress/markdown/lib/containers.js similarity index 100% rename from lib/markdown/containers.js rename to packages/@vuepress/markdown/lib/containers.js diff --git a/lib/markdown/highlight.js b/packages/@vuepress/markdown/lib/highlight.js similarity index 90% rename from lib/markdown/highlight.js rename to packages/@vuepress/markdown/lib/highlight.js index 02d5e3ff55..dbb6437206 100644 --- a/lib/markdown/highlight.js +++ b/packages/@vuepress/markdown/lib/highlight.js @@ -1,8 +1,6 @@ -const chalk = require('chalk') const prism = require('prismjs') const loadLanguages = require('prismjs/components/index') -const escapeHtml = require('escape-html') -const logger = require('../util/logger') +const { logger, chalk, escapeHtml } = require('@vuepress/shared-utils') // required to make embedded highlighting work... loadLanguages(['markup', 'css', 'javascript']) diff --git a/lib/markdown/highlightLines.js b/packages/@vuepress/markdown/lib/highlightLines.js similarity index 100% rename from lib/markdown/highlightLines.js rename to packages/@vuepress/markdown/lib/highlightLines.js diff --git a/lib/markdown/hoist.js b/packages/@vuepress/markdown/lib/hoist.js similarity index 100% rename from lib/markdown/hoist.js rename to packages/@vuepress/markdown/lib/hoist.js diff --git a/packages/@vuepress/markdown/lib/index.js b/packages/@vuepress/markdown/lib/index.js new file mode 100644 index 0000000000..728aacbe9c --- /dev/null +++ b/packages/@vuepress/markdown/lib/index.js @@ -0,0 +1,131 @@ +'use strict' + +/** + * Module dependencies. + */ + +const Config = require('markdown-it-chain') +const highlight = require('./highlight') +const highlightLinesPlugin = require('./highlightLines') +const preWrapperPlugin = require('./preWrapper') +const lineNumbersPlugin = require('./lineNumbers') +const componentPlugin = require('./component') +const hoistScriptStylePlugin = require('./hoist') +const convertRouterLinkPlugin = require('./link') +const containersPlugin = require('./containers') +const snippetPlugin = require('./snippet') +const emojiPlugin = require('markdown-it-emoji') +const anchorPlugin = require('markdown-it-anchor') +const tocPlugin = require('markdown-it-table-of-contents') +const _slugify = require('./slugify') +const { parseHeaders } = require('@vuepress/shared-utils') + +/** + * Create markdown by config. + */ + +module.exports = ({ + slugify, + externalLinks, + anchor, + toc, + lineNumbers, + beforeInstantiate, + afterInstantiate +} = {}) => { + // allow user config slugify + slugify = slugify || _slugify + + // using chainedAPI + const config = new Config() + + config + .options + .html(true) + .highlight(highlight) + .end() + + .plugin('component') + .use(componentPlugin) + .end() + + .plugin('highlight-lines') + .use(highlightLinesPlugin) + .end() + + .plugin('pre-wrapper') + .use(preWrapperPlugin) + .end() + + .plugin('snippet') + .use(snippetPlugin) + .end() + + .plugin('convert-router-link') + .use(convertRouterLinkPlugin, [Object.assign({ + target: '_blank', + rel: 'noopener noreferrer' + }, externalLinks)]) + .end() + + .plugin('hoist-script-style') + .use(hoistScriptStylePlugin) + .end() + + .plugin('containers') + .use(containersPlugin) + .end() + + .plugin('emoji') + .use(emojiPlugin) + .end() + + .plugin('anchor') + .use(anchorPlugin, [Object.assign({ + slugify, + permalink: true, + permalinkBefore: true, + permalinkSymbol: '#' + }, anchor)]) + .end() + + .plugin('toc') + .use(tocPlugin, [Object.assign({ + slugify, + includeLevel: [2, 3], + format: parseHeaders + }, toc)]) + .end() + + if (lineNumbers) { + config + .plugin('line-numbers') + .use(lineNumbersPlugin) + } + + beforeInstantiate && beforeInstantiate(config) + + const md = config.toMd() + + afterInstantiate && afterInstantiate(md) + + module.exports.dataReturnable(md) + + // expose slugify + md.slugify = slugify + + return md +} + +module.exports.dataReturnable = function dataReturnable (md) { + // override render to allow custom plugins return data + const render = md.render + md.render = (...args) => { + md.__data = {} + const html = render.call(md, ...args) + return { + html, + data: md.__data + } + } +} diff --git a/lib/markdown/lineNumbers.js b/packages/@vuepress/markdown/lib/lineNumbers.js similarity index 100% rename from lib/markdown/lineNumbers.js rename to packages/@vuepress/markdown/lib/lineNumbers.js diff --git a/lib/markdown/link.js b/packages/@vuepress/markdown/lib/link.js similarity index 100% rename from lib/markdown/link.js rename to packages/@vuepress/markdown/lib/link.js diff --git a/lib/markdown/preWrapper.js b/packages/@vuepress/markdown/lib/preWrapper.js similarity index 100% rename from lib/markdown/preWrapper.js rename to packages/@vuepress/markdown/lib/preWrapper.js diff --git a/lib/markdown/slugify.js b/packages/@vuepress/markdown/lib/slugify.js similarity index 100% rename from lib/markdown/slugify.js rename to packages/@vuepress/markdown/lib/slugify.js diff --git a/lib/markdown/snippet.js b/packages/@vuepress/markdown/lib/snippet.js similarity index 96% rename from lib/markdown/snippet.js rename to packages/@vuepress/markdown/lib/snippet.js index a677e12f77..834ff496a1 100644 --- a/lib/markdown/snippet.js +++ b/packages/@vuepress/markdown/lib/snippet.js @@ -1,4 +1,4 @@ -const fs = require('fs') +const { fs } = require('@vuepress/shared-utils') module.exports = function snippet (md, options = {}) { const root = options.root || process.cwd() diff --git a/packages/@vuepress/markdown/package.json b/packages/@vuepress/markdown/package.json new file mode 100644 index 0000000000..5e9ff0d41c --- /dev/null +++ b/packages/@vuepress/markdown/package.json @@ -0,0 +1,37 @@ +{ + "name": "@vuepress/markdown", + "version": "1.0.0", + "description": "markdown for vuepress", + "main": "lib/index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator", + "markdown" + ], + "dependencies": { + "@vuepress/shared-utils": "^1.0.0", + "markdown-it": "^8.4.1", + "markdown-it-anchor": "^5.0.2", + "markdown-it-container": "^2.0.0", + "markdown-it-emoji": "^1.4.0", + "markdown-it-table-of-contents": "^0.4.0", + "markdown-it-chain": "^1.2.0", + "prismjs": "^1.13.0", + "diacritics": "^1.3.0" + }, + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/markdown#readme" +} diff --git a/packages/@vuepress/plugin-active-header-links/.npmignore b/packages/@vuepress/plugin-active-header-links/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-active-header-links/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-active-header-links/README.md b/packages/@vuepress/plugin-active-header-links/README.md new file mode 100644 index 0000000000..5063050105 --- /dev/null +++ b/packages/@vuepress/plugin-active-header-links/README.md @@ -0,0 +1,3 @@ +# @vuepress/plugin-active-header-links + +> active-header-links plugin for vuepress \ No newline at end of file diff --git a/packages/@vuepress/plugin-active-header-links/index.js b/packages/@vuepress/plugin-active-header-links/index.js new file mode 100644 index 0000000000..7831662368 --- /dev/null +++ b/packages/@vuepress/plugin-active-header-links/index.js @@ -0,0 +1,5 @@ +const path = require('path') + +module.exports = { + clientRootMixin: path.resolve(__dirname, 'mixin.js') +} diff --git a/lib/app/root-mixins/activeHeaderLinks.js b/packages/@vuepress/plugin-active-header-links/mixin.js similarity index 75% rename from lib/app/root-mixins/activeHeaderLinks.js rename to packages/@vuepress/plugin-active-header-links/mixin.js index 6cad69d5cc..4692f76afe 100644 --- a/lib/app/root-mixins/activeHeaderLinks.js +++ b/packages/@vuepress/plugin-active-header-links/mixin.js @@ -1,18 +1,20 @@ -import store from '@app/store' +import Vue from 'vue' import throttle from 'lodash.throttle' export default { mounted () { window.addEventListener('scroll', this.onScroll) }, + methods: { onScroll: throttle(function () { this.setActiveHash() }, 300), + setActiveHash () { const sidebarLinks = [].slice.call(document.querySelectorAll('.sidebar-link')) const anchors = [].slice.call(document.querySelectorAll('.header-anchor')) - .filter(anchor => sidebarLinks.some(sidebarLink => sidebarLink.hash === anchor.hash)) + .filter(anchor => sidebarLinks.some(sidebarLink => sidebarLink.hash === anchor.hash)) const scrollTop = Math.max( window.pageYOffset, @@ -25,15 +27,15 @@ export default { const nextAnchor = anchors[i + 1] const isActive = i === 0 && scrollTop === 0 || - (scrollTop >= anchor.parentElement.offsetTop + 10 && - (!nextAnchor || scrollTop < nextAnchor.parentElement.offsetTop - 10)) + (scrollTop >= anchor.parentElement.offsetTop + 10 && + (!nextAnchor || scrollTop < nextAnchor.parentElement.offsetTop - 10)) if (isActive && decodeURIComponent(this.$route.hash) !== decodeURIComponent(anchor.hash)) { - store.disableScrollBehavior = true + Vue.$store.set('disableScrollBehavior', true) this.$router.replace(decodeURIComponent(anchor.hash), () => { // execute after scrollBehavior handler. this.$nextTick(() => { - store.disableScrollBehavior = false + Vue.$store.set('disableScrollBehavior', false) }) }) return @@ -41,6 +43,7 @@ export default { } } }, + beforeDestroy () { window.removeEventListener('scroll', this.onScroll) } diff --git a/packages/@vuepress/plugin-active-header-links/package.json b/packages/@vuepress/plugin-active-header-links/package.json new file mode 100644 index 0000000000..e3f613584a --- /dev/null +++ b/packages/@vuepress/plugin-active-header-links/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vuepress/plugin-active-header-links", + "version": "1.0.0", + "description": "active-header-links plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "dependencies": { + "lodash.throttle": "^4.1.1" + }, + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-active-header-links#readme" +} \ No newline at end of file diff --git a/packages/@vuepress/plugin-back-to-top/.npmignore b/packages/@vuepress/plugin-back-to-top/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-back-to-top/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-back-to-top/BackToTop.vue b/packages/@vuepress/plugin-back-to-top/BackToTop.vue new file mode 100644 index 0000000000..35877254a0 --- /dev/null +++ b/packages/@vuepress/plugin-back-to-top/BackToTop.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/packages/@vuepress/plugin-back-to-top/README.md b/packages/@vuepress/plugin-back-to-top/README.md new file mode 100644 index 0000000000..9e8260ae87 --- /dev/null +++ b/packages/@vuepress/plugin-back-to-top/README.md @@ -0,0 +1,15 @@ +# @vuepress/plugin-back-to-top + +> back-to-top plugin for vuepress + +## Usage + +```javascript +module.exports = { + plugins: ['back-to-top'] +} +``` + +## Options + +No options for now. \ No newline at end of file diff --git a/packages/@vuepress/plugin-back-to-top/client.js b/packages/@vuepress/plugin-back-to-top/client.js new file mode 100644 index 0000000000..2770de013f --- /dev/null +++ b/packages/@vuepress/plugin-back-to-top/client.js @@ -0,0 +1,5 @@ +import BackToTop from './BackToTop.vue' + +export default ({ Vue }) => { + Vue.component('BackToTop', BackToTop) +} diff --git a/packages/@vuepress/plugin-back-to-top/index.js b/packages/@vuepress/plugin-back-to-top/index.js new file mode 100644 index 0000000000..ecfc0b64cf --- /dev/null +++ b/packages/@vuepress/plugin-back-to-top/index.js @@ -0,0 +1,9 @@ +const path = require('path') + +module.exports = (options, context) => ({ + enhanceAppFiles: [ + path.resolve(__dirname, 'client.js') + ], + + globalUIComponents: 'BackToTop' +}) diff --git a/packages/@vuepress/plugin-back-to-top/package.json b/packages/@vuepress/plugin-back-to-top/package.json new file mode 100644 index 0000000000..a402612ed1 --- /dev/null +++ b/packages/@vuepress/plugin-back-to-top/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vuepress/plugin-back-to-top", + "version": "1.0.0", + "description": "back-to-top plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/back-to-top#readme", + "dependencies": { + "lodash.debounce": "^4.0.8" + } +} \ No newline at end of file diff --git a/packages/@vuepress/plugin-blog/.npmignore b/packages/@vuepress/plugin-blog/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-blog/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-blog/README.md b/packages/@vuepress/plugin-blog/README.md new file mode 100644 index 0000000000..ed7755e68a --- /dev/null +++ b/packages/@vuepress/plugin-blog/README.md @@ -0,0 +1,3 @@ +# @vuepress/plugin-blog + +> theme plugin for vuepress diff --git a/packages/@vuepress/plugin-blog/clientPlugin.js b/packages/@vuepress/plugin-blog/clientPlugin.js new file mode 100644 index 0000000000..98b88de794 --- /dev/null +++ b/packages/@vuepress/plugin-blog/clientPlugin.js @@ -0,0 +1,63 @@ +import { findPageByKey } from '@app/util' +import tagMeta from '@dynamic/tag' +import categoryMeta from '@dynamic/category' + +class Classifiable { + constructor (metaMap, pages) { + this._metaMap = Object.assign({}, metaMap) + Object.keys(this._metaMap).forEach(name => { + const { pageKeys } = this._metaMap[name] + this._metaMap[name].posts = pageKeys.map(key => findPageByKey(pages, key)) + }) + } + + get length () { + return Object.keys(this._metaMap).length + } + + get map () { + return this._metaMap + } + + get list () { + return this.toArray() + } + + toArray () { + const tags = [] + Object.keys(this._metaMap).forEach(name => { + const { posts, path } = this._metaMap[name] + tags.push({ name, posts, path }) + }) + return tags + } + + getItemByName (name) { + return this._metaMap[name] + } +} + +export default ({ Vue }) => { + Vue.mixin({ + computed: { + $tags () { + const { pages } = this.$site + const tags = new Classifiable(tagMeta, pages) + return tags + }, + $tag () { + const tagName = this.$route.meta.tagName + return this.$tags.getItemByName(tagName) + }, + $categories () { + const { pages } = this.$site + const categories = new Classifiable(categoryMeta, pages) + return categories + }, + $category () { + const categoryName = this.$route.meta.categoryName + return this.$categories.getItemByName(categoryName) + } + } + }) +} diff --git a/packages/@vuepress/plugin-blog/index.js b/packages/@vuepress/plugin-blog/index.js new file mode 100644 index 0000000000..d09ddc6d2b --- /dev/null +++ b/packages/@vuepress/plugin-blog/index.js @@ -0,0 +1,163 @@ +const path = require('path') +const { datatypes: { isString }} = require('@vuepress/shared-utils') + +module.exports = (options, ctx) => { + const { layoutComponentMap } = ctx + const { + pageEnhancers = [], + categoryIndexPageUrl = '/category/', + tagIndexPageUrl = '/tag/' + } = options + + const isLayoutExists = name => layoutComponentMap[name] !== undefined + const getLayout = (name, fallback) => isLayoutExists(name) ? name : fallback + const isDirectChild = regularPath => path.parse(regularPath).dir === '/' + + const enhancers = [ + { + when: ({ regularPath }) => isDirectChild(regularPath), + frontmatter: { layout: getLayout('Page', 'Layout') }, + data: { type: 'page' } + }, + { + when: ({ regularPath }) => regularPath.startsWith('/category/'), + frontmatter: { layout: getLayout('Category', 'Page') } + }, + { + when: ({ regularPath }) => regularPath === categoryIndexPageUrl, + frontmatter: { layout: getLayout('Categories', 'Page') } + }, + { + when: ({ regularPath }) => regularPath.startsWith('/tag/'), + frontmatter: { layout: getLayout('Tag', 'Page') } + }, + { + when: ({ regularPath }) => regularPath === tagIndexPageUrl, + frontmatter: { layout: getLayout('Tags', 'Page') } + }, + { + when: ({ regularPath }) => regularPath === '/', + frontmatter: { layout: getLayout('Layout') } + }, + { + when: ({ regularPath }) => regularPath.startsWith('/_posts/'), + frontmatter: { + layout: getLayout('Post', 'Page'), + permalink: '/:year/:month/:day/:slug' + }, + data: { type: 'post' } + }, + ...pageEnhancers + ] + + return { + /** + * Modify page's metadata according to the habits of blog users. + */ + extendPageData (pageCtx) { + const { frontmatter: rawFrontmatter } = pageCtx + + enhancers.forEach(({ + when, + data = {}, + frontmatter = {} + }) => { + if (when(pageCtx)) { + Object.assign(rawFrontmatter, frontmatter) + Object.assign(pageCtx, data) + } + }) + }, + + /** + * Create tag page and category page. + */ + ready () { + const { pages } = ctx + const tagMap = {} + const categoryMap = {} + + const curryHandler = (scope, map) => (key, pageKey) => { + if (key) { + if (!map[key]) { + map[key] = {} + map[key].path = `/${scope}/${key}.html` + map[key].pageKeys = [] + } + map[key].pageKeys.push(pageKey) + } + } + + const handleTag = curryHandler('tag', tagMap) + const handleCategory = curryHandler('category', categoryMap) + + pages.forEach(({ + key, + frontmatter: { + tag, + tags, + category, + categories + } + }) => { + if (isString(tag)) { + handleTag(tag, key) + } + if (Array.isArray(tags)) { + tags.forEach(tag => handleTag(tag, key)) + } + if (isString(category)) { + handleCategory(categories, key) + } + if (Array.isArray(categories)) { + categories.forEach(category => handleCategory(category, key)) + } + }) + + ctx.tagMap = tagMap + ctx.categoryMap = categoryMap + + const extraPages = [ + { + permalink: tagIndexPageUrl, + frontmatter: { title: `Tags` } + }, + { + permalink: categoryIndexPageUrl, + frontmatter: { title: `Categories` } + }, + ...Object.keys(tagMap).map(tagName => ({ + permalink: tagMap[tagName].path, + meta: { tagName }, + frontmatter: { title: `${tagName} | Tag` } + })), + ...Object.keys(categoryMap).map(categoryName => ({ + permalink: categoryMap[categoryName].path, + meta: { categoryName }, + frontmatter: { title: `${categoryName} | Category` } + })) + ] + extraPages.forEach(page => ctx.addPage(page)) + }, + + /** + * Generate tag and category metadata. + */ + async clientDynamicModules () { + return [ + { + name: 'tag.js', + content: `export default ${JSON.stringify(ctx.tagMap, null, 2)}` + }, + { + name: 'category.js', + content: `export default ${JSON.stringify(ctx.categoryMap, null, 2)}` + } + ] + }, + + enhanceAppFiles: [ + path.resolve(__dirname, 'clientPlugin.js') + ] + } +} diff --git a/packages/@vuepress/plugin-blog/package.json b/packages/@vuepress/plugin-blog/package.json new file mode 100644 index 0000000000..503103e0a0 --- /dev/null +++ b/packages/@vuepress/plugin-blog/package.json @@ -0,0 +1,25 @@ +{ + "name": "@vuepress/plugin-blog", + "version": "1.0.0", + "description": "blog plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-blog#readme" +} diff --git a/packages/@vuepress/plugin-google-analytics/.npmignore b/packages/@vuepress/plugin-google-analytics/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-google-analytics/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-google-analytics/README.md b/packages/@vuepress/plugin-google-analytics/README.md new file mode 100644 index 0000000000..be05a08e8b --- /dev/null +++ b/packages/@vuepress/plugin-google-analytics/README.md @@ -0,0 +1,12 @@ +# @vuepress/plugin-google-analytics + +> google-analytics plugin for vuepress + +## Options + +### ga + +- Type: `string` +- Default: `/i18n/` + +Provide the Google Analytics ID to enable integration. diff --git a/packages/@vuepress/plugin-google-analytics/index.js b/packages/@vuepress/plugin-google-analytics/index.js new file mode 100644 index 0000000000..cb94b78ed8 --- /dev/null +++ b/packages/@vuepress/plugin-google-analytics/index.js @@ -0,0 +1,14 @@ +const path = require('path') + +module.exports = (options = {}, context) => ({ + define () { + const { siteConfig = {}} = context + const ga = options.ga || siteConfig.ga + const GA_ID = ga || false + return { GA_ID } + }, + + enhanceAppFiles: [ + path.resolve(__dirname, 'inject.js') + ] +}) diff --git a/packages/@vuepress/plugin-google-analytics/inject.js b/packages/@vuepress/plugin-google-analytics/inject.js new file mode 100644 index 0000000000..a2e1be9de4 --- /dev/null +++ b/packages/@vuepress/plugin-google-analytics/inject.js @@ -0,0 +1,28 @@ +/* global GA_ID, ga */ + +export default ({ router }) => { +// Google analytics integration + if (process.env.NODE_ENV === 'production' && GA_ID) { + (function (i, s, o, g, r, a, m) { + i['GoogleAnalyticsObject'] = r + i[r] = i[r] || function () { + (i[r].q = i[r].q || []).push(arguments) + } + i[r].l = 1 * new Date() + a = s.createElement(o) + m = s.getElementsByTagName(o)[0] + a.async = 1 + a.src = g + m.parentNode.insertBefore(a, m) + })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga') + + ga('create', GA_ID, 'auto') + ga('send', 'pageview') + + router.afterEach(function (to) { + ga('set', 'page', to.fullPath) + ga('send', 'pageview') + }) + } +} + diff --git a/packages/@vuepress/plugin-google-analytics/package.json b/packages/@vuepress/plugin-google-analytics/package.json new file mode 100644 index 0000000000..5763de58f3 --- /dev/null +++ b/packages/@vuepress/plugin-google-analytics/package.json @@ -0,0 +1,25 @@ +{ + "name": "@vuepress/plugin-google-analytics", + "version": "1.0.0", + "description": "google-analytics plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-google-analytics#readme" +} \ No newline at end of file diff --git a/packages/@vuepress/plugin-i18n-ui/.npmignore b/packages/@vuepress/plugin-i18n-ui/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-i18n-ui/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-i18n-ui/README.md b/packages/@vuepress/plugin-i18n-ui/README.md new file mode 100644 index 0000000000..f748c745b0 --- /dev/null +++ b/packages/@vuepress/plugin-i18n-ui/README.md @@ -0,0 +1,12 @@ +# @vuepress/plugin-i18n-ui + +> i18n-ui plugin for vuepress + +## Plugin Options + +### route + +- Type: `string` +- Default: `/i18n/` + +Path to the i18n ui page. \ No newline at end of file diff --git a/packages/@vuepress/plugin-i18n-ui/client.js b/packages/@vuepress/plugin-i18n-ui/client.js new file mode 100644 index 0000000000..41651afa7a --- /dev/null +++ b/packages/@vuepress/plugin-i18n-ui/client.js @@ -0,0 +1,7 @@ +import Layout from './index.vue' +// TODO move default theme styl to core. +import '@theme/styles/theme.styl' + +export default ({ Vue }) => { + Vue.component('I18nUILayout', Layout) +} diff --git a/packages/@vuepress/plugin-i18n-ui/index.js b/packages/@vuepress/plugin-i18n-ui/index.js new file mode 100644 index 0000000000..2639f197db --- /dev/null +++ b/packages/@vuepress/plugin-i18n-ui/index.js @@ -0,0 +1,21 @@ +const path = require('path') + +module.exports = (pluginOptions = {}, context) => ({ + name: 'i18n-ui', + + // This plugin will be enabled only at development mode. + enabled: !context.isProd, + + enhanceAppFiles: [ + path.resolve(__dirname, 'client.js') + ], + + additionalPages: [ + { + permalink: pluginOptions.permalink || '/i18n/', + frontmatter: { + 'layout': 'I18nUILayout' + } + } + ] +}) diff --git a/packages/@vuepress/plugin-i18n-ui/index.vue b/packages/@vuepress/plugin-i18n-ui/index.vue new file mode 100644 index 0000000000..185c00e8b2 --- /dev/null +++ b/packages/@vuepress/plugin-i18n-ui/index.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/packages/@vuepress/plugin-i18n-ui/package.json b/packages/@vuepress/plugin-i18n-ui/package.json new file mode 100644 index 0000000000..ed3407529b --- /dev/null +++ b/packages/@vuepress/plugin-i18n-ui/package.json @@ -0,0 +1,25 @@ +{ + "name": "@vuepress/plugin-i18n-ui", + "version": "1.0.0", + "description": "i18n-ui plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-i18n-ui#readme" +} \ No newline at end of file diff --git a/packages/@vuepress/plugin-last-updated/.npmignore b/packages/@vuepress/plugin-last-updated/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-last-updated/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-last-updated/README.md b/packages/@vuepress/plugin-last-updated/README.md new file mode 100644 index 0000000000..e05d30bf6f --- /dev/null +++ b/packages/@vuepress/plugin-last-updated/README.md @@ -0,0 +1,27 @@ +# @vuepress/plugin-last-updated + +> last-updated plugin for vuepress + +> Note that this plugin has been included in the core. + +## Options + +### transformer + +- Type: `function` +- Default: `undefined` + +By default, this plugin produces a 13-bit timestamp for each page, you can also pass in a transformer to convert it to any format that you want. + +``` javascript +const timeago = require("timeago.js"); + +module.exports = { + plugins: [ + [ + 'last-updated', + { transformer: timeago.format } + ] + ] +} +``` diff --git a/packages/@vuepress/plugin-last-updated/index.js b/packages/@vuepress/plugin-last-updated/index.js new file mode 100644 index 0000000000..2324244f30 --- /dev/null +++ b/packages/@vuepress/plugin-last-updated/index.js @@ -0,0 +1,14 @@ +const spawn = require('cross-spawn') + +module.exports = (options = {}, context) => ({ + extendPageData ({ _filePath }) { + const { transformer } = options + const timestamp = getGitLastUpdatedTimeStamp(_filePath) + const lastUpdated = typeof transformer === 'function' ? transformer(timestamp) : timestamp + return { lastUpdated } + } +}) + +function getGitLastUpdatedTimeStamp (filePath) { + return parseInt(spawn.sync('git', ['log', '-1', '--format=%ct', filePath]).stdout.toString('utf-8')) * 1000 +} diff --git a/packages/@vuepress/plugin-last-updated/package.json b/packages/@vuepress/plugin-last-updated/package.json new file mode 100644 index 0000000000..ff57f71051 --- /dev/null +++ b/packages/@vuepress/plugin-last-updated/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vuepress/plugin-last-updated", + "version": "1.0.0", + "description": "last-updated plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-last-updated#readme", + "dependencies": { + "cross-spawn": "^6.0.5" + } +} \ No newline at end of file diff --git a/packages/@vuepress/plugin-medium-zoom/.npmignore b/packages/@vuepress/plugin-medium-zoom/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-medium-zoom/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-medium-zoom/README.md b/packages/@vuepress/plugin-medium-zoom/README.md new file mode 100644 index 0000000000..89e7bf7a23 --- /dev/null +++ b/packages/@vuepress/plugin-medium-zoom/README.md @@ -0,0 +1,3 @@ +# @vuepress/plugin-medium-zoom + +> medium-zoom plugin for vuepress \ No newline at end of file diff --git a/packages/@vuepress/plugin-medium-zoom/index.js b/packages/@vuepress/plugin-medium-zoom/index.js new file mode 100644 index 0000000000..7831662368 --- /dev/null +++ b/packages/@vuepress/plugin-medium-zoom/index.js @@ -0,0 +1,5 @@ +const path = require('path') + +module.exports = { + clientRootMixin: path.resolve(__dirname, 'mixin.js') +} diff --git a/packages/@vuepress/plugin-medium-zoom/mixin.js b/packages/@vuepress/plugin-medium-zoom/mixin.js new file mode 100644 index 0000000000..24369a681c --- /dev/null +++ b/packages/@vuepress/plugin-medium-zoom/mixin.js @@ -0,0 +1,10 @@ +import './style.css' +import zoom from 'medium-zoom' + +export default { + mounted () { + setTimeout(() => { + zoom('.content img') + }, 1000) + } +} diff --git a/packages/@vuepress/plugin-medium-zoom/package.json b/packages/@vuepress/plugin-medium-zoom/package.json new file mode 100644 index 0000000000..2c28d99bf8 --- /dev/null +++ b/packages/@vuepress/plugin-medium-zoom/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vuepress/plugin-medium-zoom", + "version": "1.0.0", + "description": "medium-zoom plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "dependencies": { + "medium-zoom": "^0.4.0" + }, + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-medium-zoom#readme" +} diff --git a/packages/@vuepress/plugin-medium-zoom/style.css b/packages/@vuepress/plugin-medium-zoom/style.css new file mode 100644 index 0000000000..50437531a8 --- /dev/null +++ b/packages/@vuepress/plugin-medium-zoom/style.css @@ -0,0 +1,7 @@ +.medium-zoom-overlay { + z-index: 100; +} + +.medium-zoom-overlay ~ img { + z-index: 101; +} diff --git a/packages/@vuepress/plugin-pagination/.npmignore b/packages/@vuepress/plugin-pagination/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-pagination/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-pagination/README.md b/packages/@vuepress/plugin-pagination/README.md new file mode 100644 index 0000000000..a4eabe695e --- /dev/null +++ b/packages/@vuepress/plugin-pagination/README.md @@ -0,0 +1,4 @@ +# @vuepress/plugin-pagination + +> pagination plugin for vuepress + diff --git a/packages/@vuepress/plugin-pagination/clientPlugin.js b/packages/@vuepress/plugin-pagination/clientPlugin.js new file mode 100644 index 0000000000..f61fcbf7b0 --- /dev/null +++ b/packages/@vuepress/plugin-pagination/clientPlugin.js @@ -0,0 +1,71 @@ +import paginationMeta from '@dynamic/pagination' + +class Pagination { + constructor (pagination, { pages, route }) { + let { postsFilter, postsSorter } = pagination + + /* eslint-disable no-eval */ + postsFilter = eval(postsFilter) + postsSorter = eval(postsSorter) + + const { path } = route + const { paginationPages } = pagination + + paginationPages.forEach((page, index) => { + if (page.path === path) { + this.paginationIndex = index + } + }) + + if (!this.paginationIndex) { + this.paginationIndex = 0 + } + + this._paginationPages = paginationPages + this._currentPage = paginationPages[this.paginationIndex] + this._posts = pages.filter(postsFilter).sort(postsSorter) + } + + get length () { + return this._paginationPages.length + } + + get posts () { + const [start, end] = this._currentPage.interval + return this._posts.slice(start, end) + } + + get hasPrev () { + return this.paginationIndex !== 0 + } + + get prevLink () { + if (this.hasPrev) { + return this._paginationPages[this.paginationIndex - 1].path + } + } + + get hasNext () { + return this.paginationIndex !== this.length - 1 + } + + get nextLink () { + if (this.hasNext) { + return this._paginationPages[this.paginationIndex + 1].path + } + } +} + +export default ({ Vue }) => { + Vue.mixin({ + computed: { + $pagination () { + const { pages } = this.$site + const pagination = new Pagination(paginationMeta, { + pages, route: this.$route + }) + return pagination + } + } + }) +} diff --git a/packages/@vuepress/plugin-pagination/index.js b/packages/@vuepress/plugin-pagination/index.js new file mode 100644 index 0000000000..407ff4a904 --- /dev/null +++ b/packages/@vuepress/plugin-pagination/index.js @@ -0,0 +1,69 @@ +const path = require('path') + +function getIntervallers (max, interval) { + const count = Math.floor(max / interval) + const arr = [...Array(count + 1)] + return arr.map((v, index) => { + const start = index * interval + const end = (index + 1) * interval - 1 + return [start, end > max ? max : end] + }) +} + +module.exports = (options, ctx) => ({ + enhanceAppFiles: [ + path.resolve(__dirname, 'clientPlugin.js') + ], + + ready () { + let { postsFilter, postsSorter } = options + postsFilter = postsFilter || (({ type }) => type === 'post') + postsSorter = postsSorter || ((prev, next) => { + const prevTime = new Date(prev.frontmatter.date).getTime() + const nextTime = new Date(next.frontmatter.date).getTime() + return prevTime - nextTime > 0 ? -1 : 1 + }) + + const { pages } = ctx + const posts = pages.filter(postsFilter) + const { + perPagePosts = 10, + paginationDir = 'page', + firstPagePath = '/', + layout = 'Layout' + } = options + + const intervallers = getIntervallers(posts.length, perPagePosts) + const pagination = { + paginationPages: intervallers.map((interval, index) => { + const path = index === 0 + ? firstPagePath + : `/${paginationDir}/${index + 1}/` + return { path, interval } + }), + postsFilter: postsFilter.toString(), + postsSorter: postsSorter.toString() + } + + ctx.pagination = pagination + pagination.paginationPages.forEach(({ path }, index) => { + if (path === '/') { + return + } + ctx.addPage({ + permalink: path, + frontmatter: { + layout, + title: `Page ${index + 1}` + } + }) + }) + }, + + async clientDynamicModules () { + return { + name: 'pagination.js', + content: `export default ${JSON.stringify(ctx.pagination, null, 2)}` + } + } +}) diff --git a/packages/@vuepress/plugin-pagination/package.json b/packages/@vuepress/plugin-pagination/package.json new file mode 100644 index 0000000000..926150129f --- /dev/null +++ b/packages/@vuepress/plugin-pagination/package.json @@ -0,0 +1,25 @@ +{ + "name": "@vuepress/plugin-pagination", + "version": "1.0.0", + "description": "pagination plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-pagination#readme" +} \ No newline at end of file diff --git a/packages/@vuepress/plugin-pwa/.npmignore b/packages/@vuepress/plugin-pwa/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-pwa/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-pwa/README.md b/packages/@vuepress/plugin-pwa/README.md new file mode 100644 index 0000000000..9939adfad4 --- /dev/null +++ b/packages/@vuepress/plugin-pwa/README.md @@ -0,0 +1,199 @@ +# @vuepress/plugin-pwa + +> PWA plugin for vuepress + +## Options + +### serviceWorker + +- Type: `boolean` +- Default: `true` + +If set to `true`, VuePress will automatically generate and register a service worker that caches the content for offline use (only enabled in production). + +### updatePopup + +- Type: `boolean|popupConfig` +- Default: `undefined` + +```typescript +interface normalPopupConfig { + message: string; // defaults to 'New content is available.' + buttonText: string; // defaults to 'Refresh' +} + +interface localedPopupConfig { + [localePath: string]: normalPopupConfig +} + +type popupConfig = normalPopupConfig | localedPopupConfig +``` + +This option enables the popup to refresh contents. The popup will be shown when the site is updated (i.e. service worker is updated). It provides `refresh` button to allow users to refresh contents immediately. + +> If without the `refresh` button, the new service worker will be active after all [clients](https://developer.mozilla.org/en-US/docs/Web/API/Clients) are closed. This means that visitors cannot see new contents until they close all tabs of your site. But the `refresh` button activates the new service worker immediately. + +### popupComponent + +- Type: `string` +- Default: `undefined` + +A custom component to replace the default popup component. + +**Also see:** + +- [Customize the SW-Update Popup UI](#customize-the-sw-update-popup-ui) + +## Migration from 0.x.x + +Now that we have plugin API, all features' options that are in plugin's areas will become plugin options. + +### Service Worker + +``` diff +module.exports = { +- serviceWorker: true, ++ plugins: ['@vuepress/pwa'] +} +``` + +### SW-Update Popup + +``` diff +module.exports = { + themeConfig: { +- serviceWorker: { +- updatePopup: { +- message: "New content is available.", +- buttonText: "Refresh" +- } +- } + }, ++ plugins: { ++ '@vuepress/pwa': { ++ serviceWorker: true, ++ updatePopup: { ++ message: "New content is available.", ++ buttonText: "Refresh" ++ } ++ } ++ } +} +``` + +For i18n user: + +``` diff +module.exports = { + themeConfig: { + '/': { +- serviceWorker: { +- updatePopup: { +- message: "New content is available.", +- buttonText: "Refresh" +- } +- } + }, + '/zh/': { +- serviceWorker: { +- updatePopup: { +- message: "发现新内容可用", +- buttonText: "刷新" +- } +- } + } + }, ++ plugins: { ++ '@vuepress/pwa': { ++ serviceWorker: true, ++ updatePopup: { ++ '/': { ++ message: "New content is available.", ++ buttonText: "Refresh" ++ }, ++ '/zh/': { ++ message: "发现新内容可用", ++ buttonText: "刷新" ++ } ++ } ++ } ++ } +``` + +It's worth mentioning that the PWA plugin has above i18n built in, so if you want to use the default i18n directly, you can abbreviate the above configuration as: + +```js +module.exports = { + plugins: { + '@vuepress/pwa': { + serviceWorker: true, + updatePopup: true + } + } +} +``` + +Feel free to submit PRs to improve the default [i18n configuration](https://github.com/vuejs/vuepress/blob/next/packages/%40vuepress/plugin-pwa/lib/i18n.js). + +## Customize the SW-Update Popup UI + +The default sw-update popup component provides a default slot which gives you the ability to fully control the appearence of the popup. + +First, you need to create a global component (e.g. `MySWUpdatePopup`) at `.vuepress/components`. A simple component created based on the default component is as follows: + +```vue + + + + + +``` + +Then, update your plugin: + +``` diff +module.exports = { + plugins: { + '@vuepress/pwa': { + serviceWorker: true, ++ popupComponent: 'MySWUpdatePopup', + updatePopup: true + } + } +} +``` + +**Also see:** + +- [VuePress > Using Components](https://vuepress.vuejs.org/guide/using-vue.html#using-components) +- [Vue > Scoped Slots](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots) diff --git a/packages/@vuepress/plugin-pwa/index.js b/packages/@vuepress/plugin-pwa/index.js new file mode 100644 index 0000000000..a7d17140b7 --- /dev/null +++ b/packages/@vuepress/plugin-pwa/index.js @@ -0,0 +1,49 @@ +const path = require('path') +const { logger, fs } = require('@vuepress/shared-utils') + +module.exports = (options, context) => ({ + ready () { + options = Object.assign({ + serviceWorker: true + }, options) + }, + + define () { + const { serviceWorker, updatePopup } = options + const base = context.base || '/' + return { + SW_BASE_URL: base, + SW_ENABLED: !!serviceWorker, + SW_UPDATE_POPUP: updatePopup || false + } + }, + + // TODO support components option + // components: [ + // { name: 'SWUpdatePopup', path: path.resolve(__dirname, 'lib/SWUpdatePopup.vue') } + // ], + + globalUIComponents: options.popupComponent || 'SWUpdatePopup', + + enhanceAppFiles: [path.resolve(__dirname, 'lib/inject.js')], + + async generated () { + const { serviceWorker } = options + const { outDir } = context + const swFilePath = path.resolve(outDir, 'service-worker.js') + if (serviceWorker) { + logger.wait('\nGenerating service worker...') + const wbb = require('workbox-build') + await wbb.generateSW({ + swDest: swFilePath, + globDirectory: outDir, + globPatterns: ['**\/*.{js,css,html,png,jpg,jpeg,gif,svg,woff,woff2,eot,ttf,otf}'] + }) + await fs.writeFile( + swFilePath, + await fs.readFile(path.resolve(__dirname, 'lib/skip-waiting.js'), 'utf8'), + { flag: 'a' } + ) + } + } +}) diff --git a/lib/app/SWUpdateEvent.js b/packages/@vuepress/plugin-pwa/lib/SWUpdateEvent.js similarity index 100% rename from lib/app/SWUpdateEvent.js rename to packages/@vuepress/plugin-pwa/lib/SWUpdateEvent.js diff --git a/packages/@vuepress/plugin-pwa/lib/SWUpdatePopup.vue b/packages/@vuepress/plugin-pwa/lib/SWUpdatePopup.vue new file mode 100644 index 0000000000..f7d04aa716 --- /dev/null +++ b/packages/@vuepress/plugin-pwa/lib/SWUpdatePopup.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/packages/@vuepress/plugin-pwa/lib/event.js b/packages/@vuepress/plugin-pwa/lib/event.js new file mode 100644 index 0000000000..8853b4d295 --- /dev/null +++ b/packages/@vuepress/plugin-pwa/lib/event.js @@ -0,0 +1,3 @@ +import Vue from 'vue' + +export default new Vue() diff --git a/packages/@vuepress/plugin-pwa/lib/i18n.js b/packages/@vuepress/plugin-pwa/lib/i18n.js new file mode 100644 index 0000000000..adc33bbc8e --- /dev/null +++ b/packages/@vuepress/plugin-pwa/lib/i18n.js @@ -0,0 +1,10 @@ +export const popupConfig = { + '/': { + message: 'New content is available.', + buttonText: 'Refresh' + }, + '/zh/': { + message: '发现新内容可用', + buttonText: '刷新' + } +} diff --git a/packages/@vuepress/plugin-pwa/lib/inject.js b/packages/@vuepress/plugin-pwa/lib/inject.js new file mode 100644 index 0000000000..44e0f98f0f --- /dev/null +++ b/packages/@vuepress/plugin-pwa/lib/inject.js @@ -0,0 +1,52 @@ +/* global SW_BASE_URL, SW_ENABLED, GA_ID, ga, SW_UPDATE_POPUP */ + +import Vue from 'vue' +import { register } from 'register-service-worker' +import SWUpdateEvent from './SWUpdateEvent' +import event from './event' + +if (SW_UPDATE_POPUP) { + Vue.component('SWUpdatePopup', () => import('./SWUpdatePopup.vue')) +} + +export default ({ router, isServer }) => { + // Register service worker + router.onReady(() => { + if (process.env.NODE_ENV === 'production' && + !isServer && + SW_ENABLED) { + register(`${SW_BASE_URL}service-worker.js`, { + ready () { + console.log('[vuepress:sw] Service worker is active.') + event.$emit('sw-ready') + }, + + cached (registration) { + console.log('[vuepress:sw] Content has been cached for offline use.') + event.$emit('sw-cached', new SWUpdateEvent(registration)) + }, + + updated (registration) { + console.log('[vuepress:sw] Content updated.') + event.$emit('sw-updated', new SWUpdateEvent(registration)) + }, + + offline () { + console.log('[vuepress:sw] No internet connection found. App is running in offline mode.') + event.$emit('sw-offline') + }, + + error (err) { + console.error('[vuepress:sw] Error during service worker registration:', err) + event.$emit('sw-error', err) + if (GA_ID) { + ga('send', 'exception', { + exDescription: err.message, + exFatal: false + }) + } + } + }) + } + }) +} diff --git a/lib/service-worker/skip-waiting.js b/packages/@vuepress/plugin-pwa/lib/skip-waiting.js similarity index 100% rename from lib/service-worker/skip-waiting.js rename to packages/@vuepress/plugin-pwa/lib/skip-waiting.js diff --git a/packages/@vuepress/plugin-pwa/package.json b/packages/@vuepress/plugin-pwa/package.json new file mode 100644 index 0000000000..c78734afad --- /dev/null +++ b/packages/@vuepress/plugin-pwa/package.json @@ -0,0 +1,30 @@ +{ + "name": "@vuepress/plugin-pwa", + "version": "1.0.0", + "description": "pwa plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "dependencies": { + "register-service-worker": "^1.5.2", + "@vuepress/shared-utils": "^1.0.0", + "workbox-build": "^3.1.0" + }, + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-pwa#readme" +} \ No newline at end of file diff --git a/packages/@vuepress/plugin-register-components/.npmignore b/packages/@vuepress/plugin-register-components/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-register-components/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-register-components/README.md b/packages/@vuepress/plugin-register-components/README.md new file mode 100644 index 0000000000..a89d595a27 --- /dev/null +++ b/packages/@vuepress/plugin-register-components/README.md @@ -0,0 +1,54 @@ +# @vuepress/plugin-register-components + +> register-components plugin for vuepress + +## Plugin Options + +### componentsDir + +- Type: `Array | String` +- Default: `[]` + +All components in this directory will be registered as global components, naming of components will follow the components found in [.vuepress/components](https://vuepress.vuejs.org/guide/using-vue.html#using-components). + +- Usage: + +``` js +module.exports = { + plugins: [ + [ + 'register-components', + { + componentDir: somepath + } + ] + ] +} +``` + +### components + +- Type: `{ name: string, path: string }` +- Default: `[]` + +Register global components by explicit name and path. + +- Usage: + +``` js +module.exports = { + plugins: [ + [ + 'register-components', + { + components: [ + { + name: 'V-Card', + path: 'path/to/card.vue' + } + ] + } + ] + ] +} +``` \ No newline at end of file diff --git a/packages/@vuepress/plugin-register-components/index.js b/packages/@vuepress/plugin-register-components/index.js new file mode 100644 index 0000000000..3496d86a4d --- /dev/null +++ b/packages/@vuepress/plugin-register-components/index.js @@ -0,0 +1,60 @@ +const { fs, globby } = require('@vuepress/shared-utils') +const path = require('path') + +function fileToComponentName (file) { + return file + .replace(/\/|\\/g, '-') + .replace(/\.vue$/, '') +} + +async function resolveComponents (componentDir) { + if (!fs.existsSync(componentDir)) { + return + } + return (await globby(['**/*.vue'], { cwd: componentDir })) +} + +// Since this plugin can ben used by multiple times, we need to +// give each generated files a uid or the previous file would be +// overwritten. +let moduleId = 0 + +module.exports = (options, context) => ({ + multiple: true, + + async enhanceAppFiles () { + const { componentsDir = [], components = [] } = options + const baseDirs = Array.isArray(componentsDir) ? componentsDir : [componentsDir] + + function importCode (name, absolutePath) { + return `Vue.component(${JSON.stringify(name)}, () => import(${JSON.stringify(absolutePath)}))` + } + + function genImport (baseDir, file) { + const name = fileToComponentName(file) + const absolutePath = path.resolve(baseDir, file) + const code = importCode(name, absolutePath) + return code + } + + let code = '' + + // 1. Register components in specified directories + for (const baseDir of baseDirs) { + const files = await resolveComponents(baseDir) || [] + code += files.map(file => genImport(baseDir, file)).join('\n') + '\n' + } + + // 2. Register named components. + code += components.map(({ name, path: absolutePath }) => importCode(name, absolutePath)) + + code = `import Vue from 'vue'\n` + code + '\n' + + return [ + { + name: `global-components-${++moduleId}.js`, + content: code + } + ] + } +}) diff --git a/packages/@vuepress/plugin-register-components/package.json b/packages/@vuepress/plugin-register-components/package.json new file mode 100644 index 0000000000..766a22a6f8 --- /dev/null +++ b/packages/@vuepress/plugin-register-components/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vuepress/plugin-register-components", + "version": "1.0.0", + "description": "register-global-components plugin for vuepress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-register-components#readme", + "dependencies": { + "@vuepress/shared-utils": "^1.0.0" + } +} \ No newline at end of file diff --git a/packages/@vuepress/plugin-search/.npmignore b/packages/@vuepress/plugin-search/.npmignore new file mode 100644 index 0000000000..18f0a334a4 --- /dev/null +++ b/packages/@vuepress/plugin-search/.npmignore @@ -0,0 +1,2 @@ +__tests__ +__mocks__ \ No newline at end of file diff --git a/packages/@vuepress/plugin-search/README.md b/packages/@vuepress/plugin-search/README.md new file mode 100644 index 0000000000..98f77fcfb5 --- /dev/null +++ b/packages/@vuepress/plugin-search/README.md @@ -0,0 +1,42 @@ +# @vuepress/plugin-search + +> header-based search plugin for vuepress + +## Usage + +1. Enable this plugin: + +```js +// .vuepress/config.js or themedir/index.js + +module.exports = { + plugins: [ + ['@vuepress/search', { + searchMaxSuggestions: 10 + }] + ], + // Tweak the default color via palette. + palette: { + $accentColor: '#b58900', + $textColor: '#586e75', + $borderColor: '#eaecef', + $codeBgColor: '#282c34', + $arrowBgColor: '#ccc' + } +} +``` + +2. Using search component: + +```vue +import SearchBox from '@SearchBox' +``` + +## Options + +### searchMaxSuggestions + +- Type: `number` +- Default: `true` + +Set the maximum number of results for search diff --git a/lib/default-theme/SearchBox.vue b/packages/@vuepress/plugin-search/SearchBox.vue similarity index 96% rename from lib/default-theme/SearchBox.vue rename to packages/@vuepress/plugin-search/SearchBox.vue index 9860855244..76016dd96a 100644 --- a/lib/default-theme/SearchBox.vue +++ b/packages/@vuepress/plugin-search/SearchBox.vue @@ -36,6 +36,7 @@ diff --git a/lib/default-theme/Layout.vue b/packages/@vuepress/theme-default/layouts/Layout.vue similarity index 86% rename from lib/default-theme/Layout.vue rename to packages/@vuepress/theme-default/layouts/Layout.vue index ee2e6abedb..0ab5760482 100644 --- a/lib/default-theme/Layout.vue +++ b/packages/@vuepress/theme-default/layouts/Layout.vue @@ -51,28 +51,24 @@ slot="bottom" /> - - - + diff --git a/lib/default-theme/util.js b/packages/@vuepress/theme-default/layouts/util.js similarity index 86% rename from lib/default-theme/util.js rename to packages/@vuepress/theme-default/layouts/util.js index ef95bea552..01937e9888 100644 --- a/lib/default-theme/util.js +++ b/packages/@vuepress/theme-default/layouts/util.js @@ -59,10 +59,10 @@ export function resolvePage (pages, rawPath, base) { } const path = normalize(rawPath) for (let i = 0; i < pages.length; i++) { - if (normalize(pages[i].path) === path) { + if (normalize(pages[i].regularPath) === path) { return Object.assign({}, pages[i], { type: 'page', - path: ensureExt(rawPath) + path: ensureExt(pages[i].path) }) } } @@ -108,7 +108,14 @@ function resolvePath (relative, base, append) { return stack.join('/') } -export function resolveSidebarItems (page, route, site, localePath) { +/** + * @param { Page } page + * @param { string } regularPath + * @param { SiteData } site + * @param { string } localePath + * @returns { SidebarGroup } + */ +export function resolveSidebarItems (page, regularPath, site, localePath) { const { pages, themeConfig } = site const localeConfig = localePath && themeConfig.locales @@ -124,13 +131,17 @@ export function resolveSidebarItems (page, route, site, localePath) { if (!sidebarConfig) { return [] } else { - const { base, config } = resolveMatchingConfig(route, sidebarConfig) + const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig) return config ? config.map(item => resolveItem(item, pages, base)) : [] } } +/** + * @param { Page } page + * @returns { SidebarGroup } + */ function resolveHeaders (page) { const headers = groupHeaders(page.headers || []) return [{ @@ -167,7 +178,12 @@ export function resolveNavLinkItem (linkItem) { }) } -export function resolveMatchingConfig (route, config) { +/** + * @param { Route } route + * @param { Array | Array | [link: string]: SidebarConfig } config + * @returns { base: string, config: SidebarConfig } + */ +export function resolveMatchingConfig (regularPath, config) { if (Array.isArray(config)) { return { base: '/', @@ -175,7 +191,7 @@ export function resolveMatchingConfig (route, config) { } } for (const base in config) { - if (ensureEndingSlash(route.path).indexOf(base) === 0) { + if (ensureEndingSlash(regularPath).indexOf(base) === 0) { return { base, config: config[base] diff --git a/packages/@vuepress/theme-default/noopModule.js b/packages/@vuepress/theme-default/noopModule.js new file mode 100644 index 0000000000..b1c6ea436a --- /dev/null +++ b/packages/@vuepress/theme-default/noopModule.js @@ -0,0 +1 @@ +export default {} diff --git a/packages/@vuepress/theme-default/package.json b/packages/@vuepress/theme-default/package.json new file mode 100644 index 0000000000..f5676fa02d --- /dev/null +++ b/packages/@vuepress/theme-default/package.json @@ -0,0 +1,33 @@ +{ + "name": "@vuepress/theme-default", + "version": "1.0.0", + "description": "Default theme for VuePress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "vuepress", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/theme-default#readme", + "dependencies": { + "@vuepress/plugin-active-header-links": "1.0.0", + "@vuepress/plugin-search": "1.0.0", + "docsearch.js": "^2.5.2", + "nprogress": "^0.2.0", + "stylus": "^0.54.5", + "stylus-loader": "^3.0.2" + } +} diff --git a/packages/@vuepress/theme-default/plugin.js b/packages/@vuepress/theme-default/plugin.js new file mode 100644 index 0000000000..1aee3b5bbf --- /dev/null +++ b/packages/@vuepress/theme-default/plugin.js @@ -0,0 +1,23 @@ +const path = require('path') + +module.exports = (options, context) => ({ + name: 'default-theme', + + chainWebpack (config, isServer) { + const { themeConfig, siteConfig } = context + + // resolve algolia + const isAlgoliaSearch = ( + themeConfig.algolia || + Object.keys(siteConfig.locales && themeConfig.locales || {}) + .some(base => themeConfig.locales[base].algolia) + ) + + config.resolve + .alias + .set('@default-theme', path.resolve('.')) + .set('@AlgoliaSearchBox', isAlgoliaSearch + ? path.resolve(__dirname, 'src/AlgoliaSearchBox.vue') + : path.resolve(__dirname, 'noopModule.js')) + } +}) diff --git a/lib/default-theme/styles/arrow.styl b/packages/@vuepress/theme-default/styles/arrow.styl similarity index 100% rename from lib/default-theme/styles/arrow.styl rename to packages/@vuepress/theme-default/styles/arrow.styl diff --git a/lib/default-theme/styles/code.styl b/packages/@vuepress/theme-default/styles/code.styl similarity index 98% rename from lib/default-theme/styles/code.styl rename to packages/@vuepress/theme-default/styles/code.styl index 8383c6e3d7..f032225e5f 100644 --- a/lib/default-theme/styles/code.styl +++ b/packages/@vuepress/theme-default/styles/code.styl @@ -1,4 +1,4 @@ -@require './config' +@import '~@app/style/config' .content code diff --git a/lib/default-theme/styles/custom-blocks.styl b/packages/@vuepress/theme-default/styles/custom-blocks.styl similarity index 100% rename from lib/default-theme/styles/custom-blocks.styl rename to packages/@vuepress/theme-default/styles/custom-blocks.styl diff --git a/lib/default-theme/styles/mobile.styl b/packages/@vuepress/theme-default/styles/mobile.styl similarity index 100% rename from lib/default-theme/styles/mobile.styl rename to packages/@vuepress/theme-default/styles/mobile.styl diff --git a/lib/default-theme/styles/nprogress.styl b/packages/@vuepress/theme-default/styles/nprogress.styl similarity index 100% rename from lib/default-theme/styles/nprogress.styl rename to packages/@vuepress/theme-default/styles/nprogress.styl diff --git a/lib/default-theme/styles/theme.styl b/packages/@vuepress/theme-default/styles/theme.styl similarity index 98% rename from lib/default-theme/styles/theme.styl rename to packages/@vuepress/theme-default/styles/theme.styl index a733861fa6..dfc58636db 100644 --- a/lib/default-theme/styles/theme.styl +++ b/packages/@vuepress/theme-default/styles/theme.styl @@ -1,4 +1,4 @@ -@require './config' +@require '~@app/style/config' @require './nprogress' @require './code' @require './custom-blocks' @@ -187,4 +187,4 @@ th, td .page padding-left 0 -@require './mobile.styl' +@require 'mobile.styl' diff --git a/lib/default-theme/styles/toc.styl b/packages/@vuepress/theme-default/styles/toc.styl similarity index 100% rename from lib/default-theme/styles/toc.styl rename to packages/@vuepress/theme-default/styles/toc.styl diff --git a/lib/default-theme/styles/wrapper.styl b/packages/@vuepress/theme-default/styles/wrapper.styl similarity index 100% rename from lib/default-theme/styles/wrapper.styl rename to packages/@vuepress/theme-default/styles/wrapper.styl diff --git a/docs/.vuepress/components/Bit.vue b/packages/docs/docs/.vuepress/components/Bit.vue similarity index 100% rename from docs/.vuepress/components/Bit.vue rename to packages/docs/docs/.vuepress/components/Bit.vue diff --git a/docs/.vuepress/components/Foo/Bar.vue b/packages/docs/docs/.vuepress/components/Foo/Bar.vue similarity index 100% rename from docs/.vuepress/components/Foo/Bar.vue rename to packages/docs/docs/.vuepress/components/Foo/Bar.vue diff --git a/docs/.vuepress/components/OtherComponent.vue b/packages/docs/docs/.vuepress/components/OtherComponent.vue similarity index 100% rename from docs/.vuepress/components/OtherComponent.vue rename to packages/docs/docs/.vuepress/components/OtherComponent.vue diff --git a/docs/.vuepress/components/demo-1.vue b/packages/docs/docs/.vuepress/components/demo-1.vue similarity index 100% rename from docs/.vuepress/components/demo-1.vue rename to packages/docs/docs/.vuepress/components/demo-1.vue diff --git a/docs/.vuepress/config.js b/packages/docs/docs/.vuepress/config.js similarity index 85% rename from docs/.vuepress/config.js rename to packages/docs/docs/.vuepress/config.js index f56003051c..346ff55bc2 100644 --- a/docs/.vuepress/config.js +++ b/packages/docs/docs/.vuepress/config.js @@ -1,5 +1,5 @@ module.exports = { - dest: 'vuepress', + dest: '../../vuepress', locales: { '/': { lang: 'en-US', @@ -23,29 +23,21 @@ module.exports = { ['meta', { name: 'msapplication-TileImage', content: '/icons/msapplication-icon-144x144.png' }], ['meta', { name: 'msapplication-TileColor', content: '#000000' }] ], - serviceWorker: true, - theme: 'vue', themeConfig: { repo: 'vuejs/vuepress', editLinks: true, docsDir: 'docs', // #697 Provided by the official algolia team. - algolia: { - apiKey: '3a539aab83105f01761a137c61004d85', - indexName: 'vuepress' - }, + // algolia: { + // apiKey: '3a539aab83105f01761a137c61004d85', + // indexName: 'vuepress' + // }, locales: { '/': { label: 'English', selectText: 'Languages', editLinkText: 'Edit this page on GitHub', lastUpdated: 'Last Updated', - serviceWorker: { - updatePopup: { - message: "New content is available.", - buttonText: "Refresh" - } - }, nav: [ { text: 'Guide', @@ -55,6 +47,14 @@ module.exports = { text: 'Config Reference', link: '/config/' }, + { + text: 'Plugin', + link: '/plugin/' + }, + { + text: 'Theme', + link: '/theme/' + }, { text: 'Default Theme Config', link: '/default-theme-config/' @@ -73,12 +73,6 @@ module.exports = { selectText: '选择语言', editLinkText: '在 GitHub 上编辑此页', lastUpdated: '上次更新', - serviceWorker: { - updatePopup: { - message: "发现新内容可用", - buttonText: "刷新" - } - }, nav: [ { text: '指南', @@ -102,6 +96,15 @@ module.exports = { } } } + }, + plugins: { + '@vuepress/i18n-ui': true, + '@vuepress/back-to-top': true, + '@vuepress/pwa': { + serviceWorker: true, + updatePopup: true + }, + '@vuepress/plugin-medium-zoom': true } } diff --git a/docs/.vuepress/public/logo.png b/packages/docs/docs/.vuepress/images/logo.png similarity index 100% rename from docs/.vuepress/public/logo.png rename to packages/docs/docs/.vuepress/images/logo.png diff --git a/docs/.vuepress/public/hero.png b/packages/docs/docs/.vuepress/public/hero.png similarity index 100% rename from docs/.vuepress/public/hero.png rename to packages/docs/docs/.vuepress/public/hero.png diff --git a/docs/.vuepress/public/icons/android-chrome-192x192.png b/packages/docs/docs/.vuepress/public/icons/android-chrome-192x192.png similarity index 100% rename from docs/.vuepress/public/icons/android-chrome-192x192.png rename to packages/docs/docs/.vuepress/public/icons/android-chrome-192x192.png diff --git a/docs/.vuepress/public/icons/android-chrome-512x512.png b/packages/docs/docs/.vuepress/public/icons/android-chrome-512x512.png similarity index 100% rename from docs/.vuepress/public/icons/android-chrome-512x512.png rename to packages/docs/docs/.vuepress/public/icons/android-chrome-512x512.png diff --git a/docs/.vuepress/public/icons/apple-touch-icon-120x120.png b/packages/docs/docs/.vuepress/public/icons/apple-touch-icon-120x120.png similarity index 100% rename from docs/.vuepress/public/icons/apple-touch-icon-120x120.png rename to packages/docs/docs/.vuepress/public/icons/apple-touch-icon-120x120.png diff --git a/docs/.vuepress/public/icons/apple-touch-icon-152x152.png b/packages/docs/docs/.vuepress/public/icons/apple-touch-icon-152x152.png similarity index 100% rename from docs/.vuepress/public/icons/apple-touch-icon-152x152.png rename to packages/docs/docs/.vuepress/public/icons/apple-touch-icon-152x152.png diff --git a/docs/.vuepress/public/icons/apple-touch-icon-180x180.png b/packages/docs/docs/.vuepress/public/icons/apple-touch-icon-180x180.png similarity index 100% rename from docs/.vuepress/public/icons/apple-touch-icon-180x180.png rename to packages/docs/docs/.vuepress/public/icons/apple-touch-icon-180x180.png diff --git a/docs/.vuepress/public/icons/apple-touch-icon-60x60.png b/packages/docs/docs/.vuepress/public/icons/apple-touch-icon-60x60.png similarity index 100% rename from docs/.vuepress/public/icons/apple-touch-icon-60x60.png rename to packages/docs/docs/.vuepress/public/icons/apple-touch-icon-60x60.png diff --git a/docs/.vuepress/public/icons/apple-touch-icon-76x76.png b/packages/docs/docs/.vuepress/public/icons/apple-touch-icon-76x76.png similarity index 100% rename from docs/.vuepress/public/icons/apple-touch-icon-76x76.png rename to packages/docs/docs/.vuepress/public/icons/apple-touch-icon-76x76.png diff --git a/docs/.vuepress/public/icons/apple-touch-icon.png b/packages/docs/docs/.vuepress/public/icons/apple-touch-icon.png similarity index 100% rename from docs/.vuepress/public/icons/apple-touch-icon.png rename to packages/docs/docs/.vuepress/public/icons/apple-touch-icon.png diff --git a/docs/.vuepress/public/icons/favicon-16x16.png b/packages/docs/docs/.vuepress/public/icons/favicon-16x16.png similarity index 100% rename from docs/.vuepress/public/icons/favicon-16x16.png rename to packages/docs/docs/.vuepress/public/icons/favicon-16x16.png diff --git a/docs/.vuepress/public/icons/favicon-32x32.png b/packages/docs/docs/.vuepress/public/icons/favicon-32x32.png similarity index 100% rename from docs/.vuepress/public/icons/favicon-32x32.png rename to packages/docs/docs/.vuepress/public/icons/favicon-32x32.png diff --git a/docs/.vuepress/public/icons/msapplication-icon-144x144.png b/packages/docs/docs/.vuepress/public/icons/msapplication-icon-144x144.png similarity index 100% rename from docs/.vuepress/public/icons/msapplication-icon-144x144.png rename to packages/docs/docs/.vuepress/public/icons/msapplication-icon-144x144.png diff --git a/docs/.vuepress/public/icons/mstile-150x150.png b/packages/docs/docs/.vuepress/public/icons/mstile-150x150.png similarity index 100% rename from docs/.vuepress/public/icons/mstile-150x150.png rename to packages/docs/docs/.vuepress/public/icons/mstile-150x150.png diff --git a/docs/.vuepress/public/icons/safari-pinned-tab.svg b/packages/docs/docs/.vuepress/public/icons/safari-pinned-tab.svg similarity index 100% rename from docs/.vuepress/public/icons/safari-pinned-tab.svg rename to packages/docs/docs/.vuepress/public/icons/safari-pinned-tab.svg diff --git a/docs/.vuepress/public/line-numbers-desktop.png b/packages/docs/docs/.vuepress/public/line-numbers-desktop.png similarity index 100% rename from docs/.vuepress/public/line-numbers-desktop.png rename to packages/docs/docs/.vuepress/public/line-numbers-desktop.png diff --git a/docs/.vuepress/public/line-numbers-mobile.gif b/packages/docs/docs/.vuepress/public/line-numbers-mobile.gif similarity index 100% rename from docs/.vuepress/public/line-numbers-mobile.gif rename to packages/docs/docs/.vuepress/public/line-numbers-mobile.gif diff --git a/packages/docs/docs/.vuepress/public/logo.png b/packages/docs/docs/.vuepress/public/logo.png new file mode 100644 index 0000000000..60e17006ad Binary files /dev/null and b/packages/docs/docs/.vuepress/public/logo.png differ diff --git a/docs/.vuepress/public/manifest.json b/packages/docs/docs/.vuepress/public/manifest.json similarity index 100% rename from docs/.vuepress/public/manifest.json rename to packages/docs/docs/.vuepress/public/manifest.json diff --git a/packages/docs/docs/.vuepress/public/plugin.png b/packages/docs/docs/.vuepress/public/plugin.png new file mode 100644 index 0000000000..e22cec9922 Binary files /dev/null and b/packages/docs/docs/.vuepress/public/plugin.png differ diff --git a/docs/README.md b/packages/docs/docs/README.md similarity index 100% rename from docs/README.md rename to packages/docs/docs/README.md diff --git a/docs/config/README.md b/packages/docs/docs/config/README.md similarity index 100% rename from docs/config/README.md rename to packages/docs/docs/config/README.md diff --git a/docs/default-theme-config/README.md b/packages/docs/docs/default-theme-config/README.md similarity index 100% rename from docs/default-theme-config/README.md rename to packages/docs/docs/default-theme-config/README.md diff --git a/docs/guide/README.md b/packages/docs/docs/guide/README.md similarity index 75% rename from docs/guide/README.md rename to packages/docs/docs/guide/README.md index 2ab95d5003..5551339e04 100644 --- a/docs/guide/README.md +++ b/packages/docs/docs/guide/README.md @@ -2,7 +2,7 @@ -VuePress is composed of two parts: a minimalistic static site generator with a Vue-powered theming system, and a default theme optimized for writing technical documentation. It was created to support the documentation needs of Vue's own sub projects. +VuePress is composed of two parts: a [minimalistic static site generator](https://github.com/vuejs/vuepress/tree/next/packages/%40vuepress/core) with a Vue-powered theming system and [Plugin API](../plugin/README.md), and a [default theme](../default-theme-config/README.md) optimized for writing technical documentation. It was created to support the documentation needs of Vue's own sub projects. Each page generated by VuePress has its own pre-rendered static HTML, providing great loading performance and is SEO-friendly. Once the page is loaded, however, Vue takes over the static content and turns it into a full Single-Page Application (SPA). Additional pages are fetched on demand as the user navigates around the site. @@ -14,15 +14,28 @@ During the build, we create a server-rendered version of the app and render the Each markdown file is compiled into HTML with [markdown-it](https://github.com/markdown-it/markdown-it) and then processed as the template of a Vue component. This allows you to directly use Vue inside your markdown files and is great when you need to embed dynamic content. +## Life cycle + +Below is a diagram for the VuePress application lifecycle in dev and build modes. + +![image](/plugin.png) + +It is worth noting that during the `prepare` phase, VuePress does the following: + +1. Resolve config +2. Initialize and apply [Plugin API](../plugin/README.md) +3. Initialize [webpack](http://webpack.js.org/) configuration and create [markdown-it](https://github.com/markdown-it/markdown-it) instance + ## Features -- [Built-in markdown extensions](./markdown.md) optimized for technical documentation -- [Ability to leverage Vue inside markdown files](./using-vue.md) -- [Vue-powered custom theme system](./custom-themes.md) +- [Powerful Plugin API](../plugin/README.md) +- [Built-in markdown extensions](markdown.md) optimized for technical documentation +- [Ability to leverage Vue inside markdown files](using-vue.md) +- [Vue-powered custom theme system](custom-themes.md) - [Automatic Service Worker generation](../config/README.md#serviceworker) - [Google Analytics Integration](../config/README.md#ga) - ["Last Updated" based on Git](../default-theme-config/README.md#last-updated) -- [Multi-language support](./i18n.md) +- [Multi-language support](i18n.md) - A default theme with: - Responsive layout - [Optional Homepage](../default-theme-config/README.md#homepage) @@ -31,15 +44,6 @@ Each markdown file is compiled into HTML with [markdown-it](https://github.com/m - Customizable [navbar](../default-theme-config/README.md#navbar) and [sidebar](../default-theme-config/README.md#sidebar) - [Auto-generated GitHub link and page edit links](../default-theme-config/README.md#git-repo-and-edit-links) -## Todo - -VuePress is still a work in progress. There are a few things that it currently does not support but are planned: - -- Plugin support -- Blogging support - -Contributions are welcome! - ## Why Not ...? ### Nuxt diff --git a/docs/guide/assets.md b/packages/docs/docs/guide/assets.md similarity index 96% rename from docs/guide/assets.md rename to packages/docs/docs/guide/assets.md index 04a6eb31fe..1d4b8f5571 100644 --- a/docs/guide/assets.md +++ b/packages/docs/docs/guide/assets.md @@ -1,3 +1,8 @@ +--- +permalink: :year/:month/:day/:slug +date: 2018-08-15 11:22:33 +--- + # Asset Handling ## Relative URLs diff --git a/docs/guide/basic-config.md b/packages/docs/docs/guide/basic-config.md similarity index 96% rename from docs/guide/basic-config.md rename to packages/docs/docs/guide/basic-config.md index e333472466..3c38da0633 100644 --- a/docs/guide/basic-config.md +++ b/packages/docs/docs/guide/basic-config.md @@ -34,7 +34,7 @@ You can also use YAML (`.vuepress/config.yml`) or TOML (`.vuepress/config.toml`) A VuePress theme is responsible for all the layout and interactivity details of your site. VuePress ships with a default theme (you are looking at it right now) which is designed for technical documentation. It exposes a number of options that allow you to customize the navbar, sidebar and homepage, etc. For details, check out the [Default Theme Config](../default-theme-config/README.md) page. -If you wish to develop a custom theme, see [Custom Themes](./custom-themes.md). +If you wish to develop a custom theme, see [Custom Themes](custom-themes.md). ## App Level Enhancements diff --git a/docs/guide/custom-themes.md b/packages/docs/docs/guide/custom-themes.md similarity index 99% rename from docs/guide/custom-themes.md rename to packages/docs/docs/guide/custom-themes.md index ffdbcf520e..374597dba0 100644 --- a/docs/guide/custom-themes.md +++ b/packages/docs/docs/guide/custom-themes.md @@ -58,7 +58,6 @@ Finally, don't forget that `this.$route` and `this.$router` are also available a ::: tip `lastUpdated` is the UNIX timestamp of this file's last git commit, for more details, refer to [Last Updated](../default-theme-config/README.md#last-updated). - ::: ## Content Excerpt diff --git a/docs/guide/deploy.md b/packages/docs/docs/guide/deploy.md similarity index 100% rename from docs/guide/deploy.md rename to packages/docs/docs/guide/deploy.md diff --git a/docs/guide/getting-started.md b/packages/docs/docs/guide/getting-started.md similarity index 95% rename from docs/guide/getting-started.md rename to packages/docs/docs/guide/getting-started.md index ab48f0ad6b..9432ce2d15 100644 --- a/docs/guide/getting-started.md +++ b/packages/docs/docs/guide/getting-started.md @@ -63,4 +63,4 @@ To generate static assets, run: yarn docs:build # Or npm run docs:build ``` -By default the built files will be in `.vuepress/dist`, which can be configured via the `dest` field in `.vuepress/config.js`. The built files can be deployed to any static file server. See [Deployment Guide](./deploy.md) for guides on deploying to popular services. +By default the built files will be in `.vuepress/dist`, which can be configured via the `dest` field in `.vuepress/config.js`. The built files can be deployed to any static file server. See [Deployment Guide](deploy.md) for guides on deploying to popular services. diff --git a/docs/guide/i18n.md b/packages/docs/docs/guide/i18n.md similarity index 100% rename from docs/guide/i18n.md rename to packages/docs/docs/guide/i18n.md diff --git a/docs/guide/markdown.md b/packages/docs/docs/guide/markdown.md similarity index 100% rename from docs/guide/markdown.md rename to packages/docs/docs/guide/markdown.md diff --git a/docs/guide/using-vue.md b/packages/docs/docs/guide/using-vue.md similarity index 100% rename from docs/guide/using-vue.md rename to packages/docs/docs/guide/using-vue.md diff --git a/packages/docs/docs/plugin/README.md b/packages/docs/docs/plugin/README.md new file mode 100644 index 0000000000..b310662af1 --- /dev/null +++ b/packages/docs/docs/plugin/README.md @@ -0,0 +1,646 @@ +--- +sidebar: auto +--- + +# Plugins + +## Writing a Plugin + +Plugins usually add global-level functionality to VuePress. There is no strictly defined scope for a plugin - there are typically several types of plugins: + +1. Extend the data generated at compile time. e.g. [@vuepress/plugin-last-updated](https://github.com/vuejs/vuepress/tree/next/packages/@vuepress/plugin-last-updated). +2. Generate extra files before or after compilation. e.g. [@vuepress/plugin-pwa](https://github.com/vuejs/vuepress/tree/next/packages/%40vuepress/plugin-pwa) +3. Add extra pages. e.g. [@vuepress/plugin-i18n-ui](https://github.com/vuejs/vuepress/tree/next/packages/@vuepress/plugin-i18n-ui) +4. Inject global UI. e.g. [@vuepress/plugin-back-to-top](https://github.com/vuejs/vuepress/tree/next/packages/%40vuepress/plugin-back-to-top). + +A plugin should export a `plain object`(`#1`). If the plugin needs to take options, it can be a function that exports a plain object(`#2`). The function will be called with the plugin's options as the first argument, along with [context](#plugin-context) which provides some compile-time metadata. + +``` js +// #1 +module.exports = { + // ... +} +``` + +``` js +// #2 +module.exports = (options, ctx) => { + return { + // ... + } +} +``` + +::: tip +A VuePress plugin module should leverage `CommonJS Module` because VuePress plugins runs on the Node side. +::: + +## Using a plugin + +You can use plugins by doing some configuration at `.vuepress/config.js`: + +``` js +module.exports = { + plugins: [ + require('./my-plugin.js') + ] +} +``` + +### Use plugins from a dependency + +A plugin can be published on npm in `CommonJS` format as `vuepress-plugin-xxx`. then you can use it: + +``` js +module.exports = { + plugins: [ 'vuepress-plugin-xx' ] +} +``` + +### Plugin Shorthand + +If you prefix the plugin with `vuepress-plugin-`, you can use a shorthand to leave out that prefix: + +``` js +module.exports = { + plugins: [ 'xxx' ] +} +``` + +Same with: + +``` js +module.exports = { + plugins: [ 'vuepress-plugin-xxx' ] +} +``` + +This also works with [Scoped Packages](https://docs.npmjs.com/misc/scope): + +``` js +module.exports = { + plugins: [ '@org/vuepress-plugin-xxx', '@vuepress/plugin-xxx' ] +} +``` + +Shorthand: + +``` js +module.exports = { + plugins: [ '@org/xxx', '@vuepress/xxx' ] +} +``` + +::: warning Note +The plugin whose name starts with `@vuepress/plugin-` is an officially maintained plugin. +::: + +### Plugin options + +#### Babel Style + +Plugins can have options specified by wrapping the name and an options object in an array inside your config: + +``` js +module.exports = { + plugins: [ + [ + require('./my-plugin.js'), + { /* options */ } + ] + ] +} +``` + +Since this style is consistent with [babel's Plugin/Preset Options](https://babeljs.io/docs/en/plugins#plugin-preset-options), we call it `Babel Style`. + +#### Object Style + +VuePress also provides a simpler way to use plugins from a dependency: + +``` js +module.exports = { + plugins: { + 'xxx': { /* options */ } + } +} +``` + +::: warning Note +The plugin can be disabled when `false` is explicitly passed as option. + +- Babel style + +``` js +module.exports = { + plugins: [ + [ 'xxx', false ] // disabled. + ] +} +``` + +- Object style + +``` js +module.exports = { + plugins: { + 'xxx': false // disabled. + } +} +``` + +::: + +## Options + +### name + +- Type: `string` +- Default: undefined + +The name of the plugin. + +Internally, vuepress will use the plugin's package name as the plugin name. When your plugin is a local plugin (i.e. using a pure plugin function directly), please be sure to configure this option, that is good for debug tracking. + +```js +module.exports = { + plugins: [ + [ + (pluginOptions, ctx) => ({ + name: 'my-xxx-plugin' + // ... the rest of options + }) + ] + ] +} +``` + +### enabled + +- Type: `boolean` +- Default: true + +Configure whether to enable this plugin. e.g. if you want to enable a plugin only in development mode: + +```js +module.exports = (options, ctx) => { + return { + enabled: !ctx.isProd + } +} +``` + +### chainWebpack + +- Type: `Function` +- Default: undefined + +Modify the internal webpack config with [webpack-chain](https://github.com/mozilla-neutrino/webpack-chain). + +```js +module.exports = { + chainWebpack (config, isServer) { + // config is an instance of ChainableConfig + } +} +``` + +::: tip +Since VuePress is a Vue-SSR based application, there will be two webpack configurations, `isServer` is used to determine whether the current webpack config is applied to the server or client. + +**Also see:** + +- [Vue SSR > Build Configuration](https://ssr.vuejs.org/guide/build-config.html) +::: + +### define + +- Type: `Object|Function` +- Default: undefined + +Since using [DefinePlugin](https://webpack.js.org/plugins/define-plugin/) via [chainWebpack](chainwebpack) would be a little complicated: + +```js +module.exports = { + chainWebpack (config) { + config.plugin('injections').tap(([options]) => [ + Object.assign(options, { + SW_BASE_URL: JSON.stringify('/') + }) + ]) + } +} +``` + +VuePress specifically opened up a more concise `define` option, note that the values has been automatically processed by `JSON.stringify`. + +- Object Usage: + +```js +module.exports = { + define: { + SW_BASE_URL: '/', + } +} +``` + +- Function Usage: + +```js +module.exports = (options, ctx) => ({ + define () { + return { + SW_BASE_URL: ctx.base || '/', + SW_ENABLED: !!options.enabled, + } + } +}) +``` + +### alias + +- Type: `Object|Function` +- Default: undefined + +We can set aliases via [chainWebpack](chainwebpack): + +```js +module.exports = (options, ctx) => ({ + chainWebpack (config) { + config.resolve.alias.set('@theme', ctx.themePath) + } +}) +``` + +But `alias` option makes this process more like configuration: + +```js +module.exports = (options, ctx) => ({ + alias: { + '@theme': ctx.themePath + } +}) +``` + +### enhanceDevServer + +- Type: `Function` +- Default: undefined + +Enhance the underlying [Koa](https://github.com/koajs/koa) app. + +``` js +module.exports = { + enhanceDevServer (app) { + // ... + } +} +``` + +A simple plugin to create a sub public directory is as follows: + +```js +const path = require('path') + +module.exports = (options, ctx) => { + const imagesAssetsPath = path.resolve(ctx.sourceDir, '.vuepress/images') + + return { + // For development + enhanceDevServer (app) { + const mount = require('koa-mount') + const serveStatic = require('koa-static') + app.use(mount(path.join(ctx.publicPath, 'images'), serveStatic(imagesAssetsPath))) + }, + + // For production + async generated () { + const { fs } = require('@vuepress/shared-utils') + await fs.copy(imagesAssetsPath, path.resolve(ctx.outDir, 'images')) + } + } +} +``` + +### extendMarkdown + +- Type: `Function` +- Default: `undefined` + +A function to modify default config or apply additional plugins to the [markdown-it](https://github.com/markdown-it/markdown-it) instance used to render source files. Example: + +```js +module.exports = { + extendMarkdown: md => { + md.set({ breaks: true }) + md.use(require('markdown-it-xxx')) + } +} +``` + +### chainMarkdown + +- Type: `Function` +- Default: `undefined` + +Modify the internal markdown config with [markdown-it-chain](https://github.com/ulivz/markdown-it-chain) —— A chaining API like [webpack-chain](https://github.com/mozilla-neutrino/webpack-chain) but for [markdown-it](https://github.com/markdown-it/markdown-it). + +```js +module.exports = { + chainMarkdown (config) { + // Interact with 'options' in new MarkdownIt + // Ref: https://markdown-it.github.io/markdown-it/#MarkdownIt.new + config + .options + .link(true) + .breaks(true) + + // Modify the arguments of internal plugin. + config + .plugin('anchor') + .tap(([options]) => [ + Object.assign(options, { permalinkSymbol: '#' }) + ]) + + // Add extra markdown-it plugin + config + .plugin('sup') + .add(require('markdown-it-sup')) + + // Remove internal plugin + config.plugins.delete('snippet') + } +} +``` + +**Also see:** + +- [Internal plugins in VuePress](https://github.com/vuejs/vuepress/blob/next/packages/%40vuepress/core/lib/markdown/index.js) +- [Config plugins](https://github.com/neutrinojs/webpack-chain#config-plugins) + +### enhanceAppFiles + +- Type: `Array | AsyncFunction` +- Default: `undefined` + +This option accepts an array containing the file paths, or a function that returns this array, which allows you to do some [App Level Enhancements](../guide/basic-config.md#theme-configuration). + +``` js +module.exports = { + enhanceAppFiles: [ + path.resolve(__dirname, 'client.js') + ] +} +``` + +The file can `export default` a hook function which will work like `.vuepress/enhanceApp.js`, or any client side code snippets. + +It's worth mentioning that in order for plugin developers to be able to do more things at compile time, this option also supports dynamic code: + +``` js +module.exports = (option, context) => { + return { + enhanceAppFiles: [{ + name: 'dynamic-code', + content: `export default ({ Vue }) => { Vue.mixin('$source', '${context.sourceDir}') }` + }] + } +} +``` + +### clientDynamicModules + +- Type: `Function` +- Default: `undefined` + +Sometimes, you may want to generate some client modules at compile time. + +``` js +module.exports = (options, context) => ({ + clientDynamicModules() { + return { + name: 'constans.js', + content: `export const SOURCE_DIR = '${context.sourceDir}'` + } + } +}) +``` + +Then you can use this module at client side code by: + +``` js +import { SOURCE_DIR } from '@dynamic/constans' +``` + +::: tip Q & A +**Q**: Both `clientDynamicModules` and `enhanceAppFiles` can generate dynamic javascript code during build time, so what is the difference between the two? + +**A**: The files generated by `clientDynamicModules` needs to be imported as `@dynamic/xxx` by the consumers themselves. But all the files generated by `enhanceAppFiles` will be loaded automatically when the APP is initialized on the client side. +::: + +### extendPageData + +- Type: `Function` +- Default: `undefined` + +A function that exports a plain object which will be merged into each page's data object. This function will be invoking once for each page at compile time. + +``` js +module.exports = { + extendPageData ({ + _filePath, // file's absolute path + _i18n, // access the client global mixins at build time, e.g _i18n.$localePath. + _content, // file's raw content string + _strippedContent, // file's content string without frontmatter + key, // page's unique hash key + frontmatter, // page's frontmatter object + regularPath, // current page's default link (follow the file hierarchy) + path, // current page's permalink + }) { + return { + // ... + } + } +} +``` + +::: warning Note +These fields starting with an `_` means you can only access them during build time. +::: + +e.g. + +``` js +module.exports = { + extendPageData ({ content }) { + return { + size: (content.length / 1024).toFixed(2) + 'kb' + } + } +} +``` + +Then you can use this value via `this.$page.size` in any Vue component. + +### clientRootMixin + +- Type: `String` +- Default: `undefined` + +A path to the mixin file which allow you to control the life cycle of root component. + +``` js +// plugin's entry +const path = require('path') + +module.exports = { + clientRootMixin: path.resolve(__dirname, 'mixin.js') +} +``` + +``` js +// mixin.js +export default { + created () {}, + mounted () {} +} +``` + +### additionalPages + +- Type: `Array|Function` +- Default: `undefined` + +Add a page pointing to a markdown file: + +```js +const path = require('path') + +module.exports = { + additionalPages: [ + { + path: '/readme/', + filePath: path.resolve(__dirname, '../../README.md') + } + ] +} +``` + +Add a page with explicit content: + +```js +module.exports = { + async additionalPages () { + const rp = require('request-promise'); + + // VuePress doesn't have request library built-in + // you need to install it yourself. + const content = await rp('https://github.com/vuejs/vuepress/blob/master/CHANGELOG.md'); + return [ + { + path: '/readme/', + content + } + ] + } +} +``` + +Add a pure route: + +```js +module.exports = { + additionalPages: [ + { + path: '/alpha/', + frontmatter: { + layout: 'MyLayout' + } + } + ] +} +``` + +### globalUIComponents + +- Type: `Array|String` +- Default: `undefined` + +You might want to inject some global UI fixed somewhere on the page, e.g. `back-to-top`, `popup`. In VuePress, **a global UI is a Vue component**, you can define the component's name(s) in the plugin, e.g. + +``` js +module.exports = { + globalUIComponents: [ + 'Component-1', + 'Component-2' + ] +} +``` + +Then, VuePress will automatically inject these components behind the theme container: + +```html +
+
...
+
+ + +
+
+``` + +## Context + +Starting with VuePress 1.x.x, VuePress provides an `AppContext` object that stores all the state of the current app and can be accessed through the plugin API. + +::: warning Note +Context of each plugin is a isolated context, they just inherit from the same app context. +::: + +```js +module.exports = (options, context) => { + // ... +} +``` + +### context.isProd + +- Type: `boolean` + +Whether vuepress run in production environment mode. + +### context.sourceDir + +- Type: `string` + +Root directory where the documents are located. + +### context.tempPath + +- Type: `string` + +Root directory where the temporary files are located. + +### context.outDir + +- Type: `string` + +Output path. + +### context.themePath + +- Type: `string` + +The path of the currently active theme. + +### context.base + +- Type: `string` + +See: [base](../config/README.md#base). + +### context.writeTemp + +- Type: `Function` + +A utility for writing temporary files to tempPath. diff --git a/packages/docs/docs/theme/README.md b/packages/docs/docs/theme/README.md new file mode 100644 index 0000000000..d28cf8373a --- /dev/null +++ b/packages/docs/docs/theme/README.md @@ -0,0 +1,96 @@ +--- +sidebar: auto +--- + +# Theme + +## Background + +Before 1.x.x, vuepress retrieves all markdown files in the documents source directory and defines the page links based on the file hierarchy. e.g. if you have the following file structure: + +``` +├── package.json +└── source + ├── _post + │   └── intro-vuepress.md + ├── index.md + └── tags.md +``` + +Then you will get following available pages: + +``` +/source/ +/source/tags.html +/source/_post/intro-vuepress.html +``` + +However, for a blog system, we hope that the link of a post can be customized. VuePress started supporting this feature from `1.0.0`. which is known as `permalink`. Then, the actual pages would be: + +``` +/source/ +/source/tags/ +/source/2018/4/1/intro-vuepress.html +``` + +It seems that we have seen the shadow of the blog. Let's continue to look down. + +## Permalinks + +A permalink is a URL that is intended to remain unchanged for many years into the future, yielding a hyperlink that is less susceptible to link rot[1]. VuePress supports a flexible way to build permalinks, allowing you to leverage various template variables. + +The default permalink is `/:regular`. + +### Configure Permalinks + +You can configure globally to apply it for all pages: + +```js +// .vuepress/config.js +module.exports = { + permalink: '/:year/:month/:day/:slug' +} +``` + +Alternatively, you can also set permalink on a page only, and it will have a higher priority than the global settings. + +📝 __hello.md__: + +```markdown +--- +title: Hello World +permalink: /hello-world +--- + +Hello! +``` + +### Template Variables + +| Variable | Description | +|---|---| +|:year|Published year of posts (4-digit)| +|:month|Published month of posts (2-digit)| +|:i_month|Published month of posts (Without leading zeros)| +|:day|Published day of posts (2-digit)| +|:i_day|Published day of posts (Without leading zeros)| +|:slug|Slugified file path (Without extension)| +|:regular| Permalink generated by VuePress by default, for implementation see [here](https://github.com/vuejs/vuepress/blob/next/packages/%40vuepress/shared-utils/lib/fileToPath.js) | + +## Writing a theme + +TODO. integrate with the old docs. + +## Theme API + +### layout + +TODO + +### notFound + +TODO + +### plugins + +TODO diff --git a/docs/zh/README.md b/packages/docs/docs/zh/README.md similarity index 100% rename from docs/zh/README.md rename to packages/docs/docs/zh/README.md diff --git a/docs/zh/config/README.md b/packages/docs/docs/zh/config/README.md similarity index 100% rename from docs/zh/config/README.md rename to packages/docs/docs/zh/config/README.md diff --git a/docs/zh/default-theme-config/README.md b/packages/docs/docs/zh/default-theme-config/README.md similarity index 100% rename from docs/zh/default-theme-config/README.md rename to packages/docs/docs/zh/default-theme-config/README.md diff --git a/docs/zh/guide/README.md b/packages/docs/docs/zh/guide/README.md similarity index 93% rename from docs/zh/guide/README.md rename to packages/docs/docs/zh/guide/README.md index 28dbfb1919..e48bd4c9f1 100644 --- a/docs/zh/guide/README.md +++ b/packages/docs/docs/zh/guide/README.md @@ -12,13 +12,13 @@ VuePress 由两部分组成:一部分是支持用 Vue 开发主题的极简静 ## 特性 -- 为技术文档而优化的 [内置 Markdown 拓展](./markdown.md) -- [在 Markdown 文件中使用 Vue 组件的能力](./using-vue.md) -- [Vue 驱动的自定义主题系统](./custom-themes.md) +- 为技术文档而优化的 [内置 Markdown 拓展](markdown.md) +- [在 Markdown 文件中使用 Vue 组件的能力](using-vue.md) +- [Vue 驱动的自定义主题系统](custom-themes.md) - [自动生成 Service Worker](../config/README.md#serviceworker) - [Google Analytics 集成](../config/README.md#ga) - [基于 Git 的 “最后更新时间”](../default-theme-config/README.md#最后更新时间) -- [多语言支持](./i18n.md) +- [多语言支持](i18n.md) - 默认主题包含: - 响应式布局 - [可选的主页](../default-theme-config/README.md#首页) diff --git a/docs/zh/guide/assets.md b/packages/docs/docs/zh/guide/assets.md similarity index 100% rename from docs/zh/guide/assets.md rename to packages/docs/docs/zh/guide/assets.md diff --git a/docs/zh/guide/basic-config.md b/packages/docs/docs/zh/guide/basic-config.md similarity index 98% rename from docs/zh/guide/basic-config.md rename to packages/docs/docs/zh/guide/basic-config.md index 3ac5f459d1..686ff82940 100644 --- a/docs/zh/guide/basic-config.md +++ b/packages/docs/docs/zh/guide/basic-config.md @@ -34,7 +34,7 @@ module.exports = { 一个 VuePress 主题应该负责整个网站的布局和交互细节。在 VuePress 中,目前自带了一个默认的主题(正是你现在所看到的),它是为技术文档而设计的。同时,默认主题提供了一些选项,让你可以去自定义导航栏(navbar)、 侧边栏(sidebar)和 首页(homepage) 等,详情请参见 [默认主题](../default-theme-config/README.md) 。 -如果你想开发一个自定义主题,可以参考 [自定义主题](./custom-themes.md)。 +如果你想开发一个自定义主题,可以参考 [自定义主题](custom-themes.md)。 ## 应用级别的配置 diff --git a/docs/zh/guide/custom-themes.md b/packages/docs/docs/zh/guide/custom-themes.md similarity index 100% rename from docs/zh/guide/custom-themes.md rename to packages/docs/docs/zh/guide/custom-themes.md diff --git a/docs/zh/guide/deploy.md b/packages/docs/docs/zh/guide/deploy.md similarity index 100% rename from docs/zh/guide/deploy.md rename to packages/docs/docs/zh/guide/deploy.md diff --git a/docs/zh/guide/getting-started.md b/packages/docs/docs/zh/guide/getting-started.md similarity index 96% rename from docs/zh/guide/getting-started.md rename to packages/docs/docs/zh/guide/getting-started.md index 402a03530b..6040e71dab 100644 --- a/docs/zh/guide/getting-started.md +++ b/packages/docs/docs/zh/guide/getting-started.md @@ -67,4 +67,4 @@ yarn docs:dev # 或者:npm run docs:dev yarn docs:build # 或者:npm run docs:build ``` -默认情况下,文件将会被生成在 `.vuepress/dist`,当然,你也可以通过 `.vuepress/config.js` 中的 `dest` 字段来修改,生成的文件可以部署到任意的静态文件服务器上,参考 [部署](./deploy.md) 来了解更多。 +默认情况下,文件将会被生成在 `.vuepress/dist`,当然,你也可以通过 `.vuepress/config.js` 中的 `dest` 字段来修改,生成的文件可以部署到任意的静态文件服务器上,参考 [部署](deploy.md) 来了解更多。 diff --git a/docs/zh/guide/i18n.md b/packages/docs/docs/zh/guide/i18n.md similarity index 100% rename from docs/zh/guide/i18n.md rename to packages/docs/docs/zh/guide/i18n.md diff --git a/docs/zh/guide/markdown.md b/packages/docs/docs/zh/guide/markdown.md similarity index 100% rename from docs/zh/guide/markdown.md rename to packages/docs/docs/zh/guide/markdown.md diff --git a/docs/zh/guide/using-vue.md b/packages/docs/docs/zh/guide/using-vue.md similarity index 100% rename from docs/zh/guide/using-vue.md rename to packages/docs/docs/zh/guide/using-vue.md diff --git a/packages/docs/package.json b/packages/docs/package.json new file mode 100644 index 0000000000..411af4f07f --- /dev/null +++ b/packages/docs/package.json @@ -0,0 +1,32 @@ +{ + "private": true, + "version": "1.0.0", + "name": "docs", + "description": "docs of VuePress", + "scripts": { + "dev": "vuepress dev docs --temp .temp", + "build": "vuepress build docs --temp .temp" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "generator" + ], + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress#readme", + "devDependencies": { + "vuepress": "^1.0.0", + "@vuepress/plugin-back-to-top": "^1.0.0", + "@vuepress/plugin-pwa": "^1.0.0", + "@vuepress/plugin-i18n-ui": "^1.0.0", + "@vuepress/plugin-medium-zoom": "^1.0.0" + } +} diff --git a/packages/vuepress/package.json b/packages/vuepress/package.json new file mode 100644 index 0000000000..eb1fcb7ba4 --- /dev/null +++ b/packages/vuepress/package.json @@ -0,0 +1,29 @@ +{ + "name": "vuepress", + "version": "1.0.0", + "description": "Minimalistic doc generator with Vue component based layout system", + "main": "vuepress.js", + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git" + }, + "keywords": [ + "documentation", + "vue", + "generator" + ], + "bin": { + "vuepress": "vuepress.js" + }, + "author": "Evan You", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress#readme", + "dependencies": { + "@vuepress/core": "^1.0.0", + "@vuepress/cli": "^1.0.0", + "@vuepress/theme-default": "^1.0.0" + } +} diff --git a/packages/vuepress/vuepress.js b/packages/vuepress/vuepress.js new file mode 100755 index 0000000000..c885579271 --- /dev/null +++ b/packages/vuepress/vuepress.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('@vuepress/cli').bootstrap({ theme: '@vuepress/default' }) diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js new file mode 100644 index 0000000000..65e8599121 --- /dev/null +++ b/scripts/bootstrap.js @@ -0,0 +1,57 @@ +// create package.json and README for packages that don't have one yet + +const fs = require('fs') +const path = require('path') +const baseVersion = require('../packages/@vuepress/core/package.json').version + +const packagesDir = path.resolve(__dirname, '../packages/@vuepress') +const files = fs.readdirSync(packagesDir) + +files.forEach(pkg => { + if (pkg.charAt(0) === '.') return + + const isPlugin = /^plugin-/.test(pkg) + const desc = isPlugin + ? `${pkg.replace('plugin-', '')} plugin for vuepress` + : `${pkg} for vuepress` + + const pkgPath = path.join(packagesDir, pkg, `package.json`) + if (!fs.existsSync(pkgPath)) { + const json = { + 'name': `@vuepress/${pkg}`, + 'version': baseVersion, + 'description': desc, + 'main': 'index.js', + 'publishConfig': { + 'access': 'public' + }, + 'repository': { + 'type': 'git', + 'url': 'git+https://github.com/vuejs/vuepress.git' + }, + 'keywords': [ + 'documentation', + 'vue', + 'vuepress', + 'generator' + ], + 'author': 'Evan You', + 'license': 'MIT', + 'bugs': { + 'url': 'https://github.com/vuejs/vuepress/issues' + }, + 'homepage': `https://github.com/vuejs/vuepress/packages/@vuepress/${pkg}#readme` + } + fs.writeFileSync(pkgPath, JSON.stringify(json, null, 2)) + } + + const readmePath = path.join(packagesDir, pkg, `README.md`) + if (!fs.existsSync(readmePath)) { + fs.writeFileSync(readmePath, `# @vuepress/${pkg}\n\n> ${desc}`) + } + + const npmIgnorePath = path.join(packagesDir, pkg, `.npmignore`) + if (!fs.existsSync(npmIgnorePath)) { + fs.writeFileSync(npmIgnorePath, `__tests__\n__mocks__`) + } +}) diff --git a/scripts/genChangelog.js b/scripts/genChangelog.js new file mode 100644 index 0000000000..544ab5cd82 --- /dev/null +++ b/scripts/genChangelog.js @@ -0,0 +1,27 @@ +const execa = require('execa') +const cc = require('conventional-changelog') +const config = require('@vue/conventional-changelog') + +const gen = module.exports = version => { + const fileStream = require('fs').createWriteStream(`CHANGELOG.md`) + + cc({ + config, + releaseCount: 0, + pkg: { + transform (pkg) { + pkg.version = `v${version}` + return pkg + } + } + }).pipe(fileStream).on('close', async () => { + delete process.env.PREFIX + await execa('git', ['add', '-A'], { stdio: 'inherit' }) + await execa('git', ['commit', '-m', `chore: ${version} changelog`], { stdio: 'inherit' }) + }) +} + +if (process.argv[2] === 'run') { + const version = require('../lerna.json').version + gen(version) +} diff --git a/scripts/jest.config.js b/scripts/jest.config.js new file mode 100644 index 0000000000..dcb002f565 --- /dev/null +++ b/scripts/jest.config.js @@ -0,0 +1,14 @@ +const path = require('path') +const createJestConfig = require('@vuepress/test-utils/createJestConfig') + +module.exports = createJestConfig({ + rootDir: path.resolve(__dirname, '..'), + moduleNameMapper: { + '^@/(.*)$': '/$1', + '^@core/(.*)$': '/packages/@vuepress/core/$1' + }, + modulePathIgnorePatterns: [ + '/packages/@vuepress/core/__test__/prepare/prepare.spec.js', + '/packages/@vuepress/core/__test__/plugin-api/AsyncOption.spec.js' + ] +}) diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000000..96fd96b55a --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,104 @@ +/** + + How to do a release: + + 1. Make sure you have publish access for all packages: + - You must be in the VuePress team in the npm @vuepress organization + - Make sure you DO NOT have npm per-publish 2-factor / OTP enabled, as it + does not work with Lerna (which we use for batch publishing). + + 2. Run `yarn release`, follow prompts + + 3A. If everything works properly, the tag should have been auto-pushed and a + local changelog commit should have been generated. Go to 4. + + 3B. If the publish fails half-way, things have gotten hairy. Now you need to + go to npm to check which packages have been published and manually publish + the ones that have not been published yet. After all have been published: + + 3B.1. Push the release git tag to GitHub. + 3B.2. Run `yarn changelog` to generate changelog commit. + + 4. Push the changelog commit to `next` branch. + + 5. Go to GitHub and verify that the changelog is live. + + 6. Go to GitHub releases page and publish the release. + + */ + +process.env.VUE_CLI_RELEASE = true + +const execa = require('execa') +const semver = require('semver') +const inquirer = require('inquirer') + +const curVersion = require('../lerna.json').version + +const release = async () => { + console.log(`Current version: ${curVersion}`) + + const bumps = ['patch', 'minor', 'major', 'prerelease', 'premajor'] + const versions = {} + bumps.forEach(b => { + versions[b] = semver.inc(curVersion, b) + }) + const bumpChoices = bumps.map(b => ({ name: `${b} (${versions[b]})`, value: b })) + + const { bump, customVersion } = await inquirer.prompt([ + { + name: 'bump', + message: 'Select release type:', + type: 'list', + choices: [ + ...bumpChoices, + { name: 'custom', value: 'custom' } + ] + }, + { + name: 'customVersion', + message: 'Input version:', + type: 'input', + when: answers => answers.bump === 'custom' + } + ]) + + const version = customVersion || versions[bump] + + const { yes } = await inquirer.prompt([{ + name: 'yes', + message: `Confirm releasing ${version}?`, + type: 'list', + choices: ['N', 'Y'] + }]) + + if (yes === 'N') { + console.log('[release] cancelled.') + return + } + + const releaseArguments = [ + 'publish', + '--repo-version', + version, + '--force-publish', + '*' + ] + + console.log(`lerna ${releaseArguments.join(' ')}`) + + await execa(require.resolve('lerna/bin/lerna'), [ + 'publish', + '--repo-version', + version, + '--force-publish', + '*' + ], { stdio: 'inherit' }) + + require('./genChangelog')(version) +} + +release().catch(err => { + console.error(err) + process.exit(1) +}) diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 13cc002e9c..0000000000 --- a/scripts/release.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -echo "Select a option to release (input a serial number):" -echo - -select VERSION in patch minor major "Specific Version" - do - echo - if [[ $REPLY =~ ^[1-4]$ ]]; then - if [[ $REPLY == 4 ]]; then - read -p "Enter a specific version: " -r VERSION - echo - if [[ -z $REPLY ]]; then - VERSION=$REPLY - fi - fi - - read -p "Release $VERSION - are you sure? (y/n) " -n 1 -r - echo - - if [[ $REPLY =~ ^[Yy]$ || -z $REPLY ]]; then - # pre release task - npm run lint - npm run test - - # bump version - npm version $VERSION - NEW_VERSION=$(node -p "require('./package.json').version") - echo Releasing ${NEW_VERSION} ... - - # npm release - npm whoami - npm publish - echo "✅ Released to npm." - - # github release - git add CHANGELOG.md - git commit -m "chore: changelog" - git push - git push origin refs/tags/v${NEW_VERSION} - echo "✅ Released to Github." - else - echo Cancelled - fi - break - else - echo Invalid \"${REPLY}\" - echo "To continue, please input a serial number(1-4) of an option." - echo - fi - done - diff --git a/scripts/test.js b/scripts/test.js new file mode 100644 index 0000000000..489d67d4f6 --- /dev/null +++ b/scripts/test.js @@ -0,0 +1,26 @@ +const minimist = require('minimist') +const createJestRunner = require('@vuepress/test-utils/createJestRunner') + +const rawArgs = process.argv.slice(2) +const args = minimist(rawArgs) + +let regex +if (args.p) { + const packages = (args.p || args.package).split(',').join('|') + regex = `.*@vuepress/(${packages}|plugin-(${packages}))/.*\\.spec\\.js$` + const i = rawArgs.indexOf('-p') + rawArgs.splice(i, 2) +} + +const jestRunner = createJestRunner( + [ + '--config', 'scripts/jest.config.js', + '--runInBand', + ...(regex ? [regex] : []) + ]) + +;(jestRunner)().catch(err => { + console.error(err) + process.exit(1) +}) + diff --git a/test/app/Content.spec.js b/test/app/Content.spec.js deleted file mode 100644 index 8049befc88..0000000000 --- a/test/app/Content.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -import Content from '@/app/components/Content' -import { mount } from '@vue/test-utils' -import { getRouter, modeTestRunner } from '../util' - -function test (mode, localVue) { - it(`${mode} - add custom class by default.`, () => { - const wrapper = mount(Content, { - localVue, - router: getRouter() - }) - expect(wrapper.contains('.custom')).toBe(true) - }) - - it(`${mode} - remove custom when custom set to false.`, () => { - const wrapper = mount(Content, { - // https://vue-test-utils.vuejs.org/api/options.html#context - context: { - props: { - custom: false - } - }, - localVue, - router: getRouter() - }) - expect(wrapper.contains('.custom')).toBe(false) - }) -} - -modeTestRunner('Content', test) - diff --git a/test/default-theme/__snapshots__/DropdownLink.spec.js.snap b/test/default-theme/__snapshots__/DropdownLink.spec.js.snap deleted file mode 100644 index a302d2112a..0000000000 --- a/test/default-theme/__snapshots__/DropdownLink.spec.js.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DropdownLink $i18n - renders dropdown link. 1`] = ` - -`; - -exports[`DropdownLink $simple - renders dropdown link. 1`] = ` - -`; diff --git a/test/default-theme/__snapshots__/NavLink.spec.js.snap b/test/default-theme/__snapshots__/NavLink.spec.js.snap deleted file mode 100644 index 4b9cd02ccc..0000000000 --- a/test/default-theme/__snapshots__/NavLink.spec.js.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NavLink $i18n - renders nav link with external link 1`] = ` - - Vue - - -`; - -exports[`NavLink $i18n - renders nav link with internal link 1`] = `VuePress`; - -exports[`NavLink $simple - renders nav link with external link 1`] = ` - - Vue - - -`; - -exports[`NavLink $simple - renders nav link with internal link 1`] = `VuePress`; diff --git a/test/docs-simple/README.md b/test/docs-simple/README.md deleted file mode 100644 index 9e126f919b..0000000000 --- a/test/docs-simple/README.md +++ /dev/null @@ -1 +0,0 @@ -# Hello Simple diff --git a/test/markdown/fragments/code-snippet-highlightLines-multiple.md b/test/markdown/fragments/code-snippet-highlightLines-multiple.md deleted file mode 100644 index 01aef2de27..0000000000 --- a/test/markdown/fragments/code-snippet-highlightLines-multiple.md +++ /dev/null @@ -1 +0,0 @@ -<<< @/test/markdown/fragments/snippet.js{1-3} diff --git a/test/markdown/fragments/code-snippet-highlightLines-single.md b/test/markdown/fragments/code-snippet-highlightLines-single.md deleted file mode 100644 index 1916b68411..0000000000 --- a/test/markdown/fragments/code-snippet-highlightLines-single.md +++ /dev/null @@ -1 +0,0 @@ -<<< @/test/markdown/fragments/snippet.js{1,3} diff --git a/test/markdown/fragments/code-snippet.md b/test/markdown/fragments/code-snippet.md deleted file mode 100644 index 65e9148549..0000000000 --- a/test/markdown/fragments/code-snippet.md +++ /dev/null @@ -1 +0,0 @@ -<<< @/test/markdown/fragments/snippet.js diff --git a/test/prepare.js b/test/prepare.js deleted file mode 100644 index 9960d5dfb1..0000000000 --- a/test/prepare.js +++ /dev/null @@ -1,21 +0,0 @@ -const path = require('path') -const fs = require('fs-extra') -const prepare = require('../lib/prepare') -const logger = require('../lib/util/logger') - -const tempPath = path.resolve(__dirname, '.temp') -const siteDatePath = path.resolve(__dirname, '../lib/app/.temp/siteData.js') - -async function prepareForTest () { - await fs.ensureDir(tempPath) - - await prepare(path.resolve(__dirname, '../docs')) - await fs.copy(siteDatePath, path.join(tempPath, 'siteData-i18n.js')) - - await prepare(path.resolve(__dirname, 'docs-simple')) - await fs.copy(siteDatePath, path.join(tempPath, 'siteData-simple.js')) -} - -prepareForTest().then(() => { - logger.wait('Preparing for testing ...') -}) diff --git a/test/prepare/fixtures/docs-custom-theme/README.md b/test/prepare/fixtures/docs-custom-theme/README.md deleted file mode 100644 index fdb4d2b582..0000000000 --- a/test/prepare/fixtures/docs-custom-theme/README.md +++ /dev/null @@ -1 +0,0 @@ -# Simple Docs diff --git a/test/prepare/fixtures/docs-simple-config/README.md b/test/prepare/fixtures/docs-simple-config/README.md deleted file mode 100644 index fdb4d2b582..0000000000 --- a/test/prepare/fixtures/docs-simple-config/README.md +++ /dev/null @@ -1 +0,0 @@ -# Simple Docs diff --git a/test/prepare/fixtures/docs-simple/README.md b/test/prepare/fixtures/docs-simple/README.md deleted file mode 100644 index fdb4d2b582..0000000000 --- a/test/prepare/fixtures/docs-simple/README.md +++ /dev/null @@ -1 +0,0 @@ -# Simple Docs diff --git a/test/prepare/resolveOptions.spec.js b/test/prepare/resolveOptions.spec.js deleted file mode 100644 index 433c565214..0000000000 --- a/test/prepare/resolveOptions.spec.js +++ /dev/null @@ -1,95 +0,0 @@ -import path from 'path' -import resolveOptions from '@/prepare/resolveOptions.js' - -const DEFAULT_THEME_PATH = path.resolve(__dirname, '../../lib/default-theme') -const DEFAULT_THEME_LAYOUT_PATH = path.resolve(DEFAULT_THEME_PATH, 'Layout.vue') -const DEFAULT_THEME_NOT_FOOUND_PATH = path.resolve(DEFAULT_THEME_PATH, 'NotFound.vue') - -describe('prepare - resolveOptions', () => { - test('single file docs without config.', async () => { - const { docsDir } = getDocsPaths('docs-simple') - const options = await resolveOptions(docsDir) - const { - siteConfig, - siteData, - sourceDir, - outDir, - publicPath, - pageFiles, - pagesData, - themePath, - themeLayoutPath, - themeNotFoundPath, - themeEnhanceAppPath, - useDefaultTheme, - isAlgoliaSearch, - markdown - } = options - expect(siteConfig).toEqual({}) - expect(siteData).toEqual({ - title: '', - description: '', - base: '/', - pages: pagesData, - themeConfig: {}, - locales: undefined - }) - expect(sourceDir).toBe(docsDir) - expect(outDir).toBe(path.resolve(docsDir, '.vuepress/dist')) - expect(publicPath).toBe('/') - expect(pageFiles).toHaveLength(1) - expect(pageFiles[0]).toBe('README.md') - expect(themePath).toBe(DEFAULT_THEME_PATH) - expect(themeLayoutPath).toBe(DEFAULT_THEME_LAYOUT_PATH) - expect(themeNotFoundPath).toBe(DEFAULT_THEME_NOT_FOOUND_PATH) - expect(themeEnhanceAppPath).toBe(null) - expect(useDefaultTheme).toBe(true) - expect(isAlgoliaSearch).toBe(false) - expect(typeof markdown).toBe('object') - }) - - test('single file docs with config', async () => { - const { docsDir, configPath } = getDocsPaths('docs-simple-config') - const options = await resolveOptions(docsDir) - const { - siteConfig, - outDir, - publicPath - } = options - expect(siteConfig).toEqual(require(configPath)) - expect(siteConfig.base).toBe('vuepress') - expect(siteConfig.dest).toBe('vuepress') - expect(outDir).toBe(path.resolve('vuepress')) - expect(publicPath).toBe('vuepress') - }) - - test('simple docs with custom theme', async () => { - const paths = getDocsPaths('docs-custom-theme') - const options = await resolveOptions(paths.docsDir) - const { - themePath, - themeLayoutPath, - themeNotFoundPath, - useDefaultTheme - } = options - expect(useDefaultTheme).toBe(false) - expect(themePath).toBe(paths.themePath) - expect(themeLayoutPath).toBe(paths.themeLayoutPath) - expect(themeNotFoundPath).toBe(DEFAULT_THEME_NOT_FOOUND_PATH) // fallbacks to default theme's NotFound component. - }) -}) - -function getDocsPaths (name) { - const docsDir = path.join(__dirname, `fixtures/${name}`) - const configPath = path.join(docsDir, '.vuepress/config.js') - const themePath = path.join(docsDir, '.vuepress/theme') - const themeLayoutPath = path.join(themePath, 'Layout.vue') - const themeNotFoundPath = path.join(themePath, 'NotFound.vue') - return { - docsDir, - configPath, - themePath, - themeLayoutPath, - themeNotFoundPath - } -} diff --git a/test/prepare/util.spec.js b/test/prepare/util.spec.js deleted file mode 100644 index ca8e1691a4..0000000000 --- a/test/prepare/util.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import { - isIndexFile, - fileToPath -} from '@/prepare/util.js' - -describe('prepare - util', () => { - test('isIndexFile', () => { - [ - 'README.md', - 'readme.md', - 'INDEX.md', - 'index.md', - 'foo/README.md', - 'foo/index.md' - ].forEach(file => { - expect(isIndexFile(file)).toBe(true) - }); - [ - 'foo/one.md', - 'one.md' - ].forEach(file => { - expect(isIndexFile(file)).toBe(false) - }) - }) - - test('fileToPath', () => { - const asserts = { - 'README.md': '/', - 'foo/README.md': '/foo/', - 'foo.md': '/foo.html', - 'foo/bar.md': '/foo/bar.html' - } - Object.keys(asserts).forEach(file => { - expect(fileToPath(file)).toBe(asserts[file]) - }) - }) -}) diff --git a/test/util.js b/test/util.js deleted file mode 100644 index 1696c5c552..0000000000 --- a/test/util.js +++ /dev/null @@ -1,42 +0,0 @@ -import { createLocalVue } from '@vue/test-utils' -import dataMixin from '@/app/dataMixin' -import Router from 'vue-router' -import { mockComponent } from './hoc' -import { siteData as i18nSiteData } from './.temp/siteData-i18n' -import { siteData as simpleSiteData } from './.temp/siteData-simple' - -export function getRouter () { - return new Router() -} - -export function getLocalVueByMode (mode) { - const localVue = createLocalVue() - localVue.use(Router) - - // register global component - localVue.component('OutboundLink', mockComponent('outbound-link')) - - // register page component in root route. - localVue.component(i18nSiteData.pages[0].key, mockComponent('page-component')) - localVue.component(simpleSiteData.pages[0].key, mockComponent('page-component')) - - // data mixin - if (mode === 'i18n') { - localVue.mixin(dataMixin(i18nSiteData)) - } else { - localVue.mixin(dataMixin(simpleSiteData)) - } - - return localVue -} - -export function modeTestRunner (description, testFn, modes = ['simple', 'i18n']) { - if (!Array.isArray(modes)) { - modes = [modes] - } - modes.forEach(mode => { - describe(description, () => { - testFn(mode, getLocalVueByMode(mode)) - }) - }) -} diff --git a/yarn.lock b/yarn.lock index 3fa3cf59df..7a718c207d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,6 +14,12 @@ dependencies: "@babel/highlight" "7.0.0-beta.47" +"@babel/code-frame@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-rc.1.tgz#5c2154415d6c09959a71845ef519d11157e95d10" + dependencies: + "@babel/highlight" "7.0.0-rc.1" + "@babel/code-frame@^7.0.0-beta.35": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.54.tgz#0024f96fdf7028a21d68e273afd4e953214a1ead" @@ -40,6 +46,25 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/core@^7.0.0-beta.47": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.0.0-rc.1.tgz#53c84fd562e13325f123d5951184eec97b958204" + dependencies: + "@babel/code-frame" "7.0.0-rc.1" + "@babel/generator" "7.0.0-rc.1" + "@babel/helpers" "7.0.0-rc.1" + "@babel/parser" "7.0.0-rc.1" + "@babel/template" "7.0.0-rc.1" + "@babel/traverse" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + convert-source-map "^1.1.0" + debug "^3.1.0" + json5 "^0.5.0" + lodash "^4.17.10" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + "@babel/generator@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" @@ -60,12 +85,28 @@ source-map "^0.5.0" trim-right "^1.0.1" +"@babel/generator@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-rc.1.tgz#739c87d70b31aeed802bd6bc9fd51480065c45e8" + dependencies: + "@babel/types" "7.0.0-rc.1" + jsesc "^2.5.1" + lodash "^4.17.10" + source-map "^0.5.0" + trim-right "^1.0.1" + "@babel/helper-annotate-as-pure@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.47.tgz#354fb596055d9db369211bf075f0d5e93904d6f6" dependencies: "@babel/types" "7.0.0-beta.47" +"@babel/helper-annotate-as-pure@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-rc.1.tgz#4a9042a4a35f835d45c649f68f364cc7ed7dcb05" + dependencies: + "@babel/types" "7.0.0-rc.1" + "@babel/helper-builder-binary-assignment-operator-visitor@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-beta.47.tgz#d5917c29ee3d68abc2c72f604bc043f6e056e907" @@ -73,6 +114,13 @@ "@babel/helper-explode-assignable-expression" "7.0.0-beta.47" "@babel/types" "7.0.0-beta.47" +"@babel/helper-builder-binary-assignment-operator-visitor@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-rc.1.tgz#df64de2375585e23a0aaa5708ea137fb21157374" + dependencies: + "@babel/helper-explode-assignable-expression" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + "@babel/helper-call-delegate@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.47.tgz#96b7804397075f722a4030d3876f51ec19d8829b" @@ -81,6 +129,14 @@ "@babel/traverse" "7.0.0-beta.47" "@babel/types" "7.0.0-beta.47" +"@babel/helper-call-delegate@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-rc.1.tgz#7516f71b13c81560bb91fb6b1fae3a1e0345d37d" + dependencies: + "@babel/helper-hoist-variables" "7.0.0-rc.1" + "@babel/traverse" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + "@babel/helper-define-map@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.0.0-beta.47.tgz#43a9def87c5166dc29630d51b3da9cc4320c131c" @@ -89,6 +145,14 @@ "@babel/types" "7.0.0-beta.47" lodash "^4.17.5" +"@babel/helper-define-map@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.0.0-rc.1.tgz#a7f920b33651bc540253313b336864754926e75b" + dependencies: + "@babel/helper-function-name" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + lodash "^4.17.10" + "@babel/helper-explode-assignable-expression@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-beta.47.tgz#56b688e282a698f4d1cf135453a11ae8af870a19" @@ -96,6 +160,13 @@ "@babel/traverse" "7.0.0-beta.47" "@babel/types" "7.0.0-beta.47" +"@babel/helper-explode-assignable-expression@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-rc.1.tgz#114359f835a2d97161a895444e45b80317c6d765" + dependencies: + "@babel/traverse" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + "@babel/helper-function-name@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" @@ -112,6 +183,14 @@ "@babel/template" "7.0.0-beta.47" "@babel/types" "7.0.0-beta.47" +"@babel/helper-function-name@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-rc.1.tgz#20b2cc836a53c669f297c8d309fc553385c5cdde" + dependencies: + "@babel/helper-get-function-arity" "7.0.0-rc.1" + "@babel/template" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + "@babel/helper-get-function-arity@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" @@ -124,18 +203,36 @@ dependencies: "@babel/types" "7.0.0-beta.47" +"@babel/helper-get-function-arity@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-rc.1.tgz#60185957f72ed73766ce74c836ac574921743c46" + dependencies: + "@babel/types" "7.0.0-rc.1" + "@babel/helper-hoist-variables@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.47.tgz#ce295d1d723fe22b2820eaec748ed701aa5ae3d0" dependencies: "@babel/types" "7.0.0-beta.47" +"@babel/helper-hoist-variables@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-rc.1.tgz#6d0ff35d599fc7dd9dadaac444e99b7976238aec" + dependencies: + "@babel/types" "7.0.0-rc.1" + "@babel/helper-member-expression-to-functions@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0-beta.47.tgz#35bfcf1d16dce481ef3dec66d5a1ae6a7d80bb45" dependencies: "@babel/types" "7.0.0-beta.47" +"@babel/helper-member-expression-to-functions@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0-rc.1.tgz#03a3b200fc00f8100dbcef9a351b69cfc0234b4f" + dependencies: + "@babel/types" "7.0.0-rc.1" + "@babel/helper-module-imports@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.47.tgz#5af072029ffcfbece6ffbaf5d9984c75580f3f04" @@ -143,6 +240,13 @@ "@babel/types" "7.0.0-beta.47" lodash "^4.17.5" +"@babel/helper-module-imports@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0-rc.1.tgz#c6269fa9dc451152895f185f0339d45f32c52e75" + dependencies: + "@babel/types" "7.0.0-rc.1" + lodash "^4.17.10" + "@babel/helper-module-transforms@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.47.tgz#7eff91fc96873bd7b8d816698f1a69bbc01f3c38" @@ -154,22 +258,49 @@ "@babel/types" "7.0.0-beta.47" lodash "^4.17.5" +"@babel/helper-module-transforms@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-rc.1.tgz#15aa371352a37d527b233bd22d25f709ae5feaba" + dependencies: + "@babel/helper-module-imports" "7.0.0-rc.1" + "@babel/helper-simple-access" "7.0.0-rc.1" + "@babel/helper-split-export-declaration" "7.0.0-rc.1" + "@babel/template" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + lodash "^4.17.10" + "@babel/helper-optimise-call-expression@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.47.tgz#085d864d0613c5813c1b7c71b61bea36f195929e" dependencies: "@babel/types" "7.0.0-beta.47" +"@babel/helper-optimise-call-expression@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-rc.1.tgz#482d8251870f61d88c9800fd3e58128e14ff8c98" + dependencies: + "@babel/types" "7.0.0-rc.1" + "@babel/helper-plugin-utils@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-beta.47.tgz#4f564117ec39f96cf60fafcde35c9ddce0e008fd" +"@babel/helper-plugin-utils@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-rc.1.tgz#3e277eae59818e7d4caf4174f58a7a00d441336e" + "@babel/helper-regex@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0-beta.47.tgz#b8e3b53132c4edbb04804242c02ffe4d60316971" dependencies: lodash "^4.17.5" +"@babel/helper-regex@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0-rc.1.tgz#591bf828846d91fea8c93d1bf3030bd99dbd94ce" + dependencies: + lodash "^4.17.10" + "@babel/helper-remap-async-to-generator@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.47.tgz#444dc362f61470bd61a745ebb364431d9ca186c2" @@ -180,6 +311,16 @@ "@babel/traverse" "7.0.0-beta.47" "@babel/types" "7.0.0-beta.47" +"@babel/helper-remap-async-to-generator@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-rc.1.tgz#cc32d270ca868245d0ac0a32d70dc83a6ce77db9" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-rc.1" + "@babel/helper-wrap-function" "7.0.0-rc.1" + "@babel/template" "7.0.0-rc.1" + "@babel/traverse" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + "@babel/helper-replace-supers@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.47.tgz#310b206a302868a792b659455ceba27db686cbb7" @@ -189,6 +330,15 @@ "@babel/traverse" "7.0.0-beta.47" "@babel/types" "7.0.0-beta.47" +"@babel/helper-replace-supers@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-rc.1.tgz#cab8d7a6c758e4561fb285f4725c850d68c1c3db" + dependencies: + "@babel/helper-member-expression-to-functions" "7.0.0-rc.1" + "@babel/helper-optimise-call-expression" "7.0.0-rc.1" + "@babel/traverse" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + "@babel/helper-simple-access@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.47.tgz#234d754acbda9251a10db697ef50181eab125042" @@ -197,6 +347,14 @@ "@babel/types" "7.0.0-beta.47" lodash "^4.17.5" +"@babel/helper-simple-access@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.0.0-rc.1.tgz#ab3b179b5f009a1e17207b227c37410ad8d73949" + dependencies: + "@babel/template" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + lodash "^4.17.10" + "@babel/helper-split-export-declaration@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" @@ -209,6 +367,12 @@ dependencies: "@babel/types" "7.0.0-beta.47" +"@babel/helper-split-export-declaration@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-rc.1.tgz#b00323834343fd0210f1f46c7a53521ad53efa5e" + dependencies: + "@babel/types" "7.0.0-rc.1" + "@babel/helper-wrap-function@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.47.tgz#6528b44a3ccb4f3aeeb79add0a88192f7eb81161" @@ -218,6 +382,15 @@ "@babel/traverse" "7.0.0-beta.47" "@babel/types" "7.0.0-beta.47" +"@babel/helper-wrap-function@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-rc.1.tgz#168454fe350e9ead8d91cdc581597ea506e951ff" + dependencies: + "@babel/helper-function-name" "7.0.0-rc.1" + "@babel/template" "7.0.0-rc.1" + "@babel/traverse" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + "@babel/helpers@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.0.0-beta.47.tgz#f9b42ed2e4d5f75ec0fb2e792c173e451e8d40fd" @@ -226,6 +399,14 @@ "@babel/traverse" "7.0.0-beta.47" "@babel/types" "7.0.0-beta.47" +"@babel/helpers@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.0.0-rc.1.tgz#e59092cdf4b28026b3fc9d272e27e0ef152b4bee" + dependencies: + "@babel/template" "7.0.0-rc.1" + "@babel/traverse" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + "@babel/highlight@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" @@ -250,6 +431,18 @@ esutils "^2.0.2" js-tokens "^3.0.0" +"@babel/highlight@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-rc.1.tgz#e0ca4731fa4786f7b9500421d6ff5e5a7753e81e" + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +"@babel/parser@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.0.0-rc.1.tgz#d009a9bba8175d7b971e30cd03535b278c44082d" + "@babel/plugin-proposal-async-generator-functions@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-beta.47.tgz#571142284708c5ad4ec904d9aa705461a010be53" @@ -258,6 +451,14 @@ "@babel/helper-remap-async-to-generator" "7.0.0-beta.47" "@babel/plugin-syntax-async-generators" "7.0.0-beta.47" +"@babel/plugin-proposal-async-generator-functions@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-rc.1.tgz#70d4ca787485487370a82e380c39c8c233bca639" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-remap-async-to-generator" "7.0.0-rc.1" + "@babel/plugin-syntax-async-generators" "7.0.0-rc.1" + "@babel/plugin-proposal-class-properties@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.0.0-beta.47.tgz#08c1a1dfc92d0f5c37b39096c6fb883e1ca4b0f5" @@ -303,6 +504,13 @@ "@babel/helper-plugin-utils" "7.0.0-beta.47" "@babel/plugin-syntax-object-rest-spread" "7.0.0-beta.47" +"@babel/plugin-proposal-object-rest-spread@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-rc.1.tgz#bc7ce898a48831fd733b251fd5ae46f986c905d8" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-syntax-object-rest-spread" "7.0.0-rc.1" + "@babel/plugin-proposal-optional-catch-binding@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0-beta.47.tgz#8c6453919537517ea773bb8f3fceda4250795efa" @@ -310,6 +518,13 @@ "@babel/helper-plugin-utils" "7.0.0-beta.47" "@babel/plugin-syntax-optional-catch-binding" "7.0.0-beta.47" +"@babel/plugin-proposal-optional-catch-binding@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0-rc.1.tgz#4ee80c9e4b6feb4c0c737bd996da3ee3fb9837d2" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-syntax-optional-catch-binding" "7.0.0-rc.1" + "@babel/plugin-proposal-throw-expressions@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.0.0-beta.47.tgz#9a67f8b0852b4b0b255eff5d6d25fa436928424f" @@ -325,12 +540,26 @@ "@babel/helper-regex" "7.0.0-beta.47" regexpu-core "^4.1.4" +"@babel/plugin-proposal-unicode-property-regex@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0-rc.1.tgz#02d0c33839eb52c93164907fb43b36c5a4afbc6c" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-regex" "7.0.0-rc.1" + regexpu-core "^4.2.0" + "@babel/plugin-syntax-async-generators@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-beta.47.tgz#8ab94852bf348badc866af85bd852221f0961256" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-syntax-async-generators@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-rc.1.tgz#71d016f1a241d5e735b120f6cb94b8c57d53d255" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-syntax-class-properties@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0-beta.47.tgz#de52bed12fd472c848e1562f57dd4a202fe27f11" @@ -385,12 +614,24 @@ dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-syntax-object-rest-spread@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-rc.1.tgz#42032fd87fb3b18f5686a0ab957d7f6f0db26618" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-syntax-optional-catch-binding@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0-beta.47.tgz#0b1c52b066aa36893c41450773a5adb904cd4024" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-syntax-optional-catch-binding@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0-rc.1.tgz#c125fedf2fe59e4b510c202b1a912634d896fbb8" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-syntax-throw-expressions@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.0.0-beta.47.tgz#8ca197bab3534f443eecd7eb79da47e199dafaf7" @@ -403,6 +644,12 @@ dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-arrow-functions@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-rc.1.tgz#95b369e6ded8425a00464609d29e1fd017b331b0" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-async-to-generator@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-beta.47.tgz#5723816ea1e91fa313a84e6ee9cc12ff31d46610" @@ -411,12 +658,26 @@ "@babel/helper-plugin-utils" "7.0.0-beta.47" "@babel/helper-remap-async-to-generator" "7.0.0-beta.47" +"@babel/plugin-transform-async-to-generator@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-rc.1.tgz#9e22abec137ded152e83c3aebb4d4fb1ad7cba59" + dependencies: + "@babel/helper-module-imports" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-remap-async-to-generator" "7.0.0-rc.1" + "@babel/plugin-transform-block-scoped-functions@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-beta.47.tgz#e422278e06c797b43c45f459d83c7af9d6237002" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-block-scoped-functions@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-rc.1.tgz#1b23adf0fb3a7395f6f0596a80039cfba6516750" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-block-scoping@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.47.tgz#b737cc58a81bea57efd5bda0baef9a43a25859ad" @@ -424,6 +685,13 @@ "@babel/helper-plugin-utils" "7.0.0-beta.47" lodash "^4.17.5" +"@babel/plugin-transform-block-scoping@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-rc.1.tgz#1a61565131ffd1022c04f9d3bcc4bdececf17859" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + lodash "^4.17.10" + "@babel/plugin-transform-classes@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.0.0-beta.47.tgz#7aff9cbe7b26fd94d7a9f97fa90135ef20c93fb6" @@ -437,18 +705,43 @@ "@babel/helper-split-export-declaration" "7.0.0-beta.47" globals "^11.1.0" +"@babel/plugin-transform-classes@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.0.0-rc.1.tgz#1d73cbceb4b4adca4cdad5f8f84a5c517fc0e06d" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-rc.1" + "@babel/helper-define-map" "7.0.0-rc.1" + "@babel/helper-function-name" "7.0.0-rc.1" + "@babel/helper-optimise-call-expression" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-replace-supers" "7.0.0-rc.1" + "@babel/helper-split-export-declaration" "7.0.0-rc.1" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.47.tgz#56ef2a021769a2b65e90a3e12fd10b791da9f3e0" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-computed-properties@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-rc.1.tgz#767c6e54e6928de6f1f4de341cee1ec58edce1cf" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-destructuring@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.47.tgz#452b607775fd1c4d10621997837189efc0a6d428" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-destructuring@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-rc.1.tgz#d72932088542ae1c11188cb36d58cd18ddd55aa8" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-dotall-regex@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0-beta.47.tgz#d8da9b706d4bfc68dec9d565661f83e6e8036636" @@ -457,12 +750,26 @@ "@babel/helper-regex" "7.0.0-beta.47" regexpu-core "^4.1.3" +"@babel/plugin-transform-dotall-regex@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0-rc.1.tgz#3209d77c7905883482ff9d527c2f96d0db83df0a" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-regex" "7.0.0-rc.1" + regexpu-core "^4.1.3" + "@babel/plugin-transform-duplicate-keys@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-beta.47.tgz#4aabeda051ca3007e33a207db08f1a0cf9bd253b" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-duplicate-keys@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-rc.1.tgz#59d0c76877720446f83f1fbbad7c33670c5b19b9" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-exponentiation-operator@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-beta.47.tgz#930e1abf5db9f4db5b63dbf97f3581ad0be1e907" @@ -470,12 +777,25 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "7.0.0-beta.47" "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-exponentiation-operator@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-rc.1.tgz#b8a7b7862a1e3b14510ad60e496ce5b54c2220d1" + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-for-of@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.47.tgz#527d5dc24e4a4ad0fc1d0a3990d29968cb984e76" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-for-of@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-rc.1.tgz#1ad4f8986003f38db9251fb694c4f86657e9ec18" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-function-name@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.47.tgz#fb443c81cc77f3206a863b730b35c8c553ce5041" @@ -483,12 +803,25 @@ "@babel/helper-function-name" "7.0.0-beta.47" "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-function-name@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-rc.1.tgz#e61149309db0d74df4ea3a566aac7b8794520e2d" + dependencies: + "@babel/helper-function-name" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-literals@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.47.tgz#448fad196f062163684a38f10f14e83315892e9c" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-literals@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-rc.1.tgz#314e118e99574ab5292aea92136c26e3dc8c4abb" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-modules-amd@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-beta.47.tgz#84564419b11c1be6b9fcd4c7b3a6737f2335aac4" @@ -496,6 +829,13 @@ "@babel/helper-module-transforms" "7.0.0-beta.47" "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-modules-amd@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-rc.1.tgz#3f7d83c9ecf0bf5733748e119696cc50ae05987f" + dependencies: + "@babel/helper-module-transforms" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-modules-commonjs@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.47.tgz#dfe5c6d867aa9614e55f7616736073edb3aab887" @@ -504,6 +844,14 @@ "@babel/helper-plugin-utils" "7.0.0-beta.47" "@babel/helper-simple-access" "7.0.0-beta.47" +"@babel/plugin-transform-modules-commonjs@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-rc.1.tgz#475bd3e6c3b86bb38307f715e0cbdb6cb2f431c2" + dependencies: + "@babel/helper-module-transforms" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-simple-access" "7.0.0-rc.1" + "@babel/plugin-transform-modules-systemjs@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0-beta.47.tgz#8514dbcdfca3345abd690059e7e8544e16ecbf05" @@ -511,6 +859,13 @@ "@babel/helper-hoist-variables" "7.0.0-beta.47" "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-modules-systemjs@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0-rc.1.tgz#6aca100a57c49e2622f29f177a3e088cc50ecd2e" + dependencies: + "@babel/helper-hoist-variables" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-modules-umd@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.0.0-beta.47.tgz#6dcfb9661fdd131b20b721044746a7a309882918" @@ -518,12 +873,25 @@ "@babel/helper-module-transforms" "7.0.0-beta.47" "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-modules-umd@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.0.0-rc.1.tgz#1a584cb37d252de63c90030f76c3d7d3d0ea1241" + dependencies: + "@babel/helper-module-transforms" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-new-target@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0-beta.47.tgz#4b5cb7ce30d7bffa105a1f43ed07d6ae206a4155" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-new-target@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0-rc.1.tgz#e5839320686b3c97b82bd24157282565503ae569" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-object-super@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-beta.47.tgz#ca8e5f326c5011c879f3a6ed749e58bd10fff05d" @@ -531,6 +899,13 @@ "@babel/helper-plugin-utils" "7.0.0-beta.47" "@babel/helper-replace-supers" "7.0.0-beta.47" +"@babel/plugin-transform-object-super@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-rc.1.tgz#03ffbcce806af7546fead73cecb43c0892b809f3" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-replace-supers" "7.0.0-rc.1" + "@babel/plugin-transform-parameters@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.47.tgz#46a4236040a6552a5f165fb3ddd60368954b0ddd" @@ -539,12 +914,26 @@ "@babel/helper-get-function-arity" "7.0.0-beta.47" "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-parameters@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-rc.1.tgz#c3f2f1fe179b58c968b3253cb412c8d83a3d5abc" + dependencies: + "@babel/helper-call-delegate" "7.0.0-rc.1" + "@babel/helper-get-function-arity" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-regenerator@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.47.tgz#86500e1c404055fb98fc82b73b09bd053cacb516" dependencies: regenerator-transform "^0.12.3" +"@babel/plugin-transform-regenerator@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-rc.1.tgz#8c5488ab75b7c9004d8bcf3f48a5814f946b5bb0" + dependencies: + regenerator-transform "^0.13.3" + "@babel/plugin-transform-runtime@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.0.0-beta.47.tgz#1700938fa8710909cbf28f7dd39f9b40688b09fd" @@ -558,12 +947,24 @@ dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-shorthand-properties@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-rc.1.tgz#21724d2199d988ffad690de8dbdce8b834a7f313" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-spread@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.47.tgz#3feadb02292ed1e9b75090d651b9df88a7ab5c50" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-spread@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-rc.1.tgz#3ad6d96f42175ecf7c03d92313fa1f5c24a69637" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-sticky-regex@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.47.tgz#c0aa347d76b5dc87d3b37ac016ada3f950605131" @@ -571,6 +972,13 @@ "@babel/helper-plugin-utils" "7.0.0-beta.47" "@babel/helper-regex" "7.0.0-beta.47" +"@babel/plugin-transform-sticky-regex@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-rc.1.tgz#88079689a70d80c8e9b159572979a9c2b80f7c38" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-regex" "7.0.0-rc.1" + "@babel/plugin-transform-template-literals@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.47.tgz#5f7b5badf64c4c5da79026aeab03001e62a6ee5f" @@ -578,12 +986,25 @@ "@babel/helper-annotate-as-pure" "7.0.0-beta.47" "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-template-literals@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-rc.1.tgz#c22533ce23554a0d596b208158b34b9975feb9e6" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-typeof-symbol@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-beta.47.tgz#03c612ec09213eb386a81d5fa67c234ee4b2034c" dependencies: "@babel/helper-plugin-utils" "7.0.0-beta.47" +"@babel/plugin-transform-typeof-symbol@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-rc.1.tgz#51c628dfcd2a5b6c1792b90e4f2f24b7eb993389" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-transform-unicode-regex@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-beta.47.tgz#efed0b2f1dfbf28283502234a95b4be88f7fdcb6" @@ -592,6 +1013,14 @@ "@babel/helper-regex" "7.0.0-beta.47" regexpu-core "^4.1.3" +"@babel/plugin-transform-unicode-regex@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-rc.1.tgz#b6c77bdb9a2823108210a174318ddd3c1ab6f3ce" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/helper-regex" "7.0.0-rc.1" + regexpu-core "^4.1.3" + "@babel/preset-env@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.0.0-beta.47.tgz#a3dab3b5fac4de56e3510bdbcb528f1cbdedbe2d" @@ -636,6 +1065,51 @@ invariant "^2.2.2" semver "^5.3.0" +"@babel/preset-env@^7.0.0-beta.47": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.0.0-rc.1.tgz#cb87a82fd3e44005219cd9f1cb3e9fdba907aae5" + dependencies: + "@babel/helper-module-imports" "7.0.0-rc.1" + "@babel/helper-plugin-utils" "7.0.0-rc.1" + "@babel/plugin-proposal-async-generator-functions" "7.0.0-rc.1" + "@babel/plugin-proposal-object-rest-spread" "7.0.0-rc.1" + "@babel/plugin-proposal-optional-catch-binding" "7.0.0-rc.1" + "@babel/plugin-proposal-unicode-property-regex" "7.0.0-rc.1" + "@babel/plugin-syntax-async-generators" "7.0.0-rc.1" + "@babel/plugin-syntax-object-rest-spread" "7.0.0-rc.1" + "@babel/plugin-syntax-optional-catch-binding" "7.0.0-rc.1" + "@babel/plugin-transform-arrow-functions" "7.0.0-rc.1" + "@babel/plugin-transform-async-to-generator" "7.0.0-rc.1" + "@babel/plugin-transform-block-scoped-functions" "7.0.0-rc.1" + "@babel/plugin-transform-block-scoping" "7.0.0-rc.1" + "@babel/plugin-transform-classes" "7.0.0-rc.1" + "@babel/plugin-transform-computed-properties" "7.0.0-rc.1" + "@babel/plugin-transform-destructuring" "7.0.0-rc.1" + "@babel/plugin-transform-dotall-regex" "7.0.0-rc.1" + "@babel/plugin-transform-duplicate-keys" "7.0.0-rc.1" + "@babel/plugin-transform-exponentiation-operator" "7.0.0-rc.1" + "@babel/plugin-transform-for-of" "7.0.0-rc.1" + "@babel/plugin-transform-function-name" "7.0.0-rc.1" + "@babel/plugin-transform-literals" "7.0.0-rc.1" + "@babel/plugin-transform-modules-amd" "7.0.0-rc.1" + "@babel/plugin-transform-modules-commonjs" "7.0.0-rc.1" + "@babel/plugin-transform-modules-systemjs" "7.0.0-rc.1" + "@babel/plugin-transform-modules-umd" "7.0.0-rc.1" + "@babel/plugin-transform-new-target" "7.0.0-rc.1" + "@babel/plugin-transform-object-super" "7.0.0-rc.1" + "@babel/plugin-transform-parameters" "7.0.0-rc.1" + "@babel/plugin-transform-regenerator" "7.0.0-rc.1" + "@babel/plugin-transform-shorthand-properties" "7.0.0-rc.1" + "@babel/plugin-transform-spread" "7.0.0-rc.1" + "@babel/plugin-transform-sticky-regex" "7.0.0-rc.1" + "@babel/plugin-transform-template-literals" "7.0.0-rc.1" + "@babel/plugin-transform-typeof-symbol" "7.0.0-rc.1" + "@babel/plugin-transform-unicode-regex" "7.0.0-rc.1" + browserslist "^3.0.0" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.3.0" + "@babel/preset-stage-2@7.0.0-beta.47": version "7.0.0-beta.47" resolved "https://registry.yarnpkg.com/@babel/preset-stage-2/-/preset-stage-2-7.0.0-beta.47.tgz#deb930c44d7d6e519a33174bba121a2a630ed654" @@ -686,6 +1160,15 @@ babylon "7.0.0-beta.47" lodash "^4.17.5" +"@babel/template@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-rc.1.tgz#5f9c0a481c9f22ecdb84697b3c3a34eadeeca23c" + dependencies: + "@babel/code-frame" "7.0.0-rc.1" + "@babel/parser" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + lodash "^4.17.10" + "@babel/traverse@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" @@ -716,6 +1199,20 @@ invariant "^2.2.0" lodash "^4.17.5" +"@babel/traverse@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-rc.1.tgz#867b4b45ada2d51ae2d0076f1c1d5880f8557158" + dependencies: + "@babel/code-frame" "7.0.0-rc.1" + "@babel/generator" "7.0.0-rc.1" + "@babel/helper-function-name" "7.0.0-rc.1" + "@babel/helper-split-export-declaration" "7.0.0-rc.1" + "@babel/parser" "7.0.0-rc.1" + "@babel/types" "7.0.0-rc.1" + debug "^3.1.0" + globals "^11.1.0" + lodash "^4.17.10" + "@babel/types@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" @@ -732,6 +1229,14 @@ lodash "^4.17.5" to-fast-properties "^2.0.0" +"@babel/types@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-rc.1.tgz#6abf6d14ddd9fc022617e5b62e6b32f4fa6526ad" + dependencies: + esutils "^2.0.2" + lodash "^4.17.10" + to-fast-properties "^2.0.0" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -800,6 +1305,14 @@ source-map "^0.5.6" vue-template-es2015-compiler "^1.6.0" +"@vue/conventional-changelog@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@vue/conventional-changelog/-/conventional-changelog-0.1.1.tgz#48d2227ca65c354cba4be60754ea531afd0c3718" + dependencies: + compare-func "^1.3.2" + execa "^0.10.0" + q "^1.5.1" + "@vue/test-utils@^1.0.0-beta.16": version "1.0.0-beta.21" resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.21.tgz#fe1ee11ce16072da7ef29420df4aa5c11f4560ff" @@ -1265,7 +1778,7 @@ async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" -async@^1.4.0, async@^1.5.2: +async@^1.4.0, async@^1.5.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -1390,9 +1903,9 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^23.0.0, babel-jest@^23.4.0: - version "23.4.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.4.0.tgz#22c34c392e2176f6a4c367992a7fcff69d2e8557" +babel-jest@^23.4.0, babel-jest@^23.4.2: + version "23.4.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.4.2.tgz#f276de67798a5d68f2d6e87ff518c2f6e1609877" dependencies: babel-plugin-istanbul "^4.1.6" babel-preset-jest "^23.2.0" @@ -1735,6 +2248,10 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" +byline@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + cacache@^10.0.4: version "10.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" @@ -1888,6 +2405,10 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + chokidar@^2.0.2, chokidar@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" @@ -2007,6 +2528,14 @@ cliui@^2.1.0: right-align "^0.1.1" wordwrap "0.0.2" +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + cliui@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" @@ -2023,6 +2552,13 @@ clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" +cmd-shim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb" + dependencies: + graceful-fs "^4.1.2" + mkdirp "~0.5.0" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -2084,12 +2620,23 @@ colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +columnify@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" + dependencies: + strip-ansi "^3.0.0" + wcwidth "^1.0.0" + combined-stream@1.0.6, combined-stream@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: delayed-stream "~1.0.0" +command-join@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/command-join/-/command-join-2.0.0.tgz#52e8b984f4872d952ff1bdc8b98397d27c7144cf" + commander@2.15.x, commander@^2.14.1, commander@^2.9.0, commander@~2.15.0: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" @@ -2110,7 +2657,7 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" -compare-func@^1.3.1: +compare-func@^1.3.1, compare-func@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648" dependencies: @@ -2129,7 +2676,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.5.0, concat-stream@^1.6.0: +concat-stream@^1.4.10, concat-stream@^1.5.0, concat-stream@^1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" dependencies: @@ -2218,7 +2765,7 @@ conventional-changelog-atom@^0.2.8: dependencies: q "^1.5.1" -conventional-changelog-cli@^1.3.22: +conventional-changelog-cli@^1.3.13, conventional-changelog-cli@^1.3.22: version "1.3.22" resolved "https://registry.yarnpkg.com/conventional-changelog-cli/-/conventional-changelog-cli-1.3.22.tgz#13570fe1728f56f013ff7a88878ff49d5162a405" dependencies: @@ -2324,14 +2871,14 @@ conventional-changelog@^1.1.24: conventional-changelog-jshint "^0.3.8" conventional-changelog-preset-loader "^1.1.8" -conventional-commits-filter@^1.1.6: +conventional-commits-filter@^1.1.1, conventional-commits-filter@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz#4389cd8e58fe89750c0b5fb58f1d7f0cc8ad3831" dependencies: is-subset "^0.1.1" modify-values "^1.0.0" -conventional-commits-parser@^2.1.7: +conventional-commits-parser@^2.1.1, conventional-commits-parser@^2.1.7: version "2.1.7" resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz#eca45ed6140d72ba9722ee4132674d639e644e8e" dependencies: @@ -2343,6 +2890,18 @@ conventional-commits-parser@^2.1.7: through2 "^2.0.0" trim-off-newlines "^1.0.0" +conventional-recommended-bump@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-1.2.1.tgz#1b7137efb5091f99fe009e2fe9ddb7cc490e9375" + dependencies: + concat-stream "^1.4.10" + conventional-commits-filter "^1.1.1" + conventional-commits-parser "^2.1.1" + git-raw-commits "^1.3.0" + git-semver-tags "^1.3.0" + meow "^3.3.0" + object-assign "^4.0.1" + convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" @@ -2449,7 +3008,7 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^6.0.5: +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" dependencies: @@ -2699,6 +3258,12 @@ default-require-extensions@^2.0.0: dependencies: strip-bom "^3.0.0" +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + dependencies: + clone "^1.0.2" + define-properties@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" @@ -2774,6 +3339,10 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" @@ -2890,6 +3459,10 @@ duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" +duplexer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + duplexify@^3.4.2, duplexify@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" @@ -3195,6 +3768,18 @@ exec-sh@^0.2.0: dependencies: merge "^1.2.0" +execa@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" + dependencies: + cross-spawn "^6.0.0" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" @@ -3263,14 +3848,14 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" -expect@^23.4.0: - version "23.4.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-23.4.0.tgz#6da4ecc99c1471253e7288338983ad1ebadb60c3" +expect@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-23.5.0.tgz#18999a0eef8f8acf99023fde766d9c323c2562ed" dependencies: ansi-styles "^3.2.0" - jest-diff "^23.2.0" + jest-diff "^23.5.0" jest-get-type "^22.1.0" - jest-matcher-utils "^23.2.0" + jest-matcher-utils "^23.5.0" jest-message-util "^23.4.0" jest-regex-util "^23.3.0" @@ -3299,6 +3884,14 @@ external-editor@^2.0.4: iconv-lite "^0.4.17" tmp "^0.0.33" +external-editor@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -3521,7 +4114,7 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-extra@^4.0.2: +fs-extra@^4.0.1, fs-extra@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" dependencies: @@ -3624,7 +4217,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -git-raw-commits@^1.3.6: +git-raw-commits@^1.3.0, git-raw-commits@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-1.3.6.tgz#27c35a32a67777c1ecd412a239a6c19d71b95aff" dependencies: @@ -3641,7 +4234,7 @@ git-remote-origin-url@^2.0.0: gitconfiglocal "^1.0.0" pify "^2.3.0" -git-semver-tags@^1.3.6: +git-semver-tags@^1.3.0, git-semver-tags@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-1.3.6.tgz#357ea01f7280794fe0927f2806bee6414d2caba5" dependencies: @@ -3732,6 +4325,16 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + globby@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" @@ -3918,7 +4521,7 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -hosted-git-info@^2.1.4: +hosted-git-info@^2.1.4, hosted-git-info@^2.5.0: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" @@ -3991,6 +4594,12 @@ iconv-lite@^0.4.17, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + dependencies: + safer-buffer ">= 2.1.2 < 3" + icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" @@ -4091,7 +4700,7 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -inquirer@^3.0.6: +inquirer@^3.0.6, inquirer@^3.2.2: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" dependencies: @@ -4110,7 +4719,25 @@ inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" -invariant@^2.2.0, invariant@^2.2.2: +inquirer@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.0.tgz#51adcd776f661369dc1e894859c2560a224abdd8" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.0" + figures "^2.0.0" + lodash "^4.17.10" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.1.0" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +invariant@^2.2.0, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -4521,15 +5148,15 @@ javascript-stringify@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-1.6.0.tgz#142d111f3a6e3dae8f4a9afd77d45855b5a9cce3" -jest-changed-files@^23.4.0: - version "23.4.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.0.tgz#f1b304f98c235af5d9a31ec524262c5e4de3c6ff" +jest-changed-files@^23.4.2: + version "23.4.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83" dependencies: throat "^4.0.0" -jest-cli@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.4.1.tgz#c1ffd33254caee376990aa2abe2963e0de4ca76b" +jest-cli@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.5.0.tgz#d316b8e34a38a610a1efc4f0403d8ef8a55e4492" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.1" @@ -4542,19 +5169,19 @@ jest-cli@^23.4.1: istanbul-lib-coverage "^1.2.0" istanbul-lib-instrument "^1.10.1" istanbul-lib-source-maps "^1.2.4" - jest-changed-files "^23.4.0" - jest-config "^23.4.1" + jest-changed-files "^23.4.2" + jest-config "^23.5.0" jest-environment-jsdom "^23.4.0" jest-get-type "^22.1.0" - jest-haste-map "^23.4.1" + jest-haste-map "^23.5.0" jest-message-util "^23.4.0" jest-regex-util "^23.3.0" - jest-resolve-dependencies "^23.4.1" - jest-runner "^23.4.1" - jest-runtime "^23.4.1" - jest-snapshot "^23.4.1" + jest-resolve-dependencies "^23.5.0" + jest-runner "^23.5.0" + jest-runtime "^23.5.0" + jest-snapshot "^23.5.0" jest-util "^23.4.0" - jest-validate "^23.4.0" + jest-validate "^23.5.0" jest-watcher "^23.4.0" jest-worker "^23.2.0" micromatch "^2.3.11" @@ -4568,32 +5195,33 @@ jest-cli@^23.4.1: which "^1.2.12" yargs "^11.0.0" -jest-config@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.4.1.tgz#3172fa21f0507d7f8a088ed1dbe4157057f201e9" +jest-config@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.5.0.tgz#3770fba03f7507ee15f3b8867c742e48f31a9773" dependencies: babel-core "^6.0.0" - babel-jest "^23.4.0" + babel-jest "^23.4.2" chalk "^2.0.1" glob "^7.1.1" jest-environment-jsdom "^23.4.0" jest-environment-node "^23.4.0" jest-get-type "^22.1.0" - jest-jasmine2 "^23.4.1" + jest-jasmine2 "^23.5.0" jest-regex-util "^23.3.0" - jest-resolve "^23.4.1" + jest-resolve "^23.5.0" jest-util "^23.4.0" - jest-validate "^23.4.0" - pretty-format "^23.2.0" + jest-validate "^23.5.0" + micromatch "^2.3.11" + pretty-format "^23.5.0" -jest-diff@^23.2.0: - version "23.2.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.2.0.tgz#9f2cf4b51e12c791550200abc16b47130af1062a" +jest-diff@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.5.0.tgz#250651a433dd0050290a07642946cc9baaf06fba" dependencies: chalk "^2.0.1" diff "^3.2.0" jest-get-type "^22.1.0" - pretty-format "^23.2.0" + pretty-format "^23.5.0" jest-docblock@^23.2.0: version "23.2.0" @@ -4601,12 +5229,12 @@ jest-docblock@^23.2.0: dependencies: detect-newline "^2.1.0" -jest-each@^23.4.0: - version "23.4.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.4.0.tgz#2fa9edd89daa1a4edc9ff9bf6062a36b71345143" +jest-each@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.5.0.tgz#77f7e2afe6132a80954b920006e78239862b10ba" dependencies: chalk "^2.0.1" - pretty-format "^23.2.0" + pretty-format "^23.5.0" jest-environment-jsdom@^23.4.0: version "23.4.0" @@ -4627,47 +5255,49 @@ jest-get-type@^22.1.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" -jest-haste-map@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.4.1.tgz#43a174ba7ac079ae1dd74eaf5a5fe78989474dd2" +jest-haste-map@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.5.0.tgz#d4ca618188bd38caa6cb20349ce6610e194a8065" dependencies: fb-watchman "^2.0.0" graceful-fs "^4.1.11" + invariant "^2.2.4" jest-docblock "^23.2.0" jest-serializer "^23.0.1" jest-worker "^23.2.0" micromatch "^2.3.11" sane "^2.0.0" -jest-jasmine2@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.4.1.tgz#fa192262430d418e827636e4a98423e5e7ff0fce" +jest-jasmine2@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.5.0.tgz#05fe7f1788e650eeb5a03929e6461ea2e9f3db53" dependencies: + babel-traverse "^6.0.0" chalk "^2.0.1" co "^4.6.0" - expect "^23.4.0" + expect "^23.5.0" is-generator-fn "^1.0.0" - jest-diff "^23.2.0" - jest-each "^23.4.0" - jest-matcher-utils "^23.2.0" + jest-diff "^23.5.0" + jest-each "^23.5.0" + jest-matcher-utils "^23.5.0" jest-message-util "^23.4.0" - jest-snapshot "^23.4.1" + jest-snapshot "^23.5.0" jest-util "^23.4.0" - pretty-format "^23.2.0" + pretty-format "^23.5.0" -jest-leak-detector@^23.2.0: - version "23.2.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.2.0.tgz#c289d961dc638f14357d4ef96e0431ecc1aa377d" +jest-leak-detector@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.5.0.tgz#14ac2a785bd625160a2ea968fd5d98b7dcea3e64" dependencies: - pretty-format "^23.2.0" + pretty-format "^23.5.0" -jest-matcher-utils@^23.2.0: - version "23.2.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.2.0.tgz#4d4981f23213e939e3cedf23dc34c747b5ae1913" +jest-matcher-utils@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.5.0.tgz#0e2ea67744cab78c9ab15011c4d888bdd3e49e2a" dependencies: chalk "^2.0.1" jest-get-type "^22.1.0" - pretty-format "^23.2.0" + pretty-format "^23.5.0" jest-message-util@^23.4.0: version "23.4.0" @@ -4687,42 +5317,42 @@ jest-regex-util@^23.3.0: version "23.3.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5" -jest-resolve-dependencies@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.4.1.tgz#a1d85247e2963f8b3859f6b0ec61b741b359378e" +jest-resolve-dependencies@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.5.0.tgz#10c4d135beb9d2256de1fedc7094916c3ad74af7" dependencies: jest-regex-util "^23.3.0" - jest-snapshot "^23.4.1" + jest-snapshot "^23.5.0" -jest-resolve@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.4.1.tgz#7f3c17104732a2c0c940a01256025ed745814982" +jest-resolve@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.5.0.tgz#3b8e7f67e84598f0caf63d1530bd8534a189d0e6" dependencies: browser-resolve "^1.11.3" chalk "^2.0.1" realpath-native "^1.0.0" -jest-runner@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.4.1.tgz#d41fd1459b95d35d6df685f1468c09e617c8c260" +jest-runner@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.5.0.tgz#570f7a044da91648b5bb9b6baacdd511076c71d7" dependencies: exit "^0.1.2" graceful-fs "^4.1.11" - jest-config "^23.4.1" + jest-config "^23.5.0" jest-docblock "^23.2.0" - jest-haste-map "^23.4.1" - jest-jasmine2 "^23.4.1" - jest-leak-detector "^23.2.0" + jest-haste-map "^23.5.0" + jest-jasmine2 "^23.5.0" + jest-leak-detector "^23.5.0" jest-message-util "^23.4.0" - jest-runtime "^23.4.1" + jest-runtime "^23.5.0" jest-util "^23.4.0" jest-worker "^23.2.0" source-map-support "^0.5.6" throat "^4.0.0" -jest-runtime@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.4.1.tgz#c1822eba5eb19294debe6b25b2760d0a8c532fd1" +jest-runtime@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.5.0.tgz#eb503525a196dc32f2f9974e3482d26bdf7b63ce" dependencies: babel-core "^6.0.0" babel-plugin-istanbul "^4.1.6" @@ -4731,14 +5361,14 @@ jest-runtime@^23.4.1: exit "^0.1.2" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.1.11" - jest-config "^23.4.1" - jest-haste-map "^23.4.1" + jest-config "^23.5.0" + jest-haste-map "^23.5.0" jest-message-util "^23.4.0" jest-regex-util "^23.3.0" - jest-resolve "^23.4.1" - jest-snapshot "^23.4.1" + jest-resolve "^23.5.0" + jest-snapshot "^23.5.0" jest-util "^23.4.0" - jest-validate "^23.4.0" + jest-validate "^23.5.0" micromatch "^2.3.11" realpath-native "^1.0.0" slash "^1.0.0" @@ -4756,20 +5386,19 @@ jest-serializer@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165" -jest-snapshot@^23.4.1: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.4.1.tgz#090de9acae927f6a3af3005bda40d912b83e9c96" +jest-snapshot@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.5.0.tgz#cc368ebd8513e1175e2a7277f37a801b7358ae79" dependencies: - babel-traverse "^6.0.0" babel-types "^6.0.0" chalk "^2.0.1" - jest-diff "^23.2.0" - jest-matcher-utils "^23.2.0" + jest-diff "^23.5.0" + jest-matcher-utils "^23.5.0" jest-message-util "^23.4.0" - jest-resolve "^23.4.1" + jest-resolve "^23.5.0" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^23.2.0" + pretty-format "^23.5.0" semver "^5.5.0" jest-util@^23.4.0: @@ -4794,14 +5423,14 @@ jest-validate@^23.0.0: leven "^2.1.0" pretty-format "^23.0.1" -jest-validate@^23.4.0: - version "23.4.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.4.0.tgz#d96eede01ef03ac909c009e9c8e455197d48c201" +jest-validate@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.5.0.tgz#f5df8f761cf43155e1b2e21d6e9de8a2852d0231" dependencies: chalk "^2.0.1" jest-get-type "^22.1.0" leven "^2.1.0" - pretty-format "^23.2.0" + pretty-format "^23.5.0" jest-watcher@^23.4.0: version "23.4.0" @@ -4817,12 +5446,12 @@ jest-worker@^23.2.0: dependencies: merge-stream "^1.0.1" -jest@^23.0.0: - version "23.4.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-23.4.1.tgz#39550c72f3237f63ae1b434d8d122cdf6fa007b6" +jest@^23.4.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-23.5.0.tgz#80de353d156ea5ea4a7332f7962ac79135fbc62e" dependencies: import-local "^1.0.0" - jest-cli "^23.4.1" + jest-cli "^23.5.0" joi@^11.1.1: version "11.4.0" @@ -4845,6 +5474,10 @@ js-beautify@^1.6.12, js-beautify@^1.6.14: mkdirp "~0.5.0" nopt "~3.0.1" +js-levenshtein@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.3.tgz#3ef627df48ec8cf24bacf05c0f184ff30ef413c5" + js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -5113,6 +5746,50 @@ left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" +lerna@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-2.11.0.tgz#89b5681e286d388dda5bbbdbbf6b84c8094eff65" + dependencies: + async "^1.5.0" + chalk "^2.1.0" + cmd-shim "^2.0.2" + columnify "^1.5.4" + command-join "^2.0.0" + conventional-changelog-cli "^1.3.13" + conventional-recommended-bump "^1.2.1" + dedent "^0.7.0" + execa "^0.8.0" + find-up "^2.1.0" + fs-extra "^4.0.1" + get-port "^3.2.0" + glob "^7.1.2" + glob-parent "^3.1.0" + globby "^6.1.0" + graceful-fs "^4.1.11" + hosted-git-info "^2.5.0" + inquirer "^3.2.2" + is-ci "^1.0.10" + load-json-file "^4.0.0" + lodash "^4.17.4" + minimatch "^3.0.4" + npmlog "^4.1.2" + p-finally "^1.0.0" + package-json "^4.0.1" + path-exists "^3.0.0" + read-cmd-shim "^1.0.1" + read-pkg "^3.0.0" + rimraf "^2.6.1" + safe-buffer "^5.1.1" + semver "^5.4.1" + signal-exit "^3.0.2" + slash "^1.0.0" + strong-log-transformer "^1.0.6" + temp-write "^3.3.0" + write-file-atomic "^2.3.0" + write-json-file "^2.2.0" + write-pkg "^3.1.0" + yargs "^8.0.2" + leven@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" @@ -5215,6 +5892,15 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -5423,6 +6109,12 @@ markdown-it-anchor@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-5.0.2.tgz#cdd917a05b7bf92fb736a6dae3385c6d0d0fa552" +markdown-it-chain@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/markdown-it-chain/-/markdown-it-chain-1.2.0.tgz#b11db6755a3d2ab3da89e9566fc3ac384f75fdac" + dependencies: + webpack-chain "^4.9.0" + markdown-it-container@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-2.0.0.tgz#0019b43fd02eefece2f1960a2895fba81a404695" @@ -5468,6 +6160,10 @@ media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" +medium-zoom@^0.4.0: + version "0.4.0" + resolved "http://registry.npmjs.org/medium-zoom/-/medium-zoom-0.4.0.tgz#8e13c9b754903c0c903220611af0d3cd373a4222" + mem@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" @@ -5660,6 +6356,10 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" +minimist@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de" + minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -5717,6 +6417,10 @@ modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" +moment@^2.6.0: + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -5971,7 +6675,7 @@ npm-which@^3.0.1: npm-path "^2.0.2" which "^1.2.10" -npmlog@^4.0.2: +npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" dependencies: @@ -6178,7 +6882,7 @@ p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" -package-json@^4.0.0: +package-json@^4.0.0, package-json@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" dependencies: @@ -6295,6 +6999,12 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -6669,13 +7379,20 @@ pretty-error@^2.0.2: renderkid "^2.0.1" utila "~0.4" -pretty-format@^23.0.1, pretty-format@^23.2.0: +pretty-format@^23.0.1: version "23.2.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.2.0.tgz#3b0aaa63c018a53583373c1cb3a5d96cc5e83017" dependencies: ansi-regex "^3.0.0" ansi-styles "^3.2.0" +pretty-format@^23.5.0: + version "23.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.5.0.tgz#0f9601ad9da70fe690a269cd3efca732c210687c" + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + pretty-time@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.0.0.tgz#544784adecaa2cd7d045ff8a8f1d4791c8e06e23" @@ -6842,6 +7559,12 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +read-cmd-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" + dependencies: + graceful-fs "^4.1.2" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -6849,6 +7572,13 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -6864,6 +7594,14 @@ read-pkg@^1.0.0, read-pkg@^1.1.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -6962,6 +7700,12 @@ regenerator-transform@^0.12.3: dependencies: private "^0.1.6" +regenerator-transform@^0.13.3: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" + dependencies: + private "^0.1.6" + regex-cache@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" @@ -6987,7 +7731,7 @@ regexpu-core@^1.0.0: regjsgen "^0.2.0" regjsparser "^0.1.4" -regexpu-core@^4.1.3, regexpu-core@^4.1.4: +regexpu-core@^4.1.3, regexpu-core@^4.1.4, regexpu-core@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.2.0.tgz#a3744fa03806cffe146dea4421a3e73bdcc47b1d" dependencies: @@ -6998,9 +7742,9 @@ regexpu-core@^4.1.3, regexpu-core@^4.1.4: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.0.2" -register-service-worker@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/register-service-worker/-/register-service-worker-1.5.1.tgz#4f91d8915f6019b083433b83ffe4bfd6f976f342" +register-service-worker@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/register-service-worker/-/register-service-worker-1.5.2.tgz#a4631896c38d6ec5597358f44988cc46a911912d" registry-auth-token@^3.0.1: version "3.3.2" @@ -7277,6 +8021,10 @@ schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3, schema-utils@^0.4 ajv "^6.1.0" ajv-keywords "^3.1.0" +scifi@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/scifi/-/scifi-0.1.4.tgz#4d514ea11e0b2a6a350a33c6c56addcc9c4e676e" + section-matter@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" @@ -7420,6 +8168,12 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + dependencies: + is-plain-obj "^1.0.0" + source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" @@ -7693,6 +8447,16 @@ strip-json-comments@^2.0.0, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +strong-log-transformer@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-1.0.6.tgz#f7fb93758a69a571140181277eea0c2eb1301fa3" + dependencies: + byline "^5.0.0" + duplexer "^0.1.1" + minimist "^0.1.0" + moment "^2.6.0" + through "^2.3.4" + stylus-loader@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-3.0.2.tgz#27a706420b05a38e038e7cacb153578d450513c6" @@ -7786,6 +8550,21 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" +temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + +temp-write@^3.3.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492" + dependencies: + graceful-fs "^4.1.2" + is-stream "^1.1.0" + make-dir "^1.0.0" + pify "^3.0.0" + temp-dir "^1.0.0" + uuid "^3.0.1" + tempfile@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-1.1.1.tgz#5bcc4eaecc4ab2c707d8bc11d99ccc9a2cb287f2" @@ -7840,7 +8619,7 @@ through2@^2.0.0, through2@^2.0.2: readable-stream "^2.1.5" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@~2.3.4: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -8217,7 +8996,7 @@ uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.1.0: +uuid@^3.0.1, uuid@^3.1.0: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -8345,10 +9124,6 @@ vuepress-html-webpack-plugin@^3.2.0: toposort "^1.0.0" util.promisify "1.0.0" -vuepress-theme-vue@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vuepress-theme-vue/-/vuepress-theme-vue-1.1.0.tgz#8b109e3db8cad7b18459f4d77076808110c2bcaa" - w3c-hr-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" @@ -8376,6 +9151,12 @@ watchpack@^1.5.0: graceful-fs "^4.1.2" neo-async "^2.5.0" +wcwidth@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + dependencies: + defaults "^1.0.3" + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -8387,6 +9168,13 @@ webpack-chain@^4.6.0: deepmerge "^1.5.2" javascript-stringify "^1.6.0" +webpack-chain@^4.9.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/webpack-chain/-/webpack-chain-4.11.0.tgz#41b57773d2dcdcbfd43c9df28a05b40705ae421c" + dependencies: + deepmerge "^1.5.2" + javascript-stringify "^1.6.0" + webpack-dev-middleware@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.1.3.tgz#8b32aa43da9ae79368c1bf1183f2b6cf5e1f39ed" @@ -8696,7 +9484,7 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: +write-file-atomic@^2.0.0, write-file-atomic@^2.1.0, write-file-atomic@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" dependencies: @@ -8704,6 +9492,24 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" +write-json-file@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f" + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + pify "^3.0.0" + sort-keys "^2.0.0" + write-file-atomic "^2.0.0" + +write-pkg@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.2.0.tgz#0e178fe97820d389a8928bc79535dbe68c2cff21" + dependencies: + sort-keys "^2.0.0" + write-json-file "^2.2.0" + write@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" @@ -8757,6 +9563,12 @@ yargs-parser@^10.0.0: dependencies: camelcase "^4.1.0" +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + yargs-parser@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" @@ -8780,6 +9592,24 @@ yargs@^11.0.0: y18n "^3.2.1" yargs-parser "^9.0.2" +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"