Merge branch 'master' into feat/plugin-blog/archive

This commit is contained in:
ULIVZ
2019-06-02 20:34:12 +08:00
352 changed files with 9665 additions and 7134 deletions
+37
View File
@@ -0,0 +1,37 @@
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:10.15.2
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: yarn bootstrap
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
# run tests!
- run: yarn build && yarn lint && yarn test
+2 -1
View File
@@ -6,6 +6,7 @@ module.exports = {
],
rules: {
indent: ['error', 2, { MemberExpression: 'off' }],
"no-undef": ["error"]
"no-undef": ["error"],
'operator-linebreak': ["error", "before"]
}
}
+15 -8
View File
@@ -11,7 +11,7 @@ about: Create a report to help us improve
<!-- Issues which contain questions or support requests will be closed. -->
<!-- (Update "[ ]" to "[x]" to check a box) -->
- [ ] I confirm that this is a issue rather than a question.
- [ ] I confirm that this is an issue rather than a question.
<!-- Please ask questions via following several ways. -->
<!-- https://vue-land.js.org/ -->
@@ -20,8 +20,6 @@ about: Create a report to help us improve
## Bug report
#### Version
#### Steps to reproduce
<!-- If you are reporting a bug that can ONLY be reproduced on your repository, PLEASE provide this repo link. That takes guessing work out of the way and saves us time. -->
@@ -34,8 +32,17 @@ about: Create a report to help us improve
#### Other relevant information
- Your OS:
- Node.js version:
- Browser version:
- Is this a global or local install?
- Which package manager did you use for the install?
<!-- Please choose one of the following and and fill in the information. -->
<!-- (Update "[ ]" to "[x]" to check a box) -->
- [ ] My VuePress version is lower and euqal than `1.0.0-alpha.47`:
- VuePress version:
- OS:
- Node.js version:
- Browser version:
- Is this a global or local install?
- Which package manager did you use for the install?
- Does this issue occur when all plugins are disabled?
- [ ] My VuePress version is higher than `1.0.0-alpha.47`, the following is the output of `npx vuepress info` in my VuePress project:
+1 -1
View File
@@ -19,4 +19,4 @@ about: Suggest an idea for this project
#### How should this be implemented in your opinion?
#### Are you willing to work on this yourself?**
#### Are you willing to work on this yourself?
Regular → Executable
+339 -7
View File
@@ -1,3 +1,335 @@
<a name="1.0.0-alpha.48"></a>
# [1.0.0-alpha.48](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.47...v1.0.0-alpha.48) (2019-05-14)
### Bug Fixes
* **$core:** Failed to load theme when using `layouts/Layout.vue` as entry file (close: [#1563](https://github.com/vuejs/vuepress/issues/1563)) ([#1564](https://github.com/vuejs/vuepress/issues/1564)) ([057d8bf](https://github.com/vuejs/vuepress/commit/057d8bf))
* **$core:** Search result cannot jump to the correct hash anchor (close: [#1594](https://github.com/vuejs/vuepress/issues/1594)) ([#1599](https://github.com/vuejs/vuepress/issues/1599)) ([e6af68f](https://github.com/vuejs/vuepress/commit/e6af68f))
* **$core:** Use directory name to compute slug if filename is readme or index (close: [#1443](https://github.com/vuejs/vuepress/issues/1443)) ([#1535](https://github.com/vuejs/vuepress/issues/1535)) ([9efc678](https://github.com/vuejs/vuepress/commit/9efc678))
* **$core:** webpack externals (ref: [#451](https://github.com/vuejs/vuepress/issues/451)) ([fb324d5](https://github.com/vuejs/vuepress/commit/fb324d5))
### Features
* **$core:** Infer page's date via directory name ([#1553](https://github.com/vuejs/vuepress/issues/1553)) ([2c930c9](https://github.com/vuejs/vuepress/commit/2c930c9))
* **$core:** `info` command ([#1573](https://github.com/vuejs/vuepress/issues/1573)) ([3eeb080](https://github.com/vuejs/vuepress/commit/3eeb080))
* **$core:** Prevent duplicate route ([#1525](https://github.com/vuejs/vuepress/issues/1525)) ([441f023](https://github.com/vuejs/vuepress/commit/441f023))
* **$theme-default:** Support external links in sidebar (close: [#764](https://github.com/vuejs/vuepress/issues/764))([#1534](https://github.com/vuejs/vuepress/issues/1534)) ([141bd11](https://github.com/vuejs/vuepress/commit/141bd11))
<a name="1.0.0-alpha.47"></a>
# [1.0.0-alpha.47](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.46...v1.0.0-alpha.47) (2019-04-06)
### Bug Fixes
* **$core:** `index.styl` is not injected at the end of the style bundle (close: [#1523](https://github.com/vuejs/vuepress/issues/1523)) ([dabf506](https://github.com/vuejs/vuepress/commit/dabf506))
* **$core:** `routerBase` will always get '/' (close: [#1503](https://github.com/vuejs/vuepress/issues/1503)) ([9fba549](https://github.com/vuejs/vuepress/commit/9fba549))
* **$markdown:** Snippets should allow spaces in file path (closes [#1505](https://github.com/vuejs/vuepress/issues/1505)) ([#1517](https://github.com/vuejs/vuepress/issues/1517)) ([5c307c9](https://github.com/vuejs/vuepress/commit/5c307c9))
### Features
* **$core:** assert return type for functional plugin ([#1516](https://github.com/vuejs/vuepress/issues/1516)) ([74887c5](https://github.com/vuejs/vuepress/commit/74887c5))
* **$core:** emit warning if the source directory doesn't exist (close: [#1521](https://github.com/vuejs/vuepress/issues/1521)) ([6da9a5f](https://github.com/vuejs/vuepress/commit/6da9a5f))
* **$plugin-pwa:** allow using local workbox files (close: [#539](https://github.com/vuejs/vuepress/issues/539)) ([4640614](https://github.com/vuejs/vuepress/commit/4640614))
<a name="1.0.0-alpha.46"></a>
# [1.0.0-alpha.46](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.45...v1.0.0-alpha.46) (2019-04-01)
### Bug Fixes
* **$core:** regression of introducing dynamic `routerBase` (close: [#1498](https://github.com/vuejs/vuepress/issues/1498)) ([5e12b49](https://github.com/vuejs/vuepress/commit/5e12b49))
<a name="1.0.0-alpha.45"></a>
# [1.0.0-alpha.45](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.44...v1.0.0-alpha.45) (2019-03-31)
### Bug Fixes
* **$core:** do not register component in render function ([#1449](https://github.com/vuejs/vuepress/issues/1449)) ([ef82c47](https://github.com/vuejs/vuepress/commit/ef82c47))
* **$core:** do not use stylus in outbound link ([d34e038](https://github.com/vuejs/vuepress/commit/d34e038))
* **$core:** should default host be 0.0.0.0 ([699492a](https://github.com/vuejs/vuepress/commit/699492a))
* **$markdown:** treat styl as stylus language ([#1433](https://github.com/vuejs/vuepress/issues/1433)) ([f44e2db](https://github.com/vuejs/vuepress/commit/f44e2db))
* **$markdown-loader:** always use `/` instead of `\` in `relPath` ([#1484](https://github.com/vuejs/vuepress/issues/1484)) ([944ebe4](https://github.com/vuejs/vuepress/commit/944ebe4))
* **$plugin-active-header-links:** side navigation edge case bug ([#1477](https://github.com/vuejs/vuepress/issues/1477)) ([8a11d14](https://github.com/vuejs/vuepress/commit/8a11d14))
* **$plugin-blog:** inconsistent paths of tag and category pages with index page ([#1420](https://github.com/vuejs/vuepress/issues/1420)) ([5c0e62f](https://github.com/vuejs/vuepress/commit/5c0e62f))
* **$plugin-pwa:** fix a typo in `opacity` ([#1444](https://github.com/vuejs/vuepress/issues/1444)) ([c174f0d](https://github.com/vuejs/vuepress/commit/c174f0d))
* **$theme-default:** fix wrong editLink (close: [#1115](https://github.com/vuejs/vuepress/issues/1115), [#1125](https://github.com/vuejs/vuepress/issues/1125)) ([#1419](https://github.com/vuejs/vuepress/issues/1419)) ([3b14375](https://github.com/vuejs/vuepress/commit/3b14375))
* **$theme-default:** nav url change bug (close: [#865](https://github.com/vuejs/vuepress/issues/865)) ([#1475](https://github.com/vuejs/vuepress/issues/1475)) ([521dddd](https://github.com/vuejs/vuepress/commit/521dddd))
### Features
* **$core:** allow dynamic routeBase at runtime ([fc99d59](https://github.com/vuejs/vuepress/commit/fc99d59))
* **$core:** decode page path for better readablility ([#1438](https://github.com/vuejs/vuepress/issues/1438)) ([93b2ca1](https://github.com/vuejs/vuepress/commit/93b2ca1))
* **$core:** export version ([#1486](https://github.com/vuejs/vuepress/issues/1486)) ([d7b8daf](https://github.com/vuejs/vuepress/commit/d7b8daf))
* **$core:** functional siteConfig.evergreen ([#1489](https://github.com/vuejs/vuepress/issues/1489)) ([19e0569](https://github.com/vuejs/vuepress/commit/19e0569))
* **$core:** support array as plugin options ([#1493](https://github.com/vuejs/vuepress/issues/1493)) ([9e07b1e](https://github.com/vuejs/vuepress/commit/9e07b1e))
* **$markdown:** markdown plugin (close: [#585](https://github.com/vuejs/vuepress/issues/585)) ([#1422](https://github.com/vuejs/vuepress/issues/1422)) ([9734a58](https://github.com/vuejs/vuepress/commit/9734a58))
* **$plugin-register-components:** custom name registration (close: [#656](https://github.com/vuejs/vuepress/issues/656)) ([#1418](https://github.com/vuejs/vuepress/issues/1418)) ([9c6a00b](https://github.com/vuejs/vuepress/commit/9c6a00b))
### Breaking Changes
* Depreated [@vuepress/plugin-container](https://www.npmjs.com/package/@vuepress/plugin-container) and moved it to [vuepress-plugin-container](https://www.npmjs.com/package/vuepress-plugin-container).
* Depreated [@vuepress/plugin-clean-urls](https://www.npmjs.com/package/@vuepress/plugin-clean-urls) and moved it to [vuepress-plugin-clean-urls](https://www.npmjs.com/package/vuepress-plugin-clean-urls).
<a name="1.0.0-alpha.44"></a>
# [1.0.0-alpha.44](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.43...v1.0.0-alpha.44) (2019-03-10)
### Features
* **$core:** optional `callback` when socket connection is ready under dev. ([547e4f9](https://github.com/vuejs/vuepress/commit/547e4f9))
* **$core:** return current app instance in node api ([1c2a6b2](https://github.com/vuejs/vuepress/commit/1c2a6b2))
<a name="1.0.0-alpha.43"></a>
# [1.0.0-alpha.43](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.42...v1.0.0-alpha.43) (2019-03-09)
### Bug Fixes
* **$core:** cannot render another page with pageKey (close: [#1173](https://github.com/vuejs/vuepress/issues/1173)) ([9d01514](https://github.com/vuejs/vuepress/commit/9d01514))
* **$core:** global components cannot be used as layouts (close: [#1321](https://github.com/vuejs/vuepress/issues/1321)) ([0306574](https://github.com/vuejs/vuepress/commit/0306574))
* **$core:** PascalCase layouts cannot be used with camelCase nor hyphen-delimited (close: [#1391](https://github.com/vuejs/vuepress/issues/1391)) ([3e91eba](https://github.com/vuejs/vuepress/commit/3e91eba))
* **$plugin-blog:** read `layoutComponents` from themeAPI ([#1396](https://github.com/vuejs/vuepress/issues/1396)) ([5bf4d24](https://github.com/vuejs/vuepress/commit/5bf4d24))
### Features
* **$core:** refine node api ([#1395](https://github.com/vuejs/vuepress/issues/1395)) ([e5d8ed4](https://github.com/vuejs/vuepress/commit/e5d8ed4))
* **$cli:** `--no-clear-screen` flag (close: [#1421](https://github.com/vuejs/vuepress/issues/1421)) ([e5f51de](https://github.com/vuejs/vuepress/commit/e5f51de))
<a name="1.0.0-alpha.42"></a>
# [1.0.0-alpha.42](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.41...v1.0.0-alpha.42) (2019-03-03)
### Bug Fixes
* **$theme-default:** support slot and v-pre container (close: [#1387](https://github.com/vuejs/vuepress/issues/1387)) ([#1389](https://github.com/vuejs/vuepress/issues/1389)) ([c85f62d](https://github.com/vuejs/vuepress/commit/c85f62d))
<a name="1.0.0-alpha.41"></a>
# [1.0.0-alpha.41](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.40...v1.0.0-alpha.41) (2019-03-02)
### Bug Fixes
* **$core:** Generated js have SyntaxError when source-map is enabled (close: [#1367](https://github.com/vuejs/vuepress/issues/1367)) ([#1378](https://github.com/vuejs/vuepress/issues/1378)) ([b53324d](https://github.com/vuejs/vuepress/commit/b53324d))
* **$core:** skip plugin on error (Related to [#1371](https://github.com/vuejs/vuepress/issues/1371)) [#1383](https://github.com/vuejs/vuepress/issues/1383) ([7d2c065](https://github.com/vuejs/vuepress/commit/7d2c065))
* **$core:** support theme index file which is not at root (close: [#1362](https://github.com/vuejs/vuepress/issues/1362)) ([#1376](https://github.com/vuejs/vuepress/issues/1376)) ([204cbe4](https://github.com/vuejs/vuepress/commit/204cbe4))
* **$markdown:** fix line highlighting (close: [#1364](https://github.com/vuejs/vuepress/issues/1364)) ([#1369](https://github.com/vuejs/vuepress/issues/1369)) ([5a111a2](https://github.com/vuejs/vuepress/commit/5a111a2))
* **$theme-default:** encodeURI for sidebar items which contain CJK characters (close: [#717](https://github.com/vuejs/vuepress/issues/717)) ([285b368](https://github.com/vuejs/vuepress/commit/285b368))
### Features
* **$plugin-container:** init ([#1381](https://github.com/vuejs/vuepress/issues/1381)) ([ad0ff72](https://github.com/vuejs/vuepress/commit/ad0ff72))
* **$markdown:** TOC component (close: [#1275](https://github.com/vuejs/vuepress/issues/1275)) ([#1375](https://github.com/vuejs/vuepress/issues/1375)) ([760f90b](https://github.com/vuejs/vuepress/commit/760f90b))
* **$plugin-nprogress:** allow nprogress use different color and add docs ([#1366](https://github.com/vuejs/vuepress/issues/1366)) ([1a928c7](https://github.com/vuejs/vuepress/commit/1a928c7))
<a name="1.0.0-alpha.40"></a>
# [1.0.0-alpha.40](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.39...v1.0.0-alpha.40) (2019-02-27)
### Bug Fixes
* **$cli:** re-support option `--open` (close: [#1320](https://github.com/vuejs/vuepress/issues/1320)) ([#1329](https://github.com/vuejs/vuepress/issues/1329)) ([b97e9ee](https://github.com/vuejs/vuepress/commit/b97e9ee))
* **$core:** handle redirect based on lower case ([#1333](https://github.com/vuejs/vuepress/issues/1333)) ([505fea6](https://github.com/vuejs/vuepress/commit/505fea6))
* **$core:** update outbound link icon alignment ([#1308](https://github.com/vuejs/vuepress/issues/1308)) ([6de1c30](https://github.com/vuejs/vuepress/commit/6de1c30))
* **$plugin-pwa:** service worker doesn't work under sub directory (close: [#1311](https://github.com/vuejs/vuepress/issues/1311)) ([0d56a99](https://github.com/vuejs/vuepress/commit/0d56a99))
### Features
* **$core:** refine theme api ([d16d3d5](https://github.com/vuejs/vuepress/commit/d16d3d5)) ([#1319](https://github.com/vuejs/vuepress/issues/1319))
- Check out [Theme Inheritance](https://v1.vuepress.vuejs.org/theme/inheritance.html) for more details.
* **$markdown:** code snippet hmr (close [#1309](https://github.com/vuejs/vuepress/issues/1309)) ([#1358](https://github.com/vuejs/vuepress/issues/1358)) ([8f83a17](https://github.com/vuejs/vuepress/commit/8f83a17))
* **$markdown:** refine markdown api ([#1337](https://github.com/vuejs/vuepress/issues/1337)) ([b79768c](https://github.com/vuejs/vuepress/commit/b79768c))
* **$markdown:** cache parser ([#1359](https://github.com/vuejs/vuepress/issues/1359)) ([f04adbf](https://github.com/vuejs/vuepress/commit/f04adbf))
* **$theme-default:** add ruby shortcut `rb` support for syntax highlighting ([#1312](https://github.com/vuejs/vuepress/issues/1312)) ([dad2928](https://github.com/vuejs/vuepress/commit/dad2928))
* **$theme-default:** should allow for optional `h1` text at homepage ([#1326](https://github.com/vuejs/vuepress/issues/1326)) ([598799f](https://github.com/vuejs/vuepress/commit/598799f))
* **$plugin-clean-urls:** init ([#1339](https://github.com/vuejs/vuepress/issues/1339)) ([40b3da8](https://github.com/vuejs/vuepress/commit/40b3da8))
<a name="1.0.0-alpha.39"></a>
# [1.0.0-alpha.39](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.38...v1.0.0-alpha.39) (2019-02-17)
### Bug Fixes
* **$core:** cannot read property 'globalLayout' of null (close: [#1304](https://github.com/vuejs/vuepress/issues/1304)) ([94dab12](https://github.com/vuejs/vuepress/commit/94dab12))
* **$core:** cannot use relative path in a permalink page (close: [#1227](https://github.com/vuejs/vuepress/issues/1227))([#1298](https://github.com/vuejs/vuepress/issues/1298)) ([d560e22](https://github.com/vuejs/vuepress/commit/d560e22))
- Check out the [blog post](https://vuepress-relative-paths.ulivz.com/) for more details.
<a name="1.0.0-alpha.38"></a>
# [1.0.0-alpha.38](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.37...v1.0.0-alpha.38) (2019-02-16)
### Bug Fixes
* **$plugin-pwa:** set current registration scope for service worker (close: [#1271](https://github.com/vuejs/vuepress/issues/1271)) ([#1302](https://github.com/vuejs/vuepress/issues/1302)) ([4b2b0f4](https://github.com/vuejs/vuepress/commit/4b2b0f4))
* **$theme-default:** sidebar group item cannot contain empty children (close: [#1278](https://github.com/vuejs/vuepress/issues/1278)) ([5f1eb0e](https://github.com/vuejs/vuepress/commit/5f1eb0e))
### Features
* **$core:** support global layout (close: [#1226](https://github.com/vuejs/vuepress/issues/1226)) ([c91f55a](https://github.com/vuejs/vuepress/commit/c91f55a))
* **$theme-default:** disable search box via frontmatter (close: [#1287](https://github.com/vuejs/vuepress/issues/1287)) ([#1288](https://github.com/vuejs/vuepress/issues/1288)) ([54e9eb0](https://github.com/vuejs/vuepress/commit/54e9eb0))
<a name="1.0.0-alpha.37"></a>
# [1.0.0-alpha.37](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.36...v1.0.0-alpha.37) (2019-02-08)
### Features
* **$core:** redirects for clean urls ([#1269](https://github.com/vuejs/vuepress/issues/1269)) ([213bb34](https://github.com/vuejs/vuepress/commit/213bb34))
* **$theme-default:** fallback current group node to page node if children doesn't exist ([11ce576](https://github.com/vuejs/vuepress/commit/11ce576))
* **$theme-default:** highlight sidebar link text at root level like sidebar group heading text ([05e793f](https://github.com/vuejs/vuepress/commit/05e793f))
* **$theme-default:** unify the text color of sidebar heading text and root-level sidebar link ([3e47a20](https://github.com/vuejs/vuepress/commit/3e47a20))
<a name="1.0.0-alpha.36"></a>
# [1.0.0-alpha.36](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.35...v1.0.0-alpha.36) (2019-02-03)
### Bug Fixes
* **$core:** SFC page compile error (close: [#1110](https://github.com/vuejs/vuepress/issues/1110)) ([8f5b0cd](https://github.com/vuejs/vuepress/commit/8f5b0cd))
### Features
* **$theme-default:** refine sidebar groups ([#1257](https://github.com/vuejs/vuepress/issues/1257)) ([01dd45b](https://github.com/vuejs/vuepress/commit/01dd45b)), closes [#814](https://github.com/vuejs/vuepress/issues/814) [#783](https://github.com/vuejs/vuepress/issues/783) [#287](https://github.com/vuejs/vuepress/issues/287)
- `sidebarDepth` for a specified sidebar group.
- Nested sidebar groups.
- Clickable heading for sidebar groups.
<a name="1.0.0-alpha.35"></a>
# [1.0.0-alpha.35](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.34...v1.0.0-alpha.35) (2019-01-30)
### Bug Fixes
* **$core:** bust cache of extra watching files ([075f470](https://github.com/vuejs/vuepress/commit/075f470))
* **$core:** never throw error if layout component does not exist ([#1247](https://github.com/vuejs/vuepress/issues/1247)) ([49c5983](https://github.com/vuejs/vuepress/commit/49c5983))
* **$core:** style and platte path sep on windows ([#1246](https://github.com/vuejs/vuepress/issues/1246)) ([592918a](https://github.com/vuejs/vuepress/commit/592918a))
* **$theme-default:** bitbucket edit link goes to wrong link (close: [#1235](https://github.com/vuejs/vuepress/issues/1235)) ([#1248](https://github.com/vuejs/vuepress/issues/1248)) ([3c5b3a6](https://github.com/vuejs/vuepress/commit/3c5b3a6))
### Features
* **$core:** exclude temp dir from being ignored watching ([c6797dd](https://github.com/vuejs/vuepress/commit/c6797dd))
<a name="1.0.0-alpha.34"></a>
# [1.0.0-alpha.34](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.33...v1.0.0-alpha.34) (2019-01-29)
### Bug Fixes
* **$core:** Cannot load assets when `base` is not '/' (close: [#1238](https://github.com/vuejs/vuepress/issues/1238))([#1239](https://github.com/vuejs/vuepress/issues/1239)) ([8a234bb](https://github.com/vuejs/vuepress/commit/8a234bb))
<br>This is a regression issue of leverage webpack-dev-server at [#1195](https://github.com/vuejs/vuepress/issues/1195).
* **$markdown:** Remove colon as separator for [Import Code Snippets](https://v1.vuepress.vuejs.org/guide/markdown.html#import-code-snippets) (close: [#1151](https://github.com/vuejs/vuepress/issues/1151)) ([#1236](https://github.com/vuejs/vuepress/issues/1236)) ([099d346](https://github.com/vuejs/vuepress/commit/099d346))
### Features
* **$core:** Support extra watching files ([02cc268](https://github.com/vuejs/vuepress/commit/02cc268)),
<br>e.g.
```js
// .vuepress/config.js
module.exports = {
extraWatchFiles: [
require.resolve('./sidebar.js')
// or '.vuepress/sidebar.js'
]
}
```
<a name="1.0.0-alpha.33"></a>
# [1.0.0-alpha.33](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.32...v1.0.0-alpha.33) (2019-01-27)
### Features
* **$core:** Leverage `webpack-dev-server` and sunset `webpack-serve` ([#1195](https://github.com/vuejs/vuepress/issues/1195)) ([81e3ef6](https://github.com/vuejs/vuepress/commit/81e3ef6))
- Add new plugin option api [beforeDevServer](https://v1.vuepress.vuejs.org/plugin/option-api.html#beforedevserver) and [afterDevServer](https://v1.vuepress.vuejs.org/plugin/option-api.html#afterdevserver).
- Remove `enhanceDevServer`.
- Publish [vuepress-plugin-export](https://github.com/ulivz/vuepress-plugin-export).
* **$core:** Allow a theme package using a sub directory (close [#1204](https://github.com/vuejs/vuepress/issues/1204)) ([#1206](https://github.com/vuejs/vuepress/issues/1206)) ([febe3a7](https://github.com/vuejs/vuepress/commit/febe3a7))
* **$theme-default:** Support shortcut for `sh` and `yml`. (close: [#1221](https://github.com/vuejs/vuepress/issues/1221)) ([fc5dba8](https://github.com/vuejs/vuepress/commit/fc5dba8))
### BREAKING CHANGES
* **$core:** - Plugin option `enhanceDevServer` was removed.
- **For 0.x users**, there is no any effect since we didn't expose API to modify it.
- **For 1.x users whose version of VuePress is lower than 1.0.0-alpha.33**, you should use [`beforeDevServer`](https://v1.vuepress.vuejs.org/plugin/option-api.html#beforedevserver)(i.e. [before](https://webpack.js.org/configuration/dev-server/#devserver-before) in `webpack-dev-server`) to replace `enhanceDevServer`, you can also use [`afterDevServer`](https://v1.vuepress.vuejs.org/plugin/option-api.html#afterdevserver)(i.e. [after](https://webpack.js.org/configuration/dev-server/#devserver-after) in `webpack-dev-server`) to execute custom middleware after all other middleware internally within the server.
<a name="1.0.0-alpha.32"></a>
# [1.0.0-alpha.32](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.31...v1.0.0-alpha.32) (2019-01-15)
### Bug Fixes
* **$plugin-blog,$plugin-google-analytics:** Uniform plugin file name to `enhanceAppFile` ([#1194](https://github.com/vuejs/vuepress/issues/1194)) ([dce17c6](https://github.com/vuejs/vuepress/commit/dce17c6))
* **$core:** Move cac dependency to `vuepress` package (close: [#1183](https://github.com/vuejs/vuepress/issues/1183)) ([#1184](https://github.com/vuejs/vuepress/issues/1184)) ([d160e68](https://github.com/vuejs/vuepress/commit/d160e68))
* **$plugin-pagination:** Won't work when `perPagePosts` is 1 and total posts is 2 ([#1176](https://github.com/vuejs/vuepress/issues/1176)) ([8735d2c](https://github.com/vuejs/vuepress/commit/8735d2c))
* **$shared-utils:** missing `gray-matter` dependency (close: [#1190](https://github.com/vuejs/vuepress/issues/1190)) ([02bf7b9](https://github.com/vuejs/vuepress/commit/02bf7b9))
* **$theme-default:** Tag `<main>` has no matching end tag ([#1181](https://github.com/vuejs/vuepress/issues/1181)) ([c82cc10](https://github.com/vuejs/vuepress/commit/c82cc10))
<a name="1.0.0-alpha.31"></a>
# [1.0.0-alpha.31](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.30...v1.0.0-alpha.31) (2019-01-10)
### Bug Fixes
* **$core:** Check if layout exists ([#1166](https://github.com/vuejs/vuepress/issues/1166)) ([38d1dea](https://github.com/vuejs/vuepress/commit/38d1dea))
* **$core:** `NotFound` component doesn't work (close: [#1130](https://github.com/vuejs/vuepress/issues/1130)) ([b562972](https://github.com/vuejs/vuepress/commit/b562972))
* **$core:** Mormalize override stylus file path in windows ([#1164](https://github.com/vuejs/vuepress/issues/1164)) ([9665196](https://github.com/vuejs/vuepress/commit/9665196))
* **$core:** frontmatter attribute in `additionalPages` is ignored when combined with content (close: [#1157](https://github.com/vuejs/vuepress/issues/1157)) ([87894ff](https://github.com/vuejs/vuepress/commit/87894ff))
* **$plugin-back-to-top:** use `$accent-color`. (close: [#1121](https://github.com/vuejs/vuepress/issues/1121)) ([#1122](https://github.com/vuejs/vuepress/issues/1122)) ([929da11](https://github.com/vuejs/vuepress/commit/929da11))
* **$theme-default:** style is incompatible in low version iOS (<= iOS9) ([#1136](https://github.com/vuejs/vuepress/issues/1136)) ([f46de67](https://github.com/vuejs/vuepress/commit/f46de67))
### Features
* **$new-package:** standalone nprogress plugin ([25db1ba](https://github.com/vuejs/vuepress/commit/25db1ba))
* **$plugin-google-analytics:** set `anonymizeIp` to true in order to be compliant with GDPR.([#1153](https://github.com/vuejs/vuepress/issues/1153)) ([122e024](https://github.com/vuejs/vuepress/commit/122e024))
* **$theme-default:** semantic HTML (close: [#1154](https://github.com/vuejs/vuepress/issues/1154))([#1156](https://github.com/vuejs/vuepress/issues/1156)) ([ede57b0](https://github.com/vuejs/vuepress/commit/ede57b0))
<a name="1.0.0-alpha.30"></a>
# [1.0.0-alpha.30](https://github.com/vuejs/vuepress/compare/v1.0.0-alpha.29...v1.0.0-alpha.30) (2018-12-18)
@@ -46,7 +378,7 @@
* **$core:** pass generated page paths to `generated` hook ([#925](https://github.com/vuejs/vuepress/issues/925)) ([5ee2b2b](https://github.com/vuejs/vuepress/commit/5ee2b2b))
* **$core:** `extendCli` Plugin Option API ([#1069](https://github.com/vuejs/vuepress/issues/1069)) ([e963731](https://github.com/vuejs/vuepress/commit/e963731))
- See [docs](https://vuepress.vuejs.org/plugin/option-api.html#extendcli).
- See [docs](https://v1.vuepress.vuejs.org/plugin/option-api.html#extendcli).
* **$plugin-search:** searchable paths with test RegExp ([#1032](https://github.com/vuejs/vuepress/issues/1032)) ([d6bddf1](https://github.com/vuejs/vuepress/commit/d6bddf1))
@@ -442,7 +774,7 @@
### Features
- **Plugin API**
- [Documentation](https://vuepress.vuejs.org/plugin/)
- [Documentation](https://v1.vuepress.vuejs.org/plugin/)
- Multiple official plugins
- [@vuepress/plugin-active-header-links](https://github.com/vuejs/vuepress/tree/master/packages/@vuepress/plugin-active-header-links)
- [@vuepress/plugin-back-to-top](https://github.com/vuejs/vuepress/tree/master/packages/%40vuepress/plugin-back-to-top) (Thanks to @ycmjason )
@@ -457,18 +789,18 @@
- **Refined Theme API**
- From now on, the theme will no longer use Layout.vue as the entry point, but you can have your own configuration. For example, a theme can have some built-in plugins.
- [Documentation](https://vuepress.vuejs.org/theme/)
- [Documentation](https://v1.vuepress.vuejs.org/theme/)
- **Permalinks**
- From now on, VuePress supports custom routing in addition to the default file-level-based routing.
- [Documentation](https://vuepress.vuejs.org/guide/permalinks.html)
- [Documentation](https://v1.vuepress.vuejs.org/guide/permalinks.html)
- **Markdown slots**
- [Documentation](https://vuepress.vuejs.org/guide/markdown-slot.html)
- [Documentation](https://v1.vuepress.vuejs.org/guide/markdown-slot.html)
- **Free to add new pages**
- You can add new pages with content (i.e. pointing to markdown files) or no content (i.e. common routes).
- [Documentation](https://vuepress.vuejs.org/plugin/option-api.html#additionalpages)
- [Documentation](https://v1.vuepress.vuejs.org/plugin/option-api.html#additionalpages)
- **Custom temp path**
- The running of VuePress actually depends on some temporary files generated during the build time. Before that, its default location is in `node_modules`, but now you can start configuring it, but don't forget to add it to gitignore.
@@ -620,7 +952,7 @@
* **$core:**: version data layer ([0c5b752](https://github.com/vuejs/vuepress/commit/0c5b752))
* **$default-theme:** new file-level API: `style.styl`. ([2f53f2f](https://github.com/vuejs/vuepress/commit/2f53f2f))
1. Fixed overriding css variable doesn't work at `0.11.0` (close: [#639](https://github.com/vuejs/vuepress/issues/639))
2. Split `override.styl` into two APIs: `override.styl` and `style.styl`, the former will focus on ONLY the stylus constants override, while the latter will focus on styles override or custom styles. See also: https://vuepress.vuejs.org/default-theme-config/#simple-css-override.
2. Split `override.styl` into two APIs: `override.styl` and `style.styl`, the former will focus on ONLY the stylus constants override, while the latter will focus on styles override or custom styles. See also: https://v1.vuepress.vuejs.org/default-theme-config/#simple-css-override.
<a name="0.11.0"></a>
+6 -70
View File
@@ -1,6 +1,6 @@
<p align="center">
<a href="https://vuepress.vuejs.org/" target="_blank">
<img width="280" src="https://raw.githubusercontent.com/vuejs/vuepress/master/packages/docs/docs/.vuepress/public/hero.png" alt="logo">
<img width="180" src="https://raw.githubusercontent.com/vuejs/vuepress/master/packages/docs/docs/.vuepress/public/hero.png" alt="logo">
</a>
</p>
@@ -12,94 +12,30 @@
<a href="https://discordapp.com/invite/HBherRA"><img src="https://img.shields.io/badge/Discord-join%20chat-738bd7.svg" alt="vuepress channel on Discord"></a>
</p>
> This is the branch for `VuePress Next`.
> This is the branch for `VuePress 1.x` and docs are available at https://v1.vuepress.vuejs.org.
## 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.
For 0.x, it's moved to [0.x branch](https://github.com/vuejs/vuepress/tree/0.x) and still maintained, the website was switching to https://v0.vuepress.vuejs.org.
> Note that we are working hard to improve the documentation and contributions welcome if you keep up with the latest changes.
# VuePress
> Minimalistic docs generator with Vue component based layout system
https://vuepress.vuejs.org/
For 0.x, it's moved to [0.x branch](https://github.com/vuejs/vuepress/tree/0.x) and still maintained, the website is: https://vuepress.vuejs.org.
## Install
```bash
yarn add vuepress -D # Install 0.x.x.
yarn add vuepress@next -D # Install next.
yarn add vuepress -D # Install 0.x
yarn add vuepress@next -D # Install 1.x.
```
## Features
**Built-in markdown extensions**
* [Table of Contents](https://vuepress.vuejs.org/guide/markdown.html#table-of-contents)
* [Custom Containers](https://vuepress.vuejs.org/guide/markdown.html#custom-containers)
* [Line Highlighting](https://vuepress.vuejs.org/guide/markdown.html#line-highlighting-in-code-blocks)
* [Line Numbers](https://vuepress.vuejs.org/guide/markdown.html#line-numbers)
* [Import Code Snippets](https://vuepress.vuejs.org/guide/markdown.html#import-code-snippets)
**Using Vue in Markdown**
* [Templating](https://vuepress.vuejs.org/guide/using-vue.html#templating)
* [Using Components](https://vuepress.vuejs.org/guide/using-vue.html#using-components)
**Vue-powered custom theme system**
* [Metadata](https://vuepress.vuejs.org/theme/writing-a-theme.html#site-and-page-metadata)
* [Content Excerpt](https://vuepress.vuejs.org/theme/writing-a-theme.html#content-excerpt)
**Default theme**
* Responsive layout
* [Optional Homepage](https://vuepress.vuejs.org/theme/default-theme-config.html#homepage)
* [Simple out-of-the-box header-based search](https://vuepress.vuejs.org/theme/default-theme-config.html#built-in-search)
* [Algolia Search](https://vuepress.vuejs.org/theme/default-theme-config.html#algolia-search)
* Customizable [navbar](https://vuepress.vuejs.org/theme/default-theme-config.html#navbar) and [sidebar](https://vuepress.vuejs.org/theme/default-theme-config.html#sidebar)
* [Auto-generated GitHub link and page edit links](https://vuepress.vuejs.org/theme/default-theme-config.html#git-repo-and-edit-links)
* [PWA: Popup UI to refresh contents](https://vuepress.vuejs.org/theme/default-theme-config.html#popup-ui-to-refresh-contents)
* [Last Updated](https://vuepress.vuejs.org/theme/default-theme-config.html#last-updated)
**Miscellaneous**
* [Multi-Language Support](https://vuepress.vuejs.org/guide/i18n.html)
* [Service Worker](https://vuepress.vuejs.org/config/#serviceworker)
* [Google Analytics](https://vuepress.vuejs.org/config/#ga)
## Showcase
Websites built with VuePress:
**Vue Ecosystem**
* [vue-cli](https://cli.vuejs.org/)
* [vuex](https://vuex.vuejs.org/)
* [vue-server-renderer](https://ssr.vuejs.org/)
* [vue-router](https://router.vuejs.org/)
* [vue-test-utils](https://vue-test-utils.vuejs.org/)
* [vue-loader](https://vue-loader.vuejs.org/)
* [vetur](https://vuejs.github.io/vetur/)
* [rollup-plugin-vue](https://rollup-plugin-vue.vuejs.org/)
**Community**
* [cr-vue](https://cr-vue.mio3io.com/)
* [vuesax](https://lusaxweb.github.io/vuesax/)
* [vuemeetups](https://vuemeetups.org)
Check out [Awesome Vuepress](https://github.com/ulivz/awesome-vuepress) to find awesome things related to VuePress 1.x .
## Development
> Please make sure your version of Node.js is greater than 8.
``` bash
yarn
yarn dev # serves VuePress' own docs with itself
yarn test # make sure your code change pass the test
```
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extend: 'vuepress-theme-parent'
}
+1
View File
@@ -0,0 +1 @@
module.exports = {}
@@ -0,0 +1,2 @@
module.exports = {}
// This file is just for jest.
@@ -0,0 +1,4 @@
{
"name": "vuepress-theme-without-index",
"main": "Layout.vue"
}
+3
View File
@@ -3,6 +3,9 @@ module.exports = {
'test': {
'presets': [
['@babel/preset-env', { 'targets': { 'node': 'current' }}]
],
'plugins': [
'@babel/plugin-syntax-dynamic-import'
]
}
}
+1 -1
View File
@@ -2,5 +2,5 @@
"lerna": "2.5.1",
"npmClient": "yarn",
"useWorkspaces": true,
"version": "1.0.0-alpha.30"
"version": "1.0.0-alpha.48"
}
+12 -8
View File
@@ -8,10 +8,14 @@
],
"description": "Minimalistic doc generator with Vue component based layout system",
"scripts": {
"bootstrap": "lerna bootstrap",
"bootstrap": "yarn && lerna bootstrap && yarn tsc",
"boot": "node scripts/bootstrap.js",
"dev": "yarn tsc && yarn workspace docs dev",
"build": "yarn tsc && yarn workspace docs build",
"remote-version": "node scripts/remote-version.js",
"dev": "yarn tsc && yarn dev:docs",
"dev:docs": "yarn workspace docs dev",
"build": "yarn tsc && yarn build:docs",
"build:docs": "yarn workspace docs build",
"view-info": "yarn tsc && yarn workspace docs view-info",
"show-help": "yarn workspace docs show-help",
"dev:blog": "yarn tsc && yarn workspace blog dev",
"build:blog": "yarn tsc && yarn workspace blog build",
@@ -36,18 +40,18 @@
"eslint": "^4.19.1",
"eslint-plugin-jest": "^21.15.1",
"eslint-plugin-vue-libs": "^3.0.0",
"lerna": "^3.6.0",
"lint-staged": "^7.0.4",
"lerna": "^3.14.1",
"lint-staged": "^8.1.5",
"minimist": "^1.2.0",
"yorkie": "^1.0.3",
"yorkie": "^2.0.0",
"inquirer": "^6.2.0",
"typescript": "^3.2.2",
"@types/node": "^10.12.12",
"@types/lru-cache": "^4.1.1",
"@types/fs-extra": "^5.0.4",
"@types/semver": "^5.5.0",
"@types/semver": "^6.0.0",
"@types/hash-sum": "^1.0.0",
"@types/globby": "^8.0.0",
"@types/globby": "^9.1.0",
"@types/escape-html": "^0.0.20"
}
}
@@ -1,25 +0,0 @@
<template>
<component :is="layout"/>
</template>
<script>
import Vue from 'vue'
import { getLayoutAsyncComponent } from '../util'
Vue.component('Layout', getLayoutAsyncComponent('Layout'))
Vue.component('NotFound', getLayoutAsyncComponent('NotFound'))
export default {
computed: {
layout () {
if (this.$page.path) {
if (getLayoutAsyncComponent(this.$page.frontmatter.layout)) {
return this.$page.frontmatter.layout
}
return 'Layout'
}
return 'NotFound'
}
}
}
</script>
@@ -1,11 +0,0 @@
import Store from './Store'
class VuePress extends Store {}
export default {
install (Vue) {
const ins = new VuePress()
Vue.$vuepress = ins
Vue.prototype.$vuepress = ins
}
}
-191
View File
@@ -1,191 +0,0 @@
'use strict'
module.exports = async function build (sourceDir, cliOptions = {}) {
process.env.NODE_ENV = 'production'
const webpack = require('webpack')
const readline = require('readline')
const escape = require('escape-html')
const { chalk, fs, path, logger, env, performance } = require('@vuepress/shared-utils')
const prepare = require('./prepare/index')
const createClientConfig = require('./webpack/createClientConfig')
const createServerConfig = require('./webpack/createServerConfig')
const { createBundleRenderer } = require('vue-server-renderer')
const { normalizeHeadTag, applyUserWebpackConfig } = require('./util/index')
logger.wait('Extracting site metadata...')
const ctx = await prepare(sourceDir, cliOptions, true /* isProd */)
const { outDir, cwd } = ctx
if (cwd === outDir) {
return console.error(logger.error(chalk.red('Unexpected option: outDir cannot be set to the current working directory.\n'), false))
}
await fs.emptyDir(outDir)
logger.debug('Dist directory: ' + chalk.gray(outDir))
let clientConfig = createClientConfig(ctx, cliOptions).toConfig()
let serverConfig = createServerConfig(ctx, cliOptions).toConfig()
// apply user config...
const userConfig = ctx.siteConfig.configureWebpack
if (userConfig) {
clientConfig = applyUserWebpackConfig(userConfig, clientConfig, false)
serverConfig = applyUserWebpackConfig(userConfig, serverConfig, true)
}
// compile!
const stats = await compile([clientConfig, serverConfig])
const serverBundle = require(path.resolve(outDir, 'manifest/server.json'))
const clientManifest = require(path.resolve(outDir, 'manifest/client.json'))
// remove manifests after loading them.
await fs.remove(path.resolve(outDir, 'manifest'))
// find and remove empty style chunk caused by
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
// TODO remove when it's fixed
await workaroundEmptyStyleChunk()
// create server renderer using built manifests
const renderer = createBundleRenderer(serverBundle, {
clientManifest,
runInNewContext: false,
inject: false,
shouldPrefetch: ctx.siteConfig.shouldPrefetch || (() => true),
template: await fs.readFile(ctx.ssrTemplate, 'utf-8')
})
// pre-render head tags from user config
const userHeadTags = (ctx.siteConfig.head || [])
.map(renderHeadTag)
.join('\n ')
// if the user does not have a custom 404.md, generate the theme's default
if (!ctx.pages.some(p => p.path === '/404.html')) {
ctx.addPage({ path: '/404.html' })
}
// render pages
logger.wait('Rendering static HTML...')
const pagePaths = []
for (const page of ctx.pages) {
pagePaths.push(await renderPage(page))
}
readline.clearLine(process.stdout, 0)
readline.cursorTo(process.stdout, 0)
await ctx.pluginAPI.options.generated.apply(pagePaths)
// DONE.
const relativeDir = path.relative(cwd, outDir)
logger.success(`Generated static files in ${chalk.cyan(relativeDir)}.`)
const { duration } = performance.stop()
logger.developer(`It took a total of ${chalk.cyan(`${duration}ms`)} to run the ${chalk.cyan('vuepress build')}.`)
console.log()
// --- helpers ---
function compile (config) {
return new Promise((resolve, reject) => {
webpack(config, (err, stats) => {
if (err) {
return reject(err)
}
if (stats.hasErrors()) {
stats.toJson().errors.forEach(err => {
console.error(err)
})
reject(new Error(`Failed to compile with errors.`))
return
}
if (env.isDebug && stats.hasWarnings()) {
stats.toJson().warnings.forEach(warning => {
console.warn(warning)
})
}
resolve(stats.toJson({ modules: false }))
})
})
}
function renderHeadTag (tag) {
const { tagName, attributes, innerHTML, closeTag } = normalizeHeadTag(tag)
return `<${tagName}${renderAttrs(attributes)}>${innerHTML}${closeTag ? `</${tagName}>` : ``}`
}
function renderAttrs (attrs = {}) {
const keys = Object.keys(attrs)
if (keys.length) {
return ' ' + keys.map(name => `${name}="${escape(attrs[name])}"`).join(' ')
} else {
return ''
}
}
async function renderPage (page) {
const pagePath = page.path
readline.clearLine(process.stdout, 0)
readline.cursorTo(process.stdout, 0)
process.stdout.write(`Rendering page: ${pagePath}`)
// #565 Avoid duplicate description meta at SSR.
const meta = (page.frontmatter && page.frontmatter.meta || []).filter(item => item.name !== 'description')
const pageMeta = renderPageMeta(meta)
const context = {
url: pagePath,
userHeadTags,
pageMeta,
title: 'VuePress',
lang: 'en',
description: ''
}
let html
try {
html = await renderer.renderToString(context)
} catch (e) {
console.error(logger.error(chalk.red(`Error rendering ${pagePath}:`), false))
throw e
}
const filename = decodeURIComponent(pagePath.replace(/\/$/, '/index.html').replace(/^\//, ''))
const filePath = path.resolve(outDir, filename)
await fs.ensureDir(path.dirname(filePath))
await fs.writeFile(filePath, html)
return filePath
}
function renderPageMeta (meta) {
if (!meta) return ''
return meta.map(m => {
let res = `<meta`
Object.keys(m).forEach(key => {
res += ` ${key}="${escape(m[key])}"`
})
return res + `>`
}).join('')
}
async function workaroundEmptyStyleChunk () {
const styleChunk = stats.children[0].assets.find(a => {
return /styles\.\w{8}\.js$/.test(a.name)
})
if (!styleChunk) return
const styleChunkPath = path.resolve(outDir, styleChunk.name)
const styleChunkContent = await fs.readFile(styleChunkPath, 'utf-8')
await fs.remove(styleChunkPath)
// prepend it to app.js.
// this is necessary for the webpack runtime to work properly.
const appChunk = stats.children[0].assets.find(a => {
return /app\.\w{8}\.js$/.test(a.name)
})
const appChunkPath = path.resolve(outDir, appChunk.name)
const appChunkContent = await fs.readFile(appChunkPath, 'utf-8')
await fs.writeFile(appChunkPath, styleChunkContent + appChunkContent)
}
}
@@ -8,6 +8,8 @@ import appEnhancers from '@internal/app-enhancers'
import globalUIComponents from '@internal/global-ui'
import ClientComputedMixin from '@transform/ClientComputedMixin'
import VuePress from './plugins/VuePress'
import { handleRedirectForCleanUrls } from './redirect.js'
import { getLayoutAsyncComponent } from './util'
// built-in components
import Content from './components/Content.js'
@@ -21,8 +23,8 @@ if (module.hot) {
module.hot.accept(VUEPRESS_TEMP_PATH + '/internal/siteData.js', () => {
if (siteData.base !== prevBase) {
window.alert(
`[vuepress] Site base has changed. ` +
`Please restart dev server to ensure correct asset paths.`
`[vuepress] Site base has changed. `
+ `Please restart dev server to ensure correct asset paths.`
)
}
})
@@ -41,6 +43,9 @@ Vue.component('ContentSlotsDistributor', ContentSlotsDistributor)
Vue.component('OutboundLink', OutboundLink)
// component for client-only content
Vue.component('ClientOnly', ClientOnly)
// core components
Vue.component('Layout', getLayoutAsyncComponent('Layout'))
Vue.component('NotFound', getLayoutAsyncComponent('NotFound'))
// global helper for adding base path to absolute urls
Vue.prototype.$withBase = function (path) {
@@ -53,8 +58,12 @@ Vue.prototype.$withBase = function (path) {
}
export function createApp (isServer) {
const routerBase = typeof window !== 'undefined' && window.__VUEPRESS_ROUTER_BASE__
? window.__VUEPRESS_ROUTER_BASE__
: (siteData.routerBase || siteData.base)
const router = new Router({
base: siteData.base,
base: routerBase,
mode: 'history',
fallback: false,
routes,
@@ -74,16 +83,7 @@ export function createApp (isServer) {
}
})
// redirect /foo to /foo/
router.beforeEach((to, from, next) => {
if (!/(\/|\.html)$/.test(to.path)) {
next(Object.assign({}, to, {
path: to.path + '/'
}))
} else {
next()
}
})
handleRedirectForCleanUrls(router)
const options = {}
@@ -1,4 +1,3 @@
import { isPageExists } from '../util'
export default {
props: {
@@ -10,7 +9,7 @@ export default {
},
render (h) {
const pageKey = this.pageKey || this.$parent.$page.key
if (isPageExists(pageKey)) {
if (pageKey) {
return h(pageKey)
}
return h('')
@@ -8,8 +8,7 @@ export default {
return h('div',
{
class: [
'content',
props.slotKey
`content__${props.slotKey}`
]
},
slots()[props.slotKey]
@@ -0,0 +1,21 @@
<template>
<component :is="layout"/>
</template>
<script>
export default {
computed: {
layout () {
if (this.$page.path) {
const layout = this.$page.frontmatter.layout
if (layout && (this.$vuepress.getLayoutAsyncComponent(layout)
|| this.$vuepress.getVueComponent(layout))) {
return layout
}
return 'Layout'
}
return 'NotFound'
}
}
}
</script>
@@ -5,8 +5,12 @@
</svg>
</template>
<style lang="stylus">
.icon.outbound
color #aaa
display inline-block
<style>
.icon.outbound {
color: #aaa;
display: inline-block;
vertical-align: middle;
position: relative;
top: -1px;
}
</style>
@@ -1,8 +1,8 @@
/* global VUEPRESS_TEMP_PATH */
import Vue from 'vue'
import GLobalVue from 'vue'
export default function dataMixin (I18n, siteData) {
export default function dataMixin (I18n, siteData, Vue = GLobalVue) {
prepare(siteData)
Vue.$vuepress.$set('siteData', siteData)
@@ -0,0 +1,24 @@
import Store from './Store'
import {
getPageAsyncComponent,
getLayoutAsyncComponent,
getAsyncComponent,
getVueComponent
} from '../util'
class VuePress extends Store {}
Object.assign(VuePress.prototype, {
getPageAsyncComponent,
getLayoutAsyncComponent,
getAsyncComponent,
getVueComponent
})
export default {
install (Vue) {
const ins = new VuePress()
Vue.$vuepress = ins
Vue.prototype.$vuepress = ins
}
}
@@ -0,0 +1,54 @@
// In VuePress, we have following convention about routing:
//
// - `/foo/` means source file is `/foo/{README|index}.md`
// - `/foo.html` means your source file is `/foo.md`
//
// The original design of VuePress relied on above two styles
// of routing, especially the calculation involved of routes at
// default theme. so we can't easily modify `/foo.html` directly
// to `/foo` (i.e. remove html suffix)
//
// This "temporary" utility handles redirect of clean urls, with
// this utility, you'll get:
//
// For unknown request `/foo`
// - redirect to `/foo.html` if it exists
// - redirect to `/foo/` if it exists
//
// For unknown request `/foo/`
// - redirect to `/foo.html` if it exists
//
// If all the above redirect rules don't exist, you'll get a 404
export function handleRedirectForCleanUrls (router) {
router.beforeEach((to, from, next) => {
if (isRouteExists(router, to.path)) {
next()
} else {
if (!/(\/|\.html)$/.test(to.path)) {
const endingSlashUrl = to.path + '/'
const endingHtmlUrl = to.path + '.html'
if (isRouteExists(router, endingHtmlUrl)) {
next(endingHtmlUrl)
} else if (isRouteExists(router, endingSlashUrl)) {
next(endingSlashUrl)
} else {
next()
}
} else if (/\/$/.test(to.path)) {
const endingHtmlUrl = to.path.replace(/\/$/, '') + '.html'
if (isRouteExists(router, endingHtmlUrl)) {
next(endingHtmlUrl)
} else {
next()
}
} else {
next()
}
}
})
}
function isRouteExists (router, path) {
return router.options.routes.filter(route => route.path.toLowerCase() === path.toLowerCase()).length > 0
}
@@ -17,6 +17,6 @@ $MQMobileNarrow = 419px
// code
$lineNumbersWrapperWidth = 3.5rem
$codeLang = js ts html md vue css sass scss less stylus go java c sh yaml py
$codeLang = js ts html md vue css sass scss less stylus go java c sh yaml py docker dockerfile makefile
@import '~@temp/palette.styl'
@@ -2,36 +2,89 @@ import Vue from 'vue'
import layoutComponents from '@internal/layout-components'
import pageComponents from '@internal/page-components'
/**
* Create a cached version of a pure function.
*/
function cached (fn) {
const cache = Object.create(null)
// eslint-disable-next-line func-names
return function cachedFn (str) {
const hit = cache[str]
// eslint-disable-next-line no-return-assign
return hit || (cache[str] = fn(str))
}
}
/**
* Camelize a hyphen-delimited string.
*/
const camelizeRE = /-(\w)/g
const camelize = cached(str => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})
/**
* Hyphenate a camelCase string.
*/
const hyphenateRE = /\B([A-Z])/g
const hyphenate = cached(str => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
})
/**
* Capitalize a string.
*/
const capitalize = cached(str => {
return str.charAt(0).toUpperCase() + str.slice(1)
})
/**
* This method was for securely getting Vue component when components
* are named in different style.
*
* e.g. a component named `a-b` can be also getted by `AB`, It's the
* same the other way round
*
* @param {function} getter a function of getting component by name
* @param {string} name component's name
* @returns {Component|AsyncComponent}
*/
export function getComponent (getter, name) {
if (!name) return
if (getter(name)) return getter(name)
const isKebabCase = name.includes('-')
if (isKebabCase) return getter(capitalize(camelize(name)))
return getter(capitalize(name)) || getter(hyphenate(name))
}
const asyncComponents = Object.assign({}, layoutComponents, pageComponents)
export function isPageExists (pageKey) {
return Boolean(pageComponents[pageKey])
}
export function isPageLoaded (pageKey) {
return Boolean(Vue.component(pageKey))
}
const asyncComponentsGetter = name => asyncComponents[name]
const pageComponentsGetter = layout => pageComponents[layout]
const layoutComponentsGetter = layout => layoutComponents[layout]
const globalComponentsGetter = name => Vue.component(name)
export function getPageAsyncComponent (pageKey) {
return pageComponents[pageKey]
return getComponent(pageComponentsGetter, pageKey)
}
export function isLayoutExists (layout) {
return Boolean(layoutComponents[layout])
export function getLayoutAsyncComponent (layout) {
return getComponent(layoutComponentsGetter, layout)
}
export function isLayoutLoaded (layout) {
return Boolean(Vue.component(layout))
export function getAsyncComponent (name) {
return getComponent(asyncComponentsGetter, name)
}
export function getLayoutAsyncComponent (pageKey) {
return layoutComponents[pageKey]
export function getVueComponent (name) {
return getComponent(globalComponentsGetter, name)
}
export function ensureAsyncComponentsLoaded (...names) {
return Promise.all(names.filter(v => v).map(async (name) => {
if (!Vue.component(name) && asyncComponents[name]) {
const comp = await asyncComponents[name]()
if (!getVueComponent(name) && getAsyncComponent(name)) {
const comp = await getAsyncComponent(name)()
Vue.component(name, comp.default)
}
}))
-167
View File
@@ -1,167 +0,0 @@
'use strict'
module.exports = async function dev (sourceDir, cliOptions = {}) {
const { path } = require('@vuepress/shared-utils')
const webpack = require('webpack')
const chokidar = require('chokidar')
const serve = require('webpack-serve')
const convert = require('koa-connect')
const mount = require('koa-mount')
const range = require('koa-range')
const serveStatic = require('koa-static')
const history = require('connect-history-api-fallback')
const prepare = require('./prepare/index')
const { chalk, fs, logger } = require('@vuepress/shared-utils')
const HeadPlugin = require('./webpack/HeadPlugin')
const DevLogPlugin = require('./webpack/DevLogPlugin')
const createClientConfig = require('./webpack/createClientConfig')
const { applyUserWebpackConfig } = require('./util/index')
const { frontmatterEmitter } = require('@vuepress/markdown-loader')
logger.wait('Extracting site metadata...')
const ctx = await prepare(sourceDir, cliOptions, false /* isProd */)
// setup watchers to update options and dynamically generated files
const update = (reason) => {
logger.debug(`Re-prepare due to ${chalk.cyan(reason)}`)
ctx.pluginAPI.options.updated.syncApply()
prepare(sourceDir, cliOptions, false /* isProd */).catch(err => {
console.error(logger.error(chalk.red(err.stack), false))
})
}
// watch add/remove of files
const pagesWatcher = chokidar.watch([
'**/*.md',
'.vuepress/components/**/*.vue'
], {
cwd: sourceDir,
ignored: ['.vuepress/**/*.md', 'node_modules'],
ignoreInitial: true
})
pagesWatcher.on('add', () => update('add page'))
pagesWatcher.on('unlink', () => update('unlink page'))
pagesWatcher.on('addDir', () => update('addDir'))
pagesWatcher.on('unlinkDir', () => update('unlinkDir'))
// watch config file
const configWatcher = chokidar.watch([
'.vuepress/config.js',
'.vuepress/config.yml',
'.vuepress/config.toml'
], {
cwd: sourceDir,
ignoreInitial: true
})
configWatcher.on('change', () => update('config change'))
// also listen for frontmatter changes from markdown files
frontmatterEmitter.on('update', () => update('frontmatter or headers change'))
// resolve webpack config
let config = createClientConfig(ctx)
config
.plugin('html')
// using a fork of html-webpack-plugin to avoid it requiring webpack
// internals from an incompatible version.
.use(require('vuepress-html-webpack-plugin'), [{
template: ctx.devTemplate
}])
config
.plugin('site-data')
.use(HeadPlugin, [{
tags: ctx.siteConfig.head || []
}])
const port = await resolvePort(cliOptions.port || ctx.siteConfig.port)
const { host, displayHost } = await resolveHost(cliOptions.host || ctx.siteConfig.host)
// debug in a running dev process.
process.stdin &&
process.stdin.on('data', chunk => {
const parsed = chunk.toString('utf-8').trim()
if (parsed === '*') {
console.log(Object.keys(ctx))
}
if (ctx[parsed]) {
console.log(ctx[parsed])
}
})
config
.plugin('vuepress-log')
.use(DevLogPlugin, [{
port,
displayHost,
publicPath: ctx.base
}])
config = config.toConfig()
const userConfig = ctx.siteConfig.configureWebpack
if (userConfig) {
config = applyUserWebpackConfig(userConfig, config, false /* isServer */)
}
const compiler = webpack(config)
const nonExistentDir = path.resolve(__dirname, 'non-existent')
await serve({
// avoid project cwd from being served. Otherwise if the user has index.html
// in cwd it would break the server
content: [nonExistentDir],
compiler,
host,
dev: { logLevel: 'warn' },
hot: {
port: port + 1,
logLevel: 'error'
},
logLevel: 'error',
port,
open: cliOptions.open,
add: app => {
// apply plugin options to extend dev server.
ctx.pluginAPI.options.enhanceDevServer.syncApply(app)
const userPublic = path.resolve(sourceDir, '.vuepress/public')
// enable range request
app.use(range)
// respect base when serving static files...
if (fs.existsSync(userPublic)) {
app.use(mount(ctx.base, serveStatic(userPublic)))
}
app.use(convert(history({
rewrites: [
{ from: /\.html$/, to: '/' }
]
})))
}
})
}
function resolveHost (host) {
// webpack-serve hot updates doesn't work properly over 0.0.0.0 on Windows,
// but localhost does not allow visiting over network :/
const defaultHost = process.platform === 'win32' ? 'localhost' : '0.0.0.0'
host = host || defaultHost
const displayHost = host === defaultHost && process.platform !== 'win32'
? 'localhost'
: host
return {
displayHost,
host
}
}
async function resolvePort (port) {
const portfinder = require('portfinder')
portfinder.basePort = parseInt(port) || 8080
port = await portfinder.getPortPromise()
return port
}
+25 -2
View File
@@ -1,5 +1,28 @@
'use strict'
exports.dev = require('./dev')
exports.build = require('./build')
const App = require('./node/App')
const { version } = require('../package')
const { logger } = require('@vuepress/shared-utils')
function createApp (options) {
logger.wait('Extracting site metadata...')
return new App(options)
}
async function dev (options) {
const app = createApp(options)
await app.process()
return app.dev()
}
async function build (options) {
const app = createApp(options)
await app.process()
return app.build()
}
exports.version = version
exports.createApp = createApp
exports.dev = dev
exports.build = build
exports.eject = require('./eject')
@@ -1,15 +0,0 @@
const { path } = require('@vuepress/shared-utils')
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
]
}
})
@@ -1,42 +0,0 @@
const { fs, path, logger, chalk } = require('@vuepress/shared-utils')
module.exports = (options, ctx) => ({
name: '@vuepress/internal-style',
enhanceAppFiles: [path.resolve(__dirname, 'client.js')],
async ready () {
const { sourceDir, writeTemp } = ctx
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('.vuepress/styles/palette.styl')} instead.\n`)
}
const themeStyle = path.resolve(ctx.themePath, 'styles/index.styl')
const userStyle = path.resolve(sourceDir, '.vuepress/styles/index.styl')
.replace(/[\\]+/g, '/')
const themeStyleContent = fs.existsSync(themeStyle)
? `@import(${JSON.stringify(themeStyle)})`
: ''
const userStyleContent = fs.existsSync(userStyle)
? `@import(${JSON.stringify(userStyle)})`
: ''
let styleContent = themeStyleContent + userStyleContent
if (ctx.parentThemePath) {
const parentThemeStyle = path.resolve(ctx.parentThemePath, 'styles/index.styl')
const parentThemeStyleContent = fs.existsSync(parentThemeStyle)
? `@import(${JSON.stringify(parentThemeStyle)})`
: ''
styleContent = parentThemeStyleContent + styleContent
}
await writeTemp('style.styl', styleContent)
}
})
+496
View File
@@ -0,0 +1,496 @@
'use strict'
/**
* Module dependencies.
*/
const createMarkdown = require('./createMarkdown')
const loadConfig = require('./loadConfig')
const loadTheme = require('./loadTheme')
const { getCacheLoaderOptions } = require('./CacheLoader')
const {
fs, path, logger, chalk, globby, sort,
datatypes: { isFunction },
fallback: { fsExistsFallback }
} = require('@vuepress/shared-utils')
const Page = require('./Page')
const ClientComputedMixin = require('./ClientComputedMixin')
const PluginAPI = require('./plugin-api')
const DevProcess = require('./dev')
const BuildProcess = require('./build')
const createTemp = require('./createTemp')
/**
* Expose VuePressApp.
*/
module.exports = class App {
/**
* Instantiate the app context with a new API
*
* @param {string} sourceDir
* @param {{
* plugins: pluginsConfig,
* theme: themeNameConfig
* temp: string
* }} options
*/
constructor (options = {}) {
this.options = options
this.sourceDir = this.options.sourceDir || path.join(__dirname, 'docs.fallback')
logger.debug('sourceDir', this.sourceDir)
if (!fs.existsSync(this.sourceDir)) {
logger.warn(`Source directory doesn't exist: ${chalk.yellow(this.sourceDir)}`)
}
const { tempPath, writeTemp } = createTemp(options.temp)
this.tempPath = tempPath
this.writeTemp = writeTemp
this.vuepressDir = path.resolve(this.sourceDir, '.vuepress')
this.libDir = path.join(__dirname, '../')
}
/**
* Resolve user config and initialize.
*
* @returns {void}
* @api private
*/
resolveConfigAndInitialize () {
if (this.options.siteConfig) {
this.siteConfig = this.options.siteConfig
} else {
let siteConfig = loadConfig(this.vuepressDir)
if (isFunction(siteConfig)) {
siteConfig = siteConfig(this)
}
this.siteConfig = siteConfig
}
// TODO custom cwd.
this.cwd = process.cwd()
this.base = this.siteConfig.base || '/'
this.themeConfig = this.siteConfig.themeConfig || {}
const rawOutDir = this.options.dest || this.siteConfig.dest
this.outDir = rawOutDir
? require('path').resolve(this.cwd, rawOutDir)
: require('path').resolve(this.sourceDir, '.vuepress/dist')
this.pages = [] // Array<Page>
this.pluginAPI = new PluginAPI(this)
this.ClientComputedMixinConstructor = ClientComputedMixin(this.getSiteData())
}
/**
* A asynchronous method used to prepare the context of the current app. which
* contains loading pages and plugins, apply plugins, etc.
*
* @returns {Promise<void>}
* @api private
*/
async process () {
this.resolveConfigAndInitialize()
this.normalizeHeadTagUrls()
this.themeAPI = loadTheme(this)
this.resolveTemplates()
this.resolveGlobalLayout()
this.applyInternalPlugins()
this.applyUserPlugins()
this.pluginAPI.initialize()
this.markdown = createMarkdown(this)
await this.resolvePages()
await this.pluginAPI.applyAsyncOption('additionalPages', this)
await Promise.all(
this.pluginAPI.getOption('additionalPages').appliedValues.map(async (options) => {
await this.addPage(options)
})
)
await this.pluginAPI.applyAsyncOption('ready')
await Promise.all([
this.pluginAPI.applyAsyncOption('clientDynamicModules', this),
this.pluginAPI.applyAsyncOption('enhanceAppFiles', this),
this.pluginAPI.applyAsyncOption('globalUIComponents', this)
])
}
/**
* Apply internal plugins
*
* @api private
*/
applyInternalPlugins () {
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(require('./internal-plugins/siteData'))
.use(require('./internal-plugins/routes'))
.use(require('./internal-plugins/rootMixins'))
.use(require('./internal-plugins/enhanceApp'))
.use(require('./internal-plugins/palette'))
.use(require('./internal-plugins/style'))
.use(require('./internal-plugins/layoutComponents'))
.use(require('./internal-plugins/pageComponents'))
.use(require('./internal-plugins/transformModule'))
.use(require('./internal-plugins/dataBlock'))
.use(require('./internal-plugins/frontmatterBlock'))
.use('container', {
type: 'slot',
before: info => `<template slot="${info}">`,
after: '</template>'
})
.use('container', {
type: 'v-pre',
before: '<div v-pre>',
after: '</div>'
})
.use('@vuepress/last-updated', !!shouldUseLastUpdated)
.use('@vuepress/register-components', {
componentsDir: [
path.resolve(this.sourceDir, '.vuepress/components'),
path.resolve(this.themeAPI.theme.path, 'global-components'),
this.themeAPI.existsParentTheme && path.resolve(this.themeAPI.parentTheme.path, 'global-components')
]
})
}
/**
* Apply user plugins
*
* @api private
*/
applyUserPlugins () {
this.pluginAPI.useByPluginsConfig(this.options.plugins)
if (this.themeAPI.existsParentTheme) {
this.pluginAPI.use(this.themeAPI.parentTheme.entry)
}
this.pluginAPI
.use(this.themeAPI.theme.entry)
.use(this.themeAPI.vuepressPlugin)
.use(Object.assign({}, this.siteConfig, { name: '@vuepress/internal-site-config' }))
}
/**
* 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)
}
}
}
}
})
}
}
/**
* Resolve options of cache loader.
*/
resolveCacheLoaderOptions () {
Object.assign(this, (getCacheLoaderOptions(this.siteConfig, this.options, this.cwd, this.isProd)))
}
/**
* Make template configurable
*
* Resolving Priority (devTemplate as example):
*
* 1. siteConfig.devTemplate
* 2. `dev.html` located at .vuepress/templates
* 3. themeEntryFile.devTemplate
* 4. default devTemplate
*
* @api private
*/
resolveTemplates () {
this.devTemplate = this.resolveCommonAgreementFilePath(
'devTemplate',
{
defaultValue: this.getLibFilePath('client/index.dev.html'),
siteAgreement: 'templates/dev.html',
themeAgreement: 'templates/dev.html'
}
)
this.ssrTemplate = this.resolveCommonAgreementFilePath(
'ssrTemplate',
{
defaultValue: this.getLibFilePath('client/index.ssr.html'),
siteAgreement: 'templates/ssr.html',
themeAgreement: 'templates/ssr.html'
}
)
logger.debug('SSR Template File: ' + chalk.gray(this.ssrTemplate))
logger.debug('DEV Template File: ' + chalk.gray(this.devTemplate))
}
/**
* resolve global layout
*
* @returns {string}
* @api private
*/
resolveGlobalLayout () {
this.globalLayout = this.resolveCommonAgreementFilePath(
'globalLayout',
{
defaultValue: this.getLibFilePath('client/components/GlobalLayout.vue'),
siteAgreement: `components/GlobalLayout.vue`,
themeAgreement: `layouts/GlobalLayout.vue`
}
)
logger.debug('globalLayout: ' + chalk.gray(this.globalLayout))
}
/**
* Resolve a path-type config.
*
* @param {string} configKey
* @param {string} defaultValue an absolute path
* @param {string} siteAgreement a relative path to vuepress dir
* @param {string} themeAgreement a relative path to theme dir
* @returns {string | void}
*/
resolveCommonAgreementFilePath (configKey, {
defaultValue,
siteAgreement,
themeAgreement
}) {
const siteConfigValue = this.siteConfig[configKey]
siteAgreement = this.resolveSiteAgreementFile(siteAgreement)
const themeConfigValue = this.getThemeConfigValue(configKey)
themeAgreement = this.resolveThemeAgreementFile(themeAgreement)
return fsExistsFallback([
siteConfigValue,
siteAgreement,
themeConfigValue,
themeAgreement,
defaultValue
].map(v => v))
}
/**
* Find all page source files located in sourceDir
*
* @returns {Promise<void>}
* @api private
*/
async resolvePages () {
// resolve pageFiles
const patterns = ['**/*.md', '**/*.vue', '!.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<void>}
* @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.getOption('extendPageData').items
})
const index = this.pages.findIndex(({ path }) => path === page.path)
if (index >= 0) {
// Override a page if corresponding path already exists
logger.warn(`Override existing page ${chalk.yellow(page.path)}.`)
this.pages.splice(index, 1, page)
} else {
this.pages.push(page)
}
}
/**
* Get config value of current active theme.
*
* @param {string} key
* @returns {any}
* @api private
*/
getThemeConfigValue (key) {
return this.themeAPI.theme.entry[key]
|| this.themeAPI.existsParentTheme && this.themeAPI.parentTheme.entry[key]
}
/**
* Resolve the absolute path of a theme-level agreement file,
* return `undefined` when it doesn't exists.
*
* @param {string} filepath
* @returns {string|undefined}
*/
resolveThemeAgreementFile (filepath) {
const current = path.resolve(this.themeAPI.theme.path, filepath)
if (fs.existsSync(current)) {
return current
}
if (this.themeAPI.existsParentTheme) {
const parent = path.resolve(this.themeAPI.theme.path, filepath)
if (fs.existsSync(parent)) {
return parent
}
}
}
/**
* Resolve the absolute path of a site-level agreement file,
* return `undefined` when it doesn't exists.
*
* @param {string} filepath
* @returns {string|undefined}
*/
resolveSiteAgreementFile (filepath) {
return path.resolve(this.vuepressDir, filepath)
}
/**
* 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
}
}
/**
* Get file path in core lib
*
* @param relative
* @returns {string}
* @api public
*/
getLibFilePath (relative) {
return path.join(this.libDir, relative)
}
/**
* Launch a dev process with current app context.
*
* @returns {Promise<App>}
* @api public
*/
async dev () {
this.isProd = false
this.devProcess = new DevProcess(this)
await this.devProcess.process()
const error = await new Promise(resolve => {
try {
this.devProcess
.on('fileChanged', ({ type, target }) => {
console.log(`Reload due to ${chalk.red(type)} ${chalk.cyan(path.relative(this.sourceDir, target))}`)
this.process()
})
.createServer()
.listen(resolve)
} catch (err) {
resolve(err)
}
})
if (error) {
throw error
}
return this
}
/**
* Launch a build process with current app context
*
* @returns {Promise<App>}
* @api public
*/
async build () {
this.isProd = true
this.buildProcess = new BuildProcess(this)
await this.buildProcess.process()
await this.buildProcess.render()
return this
}
}
@@ -12,12 +12,12 @@ const {
/**
* Get cache directory and cache identifier via config.
* @param {object} siteConfig
* @param {object} cliOptions
* @param {object} options
*/
exports.getCacheLoaderOptions = function (siteConfig, cliOptions, cwd, isProd) {
exports.getCacheLoaderOptions = function (siteConfig, options, cwd, isProd) {
const defaultCacheDirectory = path.resolve(__dirname, '../../node_modules/.cache/vuepress')
let cache = cliOptions.cache || siteConfig.cache || defaultCacheDirectory
let cache = options.cache || siteConfig.cache || defaultCacheDirectory
if (isBoolean(cache)) {
if (cache === true) {
@@ -38,19 +38,19 @@ exports.getCacheLoaderOptions = function (siteConfig, cliOptions, cwd, isProd) {
siteConfig.markdown
? JSON.stringify(siteConfig.markdown)
: ''
) +
(
)
+ (
siteConfig.markdown && siteConfig.markdown.extendMarkdown
? siteConfig.markdown.extendMarkdown.toString()
: ''
) +
(
)
+ (
siteConfig.extendMarkdown
? siteConfig.extendMarkdown.toString()
: ''
) +
(siteConfig.chainWebpack || '').toString() +
(siteConfig.configureWebpack || '').toString()
)
+ (siteConfig.chainWebpack || '').toString()
+ (siteConfig.configureWebpack || '').toString()
)
})
@@ -74,8 +74,8 @@ module.exports = siteData => {
const siteTitle = this.$siteTitle
const selfTitle = page.frontmatter.home ? null : (
page.frontmatter.title || // explicit title
page.title // inferred title
page.frontmatter.title // explicit title
|| page.title // inferred title
)
return siteTitle
? selfTitle
@@ -4,7 +4,7 @@
* Module dependencies.
*/
const { inferDate, DATE_RE } = require('../util/index')
const { inferDate, DATE_RE } = require('./util/index')
const {
fs,
path,
@@ -37,7 +37,7 @@ module.exports = class Page {
*/
constructor ({
path,
path: _path,
meta,
title,
content,
@@ -61,12 +61,16 @@ module.exports = class Page {
if (relative) {
this.regularPath = encodeURI(fileToPath(relative))
} else if (path) {
this.regularPath = encodeURI(path)
} else if (_path) {
this.regularPath = encodeURI(_path)
} else if (permalink) {
this.regularPath = encodeURI(permalink)
}
if (filePath) {
this.relativePath = path.relative(context.sourceDir, filePath).replace(/\\/g, '/')
}
this.key = 'v-' + hash(`${this._filePath}${this.regularPath}`)
// Using regularPath first, would be override by permalink later.
this.path = this.regularPath
@@ -121,7 +125,10 @@ module.exports = class Page {
}
if (excerpt) {
const { html } = markdown.render(excerpt)
const { html } = markdown.render(excerpt, {
frontmatter: this.frontmatter,
relativePath: this.relativePath
})
this.excerpt = html
}
} else if (this._filePath.endsWith('.vue')) {
@@ -142,6 +149,17 @@ module.exports = class Page {
this.buildPermalink()
}
/**
* name of page's parent directory.
*
* @returns {string}
* @api public
*/
get dirname () {
return path.basename(path.dirname(this._filePath || this.regularPath))
}
/**
* file name of page's source markdown file, or the last cut of regularPath.
*
@@ -161,7 +179,19 @@ module.exports = class Page {
*/
get slug () {
return slugify(this.strippedFilename)
const strippedFilename = this.strippedFilename
if (/^(index|readme)$/i.test(strippedFilename)) {
const strippedFilename = this.stripFilename(
path.basename(path.dirname(this._filePath || this.regularPath))
)
if (strippedFilename) {
return slugify(strippedFilename)
}
}
return slugify(strippedFilename)
}
/**
@@ -175,8 +205,7 @@ module.exports = class Page {
*/
get strippedFilename () {
const match = this.filename.match(DATE_RE)
return match ? match[3] : this.filename
return this.stripFilename(this.filename)
}
/**
@@ -187,7 +216,23 @@ module.exports = class Page {
*/
get date () {
return inferDate(this.frontmatter, this.filename)
return inferDate(this.frontmatter, this.filename, this.dirname)
}
/**
* 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.
*
* @param {string} fileName
* @returns {string}
* @private
*/
stripFilename (fileName) {
const match = fileName.match(DATE_RE)
return match ? match[3] : fileName
}
/**
@@ -234,7 +279,7 @@ module.exports = class Page {
/**
* Execute the page enhancers. A enhancer could do following things:
*
* 1. Modify page's frontmetter.
* 1. Modify page's frontmatter.
* 2. Add extra field to the page.
*
* @api private
@@ -1,4 +1,4 @@
const AsyncOption = require('../../lib/plugin-api/abstract/AsyncOption')
const AsyncOption = require('../../plugin-api/abstract/AsyncOption')
describe('AsyncOption', () => {
test('parallelApply', async () => {
@@ -1,4 +1,4 @@
import Option from '../../lib/plugin-api/abstract/Option'
import Option from '../../plugin-api/abstract/Option'
describe('Option', () => {
test('key', () => {
@@ -2,8 +2,8 @@ jest.mock('vuepress-plugin-a')
jest.mock('vuepress-plugin-b')
jest.mock('@org/vuepress-plugin-a')
import PluginAPI from '../../lib/plugin-api/index'
import { PLUGIN_OPTION_MAP } from '../../lib/plugin-api/constants'
import PluginAPI from '../../plugin-api/index'
import { PLUGIN_OPTION_MAP } from '../../plugin-api/constants'
describe('Plugin', () => {
test('registerOption', () => {
@@ -1,31 +1,31 @@
import { flattenPlugin } from '../../lib/plugin-api/util'
import { flattenPlugin } from '../../plugin-api/util'
describe('flattenPlugin', () => {
test('shoould hydrate plugin correctly', () => {
const plugin = { name: 'a', shortcut: 'a', module: { enhanceAppFiles: 'file' }}
test('should hydrate plugin correctly', () => {
const plugin = { name: 'a', shortcut: 'a', entry: { enhanceAppFiles: 'file' }}
const hydratedPlugin = flattenPlugin(plugin, {}, {})
expect(hydratedPlugin.name).toBe('a')
expect(hydratedPlugin.shortcut).toBe('a')
expect(hydratedPlugin.shortcut).toBe(null)
expect(hydratedPlugin.enabled).toBe(true)
expect(hydratedPlugin.enhanceAppFiles).toBe('file')
})
test('shoould set \'enabled\' to false when \'pluginOptions\' is set to false.', () => {
const plugin = { name: 'a', shortcut: 'a', module: {}}
test('should set \'enabled\' to false when \'pluginOptions\' is set to false.', () => {
const plugin = { name: 'a', shortcut: 'a', entry: {}}
const hydratedPlugin = flattenPlugin(plugin, false, {})
expect(hydratedPlugin.name).toBe('a')
expect(hydratedPlugin.shortcut).toBe('a')
expect(hydratedPlugin.shortcut).toBe(null)
expect(hydratedPlugin.enabled).toBe(false)
})
test('shoould flatten functional plugin correctly.', () => {
test('should flatten functional plugin correctly.', () => {
const config = jest.fn(() => ({ enhanceAppFiles: 'file' }))
const plugin = { name: 'a', shortcut: 'a', module: config }
const plugin = { name: 'a', shortcut: 'a', entry: config }
const pluginOptions = {}
const pluginContext = {}
const hydratedPlugin = flattenPlugin(plugin, pluginOptions, pluginContext)
expect(hydratedPlugin.name).toBe('a')
expect(hydratedPlugin.shortcut).toBe('a')
expect(hydratedPlugin.shortcut).toBe(null)
expect(hydratedPlugin.enabled).toBe(true)
expect(hydratedPlugin.enhanceAppFiles).toBe('file')
expect(config.mock.calls).toHaveLength(1)
@@ -33,9 +33,9 @@ describe('flattenPlugin', () => {
expect(Object.getPrototypeOf(config.mock.calls[0][1])).toBe(pluginContext)
})
test('shoould flatten functional plugin correctly - options defaults to \'{}\'.', () => {
test('should flatten functional plugin correctly - options defaults to \'{}\'.', () => {
const config = jest.fn(() => ({ enhanceAppFiles: 'file' }))
const plugin = { name: 'a', shortcut: 'a', module: config }
const plugin = { name: 'a', shortcut: 'a', entry: config }
const pluginOptions = undefined
const pluginContext = {}
flattenPlugin(plugin, pluginOptions, pluginContext)
@@ -1,5 +1,5 @@
const { fs, path } = require('@vuepress/shared-utils')
const prepare = require('../../lib/prepare')
const App = require('../../App')
const docsBaseDir = path.resolve(__dirname, 'fixtures')
const docsModeNames = fs.readdirSync(docsBaseDir)
@@ -9,12 +9,17 @@ const docsModes = docsModeNames.map(name => {
return { name, docsPath, docsTempPath }
})
describe('prepare', () => {
describe('App', () => {
test('should not throw error', async () => {
await Promise.all(docsModes.map(async ({ name, docsPath, docsTempPath }) => {
await fs.ensureDir(docsTempPath)
const context = await prepare(docsPath, { theme: '@vuepress/default', temp: docsTempPath })
expect(context.sourceDir).toBe(docsPath)
const app = new App({
sourceDir: docsPath,
theme: '@vuepress/default',
emp: docsTempPath
})
await app.process()
expect(app.sourceDir).toBe(docsPath)
}))
})
})
@@ -1,19 +1,28 @@
const Page = require('../../lib/prepare/Page')
const Page = require('../../Page')
const App = require('../../App')
const {
getComputed,
getMarkdown,
getDocument,
readFile
} = require('./util')
describe('Page', () => {
let app
let computed
beforeAll(async () => {
app = new App()
await app.process()
computed = new app.ClientComputedMixinConstructor()
})
test('pure route', async () => {
const page = new Page({ path: '/' })
const page = new Page({ path: '/' }, app)
expect(page.path).toBe('/')
expect(page.regularPath).toBe('/')
const computed = getComputed()
await page.process({ computed })
expect(page.path).toBe('/')
@@ -22,7 +31,7 @@ describe('Page', () => {
test('pure route - encodeURI', async () => {
const path = '/尤/'
const page = new Page({ path })
const page = new Page({ path }, app)
expect(page.path).toBe(encodeURI(path))
expect(page.regularPath).toBe(encodeURI(path))
@@ -33,7 +42,7 @@ describe('Page', () => {
const page = new Page({
path: '/',
frontmatter
})
}, app)
expect(page.frontmatter).toBe(frontmatter)
})
@@ -42,15 +51,16 @@ describe('Page', () => {
const page = new Page({
path: '/',
frontmatter
})
}, app)
expect(page.frontmatter.title).toBe('alpha')
const computed = getComputed()
const enhancers = [
{
name: 'plugin-a',
value: page => { page.frontmatter.title = 'beta' }
value: page => {
page.frontmatter.title = 'beta'
}
}
]
await page.process({ computed, enhancers })
@@ -60,14 +70,13 @@ describe('Page', () => {
test('markdown page - pointing to a markdown file', async () => {
const { relative, filePath } = getDocument('README.md')
const page = new Page({ filePath, relative })
const page = new Page({ filePath, relative }, app)
expect(page._filePath).toBe(filePath)
expect(page.regularPath).toBe('/')
expect(page.path).toBe('/')
expect(page.frontmatter).toEqual({})
const computed = getComputed()
const markdown = getMarkdown()
await page.process({ computed, markdown })
@@ -79,14 +88,13 @@ describe('Page', () => {
test('markdown page - pointing to a markdown file with frontmatter', async () => {
const { relative, filePath } = getDocument('alpha.md')
const page = new Page({ filePath, relative })
const page = new Page({ filePath, relative }, app)
expect(page._filePath).toBe(filePath)
expect(page.regularPath).toBe('/alpha.html')
expect(page.path).toBe('/alpha.html')
expect(page.frontmatter).toEqual({})
const computed = getComputed()
const markdown = getMarkdown()
await page.process({ computed, markdown })
@@ -0,0 +1,5 @@
module.exports = {
title: 'Hello VuePress',
description: '# Hello, VuePress!',
theme: 'vuepress-theme-without-index'
}
@@ -0,0 +1 @@
# Hello, VuePress!
@@ -1,15 +1,5 @@
const { fs, path } = require('@vuepress/shared-utils')
const AppContext = require('../../lib/prepare/AppContext')
const createMarkdown = require('../../../markdown/index')
function getAppContext () {
return new AppContext('.')
}
function getComputed () {
const context = getAppContext()
return new context.ClientComputedMixinConstructor()
}
const createMarkdown = require('../../../../../markdown/index')
const docsBaseDir = path.resolve(__dirname, 'fixtures/docs')
@@ -25,8 +15,6 @@ const getMarkdown = createMarkdown
const readFile = async filePath => await fs.readFile(filePath, 'utf-8')
module.exports = {
getAppContext,
getComputed,
getMarkdown,
getDocument,
readFile
@@ -0,0 +1,27 @@
jest.mock('vuepress-theme-parent')
jest.mock('vuepress-theme-child')
import ThemeAPI from '../../theme-api'
import { resolve } from 'path'
const theme = {
path: resolve(process.cwd(), '__mocks__/vuepress-theme-child'),
name: 'vuepress-theme-child',
shortcut: 'child',
entryFile: require('vuepress-theme-child')
}
const parent = {
path: resolve(process.cwd(), '__mocks__/vuepress-theme-parent'),
name: 'vuepress-theme-parent',
shortcut: 'parent',
entryFile: {}
}
describe('ThemeAPI', () => {
test('extend', async () => {
const themeAPI = new ThemeAPI(theme, parent)
console.log(themeAPI.theme.entry)
})
// loadTheme('vuepress-theme-child')
})
@@ -0,0 +1,271 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const webpack = require('webpack')
const readline = require('readline')
const escape = require('escape-html')
const { chalk, fs, path, logger, env, performance } = require('@vuepress/shared-utils')
const createClientConfig = require('../webpack/createClientConfig')
const createServerConfig = require('../webpack/createServerConfig')
const { createBundleRenderer } = require('vue-server-renderer')
const { normalizeHeadTag, applyUserWebpackConfig } = require('../util/index')
/**
* Expose Build Process Class.
*/
module.exports = class Build extends EventEmitter {
constructor (context) {
super()
process.env.NODE_ENV = 'production'
this.context = context
this.outDir = this.context.outDir
}
/**
* Doing somthing before render pages, e.g. validate and empty output directory,
* prepare webpack config.
*
* @returns {Promise<void>}
* @api public
*/
async process () {
if (this.context.cwd === this.outDir) {
throw new Error('Unexpected option: "outDir" cannot be set to the current working directory')
}
this.context.resolveCacheLoaderOptions()
await fs.emptyDir(this.outDir)
logger.debug('Dist directory: ' + chalk.gray(this.outDir))
this.prepareWebpackConfig()
}
/**
* Compile and render pages.
*
* @returns {Promise<void>}
* @api public
*/
async render () {
// compile!
const stats = await compile([this.clientConfig, this.serverConfig])
const serverBundle = require(path.resolve(this.outDir, 'manifest/server.json'))
const clientManifest = require(path.resolve(this.outDir, 'manifest/client.json'))
// remove manifests after loading them.
await fs.remove(path.resolve(this.outDir, 'manifest'))
// ref: https://github.com/vuejs/vuepress/issues/1367
if (!this.clientConfig.devtool && (!this.clientConfig.plugins
|| !this.clientConfig.plugins.some(p =>
p instanceof webpack.SourceMapDevToolPlugin
|| p instanceof webpack.EvalSourceMapDevToolPlugin
))) {
await workaroundEmptyStyleChunk(stats, this.outDir)
}
// create server renderer using built manifests
this.renderer = createBundleRenderer(serverBundle, {
clientManifest,
runInNewContext: false,
inject: false,
shouldPrefetch: this.context.siteConfig.shouldPrefetch || (() => true),
template: await fs.readFile(this.context.ssrTemplate, 'utf-8')
})
// pre-render head tags from user config
this.userHeadTags = (this.context.siteConfig.head || [])
.map(renderHeadTag)
.join('\n ')
// if the user does not have a custom 404.md, generate the theme's default
if (!this.context.pages.some(p => p.path === '/404.html')) {
this.context.addPage({ path: '/404.html' })
}
// render pages
logger.wait('Rendering static HTML...')
const pagePaths = []
for (const page of this.context.pages) {
pagePaths.push(await this.renderPage(page))
}
readline.clearLine(process.stdout, 0)
readline.cursorTo(process.stdout, 0)
await this.context.pluginAPI.applyAsyncOption('generated', pagePaths)
// DONE.
const relativeDir = path.relative(this.context.cwd, this.outDir)
logger.success(`Generated static files in ${chalk.cyan(relativeDir)}.`)
const { duration } = performance.stop()
logger.developer(`It took a total of ${chalk.cyan(`${duration}ms`)} to run the ${chalk.cyan('vuepress build')}.`)
console.log()
}
/**
* Prepare webpack config under build.
*
* @api private
*/
prepareWebpackConfig () {
this.clientConfig = createClientConfig(this.context).toConfig()
this.serverConfig = createServerConfig(this.context).toConfig()
const userConfig = this.context.siteConfig.configureWebpack
if (userConfig) {
this.clientConfig = applyUserWebpackConfig(userConfig, this.clientConfig, false)
this.serverConfig = applyUserWebpackConfig(userConfig, this.serverConfig, true)
}
}
/**
* Render page
*
* @param {Page} page
* @returns {Promise<string>}
* @api private
*/
async renderPage (page) {
const pagePath = decodeURIComponent(page.path)
readline.clearLine(process.stdout, 0)
readline.cursorTo(process.stdout, 0)
process.stdout.write(`Rendering page: ${pagePath}`)
// #565 Avoid duplicate description meta at SSR.
const meta = (page.frontmatter && page.frontmatter.meta || []).filter(item => item.name !== 'description')
const pageMeta = renderPageMeta(meta)
const context = {
url: page.path,
userHeadTags: this.userHeadTags,
pageMeta,
title: 'VuePress',
lang: 'en',
description: ''
}
let html
try {
html = await this.renderer.renderToString(context)
} catch (e) {
console.error(logger.error(chalk.red(`Error rendering ${pagePath}:`), false))
throw e
}
const filename = pagePath.replace(/\/$/, '/index.html').replace(/^\//, '')
const filePath = path.resolve(this.outDir, filename)
await fs.ensureDir(path.dirname(filePath))
await fs.writeFile(filePath, html)
return filePath
}
}
/**
* Compile a webpack application and return stats json.
*
* @param {Object} config
* @returns {Promise<Object>}
*/
function compile (config) {
return new Promise((resolve, reject) => {
webpack(config, (err, stats) => {
if (err) {
return reject(err)
}
if (stats.hasErrors()) {
stats.toJson().errors.forEach(err => {
console.error(err)
})
reject(new Error(`Failed to compile with errors.`))
return
}
if (env.isDebug && stats.hasWarnings()) {
stats.toJson().warnings.forEach(warning => {
console.warn(warning)
})
}
resolve(stats.toJson({ modules: false }))
})
})
}
/**
* Render head tag
*
* @param {Object} tag
* @returns {string}
*/
function renderHeadTag (tag) {
const { tagName, attributes, innerHTML, closeTag } = normalizeHeadTag(tag)
return `<${tagName}${renderAttrs(attributes)}>${innerHTML}${closeTag ? `</${tagName}>` : ``}`
}
/**
* Render html attributes
*
* @param {Object} attrs
* @returns {string}
*/
function renderAttrs (attrs = {}) {
const keys = Object.keys(attrs)
if (keys.length) {
return ' ' + keys.map(name => `${name}="${escape(attrs[name])}"`).join(' ')
} else {
return ''
}
}
/**
* Render meta tags
*
* @param {Array} meta
* @returns {Array<string>}
*/
function renderPageMeta (meta) {
if (!meta) return ''
return meta.map(m => {
let res = `<meta`
Object.keys(m).forEach(key => {
res += ` ${key}="${escape(m[key])}"`
})
return res + `>`
}).join('')
}
/**
* find and remove empty style chunk caused by
* https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
* TODO remove when it's fixed
*
* @param {Object} stats
* @param {String} outDir
* @returns {Promise<void>}
*/
async function workaroundEmptyStyleChunk (stats, outDir) {
const styleChunk = stats.children[0].assets.find(a => {
return /styles\.\w{8}\.js$/.test(a.name)
})
if (!styleChunk) return
const styleChunkPath = path.resolve(outDir, styleChunk.name)
const styleChunkContent = await fs.readFile(styleChunkPath, 'utf-8')
await fs.remove(styleChunkPath)
// prepend it to app.js.
// this is necessary for the webpack runtime to work properly.
const appChunk = stats.children[0].assets.find(a => {
return /app\.\w{8}\.js$/.test(a.name)
})
const appChunkPath = path.resolve(outDir, appChunk.name)
const appChunkContent = await fs.readFile(appChunkPath, 'utf-8')
await fs.writeFile(appChunkPath, styleChunkContent + appChunkContent)
}
@@ -16,12 +16,12 @@ module.exports = function (ctx) {
const beforeInstantiate = config => {
chainMarkdown && chainMarkdown(config)
ctx.pluginAPI.options.chainMarkdown.syncApply(config)
ctx.pluginAPI.applySyncOption('chainMarkdown', config)
}
const afterInstantiate = md => {
extendMarkdown && extendMarkdown(md)
ctx.pluginAPI.options.extendMarkdown.syncApply(md)
ctx.pluginAPI.applySyncOption('extendMarkdown', md)
}
return createMarkdown(
@@ -0,0 +1,42 @@
const { fs, path, chalk, logger } = require('@vuepress/shared-utils')
/**
* Create a dynamic temp utility context that allow to lanuch
* multiple apps with isolated context at the same time.
* @param tempPath
* @returns {{
* writeTemp: (function(file: string, content: string): string),
* tempPath: string
* }}
*/
module.exports = function createTemp (tempPath) {
if (!tempPath) {
tempPath = path.resolve(__dirname, '../../.temp')
} else {
tempPath = path.resolve(tempPath)
}
if (!fs.existsSync(tempPath)) {
fs.ensureDirSync(tempPath)
} else {
fs.emptyDirSync(tempPath)
}
logger.debug(`Temp directory: ${chalk.gray(tempPath)}`)
const tempCache = new Map()
async function writeTemp (file, content) {
const destPath = path.join(tempPath, file)
await fs.ensureDir(path.parse(destPath).dir)
// cache write to avoid hitting the dist if it didn't change
const cached = tempCache.get(file)
if (cached !== content) {
await fs.writeFile(destPath, content)
tempCache.set(file, content)
}
return destPath
}
return { writeTemp, tempPath }
}
@@ -0,0 +1,308 @@
'use strict'
/**
* Module dependencies.
*/
const EventEmitter = require('events').EventEmitter
const WebpackDevServer = require('webpack-dev-server')
const { frontmatterEmitter } = require('@vuepress/markdown-loader')
const webpack = require('webpack')
const chokidar = require('chokidar')
const { path, fs, logger } = require('@vuepress/shared-utils')
const HeadPlugin = require('../webpack/HeadPlugin')
const DevLogPlugin = require('../webpack/DevLogPlugin')
const createClientConfig = require('../webpack/createClientConfig')
const { applyUserWebpackConfig } = require('../util/index')
module.exports = class DevProcess extends EventEmitter {
constructor (context) {
super()
this.context = context
}
/**
* Prepare essential data for launch dev server.
*/
async process () {
this.context.resolveCacheLoaderOptions()
this.watchSourceFiles()
this.watchUserConfig()
this.watchFrontmatter()
this.setupDebugTip()
await this.resolvePort()
await this.resolveHost()
this.prepareWebpackConfig()
return this
}
/**
* Hande file's update, need to re-prepare app context.
*
* @param {string} type
* @param {string} target
*/
handleUpdate (type, target) {
logger.debug(type, target)
if (!path.isAbsolute(target)) {
target = path.join(this.context.sourceDir, target)
}
if (target.endsWith('.js')) {
// Bust cache.
delete require.cache[target]
}
this.emit('fileChanged', {
type,
target
})
}
/**
* Watch user's source document files.
*/
watchSourceFiles () {
// watch add/remove of files
this.pagesWatcher = chokidar.watch([
'**/*.md',
'.vuepress/components/**/*.vue'
], {
cwd: this.context.sourceDir,
ignored: ['.vuepress/**/*.md', 'node_modules'],
ignoreInitial: true
})
this.pagesWatcher.on('add', target => this.handleUpdate('add', target))
this.pagesWatcher.on('unlink', target => this.handleUpdate('unlink', target))
this.pagesWatcher.on('addDir', target => this.handleUpdate('addDir', target))
this.pagesWatcher.on('unlinkDir', target => this.handleUpdate('unlinkDir', target))
}
/**
* Watch user's config files and extra files.
*/
watchUserConfig () {
this.watchFiles = [
'.vuepress/config.js',
'.vuepress/config.yml',
'.vuepress/config.toml'
].concat(
(
this.context.siteConfig.extraWatchFiles || []
).map(file => normalizeWatchFilePath(file, this.context.sourceDir))
)
logger.debug('watchFiles', this.watchFiles)
this.configWatcher = chokidar.watch(this.watchFiles, {
cwd: this.context.sourceDir,
ignoreInitial: true
})
this.configWatcher.on('change', target => this.handleUpdate('change', target))
}
/**
* Also listen for frontmatter changes from markdown files
*/
watchFrontmatter () {
frontmatterEmitter.on('update', target => this.handleUpdate('frontmatter', target))
}
/**
* Resolve used port
*/
async resolvePort () {
this.port = await resolvePort(this.context.options.port || this.context.siteConfig.port)
}
/**
* Resolve used host
*/
async resolveHost () {
const { host, displayHost } = await resolveHost(this.context.options.host || this.context.siteConfig.host)
this.host = host
this.displayHost = displayHost
}
/**
* Set up a shortcut to debug context under dev.
*/
setupDebugTip () {
// debug in a running dev process.
process.stdin
&& process.stdin.on('data', chunk => {
const parsed = chunk.toString('utf-8').trim()
if (parsed === '*') {
console.log(Object.keys(this.context))
}
if (this.context[parsed]) {
console.log(this.context[parsed])
}
})
}
/**
* Prepare webpack for dev process.
*/
prepareWebpackConfig () {
// resolve webpack config
let config = createClientConfig(this.context)
config
.plugin('html')
// using a fork of html-webpack-plugin to avoid it requiring webpack
// internals from an incompatible version.
.use(require('vuepress-html-webpack-plugin'), [{
template: this.context.devTemplate
}])
config
.plugin('site-data')
.use(HeadPlugin, [{
tags: this.context.siteConfig.head || []
}])
config
.plugin('vuepress-log')
.use(DevLogPlugin, [{
port: this.port,
displayHost: this.displayHost,
publicPath: this.context.base,
clearScreen: this.context.options.clearScreen
}])
config = config.toConfig()
const userConfig = this.context.siteConfig.configureWebpack
if (userConfig) {
config = applyUserWebpackConfig(userConfig, config, false /* isServer */)
}
this.webpackConfig = config
}
/**
* Create dev server
* @returns {module.DevProcess}
*/
createServer () {
const contentBase = path.resolve(this.context.sourceDir, '.vuepress/public')
const serverConfig = Object.assign({
disableHostCheck: true,
compress: true,
clientLogLevel: 'error',
hot: true,
quiet: true,
headers: {
'access-control-allow-origin': '*'
},
open: this.context.options.open,
publicPath: this.context.base,
watchOptions: {
ignored: [
/node_modules/,
`!${this.context.tempPath}/**`
]
},
historyApiFallback: {
disableDotRule: true,
rewrites: [
{ from: /./, to: path.posix.join(this.context.base, 'index.html') }
]
},
overlay: false,
host: this.host,
contentBase,
before: (app, server) => {
if (fs.existsSync(contentBase)) {
app.use(this.context.base, require('express').static(contentBase))
}
this.context.pluginAPI.applySyncOption('beforeDevServer', app, server)
},
after: (app, server) => {
this.context.pluginAPI.applySyncOption('afterDevServer', app, server)
}
}, this.context.siteConfig.devServer || {})
WebpackDevServer.addDevServerEntrypoints(this.webpackConfig, serverConfig)
const compiler = webpack(this.webpackConfig)
this.server = new WebpackDevServer(compiler, serverConfig)
return this
}
/**
* delegate listen call.
*
* @param callback handler when connection is ready.
* @returns {module.DevProcess}
*/
listen (callback) {
this.server.listen(this.port, this.host, (err) => {
if (typeof callback === 'function') {
callback(err)
}
})
return this
}
}
/**
* Resolve host.
*
* @param {string} host user's host
* @returns {{displayHost: string, host: string}}
*/
function resolveHost (host) {
const defaultHost = '0.0.0.0'
host = host || defaultHost
const displayHost = host === defaultHost
? 'localhost'
: host
return {
displayHost,
host
}
}
/**
* Resolve port.
*
* @param {number} port user's port
* @returns {Promise<number>}
*/
async function resolvePort (port) {
const portfinder = require('portfinder')
portfinder.basePort = parseInt(port) || 8080
port = await portfinder.getPortPromise()
return port
}
/**
* Normalize file path and always return relative path,
*
* @param {string} filepath user's path
* @param {string} baseDir source directory
* @returns {string}
*/
function normalizeWatchFilePath (filepath, baseDir) {
const { isAbsolute, relative } = require('path')
if (isAbsolute(filepath)) {
return relative(baseDir, filepath)
}
return filepath
}
@@ -0,0 +1,3 @@
<template>
<Content/>
</template>
@@ -0,0 +1,3 @@
# VuePress
> `Prompts`: You are running VuePress without setting sourceDir!
@@ -0,0 +1,17 @@
const { path } = require('@vuepress/shared-utils')
module.exports = (options, context) => ({
name: '@vuepress/internal-enhance-app',
enhanceAppFiles () {
const { sourceDir, themeAPI } = context
const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js')
const files = [enhanceAppPath]
if (themeAPI.existsParentTheme) {
files.push(path.resolve(themeAPI.parentTheme.path, 'enhanceApp.js'))
}
const themeEnhanceAppPath = path.resolve(themeAPI.theme.path, 'enhanceApp.js')
files.push(themeEnhanceAppPath)
return files
}
})
@@ -12,13 +12,13 @@ module.exports = function (source, map) {
const cached = cache.get(file)
const parsed = parseStrippedFrontmatter(source)
if (cached &&
cached.data &&
parsed &&
parsed.data &&
JSON.stringify(cached.data) !== JSON.stringify(parsed.data)
if (cached
&& cached.data
&& parsed
&& parsed.data
&& JSON.stringify(cached.data) !== JSON.stringify(parsed.data)
) {
frontmatterEmitter.emit('update')
frontmatterEmitter.emit('update', file)
}
cache.set(file, parsed)
@@ -1,22 +1,13 @@
module.exports = (options, ctx) => {
const { layoutComponentMap } = ctx
const componentNames = Object.keys(layoutComponentMap)
return {
name: '@vuepress/internal-layout-components',
async clientDynamicModules () {
const componentNames = Object.keys(ctx.themeAPI.layoutComponentMap)
const code = `export default {\n${componentNames
.map(name => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(layoutComponentMap[name].path)})`)
.map(name => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(ctx.themeAPI.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)
})
}
}
}
@@ -8,7 +8,7 @@ module.exports = (options, ctx) => ({
async ready () {
// 1. enable config.styl globally.
const configFile = path.resolve(__dirname, '../../app/style/config.styl')
const configFile = ctx.getLibFilePath('client/style/config.styl')
if (!ctx.siteConfig.stylus) {
ctx.siteConfig.stylus = {
import: [configFile]
@@ -20,27 +20,33 @@ module.exports = (options, ctx) => ({
// 2. write palette.styl
const { sourceDir, writeTemp } = ctx
const themePalette = path.resolve(ctx.themePath, 'styles/palette.styl')
const themePalette = path.resolve(ctx.themeAPI.theme.path, 'styles/palette.styl')
const userPalette = path.resolve(sourceDir, '.vuepress/styles/palette.styl')
.replace(/[\\]+/g, '/')
const themePaletteContent = fs.existsSync(themePalette)
? `@import(${JSON.stringify(themePalette)})`
? `@import(${JSON.stringify(themePalette.replace(/[\\]+/g, '/'))})`
: ''
const userPaletteContent = fs.existsSync(userPalette)
? `@import(${JSON.stringify(userPalette)})`
? `@import(${JSON.stringify(userPalette.replace(/[\\]+/g, '/'))})`
: ''
// user's palette can override theme's palette.
let paletteContent = themePaletteContent + userPaletteContent
const nullComment = '// null'
if (ctx.parentThemePath) {
const parentThemePalette = path.resolve(ctx.parentThemePath, 'styles/palette.styl')
// user's palette can override theme's palette.
let paletteContent = '// Theme\'s Palette\n'
+ (themePaletteContent || nullComment)
+ '\n\n// User\'s Palette\n'
+ (userPaletteContent || nullComment)
if (ctx.themeAPI.existsParentTheme) {
const parentThemePalette = path.resolve(ctx.themeAPI.parentTheme.path, 'styles/palette.styl')
const parentThemePaletteContent = fs.existsSync(parentThemePalette)
? `@import(${JSON.stringify(parentThemePalette)})`
? `@import(${JSON.stringify(parentThemePalette.replace(/[\\]+/g, '/'))})`
: ''
paletteContent = parentThemePaletteContent + paletteContent
paletteContent = '// Parent Theme\'s Palette\n'
+ (parentThemePaletteContent || nullComment)
+ '\n\n' + paletteContent
}
await writeTemp('palette.styl', paletteContent)
@@ -1,4 +1,3 @@
const { path } = require('@vuepress/shared-utils')
const { codegen: { pathsToModuleCode }} = require('@vuepress/shared-utils')
module.exports = (options, context, api) => ({
@@ -7,7 +6,7 @@ module.exports = (options, context, api) => ({
// @internal/root-mixins
async clientDynamicModules () {
const builtInRootMixins = [
path.resolve(__dirname, '../app/root-mixins/updateMeta.js')
context.getLibFilePath('client/root-mixins/updateMeta.js')
]
const rootMixins = [
@@ -3,24 +3,23 @@ module.exports = (options, ctx) => ({
// @internal/routes
async clientDynamicModules () {
const code = importCode() + routesCode(ctx.pages)
const code = importCode(ctx.globalLayout) + routesCode(ctx.pages)
return { name: 'routes.js', content: code, dirname: 'internal' }
}
})
/**
* Import utilities
* @param {string} globalLayout path of global layout component
* @returns {string}
*/
function importCode () {
function importCode (globalLayout) {
return `
import { injectComponentOption, ensureAsyncComponentsLoaded } from '@app/util'
import rootMixins from '@internal/root-mixins'
import layoutComponents from '@internal/layout-components'
import pageComponents from '@internal/page-components'
import LayoutDistributor from '@app/components/LayoutDistributor.vue'
import GlobalLayout from ${JSON.stringify(globalLayout)}
injectComponentOption(LayoutDistributor, 'mixins', rootMixins)
injectComponentOption(GlobalLayout, 'mixins', rootMixins)
`
}
@@ -43,7 +42,7 @@ function routesCode (pages) {
{
name: ${JSON.stringify(componentName)},
path: ${JSON.stringify(pagePath)},
component: LayoutDistributor,
component: GlobalLayout,
beforeEnter: (to, from, next) => {
ensureAsyncComponentsLoaded(${JSON.stringify(layout || 'Layout')}, ${JSON.stringify(componentName)}).then(next)
},${_meta ? `\n meta: ${JSON.stringify(_meta)}` : ''}
@@ -80,7 +79,7 @@ function routesCode (pages) {
const notFoundRoute = `,
{
path: '*',
component: LayoutDistributor
component: GlobalLayout
}`
return (
@@ -0,0 +1,54 @@
const { fs, path, logger, chalk } = require('@vuepress/shared-utils')
/**
* @param options
* @param {AppContext} ctx
*/
module.exports = (options, ctx) => ({
name: '@vuepress/internal-style',
enhanceAppFiles: [path.resolve(__dirname, 'client.js')],
async ready () {
const { sourceDir, writeTemp, themeAPI } = ctx
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('.vuepress/styles/palette.styl')} instead.\n`)
}
const themeStyle = path.resolve(themeAPI.theme.path, 'styles/index.styl')
const userStyle = path.resolve(sourceDir, '.vuepress/styles/index.styl')
const themeStyleContent = fs.existsSync(themeStyle)
? `@import(${JSON.stringify(themeStyle.replace(/[\\]+/g, '/'))})`
: ''
const userStyleContent = fs.existsSync(userStyle)
? `@import(${JSON.stringify(userStyle.replace(/[\\]+/g, '/'))})`
: ''
const nullComment = '// null'
// user's styles can override theme's styles.
let styleContent = '// Theme\'s Styles\n'
+ (themeStyleContent || nullComment)
+ '\n\n// User\'s Styles\n'
+ (userStyleContent || nullComment)
if (themeAPI.existsParentTheme) {
const parentThemeStyle = path.resolve(themeAPI.parentTheme.path, 'styles/index.styl')
const parentThemeStyleContent = fs.existsSync(parentThemeStyle)
? `@import(${JSON.stringify(parentThemeStyle.replace(/[\\]+/g, '/'))})`
: ''
styleContent = '// Parent Theme\'s Styles\n'
+ (parentThemeStyleContent || nullComment)
+ '\n\n' + styleContent
}
await writeTemp('style.styl', styleContent)
}
})
@@ -11,7 +11,7 @@ module.exports = (options, ctx) => ({
async clientDynamicModules () {
const files = [
path.resolve(__dirname, '../prepare/ClientComputedMixin.js')
path.resolve(__dirname, '../ClientComputedMixin.js')
]
const modules = await Promise.all(files.map(async file => {
+107
View File
@@ -0,0 +1,107 @@
'use strict'
/**
* Module dependencies.
*/
const {
fs,
path: { resolve, parse },
moduleResolver: { getThemeResolver },
datatypes: { isString },
logger, chalk
} = require('@vuepress/shared-utils')
const ThemeAPI = require('./theme-api')
/**
* 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 {ThemeAPI}
*/
module.exports = function loadTheme (ctx) {
const themeResolver = getThemeResolver()
const theme = resolveTheme(ctx, themeResolver)
if (!theme.path) {
throw new Error(`[vuepress] You must specify a theme, or create a local custom theme. \n For more details, refer to https://vuepress.vuejs.org/guide/custom-themes.html#custom-themes. \n`)
}
let applyTip = `Apply theme ${chalk.magenta(theme.name)}`
theme.entry.name = '@vuepress/internal-theme-entry-file'
let parentTheme = {}
if (theme.entry.extend) {
parentTheme = resolveTheme(ctx, themeResolver, true, theme.entry.extend)
parentTheme.entry.name = '@vuepress/internal-parent-theme-entry-file'
applyTip += chalk.gray(` (extends ${chalk.magenta(parentTheme.name)})`)
}
logger.tip(applyTip + ' ...')
logger.debug('theme', theme.name, theme.path)
logger.debug('parentTheme', parentTheme.name, parentTheme.path)
return new ThemeAPI(theme, parentTheme, ctx)
}
function normalizeThemePath (resolved) {
const { entry, name, fromDep } = resolved
if (fromDep) {
return parse(require.resolve(name)).dir
} else if (entry.endsWith('.js') || entry.endsWith('.vue')) {
return parse(entry).dir
} else {
return entry
}
}
function resolveTheme (ctx, resolver, ignoreLocal, theme) {
const { siteConfig, options, sourceDir, vuepressDir, pluginAPI } = ctx
const localThemePath = resolve(vuepressDir, 'theme')
theme = theme || siteConfig.theme || options.theme
let path
let name
let shortcut
let entry = {}
// 1. From local
if (!ignoreLocal
&& !fs.existsSync(theme)
&& fs.existsSync(localThemePath)
&& (fs.readdirSync(localThemePath)).length > 0
) {
path = localThemePath
name = shortcut = 'local'
logger.tip(`Apply local theme at ${chalk.gray(path)}...`)
// 2. From dep
} else if (isString(theme)) {
const resolved = resolver.resolve(theme, sourceDir)
if (resolved.entry === null) {
throw new Error(`Cannot resolve theme: ${theme}.`)
}
path = normalizeThemePath(resolved)
name = resolved.name
shortcut = resolved.shortcut
} else {
return {}
}
try {
entry = pluginAPI.normalizePlugin(path, ctx.themeConfig)
} catch (error) {
entry = {}
}
return { path, name, shortcut, entry }
}
@@ -8,7 +8,6 @@ const PLUGIN_OPTION_META_MAP = {
GENERATED: { name: 'generated', types: [Function], async: true },
// options
CHAIN_WEBPACK: { name: 'chainWebpack', types: [Function] },
ENHANCE_DEV_SERVER: { name: 'enhanceDevServer', types: [Function] },
ENHANCE_APP_FILES: { name: 'enhanceAppFiles', types: [String, Object, Array, Function] },
OUT_FILES: { name: 'outFiles', types: [Object] },
EXTEND_PAGE_DATA: { name: 'extendPageData', types: [Function] },
@@ -20,7 +19,9 @@ const PLUGIN_OPTION_META_MAP = {
GLOBAL_UI_COMPONENTS: { name: 'globalUIComponents', types: [String, Array] },
DEFINE: { name: 'define', types: [Function, Object] },
ALIAS: { name: 'alias', types: [Function, Object] },
EXTEND_CLI: { name: 'extendCli', types: [Function] }
EXTEND_CLI: { name: 'extendCli', types: [Function] },
BEFORE_DEV_SERVER: { name: 'beforeDevServer', types: [Function] },
AFTER_DEV_SERVER: { name: 'afterDevServer', types: [Function] }
}
const PLUGIN_OPTION_MAP = {}
@@ -5,12 +5,12 @@
*/
const instantiateOption = require('./override/instantiateOption')
const { flattenPlugin, normalizePluginsConfig } = require('./util')
const { flattenPlugin } = require('./util')
const { PLUGIN_OPTION_MAP } = require('./constants')
const {
moduleResolver: { getPluginResolver },
datatypes: { assertTypes, isPlainObject },
logger, chalk
logger, chalk, normalizeConfig
} = require('@vuepress/shared-utils')
/**
@@ -22,6 +22,7 @@ module.exports = class PluginAPI {
this.options = {}
this._pluginContext = context
this._pluginQueue = []
this._loggedPlugins = []
this._initialized = false
this._pluginResolver = getPluginResolver()
this.initializeOptions(PLUGIN_OPTION_MAP)
@@ -84,7 +85,12 @@ module.exports = class PluginAPI {
if (isPlainObject(pluginRaw) && pluginRaw.$$normalized) {
plugin = pluginRaw
} else {
plugin = this.normalizePlugin(pluginRaw, pluginOptions)
try {
plugin = this.normalizePlugin(pluginRaw, pluginOptions)
} catch (e) {
logger.warn(e.message)
return this
}
}
if (plugin.multiple !== true) {
@@ -114,8 +120,7 @@ module.exports = class PluginAPI {
normalizePlugin (pluginRaw, pluginOptions = {}) {
let plugin = this._pluginResolver.resolve(pluginRaw)
if (!plugin.entry) {
console.warn(`[vuepress] cannot resolve plugin "${pluginRaw}"`)
return this
throw new Error(`[vuepress] cannot resolve plugin "${pluginRaw}"`)
}
plugin = flattenPlugin(plugin, pluginOptions, this._pluginContext, this)
plugin.$$normalized = true
@@ -131,7 +136,7 @@ module.exports = class PluginAPI {
*/
useByPluginsConfig (pluginsConfig) {
pluginsConfig = normalizePluginsConfig(pluginsConfig)
pluginsConfig = normalizeConfig(pluginsConfig)
pluginsConfig.forEach(([pluginRaw, pluginOptions]) => {
this.use(pluginRaw, pluginOptions)
})
@@ -169,8 +174,8 @@ module.exports = class PluginAPI {
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}`
`${chalk.gray(pluginName)} `
+ `Invalid value for "option" ${chalk.cyan(option.name)}: ${warnMsg}`
)
}
return this
@@ -195,7 +200,6 @@ module.exports = class PluginAPI {
// options
chainWebpack,
enhanceDevServer,
extendMarkdown,
chainMarkdown,
enhanceAppFiles,
@@ -207,10 +211,15 @@ module.exports = class PluginAPI {
globalUIComponents,
define,
alias,
extendCli
extendCli,
beforeDevServer,
afterDevServer
}) {
const isInternalPlugin = pluginName.startsWith('@vuepress/internal-')
logger[isInternalPlugin ? 'debug' : 'tip'](pluginLog(pluginName, shortcut))
if (!this._loggedPlugins.includes(pluginName)) {
const isInternalPlugin = pluginName.startsWith('@vuepress/internal-')
logger[isInternalPlugin ? 'debug' : 'tip'](pluginLog(pluginName, shortcut))
this._loggedPlugins.push(pluginName)
}
this
.registerOption(PLUGIN_OPTION_MAP.READY.key, ready, pluginName)
@@ -218,7 +227,6 @@ module.exports = class PluginAPI {
.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)
@@ -231,6 +239,52 @@ module.exports = class PluginAPI {
.registerOption(PLUGIN_OPTION_MAP.DEFINE.key, define, pluginName)
.registerOption(PLUGIN_OPTION_MAP.ALIAS.key, alias, pluginName)
.registerOption(PLUGIN_OPTION_MAP.EXTEND_CLI.key, extendCli, pluginName)
.registerOption(PLUGIN_OPTION_MAP.BEFORE_DEV_SERVER.key, beforeDevServer, pluginName)
.registerOption(PLUGIN_OPTION_MAP.AFTER_DEV_SERVER.key, afterDevServer, pluginName)
}
/**
* Apply synchronous option.
*
* @param {string} name
* @param {Array<any>} args
* @returns {void}
* @api public
*/
applySyncOption (name, ...args) {
logger.debug('applySyncOption: ' + name)
this.getOption(name).apply(...args)
return this
}
/**
* Apply asynchronous option.
*
* @param {string} name
* @param {Array<any>} args
* @returns {Promise<void>}
* @api public
*/
async applyAsyncOption (name, ...args) {
logger.debug('applyAsyncOption: ' + name)
await this.getOption(name).apply(...args)
}
/**
* Get exisiting option
*
* @param name
* @returns {Option}
* @api public
*/
getOption (name) {
if (!this.options[name]) {
throw new Error(`Unknown option ${name}`)
}
return this.options[name]
}
}
@@ -7,10 +7,10 @@
const Option = require('../abstract/Option')
/**
* define option.
* alias option.
*/
module.exports = class DefineOption extends Option {
module.exports = class AliasOption extends Option {
apply (config) {
super.syncApply()
const aliases = this.appliedValues

Some files were not shown because too many files have changed in this diff Show More