mirror of
https://github.com/wahyd4/vuepress.git
synced 2026-08-09 05:16:23 +10:00
Merge branch 'master' into feat/plugin-blog/archive
This commit is contained in:
@@ -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
@@ -6,6 +6,7 @@ module.exports = {
|
||||
],
|
||||
rules: {
|
||||
indent: ['error', 2, { MemberExpression: 'off' }],
|
||||
"no-undef": ["error"]
|
||||
"no-undef": ["error"],
|
||||
'operator-linebreak': ["error", "before"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extend: 'vuepress-theme-parent'
|
||||
}
|
||||
@@ -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,6 +3,9 @@ module.exports = {
|
||||
'test': {
|
||||
'presets': [
|
||||
['@babel/preset-env', { 'targets': { 'node': 'current' }}]
|
||||
],
|
||||
'plugins': [
|
||||
'@babel/plugin-syntax-dynamic-import'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+13
-13
@@ -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
-2
@@ -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('')
|
||||
+1
-2
@@ -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>
|
||||
+8
-4
@@ -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>
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/* global VUEPRESS_TEMP_PATH */
|
||||
|
||||
import Vue from 'vue'
|
||||
import GLobalVue from 'vue'
|
||||
|
||||
export default function dataMixin (I18n, siteData) {
|
||||
export default function dataMixin (I18n, siteData, Vue = GLobalVue) {
|
||||
prepare(siteData)
|
||||
Vue.$vuepress.$set('siteData', siteData)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+1
-1
@@ -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'
|
||||
+70
-17
@@ -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)
|
||||
}
|
||||
}))
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
Executable
+496
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -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()
|
||||
)
|
||||
})
|
||||
|
||||
+2
-2
@@ -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
|
||||
+55
-10
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
const AsyncOption = require('../../lib/plugin-api/abstract/AsyncOption')
|
||||
const AsyncOption = require('../../plugin-api/abstract/AsyncOption')
|
||||
|
||||
describe('AsyncOption', () => {
|
||||
test('parallelApply', async () => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import Option from '../../lib/plugin-api/abstract/Option'
|
||||
import Option from '../../plugin-api/abstract/Option'
|
||||
|
||||
describe('Option', () => {
|
||||
test('key', () => {
|
||||
+2
-2
@@ -2,8 +2,8 @@ jest.mock('vuepress-plugin-a')
|
||||
jest.mock('vuepress-plugin-b')
|
||||
jest.mock('@org/vuepress-plugin-a')
|
||||
|
||||
import PluginAPI from '../../lib/plugin-api/index'
|
||||
import { PLUGIN_OPTION_MAP } from '../../lib/plugin-api/constants'
|
||||
import PluginAPI from '../../plugin-api/index'
|
||||
import { PLUGIN_OPTION_MAP } from '../../plugin-api/constants'
|
||||
|
||||
describe('Plugin', () => {
|
||||
test('registerOption', () => {
|
||||
+12
-12
@@ -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)
|
||||
+9
-4
@@ -1,5 +1,5 @@
|
||||
const { fs, path } = require('@vuepress/shared-utils')
|
||||
const prepare = require('../../lib/prepare')
|
||||
const App = require('../../App')
|
||||
|
||||
const docsBaseDir = path.resolve(__dirname, 'fixtures')
|
||||
const docsModeNames = fs.readdirSync(docsBaseDir)
|
||||
@@ -9,12 +9,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)
|
||||
}))
|
||||
})
|
||||
})
|
||||
+21
-13
@@ -1,19 +1,28 @@
|
||||
const Page = require('../../lib/prepare/Page')
|
||||
const Page = require('../../Page')
|
||||
const App = require('../../App')
|
||||
|
||||
const {
|
||||
getComputed,
|
||||
getMarkdown,
|
||||
getDocument,
|
||||
readFile
|
||||
} = require('./util')
|
||||
|
||||
describe('Page', () => {
|
||||
let app
|
||||
let computed
|
||||
|
||||
beforeAll(async () => {
|
||||
app = new App()
|
||||
await app.process()
|
||||
computed = new app.ClientComputedMixinConstructor()
|
||||
})
|
||||
|
||||
test('pure route', async () => {
|
||||
const page = new Page({ path: '/' })
|
||||
const page = new Page({ path: '/' }, app)
|
||||
|
||||
expect(page.path).toBe('/')
|
||||
expect(page.regularPath).toBe('/')
|
||||
|
||||
const computed = getComputed()
|
||||
await page.process({ computed })
|
||||
|
||||
expect(page.path).toBe('/')
|
||||
@@ -22,7 +31,7 @@ describe('Page', () => {
|
||||
|
||||
test('pure route - encodeURI', async () => {
|
||||
const path = '/尤/'
|
||||
const page = new Page({ path })
|
||||
const page = new Page({ path }, app)
|
||||
|
||||
expect(page.path).toBe(encodeURI(path))
|
||||
expect(page.regularPath).toBe(encodeURI(path))
|
||||
@@ -33,7 +42,7 @@ describe('Page', () => {
|
||||
const page = new Page({
|
||||
path: '/',
|
||||
frontmatter
|
||||
})
|
||||
}, app)
|
||||
expect(page.frontmatter).toBe(frontmatter)
|
||||
})
|
||||
|
||||
@@ -42,15 +51,16 @@ describe('Page', () => {
|
||||
const page = new Page({
|
||||
path: '/',
|
||||
frontmatter
|
||||
})
|
||||
}, app)
|
||||
|
||||
expect(page.frontmatter.title).toBe('alpha')
|
||||
|
||||
const computed = getComputed()
|
||||
const enhancers = [
|
||||
{
|
||||
name: 'plugin-a',
|
||||
value: page => { page.frontmatter.title = 'beta' }
|
||||
value: page => {
|
||||
page.frontmatter.title = 'beta'
|
||||
}
|
||||
}
|
||||
]
|
||||
await page.process({ computed, enhancers })
|
||||
@@ -60,14 +70,13 @@ describe('Page', () => {
|
||||
|
||||
test('markdown page - pointing to a markdown file', async () => {
|
||||
const { relative, filePath } = getDocument('README.md')
|
||||
const page = new Page({ filePath, relative })
|
||||
const page = new Page({ filePath, relative }, app)
|
||||
|
||||
expect(page._filePath).toBe(filePath)
|
||||
expect(page.regularPath).toBe('/')
|
||||
expect(page.path).toBe('/')
|
||||
expect(page.frontmatter).toEqual({})
|
||||
|
||||
const computed = getComputed()
|
||||
const markdown = getMarkdown()
|
||||
await page.process({ computed, markdown })
|
||||
|
||||
@@ -79,14 +88,13 @@ describe('Page', () => {
|
||||
|
||||
test('markdown page - pointing to a markdown file with frontmatter', async () => {
|
||||
const { relative, filePath } = getDocument('alpha.md')
|
||||
const page = new Page({ filePath, relative })
|
||||
const page = new Page({ filePath, relative }, app)
|
||||
|
||||
expect(page._filePath).toBe(filePath)
|
||||
expect(page.regularPath).toBe('/alpha.html')
|
||||
expect(page.path).toBe('/alpha.html')
|
||||
expect(page.frontmatter).toEqual({})
|
||||
|
||||
const computed = getComputed()
|
||||
const markdown = getMarkdown()
|
||||
await page.process({ computed, markdown })
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
title: 'Hello VuePress',
|
||||
description: '# Hello, VuePress!',
|
||||
theme: 'vuepress-theme-without-index'
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# Hello, VuePress!
|
||||
+1
-13
@@ -1,15 +1,5 @@
|
||||
const { fs, path } = require('@vuepress/shared-utils')
|
||||
const AppContext = require('../../lib/prepare/AppContext')
|
||||
const createMarkdown = require('../../../markdown/index')
|
||||
|
||||
function getAppContext () {
|
||||
return new AppContext('.')
|
||||
}
|
||||
|
||||
function getComputed () {
|
||||
const context = getAppContext()
|
||||
return new context.ClientComputedMixinConstructor()
|
||||
}
|
||||
const createMarkdown = require('../../../../../markdown/index')
|
||||
|
||||
const docsBaseDir = path.resolve(__dirname, 'fixtures/docs')
|
||||
|
||||
@@ -25,8 +15,6 @@ const getMarkdown = createMarkdown
|
||||
const readFile = async filePath => await fs.readFile(filePath, 'utf-8')
|
||||
|
||||
module.exports = {
|
||||
getAppContext,
|
||||
getComputed,
|
||||
getMarkdown,
|
||||
getDocument,
|
||||
readFile
|
||||
@@ -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)
|
||||
}
|
||||
+2
-2
@@ -16,12 +16,12 @@ module.exports = function (ctx) {
|
||||
|
||||
const beforeInstantiate = config => {
|
||||
chainMarkdown && chainMarkdown(config)
|
||||
ctx.pluginAPI.options.chainMarkdown.syncApply(config)
|
||||
ctx.pluginAPI.applySyncOption('chainMarkdown', config)
|
||||
}
|
||||
|
||||
const afterInstantiate = md => {
|
||||
extendMarkdown && extendMarkdown(md)
|
||||
ctx.pluginAPI.options.extendMarkdown.syncApply(md)
|
||||
ctx.pluginAPI.applySyncOption('extendMarkdown', md)
|
||||
}
|
||||
|
||||
return createMarkdown(
|
||||
@@ -0,0 +1,42 @@
|
||||
const { fs, path, chalk, logger } = require('@vuepress/shared-utils')
|
||||
|
||||
/**
|
||||
* Create a dynamic temp utility context that allow to lanuch
|
||||
* multiple apps with isolated context at the same time.
|
||||
* @param tempPath
|
||||
* @returns {{
|
||||
* writeTemp: (function(file: string, content: string): string),
|
||||
* tempPath: string
|
||||
* }}
|
||||
*/
|
||||
|
||||
module.exports = function createTemp (tempPath) {
|
||||
if (!tempPath) {
|
||||
tempPath = path.resolve(__dirname, '../../.temp')
|
||||
} else {
|
||||
tempPath = path.resolve(tempPath)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(tempPath)) {
|
||||
fs.ensureDirSync(tempPath)
|
||||
} else {
|
||||
fs.emptyDirSync(tempPath)
|
||||
}
|
||||
|
||||
logger.debug(`Temp directory: ${chalk.gray(tempPath)}`)
|
||||
const tempCache = new Map()
|
||||
|
||||
async function writeTemp (file, content) {
|
||||
const destPath = path.join(tempPath, file)
|
||||
await fs.ensureDir(path.parse(destPath).dir)
|
||||
// cache write to avoid hitting the dist if it didn't change
|
||||
const cached = tempCache.get(file)
|
||||
if (cached !== content) {
|
||||
await fs.writeFile(destPath, content)
|
||||
tempCache.set(file, content)
|
||||
}
|
||||
return destPath
|
||||
}
|
||||
|
||||
return { writeTemp, tempPath }
|
||||
}
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
})
|
||||
+6
-6
@@ -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)
|
||||
+2
-11
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-11
@@ -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
-2
@@ -1,4 +1,3 @@
|
||||
const { path } = require('@vuepress/shared-utils')
|
||||
const { codegen: { pathsToModuleCode }} = require('@vuepress/shared-utils')
|
||||
|
||||
module.exports = (options, context, api) => ({
|
||||
@@ -7,7 +6,7 @@ module.exports = (options, context, api) => ({
|
||||
// @internal/root-mixins
|
||||
async clientDynamicModules () {
|
||||
const builtInRootMixins = [
|
||||
path.resolve(__dirname, '../app/root-mixins/updateMeta.js')
|
||||
context.getLibFilePath('client/root-mixins/updateMeta.js')
|
||||
]
|
||||
|
||||
const rootMixins = [
|
||||
+7
-8
@@ -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)
|
||||
}
|
||||
})
|
||||
+1
-1
@@ -11,7 +11,7 @@ module.exports = (options, ctx) => ({
|
||||
|
||||
async clientDynamicModules () {
|
||||
const files = [
|
||||
path.resolve(__dirname, '../prepare/ClientComputedMixin.js')
|
||||
path.resolve(__dirname, '../ClientComputedMixin.js')
|
||||
]
|
||||
|
||||
const modules = await Promise.all(files.map(async file => {
|
||||
+107
@@ -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 }
|
||||
}
|
||||
+3
-2
@@ -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 = {}
|
||||
+67
-13
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
Reference in New Issue
Block a user