Skip to content

VuePress Next Alpha #815

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 255 commits into from
Sep 26, 2018
Merged
Changes from all commits
Commits
Show all changes
255 commits
Select commit Hold shift + click to select a range
5b00b02
feat: support enhanceAppFiles API.
ulivz Jun 3, 2018
ab75ba9
feat: move enhanceApp and themeEnhanceApp to plugin.
ulivz Jun 3, 2018
fa4eb64
refactor: extract the resolveSiteData as a single step
ulivz Jun 3, 2018
bc46896
feat: support context.writeTemp
ulivz Jun 3, 2018
0f3fa74
feat: support chainWebpack option
ulivz Jun 3, 2018
d733f1a
refactor: (wip) reorganize
ulivz Jun 5, 2018
f19da0a
feat: support 'generated' hook and 'extendMarkdown' and 'enhanceDevSe…
ulivz Jun 5, 2018
af14e4b
feat: internal and external plugins
ulivz Jun 6, 2018
4f26102
feat: support 'dynamicClientCode' option
ulivz Jun 6, 2018
079bd23
chore: fix typo
ulivz Jun 6, 2018
8bb3287
feat: rename 'dynamicClientCode' to 'dynamicClientModules'
ulivz Jun 6, 2018
7863b56
feat: rename 'dynamicClientModules' to 'clientDynamicModules'
ulivz Jun 6, 2018
dd87d37
feat: support 'clientRootMixin' option and change 'active-header-link…
ulivz Jun 6, 2018
3ba2254
fix: make 'activeHeaderLinks' backward compatible
ulivz Jun 6, 2018
eec3048
fix: make 'lastUpdated' backward compatible
ulivz Jun 6, 2018
68d9e4b
chore: tweaks comments
ulivz Jun 6, 2018
b20c23b
feat: make 'register-global-components' fully independent.
ulivz Jun 6, 2018
a63f3ae
fix: enhance app doesn't work due to interface change
ulivz Jun 6, 2018
9cd74b1
feat: support 'additionalPages' option
ulivz Jun 6, 2018
0bcc895
feat: support 'updated' hook
ulivz Jun 6, 2018
3560fa3
Merge branch 'master' into plugin
ulivz Jun 9, 2018
e8c08ce
feat: Content component - support take pageKey to render dynamic page
ulivz Jun 10, 2018
cb63629
wip commit
ulivz Jun 10, 2018
b2c5484
Merge branch 'master' into plugin
ulivz Jun 14, 2018
3b16c5c
feat: support spearate "loadComponent" util
ulivz Jun 14, 2018
d210ec0
refactor: use async components to simplify code
ulivz Jun 14, 2018
4a4f8ed
style: change ".content" to "#content"
ulivz Jun 14, 2018
b82b5fd
chore: extract translation-ui as a separate package
ulivz Jun 14, 2018
4a578d1
Revert "style: change ".content" to "#content""
ulivz Jun 14, 2018
4c86188
wip commit
ulivz Jun 17, 2018
e659a86
chore: clean store
ulivz Jun 24, 2018
ddeed7e
docs: wip commit
ulivz Jun 26, 2018
7bb4d22
Merge branch 'master' into plugin
ulivz Jul 3, 2018
8f4bc4a
feat: enhanceAppFiles: support dynamic content
ulivz Jul 3, 2018
5fc7404
refactor: enhanceAppFiles: imporve docs and code gen
ulivz Jul 3, 2018
eaa80af
Merge branch 'master' into plugin
ulivz Jul 28, 2018
7ae2b31
Merge branch 'master' into next
ulivz Aug 9, 2018
7892556
feat: init monorepo
ulivz Aug 10, 2018
9693ae1
chore: move all docs to sub dir
ulivz Aug 10, 2018
3f65817
feat: new theme api
ulivz Aug 10, 2018
014c86b
feat: init @vuepress/plugin-translation-ui
ulivz Aug 10, 2018
e2a784d
refactor: Extract default-theme-only webpack aliases
ulivz Aug 10, 2018
642968a
refactor: Move `active-header-links` to default theme.
ulivz Aug 10, 2018
ca78f66
feat: support shortcut usage of @vuepress/plugin-xxx
ulivz Aug 10, 2018
f1e6dd4
refactor: handle when theme isn't given
ulivz Aug 11, 2018
c004fb3
feat: extract @vuepress/cli
ulivz Aug 11, 2018
0d7fce7
feat: pass plugins and theme via cli
ulivz Aug 11, 2018
2e923d5
chore: correct deps
ulivz Aug 11, 2018
590c1c4
chore: update package.json
ulivz Aug 11, 2018
c390e40
feat: refine @vuepress/plugin-register-components
ulivz Aug 11, 2018
82f06bf
chore: rename @vuepress/plugin-translation-ui to "@vuepress/plugin-i1…
ulivz Aug 11, 2018
d41344e
docs(@vuepress/plugin-i18n-ui): enhancement
ulivz Aug 11, 2018
b8e7953
docs($plugin): enhancement
ulivz Aug 11, 2018
1c170c2
docs: add theme part
ulivz Aug 11, 2018
397d01a
feat: init @vuepress/plugin-google-analytics
ulivz Aug 12, 2018
17f0acd
docs: document for default theme
ulivz Aug 12, 2018
5f07c39
feat($plugin): 'globalUIComponents' option
ulivz Aug 12, 2018
b9fdf64
feat: @vuepress/plugin-back-to-top
ulivz Aug 12, 2018
863a7b7
docs(last-updated): transformer option
ulivz Aug 12, 2018
f6956a6
docs: tweaks
ulivz Aug 12, 2018
8397a97
docs: refine "Using a plugin"
ulivz Aug 12, 2018
57f15ee
test: setup test
ulivz Aug 13, 2018
9424df3
test: 'Tapable'
ulivz Aug 13, 2018
8b0adcb
feat: refine 'resolvePlugin' and 'resolveScopePackage' and update test
ulivz Aug 14, 2018
9a50862
feat: refine plugin API
ulivz Aug 15, 2018
f346884
feat: respect name in local plugin config
ulivz Aug 15, 2018
2236365
feat: using shortcut to resolve plugin
ulivz Aug 15, 2018
bd25781
fix: correct prepare usage
ulivz Aug 15, 2018
6c364fe
refactor: enhance log output
ulivz Aug 15, 2018
08cef09
refactor(@vuepress/back-to-top): use pure css so this plugin can be u…
ulivz Aug 15, 2018
28fdd90
chore: clean all "name" fields
ulivz Aug 15, 2018
4ad784c
fix: cannot run "yarn dev" at the root project dir
ulivz Aug 15, 2018
263da7a
chore: tweaks plugin docs
ulivz Aug 15, 2018
ca7e02b
chore: remove useless name field
ulivz Aug 15, 2018
cc03da9
chore: rename Option spec
ulivz Aug 15, 2018
c64edbf
test: improve test for hydratePlugin
ulivz Aug 15, 2018
f2064a7
chore: remove console
ulivz Aug 15, 2018
d74615d
feat: init "@vuepress/plugin-pwa"
ulivz Aug 15, 2018
ac554a9
docs($pwa): enhance docs
ulivz Aug 15, 2018
4c2cccb
feat: utility "normalizeConfig"
ulivz Aug 16, 2018
1fa57f6
feat: enhance plugin API - distinguish async and sync execution
ulivz Aug 16, 2018
1b349dc
chore: update .gitignore
ulivz Aug 16, 2018
6f5c282
feat: expose isServer to enhanceApp files
ulivz Aug 16, 2018
7139532
fix: missing component registration code
ulivz Aug 16, 2018
9292660
fix: unexpected error
ulivz Aug 16, 2018
9dfd6fb
chore: remove log
ulivz Aug 16, 2018
d3e2d09
fix: yarn buiild failed.
ulivz Aug 16, 2018
c97a531
fix: ssr failed - move window accessing to mounted hook
ulivz Aug 16, 2018
841e03e
feat: plugin context - publicDir
ulivz Aug 16, 2018
1c70be6
chore: tweaks naming
ulivz Aug 16, 2018
ae6b3e9
feat: init @vuepress/shared-utils
ulivz Aug 16, 2018
07f386d
chore: disable algolia for now.
ulivz Aug 16, 2018
4282d3b
refactor: using plugin API to rewrite code generation of 'routes'
ulivz Aug 16, 2018
0652242
feat: Completely make '@vuepress/plugin-pwa' a standalone plugin
ulivz Aug 16, 2018
9b108a5
chore: correct dependency
ulivz Aug 17, 2018
86c8dbf
feat: move 'writeTemp' to shared utility and support config temp path
ulivz Aug 17, 2018
7239daf
fix: dev server hot relead failed - correct siteDate path
ulivz Aug 17, 2018
216b8d5
feat($pwa): support popupComponent and update docs
ulivz Aug 17, 2018
fa63b67
feat($pwa): built-in i18n support, support "updatePopup: true" shortcut
ulivz Aug 17, 2018
a406bef
test: fix Option test failed
ulivz Aug 17, 2018
07f3296
chore: update TODOs in README
ulivz Aug 17, 2018
7dfee46
feat: delay apply plugin and create more APIs for test
ulivz Aug 17, 2018
5350e36
test: improve test for plugin
ulivz Aug 17, 2018
268eaed
docs: tweaks path
ulivz Aug 17, 2018
2d65bf2
feat($shared-utils): codegen util
ulivz Aug 17, 2018
675833d
refactor($core): extract internal plugin root-mixins
ulivz Aug 17, 2018
4af0a02
refactor($core): extract internal plugin import-async-component
ulivz Aug 17, 2018
c05768a
chore($core): flattern internal plugins directory structure
ulivz Aug 17, 2018
dfd86ab
refactor($core): remove @vuepress/plugin-enhance-app and move it to core
ulivz Aug 17, 2018
1096663
chore($core): update core's README and remove exported util
ulivz Aug 17, 2018
e9b109f
refactor($core): refine eject
ulivz Aug 17, 2018
49d8be8
docs($cli): documentation
ulivz Aug 17, 2018
a379708
chore: clean @vuepress/plugin-test
ulivz Aug 17, 2018
e02a919
chore($vuepress): remove '@vuepress/i18n-ui' preset.
ulivz Aug 17, 2018
6a97de1
chore($theme-default): remove '@vuepress/google-analytics' preset.
ulivz Aug 17, 2018
b90e009
feat($shared-utils) env.isProd
ulivz Aug 17, 2018
8f89eef
refacotr($core): separate createCSSRule from core to shared-utils
ulivz Aug 20, 2018
8a7463a
chore($env): change isProd to function
ulivz Aug 20, 2018
17cd87d
feat: set up @vuepress/plugin-stylus
ulivz Aug 20, 2018
5e46295
feat: set up @vuepress/plugin-sass
ulivz Aug 20, 2018
4464185
feat: set up @vuepress/plugin-less
ulivz Aug 20, 2018
8780c2c
refactor($core): using camel cases at file name
ulivz Aug 20, 2018
ee32e85
feat($core): support 'dirname' field for 'clientDynamicModules'
ulivz Aug 20, 2018
fe61f99
refactor($core): extract internal plugin 'site-data'
ulivz Aug 20, 2018
5fe91bf
refactor($core): extract internal plugin 'override-css'
ulivz Aug 20, 2018
afe0e32
chore: tweak prepare index file.
ulivz Aug 20, 2018
721f503
feat: make sure the namesake plugin is only executed once.
ulivz Aug 20, 2018
8d1fa5c
chore: update plugin generation comment
ulivz Aug 20, 2018
dd9bea3
fix($core): do not pollute raw config
ulivz Aug 21, 2018
1747da1
feat: support multiple option for plugin API.
ulivz Aug 21, 2018
ac55f92
feat($core): write all enhanceApp files to separate file.
ulivz Aug 21, 2018
1be40ab
fix($register-components): avoid file overwritten.
ulivz Aug 21, 2018
b30fa4f
refactor($core): rename 'Plugin' to 'PluginAPI'
ulivz Aug 21, 2018
1553ceb
chore($core): move internal dynamic modules to 'internal' dir.
ulivz Aug 21, 2018
1d24fa8
Revert "feat: set up @vuepress/plugin-less"
ulivz Aug 21, 2018
9358387
Revert "feat: set up @vuepress/plugin-sass"
ulivz Aug 21, 2018
c1cf4b1
Revert "feat: set up @vuepress/plugin-stylus"
ulivz Aug 21, 2018
639e5d4
Revert "refacotr($core): separate createCSSRule from core to shared-u…
ulivz Aug 21, 2018
ad574ce
refactor($core): 'frontmatter' defaults to {} at build time
ulivz Aug 23, 2018
68518ff
chore: naming: self => api
ulivz Aug 23, 2018
c9e0385
chore($core): simplify ExtendPageDataOption
ulivz Aug 23, 2018
6d19b58
chore: fix typo
ulivz Aug 23, 2018
45252a9
refactor($core): extract dataMixin, try to reuse it at build time
ulivz Aug 23, 2018
afa71b5
refactor: refine core implementation (#762)
ulivz Aug 23, 2018
abc1d01
fix: regression - avoid deliver 'content' to client side
ulivz Aug 23, 2018
6a4592d
chore: simplify encode url
ulivz Aug 23, 2018
06941fd
Revert "refactor($core): extract dataMixin, try to reuse it at build …
ulivz Aug 23, 2018
c7d8ee9
feat($core): execute 'i18n' (i.e. dataMixin) on both server and clien…
ulivz Aug 23, 2018
2bdf88c
feat: blog stage 1 - permalink (#763)
ulivz Aug 26, 2018
5599ae0
test($core): use es6 import
ulivz Aug 26, 2018
c1d7aeb
test($core): migrate test for markdown
ulivz Aug 27, 2018
8e84065
feat($shared-utils): allow change temp path at runtime via env.
ulivz Aug 27, 2018
11997f0
chore: normalize prepare API.
ulivz Aug 27, 2018
d708d33
feat: allow to lanuch multiple apps with isolated context at the same…
ulivz Aug 27, 2018
eca8a61
test: init @vuepress/test-utils and migrate all tests
ulivz Aug 27, 2018
dc3b0f4
Merge branch 'master' into next
ulivz Aug 28, 2018
4487149
docs: enhance plugin documents
ulivz Aug 28, 2018
c575d62
docs: refine 'Introduction'
ulivz Aug 28, 2018
7f04708
feat($core): let 'core' provide a default NotFound component
ulivz Aug 28, 2018
b45633e
refactor($core): move core resuable utilities to 'shared-utils and fi…
ulivz Aug 28, 2018
adb74b7
fix: valid filename for case-sensitive os (#779)
cuyl Aug 30, 2018
3cf9df3
docs: blog documents (wip - 1)
ulivz Aug 30, 2018
db1ff23
Merge branch 'next' of https://github.com/vuejs/vuepress into next
ulivz Aug 30, 2018
f2c3a63
chore: write temp to docs package dir
ulivz Aug 30, 2018
3984359
docs: permalinks
ulivz Aug 30, 2018
b80c810
feat($shared-utils): enhance datatypes
ulivz Sep 1, 2018
a8fea3c
feat($shared-utils): shortcutPackageResolver
ulivz Sep 1, 2018
3daac59
chore($core): clean legacy plugin utils and tests
ulivz Sep 1, 2018
32a3752
chore($test-utils): tweaks
ulivz Sep 1, 2018
9622ed8
chore: clean package.json
ulivz Sep 1, 2018
76b74b1
chore: bump jest
ulivz Sep 2, 2018
f81f373
feat: refine theme API
ulivz Sep 2, 2018
e08cc2e
chore($core): hide internal plugin log
ulivz Sep 2, 2018
c19fb3a
fix: escape backslash (#787)
cuyl Sep 3, 2018
7f7fe18
feat: support clean and multiple custom layout. / theme default layou…
ulivz Sep 4, 2018
44fb133
Merge branch 'next' of https://github.com/vuejs/vuepress into next
ulivz Sep 4, 2018
0aa3d95
chore: tweaks
ulivz Sep 4, 2018
04723be
chore: change config load order (#804)
ekoeryanto Sep 6, 2018
cf088f5
refactor($core): simplify page components registration
ulivz Sep 6, 2018
f0a1f68
Merge branch 'next' of https://github.com/vuejs/vuepress into next
ulivz Sep 6, 2018
fe3c2d1
Revert "chore: change config load order (#804)"
ulivz Sep 6, 2018
f8b8c51
chore: clean expired code
ulivz Sep 7, 2018
99cd911
feat: markdown slots (close: #594)
ulivz Sep 7, 2018
1cba9bc
feat: init @vuepress/plugin-blog
ulivz Sep 8, 2018
20d612c
refactor: simplify the implementation of makrdown slots
ulivz Sep 8, 2018
078696e
feat($plugin-blog): correct Layout for index page and one-level page
ulivz Sep 8, 2018
b507fae
feat: empty temp dir before launching app
ulivz Sep 8, 2018
0e34e2d
refactor($core): clientDynamicModules option - use default value
ulivz Sep 8, 2018
463625c
feat($theme-blog): add 'type' field to page data and more reasonable …
ulivz Sep 8, 2018
f5e1e99
refactor($core): simplify Page's interface
ulivz Sep 8, 2018
adb3d56
chore: avoid Content component throw error
ulivz Sep 8, 2018
011c3c3
fix($core): exclude non-content pages from the page components
ulivz Sep 8, 2018
96ddb59
refactor($core): refine Page / AppContext
ulivz Sep 8, 2018
357e1a5
feat($core): detaMixin - expose $themeConfig
ulivz Sep 8, 2018
4e340fc
feat: init @vuepress/plugin-pagination
ulivz Sep 8, 2018
4492bcd
feat: @vuepress/plugin-medium-zoom
ulivz Sep 8, 2018
070fab7
chore: clean legacy code
ulivz Sep 8, 2018
6550f86
fix($core): regression of changing inferTitle util
ulivz Sep 8, 2018
feb74eb
fix($core): SSR error when async components were registered in runtime.
ulivz Sep 9, 2018
42e36ed
chore: gitignore
ulivz Sep 16, 2018
922bf13
fix: build error
ulivz Sep 16, 2018
e2b6be8
fix($core): themePath should default to modulePath
ulivz Sep 16, 2018
93f169a
fix($pagination): do not need to withBase & do not generate root html
ulivz Sep 16, 2018
24840cd
feat($shared-utils): support pass in an absolute path for theme
ulivz Sep 16, 2018
ea64033
chore: expose the dist directory to workspace root
ulivz Sep 16, 2018
eff7949
feat($core): enhanceAppFile doesn't need export default manually.
ulivz Sep 16, 2018
38b3468
feat($core): config the dev and ssr template. (close: #733)
ulivz Sep 16, 2018
2a46178
feat($cli): support '--cache' and '--no-cache' flag
ulivz Sep 16, 2018
394d013
chore: sunset 'fs.existsSync' (#847)
qianhum Sep 17, 2018
1798603
chore($pagination): reanme enhanceApp to clientPlugin
ulivz Sep 18, 2018
cb36ae6
feat($core): support passing in meta
ulivz Sep 18, 2018
163f8a5
feat($blog): support category and tag
ulivz Sep 18, 2018
e60a86f
Merge branch 'next' of https://github.com/vuejs/vuepress into next
ulivz Sep 18, 2018
8a7ee6c
feat($core): site config as plugin
ulivz Sep 19, 2018
0263f15
feat($core): 'define' plugin option and rewrite plugin API.
ulivz Sep 22, 2018
116d832
chore: fix typo
ulivz Sep 22, 2018
a122dfa
feat($core): supoort pipeline plugin API.
ulivz Sep 22, 2018
4bc4331
feat($core): use markdown-it-chain
ulivz Sep 22, 2018
a5cbd67
revert: change AsyncOption to prototype-style.
ulivz Sep 22, 2018
4d89f74
chore: add mising deps
ulivz Sep 23, 2018
91d8720
fix($test): move babel config to test-utils to get correct transform
ulivz Sep 23, 2018
3c8339f
Revert "revert: change AsyncOption to prototype-style."
ulivz Sep 23, 2018
1dc7cc6
test: refine
ulivz Sep 23, 2018
f74ccdd
test: clean Content.spec.js
ulivz Sep 23, 2018
ade62c9
fix($core): null check for Layout components
ulivz Sep 23, 2018
e4f2f18
refactor($pwa): using define API.
ulivz Sep 23, 2018
a2f34f5
test: exclude default theme
ulivz Sep 23, 2018
d910859
refactor($ga): using define
ulivz Sep 23, 2018
a5f58f7
feat($core): alias - plugin API
ulivz Sep 23, 2018
3125fc7
chore: move config.styl to core
ulivz Sep 23, 2018
c4e27a8
chore: fix typo
ulivz Sep 23, 2018
7b42984
feat($core): flatten return array of functional option
ulivz Sep 23, 2018
b0e3209
feat: plugin-search
ulivz Sep 23, 2018
63555c0
feat($core): support 'palette' API, deprecate override.styl
ulivz Sep 23, 2018
01fa9af
chore($theme-default): using search plugin
ulivz Sep 23, 2018
658ea75
refactor: unify code style.
ulivz Sep 23, 2018
1b234b3
fix: build failed - wrong import
ulivz Sep 23, 2018
91ebd10
feat($pagination): correct meta title.
ulivz Sep 24, 2018
0d57666
refactor($i18n-ui): simplify
ulivz Sep 24, 2018
fb6e8d4
refactor: some enhancement:
ulivz Sep 24, 2018
af95037
ci: set up release script
ulivz Sep 25, 2018
b6a2242
Merge branch 'next' of https://github.com/vuejs/vuepress into next
ulivz Sep 25, 2018
9a7f0cb
chore: add mising version
ulivz Sep 25, 2018
e380de3
feat($core): plugin option - chainMarkdown
ulivz Sep 25, 2018
c3cf73f
docs: update plugin part
ulivz Sep 25, 2018
53c84b3
refactor($core): separate markdown and markdown-loader from core
ulivz Sep 26, 2018
239867f
chore: clean types
ulivz Sep 26, 2018
037080d
test: Page class
ulivz Sep 26, 2018
53cfc78
Merge branch 'master' into next
ulivz Sep 26, 2018
102852c
chore: update status to alpha
ulivz Sep 26, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
9 changes: 0 additions & 9 deletions .babelrc

This file was deleted.

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -2,5 +2,6 @@
node_modules
*.log
.temp
vuepress
TODOs.md
vuepress
packages/blog-example
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -10,6 +10,12 @@
<a href="https://www.npmjs.com/package/vuepress"><img src="https://img.shields.io/npm/l/vuepress.svg" alt="License"></a>
</p>

> This is the branch for `VuePress 1.0`.
## Status: alpha

Certain combinations of plugins may not work properly, and things may change or break until we reach beta phase. Do not use in production yet unless you are adventurous.

# VuePress

> Minimalistic docs generator with Vue component based layout system
@@ -78,8 +84,11 @@ Websites built with VuePress:

VuePress is still a work in progress. There are a few things that it currently does not support but are planned:

- Plugin support
- Blogging support
- Migrate the old test.
- `@vuepress/plugin-test-utils`.
- `once` option for plugin options, which allows the same plugin only to be applied only once.
- `theme` name shortcut.
- `@vuepress/theme-blog`

Contributions are welcome!

1 change: 1 addition & 0 deletions __mocks__/@org/vuepress-plugin-a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
1 change: 1 addition & 0 deletions __mocks__/@org/vuepress-plugin-b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
1 change: 1 addition & 0 deletions __mocks__/@org/vuepress-theme-a/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
1 change: 1 addition & 0 deletions __mocks__/@vuepress/plugin-a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
1 change: 1 addition & 0 deletions __mocks__/@vuepress/theme-a/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
1 change: 1 addition & 0 deletions __mocks__/vuepress-plugin-a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
3 changes: 3 additions & 0 deletions __mocks__/vuepress-plugin-b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
multiple: true
}
1 change: 1 addition & 0 deletions __mocks__/vuepress-theme-a/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
9 changes: 9 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
'env': {
'test': {
'presets': [
['@babel/preset-env', { 'targets': { 'node': 'current' }}]
]
}
}
}
110 changes: 0 additions & 110 deletions bin/vuepress.js

This file was deleted.

6 changes: 6 additions & 0 deletions lerna.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"lerna": "2.5.1",
"npmClient": "yarn",
"useWorkspaces": true,
"version": "1.0.0"
}
74 changes: 0 additions & 74 deletions lib/app/clientEntry.js

This file was deleted.

17 changes: 0 additions & 17 deletions lib/app/components/Content.js

This file was deleted.

90 changes: 0 additions & 90 deletions lib/app/dataMixin.js

This file was deleted.

7 changes: 0 additions & 7 deletions lib/app/root-mixins/index.js

This file was deleted.

14 changes: 0 additions & 14 deletions lib/app/serverEntry.js

This file was deleted.

7 changes: 0 additions & 7 deletions lib/app/store.js

This file was deleted.

19 changes: 0 additions & 19 deletions lib/app/util.js

This file was deleted.

85 changes: 0 additions & 85 deletions lib/default-theme/SWUpdatePopup.vue

This file was deleted.

78 changes: 0 additions & 78 deletions lib/markdown/index.js

This file was deleted.

74 changes: 0 additions & 74 deletions lib/prepare/codegen.js

This file was deleted.

51 changes: 0 additions & 51 deletions lib/prepare/index.js

This file was deleted.

194 changes: 0 additions & 194 deletions lib/prepare/resolveOptions.js

This file was deleted.

80 changes: 0 additions & 80 deletions lib/prepare/util.js

This file was deleted.

83 changes: 0 additions & 83 deletions lib/util/index.js

This file was deleted.

99 changes: 21 additions & 78 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
{
"name": "vuepress",
"version": "0.14.4",
"private": true,
"workspaces": [
"packages/@vuepress/*",
"packages/vuepress",
"packages/docs",
"packages/blog-example"
],
"description": "Minimalistic doc generator with Vue component based layout system",
"main": "lib/index.js",
"bin": {
"vuepress": "bin/vuepress.js"
},
"scripts": {
"dev": "node bin/vuepress dev docs",
"build": "node bin/vuepress build docs",
"lint": "eslint --fix --ext .js,.vue bin/ lib/ test/",
"prepublishOnly": "conventional-changelog -p angular -r 2 -i CHANGELOG.md -s",
"release": "/bin/bash scripts/release.sh",
"test": "node test/prepare.js && jest --config test/jest.config.js"
"boot": "node scripts/bootstrap.js",
"dev": "yarn workspace docs dev",
"build": "yarn workspace docs build",
"dev:blog-example": "yarn workspace blog-example dev",
"build:blog-example": "yarn workspace blog-example build",
"lint": "eslint --fix packages/**/*.js packages/**/*.vue packages/**/bin/*",
"release": "yarn --pure-lockfile && node scripts/release.js",
"changelog": "node scripts/genChangelog.js run",
"test": "node scripts/test.js"
},
"repository": {
"type": "git",
@@ -38,78 +42,17 @@
"git add"
]
},
"dependencies": {
"@babel/core": "7.0.0-beta.47",
"@vue/babel-preset-app": "3.0.0-beta.11",
"autoprefixer": "^8.2.0",
"babel-loader": "8.0.0-beta.3",
"cache-loader": "^1.2.2",
"chalk": "^2.3.2",
"chokidar": "^2.0.3",
"commander": "^2.15.1",
"connect-history-api-fallback": "^1.5.0",
"copy-webpack-plugin": "^4.5.1",
"cross-spawn": "^6.0.5",
"css-loader": "^0.28.11",
"diacritics": "^1.3.0",
"docsearch.js": "^2.5.2",
"escape-html": "^1.0.3",
"file-loader": "^1.1.11",
"fs-extra": "^5.0.0",
"globby": "^8.0.1",
"gray-matter": "^4.0.1",
"js-yaml": "^3.11.0",
"koa-connect": "^2.0.1",
"koa-mount": "^3.0.0",
"koa-range": "^0.3.0",
"koa-static": "^4.0.2",
"loader-utils": "^1.1.0",
"lodash.throttle": "^4.1.1",
"lru-cache": "^4.1.2",
"markdown-it": "^8.4.1",
"markdown-it-anchor": "^5.0.2",
"markdown-it-container": "^2.0.0",
"markdown-it-emoji": "^1.4.0",
"markdown-it-table-of-contents": "^0.4.0",
"mini-css-extract-plugin": "^0.4.1",
"nprogress": "^0.2.0",
"optimize-css-assets-webpack-plugin": "^4.0.0",
"portfinder": "^1.0.13",
"postcss-loader": "^2.1.5",
"prismjs": "^1.13.0",
"register-service-worker": "^1.5.1",
"semver": "^5.5.0",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
"toml": "^2.3.3",
"url-loader": "^1.0.1",
"vue": "^2.5.16",
"vue-loader": "^15.2.4",
"vue-router": "^3.0.1",
"vue-server-renderer": "^2.5.16",
"vue-template-compiler": "^2.5.16",
"vuepress-html-webpack-plugin": "^3.2.0",
"webpack": "^4.8.1",
"webpack-chain": "^4.6.0",
"webpack-merge": "^4.1.2",
"webpack-serve": "^1.0.2",
"webpackbar": "^2.6.1",
"workbox-build": "^3.1.0"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.16",
"babel-core": "^7.0.0-0",
"babel-jest": "^23.0.0",
"conventional-changelog-cli": "^1.3.22",
"eslint": "^4.19.1",
"eslint-plugin-jest": "^21.15.1",
"eslint-plugin-vue-libs": "^3.0.0",
"jest": "^23.0.0",
"jest-serializer-vue": "^1.0.0",
"lerna": "^2.11.0",
"lint-staged": "^7.0.4",
"vue-jest": "^2.6.0",
"vuepress-theme-vue": "^1.1.0",
"yorkie": "^1.0.3"
"minimist": "^1.2.0",
"yorkie": "^1.0.3",
"inquirer": "^6.2.0",
"@vue/conventional-changelog": "^0.1.1"
},
"engines": {
"node": ">=8"
2 changes: 2 additions & 0 deletions packages/@vuepress/cli/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__tests__
__mocks__
18 changes: 18 additions & 0 deletions packages/@vuepress/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# @vuepress/cli

> cli for vuepress
## APIs

### program

Current instance of [commander.js](https://github.com/tj/commander.js)

### bootstrap(options)

Launch the cli.

#### options.plugins

#### options.theme
131 changes: 131 additions & 0 deletions packages/@vuepress/cli/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
const { chalk } = require('@vuepress/shared-utils')
const semver = require('semver')

try {
require.resolve('@vuepress/core')
} catch (err) {
console.log(chalk.red(
`\n[vuepress] @vuepress/cli ` +
`requires @vuepress/core to be installed.\n`
))
process.exit(1)
}

const pkg = require('@vuepress/core/package.json')
const requiredVersion = pkg.engines.node

if (!semver.satisfies(process.version, requiredVersion)) {
console.log(chalk.red(
`\n[vuepress] minimum Node version not met:` +
`\nYou are using Node ${process.version}, but VuePress ` +
`requires Node ${requiredVersion}.\nPlease upgrade your Node version.\n`
))
process.exit(1)
}

const program = require('commander')

exports.program = program
exports.bootstrap = function ({
plugins,
theme
} = {}) {
const path = require('path')
const { dev, build, eject } = require('@vuepress/core')

program
.version(pkg.version)
.usage('<command> [options]')

program
.command('dev [targetDir]')
.description('start development server')
.option('-p, --port <port>', 'use specified port (default: 8080)')
.option('-h, --host <host>', 'use specified host (default: 0.0.0.0)')
.option('-t, --temp <temp>', 'set the directory of the temporary file')
.option('-c, --cache <cache>', 'set the directory of cache')
.option('--no-cache', 'clean the cache before build')
.option('--debug', 'start development server in debug mode')
.action((dir = '.', { host, port, debug, temp, cache }) => {
wrapCommand(dev)(path.resolve(dir), { host, port, debug, temp, cache, plugins, theme })
})

program
.command('build [targetDir]')
.description('build dir as static site')
.option('-d, --dest <outDir>', 'specify build output dir (default: .vuepress/dist)')
.option('-t, --temp <temp>', 'set the directory of the temporary file')
.option('-c, --cache <cache>', 'set the directory of cache')
.option('--no-cache', 'clean the cache before build')
.option('--debug', 'build in development mode for debugging')
.action((dir = '.', { debug, dest, temp, cache }) => {
const outDir = dest ? path.resolve(dest) : null
wrapCommand(build)(path.resolve(dir), { debug, outDir, plugins, theme, temp, cache })
})

program
.command('eject [targetDir]')
.description('copy the default theme into .vuepress/theme for customization.')
.action((dir = '.') => {
wrapCommand(eject)(path.resolve(dir))
})

// output help information on unknown commands
program
.arguments('<command>')
.action((cmd) => {
program.outputHelp()
console.log(` ` + chalk.red(`Unknown command ${chalk.yellow(cmd)}.`))
console.log()
})

// add some useful info on help
program.on('--help', () => {
console.log()
console.log(` Run ${chalk.cyan(`vuepress <command> --help`)} for detailed usage of given command.`)
console.log()
})

program.commands.forEach(c => c.on('--help', () => console.log()))

// enhance common error messages
const enhanceErrorMessages = (methodName, log) => {
program.Command.prototype[methodName] = function (...args) {
if (methodName === 'unknownOption' && this._allowUnknownOption) {
return
}
this.outputHelp()
console.log(` ` + chalk.red(log(...args)))
console.log()
process.exit(1)
}
}

enhanceErrorMessages('missingArgument', argName => {
return `Missing required argument ${chalk.yellow(`<${argName}>`)}.`
})

enhanceErrorMessages('unknownOption', optionName => {
return `Unknown option ${chalk.yellow(optionName)}.`
})

enhanceErrorMessages('optionMissingArgument', (option, flag) => {
return `Missing required argument for option ${chalk.yellow(option.flags)}` + (
flag ? `, got ${chalk.yellow(flag)}` : ``
)
})

function wrapCommand (fn) {
return (...args) => {
return fn(...args).catch(err => {
console.error(chalk.red(err.stack))
process.exitCode = 1
})
}
}

program.parse(process.argv)
if (!process.argv.slice(2).length) {
program.outputHelp()
}
}
32 changes: 32 additions & 0 deletions packages/@vuepress/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@vuepress/cli",
"version": "1.0.0",
"description": "cli for vuepress",
"main": "index.js",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-cli.git"
},
"keywords": [
"documentation",
"vue",
"vuepress",
"generator"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vuepress/issues"
},
"dependencies": {
"chalk": "^2.3.2",
"semver": "^5.5.0"
},
"peerDependencies": {
"@vuepress/core": "^1.0.0"
},
"homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/cli#readme"
}
2 changes: 2 additions & 0 deletions packages/@vuepress/core/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__tests__
__mocks__
9 changes: 9 additions & 0 deletions packages/@vuepress/core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# @vuepress/core

## APIs

### dev(sourceDir, options)

### build(sourceDir, options)

### eject(targetDir)
24 changes: 24 additions & 0 deletions packages/@vuepress/core/__test__/plugin-api/AsyncOption.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const AsyncOption = require('../../lib/plugin-api/abstract/AsyncOption')

describe('AsyncOption', () => {
test('parallelApply', async () => {
const option = new AsyncOption('option')
const handler1 = jest.fn()
const handler2 = jest.fn()

option.add('plugin-a', handler1)
option.add('plugin-b', handler2)

// TODO for now, if a class extends from another class.
// the original methods in that class will be lost.

await option.parallelApply(1, 2)
// expect(handler1.mock.calls).toHaveLength(1)
// expect(handler2.mock.calls).toHaveLength(1)
// expect(handler1.mock.calls[0][0]).toBe(1)
// expect(handler1.mock.calls[0][1]).toBe(2)
// expect(handler2.mock.calls[0][0]).toBe(1)
// expect(handler2.mock.calls[0][1]).toBe(2)
})
})

90 changes: 90 additions & 0 deletions packages/@vuepress/core/__test__/plugin-api/Option.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Option from '../../lib/plugin-api/abstract/Option'

describe('Option', () => {
test('key', () => {
const option = new Option('option')
expect(option.key).toBe('option')
})

test('add', () => {
const option = new Option('option')
option.add('plugin-a', 'a')
option.add('plugin-b', 'b')

expect(option.items).toEqual([
{ value: 'a', name: 'plugin-a' },
{ value: 'b', name: 'plugin-b' }
])

expect(option.values).toEqual(['a', 'b'])
})

test('add - resolve array', () => {
const option = new Option('option')
option.add('plugin-a', ['a-1', 'a-2'])
option.add('plugin-b', 'b')

expect(option.items).toEqual([
{ value: 'a-1', name: 'plugin-a' },
{ value: 'a-2', name: 'plugin-a' },
{ value: 'b', name: 'plugin-b' }
])
})

test('delete', () => {
const option = new Option('option')
option.add('plugin-a', ['a-1', 'a-2'])
option.add('plugin-b', 'b')

option.delete('plugin-a')

expect(option.items).toEqual([
{ value: 'b', name: 'plugin-b' }
])
})

test('clear', () => {
const option = new Option('option')
option.add('plugin-a', ['a-1', 'a-2'])

option.clear()

expect(option.items).toEqual([])
})

test('syncApply', () => {
const option = new Option('option')
const handler1 = jest.fn()
const handler2 = jest.fn()

option.add('plugin-a', handler1)
option.add('plugin-b', handler2)

option.syncApply('p1', 'p2')
expect(handler1.mock.calls).toHaveLength(1)
expect(handler2.mock.calls).toHaveLength(1)
expect(handler1.mock.calls[0][0]).toBe('p1')
expect(handler1.mock.calls[0][1]).toBe('p2')
expect(handler2.mock.calls[0][0]).toBe('p1')
expect(handler2.mock.calls[0][1]).toBe('p2')
})

test('appliedItems', () => {
const option = new Option('option')
const fn1 = () => 'fn1'
const fn2 = () => 'fn2'
const handler1 = jest.fn(fn1)
const handler2 = jest.fn(fn2)

option.add('plugin-a', handler1)
option.add('plugin-b', handler2)

option.syncApply(1, 2)

expect(option.appliedItems).toEqual([
{ value: 'fn1', name: 'plugin-a' },
{ value: 'fn2', name: 'plugin-b' }
])
})
})

87 changes: 87 additions & 0 deletions packages/@vuepress/core/__test__/plugin-api/PluginAPI.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
jest.mock('vuepress-plugin-a')
jest.mock('vuepress-plugin-b')
jest.mock('@org/vuepress-plugin-a')

import PluginAPI from '../../lib/plugin-api/index'
import { PLUGIN_OPTION_MAP } from '../../lib/plugin-api/constants'

describe('Plugin', () => {
test('registerOption', () => {
const api = new PluginAPI()
const readyHandler = () => {}
api.registerOption(PLUGIN_OPTION_MAP.READY.key, readyHandler)
expect(api.options.ready.values).toHaveLength(1)
expect(api.options.ready.values[0]).toBe(readyHandler)
})

test('useByPluginsConfig', () => {
[
['a'],
[['a']],
[['a', true]],
{ a: true }
].forEach(pluginsConfig => {
const api = new PluginAPI()
api.useByPluginsConfig(pluginsConfig)
expect(api.enabledPlugins).toHaveLength(1)
expect(api.enabledPlugins[0].name).toBe('vuepress-plugin-a')
expect(api.disabledPlugins).toHaveLength(0)
})
})

test('useByPluginsConfig - disable plugin', () => {
[
[['a', false]],
{ a: false }
].forEach(pluginsConfig => {
const api = new PluginAPI()
api.useByPluginsConfig(pluginsConfig)
expect(api.enabledPlugins).toHaveLength(0)
expect(api.disabledPlugins).toHaveLength(1)
expect(api.disabledPlugins[0].name).toBe('vuepress-plugin-a')
})
})

test('useByPluginsConfig - get options', () => {
const pluginOptions = {};
[
[['a', pluginOptions]],
{ a: pluginOptions }
].forEach(pluginsConfig => {
const api = new PluginAPI()
api.useByPluginsConfig(pluginsConfig)
expect(api.enabledPlugins[0].$$options).toBe(pluginOptions)
})
})

test('ensure the namesake plugin is only executed once.', () => {
const pluginOptions1 = {}
const pluginOptions2 = {}
const pluginOptions3 = {}
const pluginsConfig = [
['a', pluginOptions1],
['a', pluginOptions2],
['a', pluginOptions3]
]
const api = new PluginAPI()
api.useByPluginsConfig(pluginsConfig)
expect(api.enabledPlugins).toHaveLength(1)
// using the last one
expect(api.enabledPlugins[0].$$options).toBe(pluginOptions3)
})

test('ensure a "multuple" plugin can be applied multuple times.', () => {
const pluginOptions1 = { a: 1 }
const pluginOptions2 = { b: 1 }
const pluginsConfig = [
['b', pluginOptions1],
['b', pluginOptions2]
]
const api = new PluginAPI()
api.useByPluginsConfig(pluginsConfig)
expect(api.enabledPlugins).toHaveLength(2)
// using the last one
expect(api.enabledPlugins[0].$$options).toBe(pluginOptions1)
expect(api.enabledPlugins[1].$$options).toBe(pluginOptions2)
})
})
45 changes: 45 additions & 0 deletions packages/@vuepress/core/__test__/plugin-api/PluginUtil.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { flattenPlugin } from '../../lib/plugin-api/util'

describe('flattenPlugin', () => {
test('shoould hydrate plugin correctly', () => {
const plugin = { name: 'a', shortcut: 'a', module: { enhanceAppFiles: 'file' }}
const hydratedPlugin = flattenPlugin(plugin, {}, {})
expect(hydratedPlugin.name).toBe('a')
expect(hydratedPlugin.shortcut).toBe('a')
expect(hydratedPlugin.enabled).toBe(true)
expect(hydratedPlugin.enhanceAppFiles).toBe('file')
})

test('shoould set \'enabled\' to false when \'pluginOptions\' is set to false.', () => {
const plugin = { name: 'a', shortcut: 'a', module: {}}
const hydratedPlugin = flattenPlugin(plugin, false, {})
expect(hydratedPlugin.name).toBe('a')
expect(hydratedPlugin.shortcut).toBe('a')
expect(hydratedPlugin.enabled).toBe(false)
})

test('shoould flatten functional plugin correctly.', () => {
const config = jest.fn(() => ({ enhanceAppFiles: 'file' }))
const plugin = { name: 'a', shortcut: 'a', module: config }
const pluginOptions = {}
const pluginContext = {}
const hydratedPlugin = flattenPlugin(plugin, pluginOptions, pluginContext)
expect(hydratedPlugin.name).toBe('a')
expect(hydratedPlugin.shortcut).toBe('a')
expect(hydratedPlugin.enabled).toBe(true)
expect(hydratedPlugin.enhanceAppFiles).toBe('file')
expect(config.mock.calls).toHaveLength(1)
expect(config.mock.calls[0][0]).toBe(pluginOptions)
expect(Object.getPrototypeOf(config.mock.calls[0][1])).toBe(pluginContext)
})

test('shoould flatten functional plugin correctly - options defaults to \'{}\'.', () => {
const config = jest.fn(() => ({ enhanceAppFiles: 'file' }))
const plugin = { name: 'a', shortcut: 'a', module: config }
const pluginOptions = undefined
const pluginContext = {}
flattenPlugin(plugin, pluginOptions, pluginContext)
expect(config.mock.calls[0][0]).toEqual({})
expect(Object.getPrototypeOf(config.mock.calls[0][1])).toBe(pluginContext)
})
})
98 changes: 98 additions & 0 deletions packages/@vuepress/core/__test__/prepare/Page.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const Page = require('../../lib/prepare/Page')
const {
getComputed,
getMarkdown,
getDocument,
readFile
} = require('./util')

describe('Page', () => {
test('pure route', async () => {
const page = new Page({ path: '/' })

expect(page.path).toBe('/')
expect(page.regularPath).toBe('/')

const computed = getComputed()
await page.process({ computed })

expect(page.path).toBe('/')
expect(page.regularPath).toBe('/')
})

test('pure route - encodeURI', async () => {
const path = '/尤/'
const page = new Page({ path })

expect(page.path).toBe(encodeURI(path))
expect(page.regularPath).toBe(encodeURI(path))
})

test('pure route - custom frontmatter', async () => {
const frontmatter = { title: 'alpha' }
const page = new Page({
path: '/',
frontmatter
})
expect(page.frontmatter).toBe(frontmatter)
})

test('pure route - enhancers', async () => {
const frontmatter = { title: 'alpha' }
const page = new Page({
path: '/',
frontmatter
})

expect(page.frontmatter.title).toBe('alpha')

const computed = getComputed()
const enhancers = [
{
name: 'plugin-a',
value: page => { page.frontmatter.title = 'beta' }
}
]
await page.process({ computed, enhancers })

expect(page.frontmatter.title).toBe('beta')
})

test('markdown page - pointing to a markdown file', async () => {
const { relative, filePath } = getDocument('README.md')
const page = new Page({ filePath, relative })

expect(page._filePath).toBe(filePath)
expect(page.regularPath).toBe('/')
expect(page.path).toBe('/')
expect(page.frontmatter).toEqual({})

const computed = getComputed()
const markdown = getMarkdown()
await page.process({ computed, markdown })

expect(page.title).toBe('Home')
const content = await readFile(filePath)
expect(page._content).toBe(content)
expect(page._strippedContent).toBe(content)
})

test('markdown page - pointing to a markdown file with frontmatter', async () => {
const { relative, filePath } = getDocument('alpha.md')
const page = new Page({ filePath, relative })

expect(page._filePath).toBe(filePath)
expect(page.regularPath).toBe('/alpha.html')
expect(page.path).toBe('/alpha.html')
expect(page.frontmatter).toEqual({})

const computed = getComputed()
const markdown = getMarkdown()
await page.process({ computed, markdown })

expect(page.title).toBe(page.frontmatter.title)
expect(page._content.startsWith('---')).toBe(true)
expect(page._strippedContent.startsWith('---')).toBe(false)
})
})

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
title: 'Hello VuePress',
description: 'Just playing around',
description: '# Hello, VuePress!',
dest: 'vuepress',
base: 'vuepress',
head: [
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Hello, VuePress!
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module.exports = {
dest: 'vuepress',
locales: {
'/': {
lang: 'en-US',
title: 'VuePress',
description: 'Vue-powered Static Site Generator'
},
'/zh/': {
lang: 'zh-CN',
title: 'VuePress',
description: 'Vue 驱动的静态网站生成器'
}
},
themeConfig: {
repo: 'vuejs/vuepress',
editLinks: true,
docsDir: 'docs',
locales: {
'/': {
label: 'English',
selectText: 'Languages',
editLinkText: 'Edit this page on GitHub',
lastUpdated: 'Last Updated'
},
'/zh/': {
label: '简体中文',
selectText: '选择语言',
editLinkText: '在 GitHub 上编辑此页',
lastUpdated: '上次更新',
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Hello, VuePress!
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# 你好, VuePress!
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
title: 'Hello VuePress',
description: 'Just playing around'
description: '# Hello, VuePress!'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Hello, VuePress!
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Home
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: VuePress Alpha
---

# Alpha
21 changes: 21 additions & 0 deletions packages/@vuepress/core/__test__/prepare/prepare.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { fs } = require('@vuepress/shared-utils')
const path = require('path')
const prepare = require('../../lib/prepare')

const docsBaseDir = path.resolve(__dirname, 'fixtures')
const docsModeNames = fs.readdirSync(docsBaseDir)
const docsModes = docsModeNames.map(name => {
const docsPath = path.resolve(docsBaseDir, name)
const docsTempPath = path.resolve(docsPath, '.vuepress/.temp')
return { name, docsPath, docsTempPath }
})

describe('prepare', () => {
test('should not throw error', async () => {
await Promise.all(docsModes.map(async ({ name, docsPath, docsTempPath }) => {
await fs.ensureDir(docsTempPath)
const context = await prepare(docsPath, { theme: '@vuepress/default', temp: docsTempPath })
expect(context.sourceDir).toBe(docsPath)
}))
})
})
34 changes: 34 additions & 0 deletions packages/@vuepress/core/__test__/prepare/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const path = require('path')
const { fs } = require('@vuepress/shared-utils')
const AppContext = require('../../lib/prepare/AppContext')
const createMarkdown = require('../../../markdown/lib/index')

function getAppContext () {
return new AppContext('.')
}

function getComputed () {
const context = getAppContext()
return new context.ClientComputedMixinConstructor()
}

const docsBaseDir = path.resolve(__dirname, 'fixtures/docs')

function getDocument (relative) {
return {
filePath: path.join(docsBaseDir, relative),
relative
}
}

const getMarkdown = createMarkdown

const readFile = async filePath => await fs.readFile(filePath, 'utf-8')

module.exports = {
getAppContext,
getComputed,
getMarkdown,
getDocument,
readFile
}
19 changes: 19 additions & 0 deletions packages/@vuepress/core/lib/app/Store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Vue from 'vue'

export default class Store {
constructor () {
this.store = new Vue({
data: {
ob: {}
}
})
}

get (key) {
return this.store.ob[key]
}

set (key, value) {
Vue.set(this.store.ob, key, value)
}
}
38 changes: 25 additions & 13 deletions lib/app/app.js → packages/@vuepress/core/lib/app/app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* global VUEPRESS_TEMP_PATH */
import Vue from 'vue'
import Router from 'vue-router'
import dataMixin from './dataMixin'
import store from './store'
import { routes } from '@temp/routes'
import { siteData } from '@temp/siteData'
import enhanceApp from '@temp/enhanceApp'
import themeEnhanceApp from '@temp/themeEnhanceApp'
import { routes } from '@internal/routes'
import { siteData } from '@internal/siteData'
import appEnhancers from '@internal/app-enhancers'
import globalUIComponents from '@internal/global-ui'
import ClientComputedMixin from '../prepare/ClientComputedMixin'
import Store from './Store'

// generated from user config
import('@temp/style.styl')
@@ -18,7 +20,7 @@ import ClientOnly from './components/ClientOnly'
// suggest dev server restart on base change
if (module.hot) {
const prevBase = siteData.base
module.hot.accept('./.temp/siteData', () => {
module.hot.accept(VUEPRESS_TEMP_PATH + '/internal/siteData.js', () => {
if (siteData.base !== prevBase) {
window.alert(
`[vuepress] Site base has changed. ` +
@@ -29,13 +31,15 @@ if (module.hot) {
}

Vue.config.productionTip = false

Vue.$store = new Store()

Vue.use(Router)
// mixin for exposing $site and $page
Vue.mixin(dataMixin(siteData))
Vue.mixin(dataMixin(ClientComputedMixin, siteData))
// component for rendering markdown content and setting title etc.
Vue.component('Content', Content)
Vue.component('OutboundLink', OutboundLink)
Vue.component('Badge', () => import('./components/Badge.vue'))
// component for client-only content
Vue.component('ClientOnly', ClientOnly)

@@ -49,7 +53,7 @@ Vue.prototype.$withBase = function (path) {
}
}

export function createApp () {
export function createApp (isServer) {
const router = new Router({
base: siteData.base,
mode: 'history',
@@ -59,7 +63,7 @@ export function createApp () {
if (saved) {
return saved
} else if (to.hash) {
if (store.disableScrollBehavior) {
if (Vue.$store.get('disableScrollBehavior')) {
return false
}
return {
@@ -84,15 +88,23 @@ export function createApp () {

const options = {}

themeEnhanceApp({ Vue, options, router, siteData })
enhanceApp({ Vue, options, router, siteData })
try {
appEnhancers.forEach(enhancer => {
if (typeof enhancer === 'function') {
enhancer({ Vue, options, router, siteData, isServer })
}
})
} catch (e) {
console.error(e)
}

const app = new Vue(
Object.assign(options, {
router,
render (h) {
return h('div', { attrs: { id: 'app' }}, [
h('router-view', { ref: 'layout' })
h('router-view', { ref: 'layout' }),
h('div', { class: 'global-ui' }, globalUIComponents.map(component => h(component)))
])
}
})
14 changes: 14 additions & 0 deletions packages/@vuepress/core/lib/app/clientEntry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* global VUEPRESS_VERSION, LAST_COMMIT_HASH*/

import { createApp } from './app'

const { app, router } = createApp(false /* isServer */)

window.__VUEPRESS_VERSION__ = {
version: VUEPRESS_VERSION,
hash: LAST_COMMIT_HASH
}

router.onReady(() => {
app.$mount('#app')
})
File renamed without changes.
34 changes: 34 additions & 0 deletions packages/@vuepress/core/lib/app/components/Content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Vue from 'vue'
import components from '@internal/page-components'

export default {
functional: true,

props: {
custom: {
type: Boolean,
default: true
},
pageKey: String,
slot: String
},

render (h, { parent, props, data }) {
const pageKey = props.pageKey || parent.$page.key

if (components[pageKey]) {
// In SSR, if a component is not registered with the component option
// vue-server-renderer will not be able to resovle it.
if (!parent.$ssrContext) {
Vue.component(pageKey, components[pageKey])
}

return h(pageKey, {
class: [props.custom ? 'custom' : '', data.class, data.staticClass],
style: data.style,
slot: props.slot || 'default'
})
}
return h('')
}
}
16 changes: 16 additions & 0 deletions packages/@vuepress/core/lib/app/components/LayoutDistributor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<component :is="layout"/>
</template>

<script>
export default {
computed: {
layout () {
if (this.$page.path) {
return this.$page.frontmatter.layout || 'Layout'
}
return 'NotFound'
}
}
}
</script>
File renamed without changes.
File renamed without changes.
37 changes: 37 additions & 0 deletions packages/@vuepress/core/lib/app/dataMixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* global VUEPRESS_TEMP_PATH */

import Vue from 'vue'

export default function dataMixin (I18n, siteData) {
prepare(siteData)
Vue.$store.set('siteData', siteData)

if (module.hot) {
module.hot.accept(VUEPRESS_TEMP_PATH + '/internal/siteData.js', () => {
prepare(siteData)
Vue.$store.set('siteData', siteData)
})
}

const I18nConstructor = I18n(Vue.$store.get('siteData'))
const i18n = new I18nConstructor()
const descriptors = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(i18n))
const computed = {}
Object.keys(descriptors).reduce((computed, key) => {
if (key.startsWith('$')) {
computed[key] = descriptors[key].get
}
return computed
}, computed)

return { computed }
}

function prepare (siteData) {
if (siteData.locales) {
Object.keys(siteData.locales).forEach(path => {
siteData.locales[path].path = path
})
}
Object.freeze(siteData)
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
38 changes: 38 additions & 0 deletions packages/@vuepress/core/lib/app/serverEntry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Vue from 'vue'
import { createApp } from './app'
import pageComponents from '@internal/page-components'
import layoutComponents from '@internal/layout-components'

export default context => new Promise((resolve, reject) => {
const { app, router } = createApp(true /* isServer */)
const { url } = context
const { fullPath } = router.resolve(url).route

if (fullPath !== url) {
return reject({ url: fullPath })
}

router.push(url)

// In SSR, if a component is not registered with the component option,
// vue-server-renderer will not able to resolve it.
//
// Build also works after deleting this, but the content of all pages
// will not appear to the output html, which is not conducive to SEO.
const asyncComponentLoadingPromises = [
...getComponentArr(pageComponents),
...getComponentArr(layoutComponents)
].map(({ name, loadFn }) => {
return loadFn().then(comp => {
Vue.component(name, comp.default)
})
})

router.onReady(() => {
Promise.all(asyncComponentLoadingPromises).then(() => resolve(app))
})
})

function getComponentArr (components) {
return Object.keys(components).map(name => ({ name, loadFn: components[name] }))
}
Original file line number Diff line number Diff line change
@@ -19,4 +19,4 @@ $MQMobileNarrow = 419px
$lineNumbersWrapperWidth = 3.5rem
$codeLang = js ts html md vue css sass scss less stylus go java c sh yaml py

@import '~@temp/override.styl'
@import '~@temp/palette.styl'
92 changes: 92 additions & 0 deletions packages/@vuepress/core/lib/app/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Inject option to Vue SFC
* @param {object} options
* @param {string} key
* @param {any} value
*/
export function injectComponentOption (options, key, value) {
const arrayInject = () => {
if (!options[key]) options[key] = []
options[key].push(...value)
}
const objectInject = () => {
if (!options[key]) options[key] = {}
Object.assign(options[key], value)
}
// const primitiveInject = () => options[key] = value

switch (key) {
case 'components': objectInject(); break
case 'mixins': arrayInject(); break
default: throw new Error('Unknown option name.')
}
}

export function findPageForPath (pages, path) {
for (let i = 0; i < pages.length; i++) {
const page = pages[i]
if (page.path === path) {
return page
}
}
return {
path: '',
frontmatter: {}
}
}

export function findPageByKey (pages, key) {
for (let i = 0; i < pages.length; i++) {
const page = pages[i]
if (page.key === key) {
return page
}
}
return {
path: '',
frontmatter: {}
}
}

/**
* Normalize config.
* This utility is mainly for plugin developers. For some
* plugins that need internationalize the text. but it's
* not recommenbded to let plugin care about to the internal
* i18n implementation, so this utility was born.
*
*
* Usage:
*
* import { normalizeConfig } from '@app/util'
* export default {
* data () {
* return { config }
* }
* computed: {
* normalizedConfig() {
* return normalizeConfig(this, config)
* }
* }
* }
*
*
* e.g.
*
* Config: : 'Text'
* Normalized Config: 'Text'
*
* Config: : { '/': 'Text', '/zh/': '文本' }
* Normalized Config: 'Text' or '文本'
*
* @param {Vue} component
* @param {any} rawConfig
* @returns {any}
*/
export function normalizeConfig (component, rawConfig) {
const { $localePath } = component
if (typeof rawConfig === 'object' && rawConfig[$localePath]) {
return rawConfig[$localePath]
}
return rawConfig
}
34 changes: 11 additions & 23 deletions lib/build.js → packages/@vuepress/core/lib/build.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
'use strict'

module.exports = async function build (sourceDir, cliOptions = {}) {
process.env.NODE_ENV = 'production'

const fs = require('fs-extra')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const readline = require('readline')
const escape = require('escape-html')

const logger = require('./util/logger')
const prepare = require('./prepare')
const { chalk, fs, logger } = require('@vuepress/shared-utils')
const prepare = require('./prepare/index')
const createClientConfig = require('./webpack/createClientConfig')
const createServerConfig = require('./webpack/createServerConfig')
const { createBundleRenderer } = require('vue-server-renderer')
const { normalizeHeadTag, applyUserWebpackConfig } = require('./util')
const { normalizeHeadTag, applyUserWebpackConfig } = require('./util/index')

logger.wait('\nExtracting site metadata...')
const options = await prepare(sourceDir)
const options = await prepare(sourceDir, cliOptions, true /* isProd */)
if (cliOptions.outDir) {
options.outDir = cliOptions.outDir
}
@@ -26,6 +26,7 @@ module.exports = async function build (sourceDir, cliOptions = {}) {
return console.error(logger.error(chalk.red('Unexpected option: outDir cannot be set to the current working directory.\n'), false))
}
await fs.remove(outDir)
logger.debug('Dist directory: ' + chalk.gray(path.resolve(outDir)))

let clientConfig = createClientConfig(options, cliOptions).toConfig()
let serverConfig = createServerConfig(options, cliOptions).toConfig()
@@ -57,7 +58,7 @@ module.exports = async function build (sourceDir, cliOptions = {}) {
runInNewContext: false,
inject: false,
shouldPrefetch: options.siteConfig.shouldPrefetch || (() => true),
template: await fs.readFile(path.resolve(__dirname, 'app/index.ssr.html'), 'utf-8')
template: await fs.readFile(options.ssrTemplate, 'utf-8')
})

// pre-render head tags from user config
@@ -67,32 +68,19 @@ module.exports = async function build (sourceDir, cliOptions = {}) {

// render pages
logger.wait('Rendering static HTML...')
for (const page of options.siteData.pages) {
for (const page of options.pages) {
await renderPage(page)
}

// if the user does not have a custom 404.md, generate the theme's default
if (!options.siteData.pages.some(p => p.path === '/404.html')) {
if (!options.pages.some(p => p.path === '/404.html')) {
await renderPage({ path: '/404.html' })
}

readline.clearLine(process.stdout, 0)
readline.cursorTo(process.stdout, 0)

if (options.siteConfig.serviceWorker) {
logger.wait('\nGenerating service worker...')
const wbb = require('workbox-build')
await wbb.generateSW({
swDest: path.resolve(outDir, 'service-worker.js'),
globDirectory: outDir,
globPatterns: ['**\/*.{js,css,html,png,jpg,jpeg,gif,svg,woff,woff2,eot,ttf,otf}']
})
await fs.writeFile(
path.resolve(outDir, 'service-worker.js'),
await fs.readFile(path.resolve(__dirname, 'service-worker/skip-waiting.js'), 'utf8'),
{ flag: 'a' }
)
}
await options.pluginAPI.options.generated.apply()

// DONE.
const relativeDir = path.relative(process.cwd(), outDir)
29 changes: 17 additions & 12 deletions lib/dev.js → packages/@vuepress/core/lib/dev.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

module.exports = async function dev (sourceDir, cliOptions = {}) {
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const chokidar = require('chokidar')
const serve = require('webpack-serve')
@@ -11,20 +11,21 @@ module.exports = async function dev (sourceDir, cliOptions = {}) {
const serveStatic = require('koa-static')
const history = require('connect-history-api-fallback')

const prepare = require('./prepare')
const logger = require('./util/logger')
const prepare = require('./prepare/index')
const { chalk, fs, logger } = require('@vuepress/shared-utils')
const HeadPlugin = require('./webpack/HeadPlugin')
const DevLogPlugin = require('./webpack/DevLogPlugin')
const createClientConfig = require('./webpack/createClientConfig')
const { applyUserWebpackConfig } = require('./util')
const { frontmatterEmitter } = require('./webpack/markdownLoader')
const { applyUserWebpackConfig } = require('./util/index')
const { frontmatterEmitter } = require('@vuepress/markdown-loader')

logger.wait('\nExtracting site metadata...')
const options = await prepare(sourceDir)
const options = await prepare(sourceDir, cliOptions, false /* isProd */)

// setup watchers to update options and dynamically generated files
const update = () => {
prepare(sourceDir).catch(err => {
options.pluginAPI.options.updated.syncApply()
prepare(sourceDir, cliOptions, false /* isProd */).catch(err => {
console.error(logger.error(chalk.red(err.stack), false))
})
}
@@ -58,14 +59,14 @@ module.exports = async function dev (sourceDir, cliOptions = {}) {
frontmatterEmitter.on('update', update)

// resolve webpack config
let config = createClientConfig(options, cliOptions)
let config = createClientConfig(options)

config
.plugin('html')
// using a fork of html-webpack-plugin to avoid it requiring webpack
// internals from an incompatible version.
.use(require('vuepress-html-webpack-plugin'), [{
template: path.resolve(__dirname, 'app/index.dev.html')
template: options.devTemplate
}])

config
@@ -82,7 +83,7 @@ module.exports = async function dev (sourceDir, cliOptions = {}) {
.use(DevLogPlugin, [{
port,
displayHost,
publicPath: options.publicPath
publicPath: options.base
}])

config = config.toConfig()
@@ -108,14 +109,18 @@ module.exports = async function dev (sourceDir, cliOptions = {}) {
logLevel: 'error',
port,
add: app => {
// apply plugin options to extend dev server.
const { pluginAPI } = options
pluginAPI.options.enhanceDevServer.syncApply(app)

const userPublic = path.resolve(sourceDir, '.vuepress/public')

// enable range request
app.use(range)

// respect base when serving static files...
if (fs.existsSync(userPublic)) {
app.use(mount(options.publicPath, serveStatic(userPublic)))
app.use(mount(options.base, serveStatic(userPublic)))
}

app.use(convert(history({
14 changes: 10 additions & 4 deletions lib/eject.js → packages/@vuepress/core/lib/eject.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
const fs = require('fs-extra')
'use strict'

const path = require('path')
const chalk = require('chalk')
const logger = require('./util/logger')
const { chalk, fs, logger } = require('@vuepress/shared-utils')

module.exports = async (dir) => {
const source = path.resolve(__dirname, 'default-theme')
try {
require.resolve('@vuepress/theme-default')
} catch (err) {
console.log(chalk.red(`\n[vuepress] cannot find '@vuepress/theme-default'\n`))
process.exit(1)
}
const source = require.resolve('@vuepress/theme-default')
const target = path.resolve(dir, '.vuepress/theme')
await fs.copy(source, target)
// remove the import to default theme override
3 changes: 2 additions & 1 deletion lib/index.js → packages/@vuepress/core/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict'

exports.dev = require('./dev')
exports.build = require('./build')
exports.eject = require('./eject')
Object.assign(exports, require('./util'))
15 changes: 15 additions & 0 deletions packages/@vuepress/core/lib/internal-plugins/enhanceApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const path = require('path')

module.exports = (options, context) => ({
name: '@vuepress/internal-enhance-app',

enhanceAppFiles () {
const { sourceDir, themePath } = context
const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js')
const themeEnhanceAppPath = path.resolve(themePath, 'enhanceApp.js')
return [
enhanceAppPath,
themeEnhanceAppPath
]
}
})
22 changes: 22 additions & 0 deletions packages/@vuepress/core/lib/internal-plugins/layoutComponents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = (options, ctx) => {
const { layoutComponentMap } = ctx
const componentNames = Object.keys(layoutComponentMap)

return {
name: '@vuepress/internal-layout-components',

async clientDynamicModules () {
const code = `export default {\n${componentNames
.map(name => ` ${JSON.stringify(name)}: () => import(${JSON.stringify(layoutComponentMap[name].path)})`)
.join(',\n')} \n}`
return { name: 'layout-components.js', content: code, dirname: 'internal' }
},

chainWebpack (config, isServer) {
const setAlias = (alias, raw) => config.resolve.alias.set(alias, raw)
componentNames.forEach(name => {
setAlias(`@${name}`, layoutComponentMap[name].path)
})
}
}
}
69 changes: 69 additions & 0 deletions packages/@vuepress/core/lib/internal-plugins/overrideCSS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const path = require('path')
const {
fs, logger, chalk,
datatypes: {
isPlainObject,
assertTypes,
isString
}
} = require('@vuepress/shared-utils')

module.exports = (options, context) => ({
name: '@vuepress/internal-override-css',

async ready () {
const { sourceDir, writeTemp } = context

const overridePath = path.resolve(sourceDir, '.vuepress/override.styl')
const hasUserOverride = fs.existsSync(overridePath)

if (hasUserOverride) {
logger.tip(`${chalk.magenta('override.styl')} has been deprecated from v1.0.0, using ${chalk.cyan('config.palette')} instead.\n`)
}

// palette API.
const themePalette = context.themePalette
const { palette: userPalette } = context.siteConfig
const themePaletteContent = resolvePaletteContent(themePalette)
const userPaletteContent = resolvePaletteContent(userPalette)
// user's palette can override theme's palette.
const paletteContent = themePaletteContent + userPaletteContent
await writeTemp('palette.styl', paletteContent)

// style.styl API.
const stylePath = path.resolve(sourceDir, '.vuepress/style.styl').replace(/[\\]+/g, '/')
const hasUserStyle = fs.existsSync(stylePath)
await writeTemp('style.styl', hasUserStyle ? `@import(${JSON.stringify(stylePath)})` : ``)

// Temporary tip, will be removed at next release.
if (hasUserOverride && !hasUserStyle) {
logger.tip(
`${chalk.magenta('override.styl')} has been split into 2 APIs, we recommend you upgrade to continue.\n` +
` See: ${chalk.magenta('https://vuepress.vuejs.org/default-theme-config/#simple-css-override')}`
)
}
}
})

function resolvePaletteContent (palette) {
const { valid, warnMsg } = assertTypes(palette, [String, Object])
if (!valid) {
if (palette !== undefined) {
logger.warn(
`[vuepress] Invalid value for "palette": ${warnMsg}`
)
}
return ''
}

if (isString(palette)) {
if (fs.existsSync(palette)) {
return `@import(${JSON.stringify(palette)})\n`
}
return ''
} else if (isPlainObject(palette)) {
return Object.keys(palette).map(variableName => {
return `${variableName} = ${palette[variableName]}`
}).join('\n') + '\n'
}
}
16 changes: 16 additions & 0 deletions packages/@vuepress/core/lib/internal-plugins/pageComponents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = (options, ctx) => {
const { pages } = ctx
// const componentNames = Object.keys(layoutComponentMap)

return {
name: '@vuepress/internal-page-components',

async clientDynamicModules () {
const code = `export default {\n${pages
.filter(({ _filePath }) => _filePath)
.map(({ key, _filePath }) => ` ${JSON.stringify(key)}: () => import(${JSON.stringify(_filePath)})`)
.join(',\n')} \n}`
return { name: 'page-components.js', content: code, dirname: 'internal' }
}
}
}
21 changes: 21 additions & 0 deletions packages/@vuepress/core/lib/internal-plugins/rootMixins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const path = require('path')
const { codegen: { pathsToModuleCode }} = require('@vuepress/shared-utils')

module.exports = (options, context, api) => ({
name: '@vuepress/internal-root-mixins',

// @internal/root-mixins
async clientDynamicModules () {
const builtInRootMixins = [
path.resolve(__dirname, '../app/root-mixins/updateMeta.js')
]

const rootMixins = [
...builtInRootMixins,
...api.options.clientRootMixin.values
]

const rootMixinsCode = pathsToModuleCode(rootMixins)
return { name: 'root-mixins.js', content: rootMixinsCode, dirname: 'internal' }
}
})
84 changes: 84 additions & 0 deletions packages/@vuepress/core/lib/internal-plugins/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module.exports = (options, ctx) => ({
name: '@vuepress/internal-routes',

// @internal/routes
async clientDynamicModules () {
const code = importCode() + routesCode(ctx.pages)
return { name: 'routes.js', content: code, dirname: 'internal' }
}
})

/**
* Import utilities
* @returns {string}
*/
function importCode () {
return `
import { injectComponentOption } from '@app/util'
import rootMixins from '@internal/root-mixins'
import components from '@internal/layout-components'
import LayoutDistributor from '@app/components/LayoutDistributor.vue'
injectComponentOption(LayoutDistributor, 'mixins', rootMixins)
injectComponentOption(LayoutDistributor, 'components', components)
`
}

/**
* Get Vue routes code.
* @param {array} pages
* @returns {string}
*/
function routesCode (pages) {
function genRoute ({
path: pagePath,
key: componentName,
regularPath,
_meta
}) {
let code = `
{
name: ${JSON.stringify(componentName)},
path: ${JSON.stringify(pagePath)},
component: LayoutDistributor,${_meta ? `\n meta: ${JSON.stringify(_meta)}` : ''}
}`

const dncodedPath = decodeURIComponent(pagePath)
if (dncodedPath !== pagePath) {
code += `,
{
path: ${JSON.stringify(dncodedPath)},
redirect: ${JSON.stringify(pagePath)}
}`
}

if (/\/$/.test(pagePath)) {
code += `,
{
path: ${JSON.stringify(pagePath + 'index.html')},
redirect: ${JSON.stringify(pagePath)}
}`
}

if (regularPath !== pagePath) {
code += `,
{
path: ${JSON.stringify(regularPath)},
redirect: ${JSON.stringify(pagePath)}
}`
}

return code
}

const notFoundRoute = `,
{
path: '*',
component: LayoutDistributor
}`

return (
`export const routes = [${pages.map(genRoute).join(',')}${notFoundRoute}\n]`
)
}

9 changes: 9 additions & 0 deletions packages/@vuepress/core/lib/internal-plugins/siteData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = (options, context) => ({
name: '@vuepress/internal-site-data',

// @internal/siteData
async clientDynamicModules () {
const code = `export const siteData = ${JSON.stringify(context.getSiteData(), null, 2)}`
return { name: 'siteData.js', content: code, dirname: 'internal' }
}
})
94 changes: 94 additions & 0 deletions packages/@vuepress/core/lib/plugin-api/abstract/AsyncOption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use strict'

/**
* Module dependencies.
*/

const { logger, chalk, datatypes: { isFunction }} = require('@vuepress/shared-utils')
const Option = require('./Option')

/**
* Expose asynchronous option class.
*/

class AsyncOption extends Option {
/**
* Asynchronous serial running
*
* @param args
* @param {Array<AsyncFunction>} args
* @api public
*/

async asyncApply (...args) {
const rawItems = this.items
this.items = []
this.appliedItems = this.items

for (const { name, value } of rawItems) {
try {
this.add(
name,
isFunction(value)
? await value(...args)
: value
)
} catch (error) {
logger.error(`${chalk.cyan(name)} apply ${chalk.cyan(this.key)} failed.`)
throw error
}
}

this.items = rawItems
}

/**
* Asynchronous serial running
*
* @param args
* @param {Array<AsyncFunction>} args
* @api public
*/

async parallelApply (...args) {
const rawItems = this.items
this.items = []
this.appliedItems = this.items

await Promise.all(rawItems.map(async ({ name, value }) => {
try {
this.add(
name,
isFunction(value)
? await value(...args)
: value
)
} catch (error) {
logger.error(`${chalk.cyan(name)} apply ${chalk.cyan(this.key)} failed.`)
throw error
}
})).catch(error => {
throw error
})

this.items = rawItems
}

/**
* Process a value via a pipeline.
*
* @param input
* @returns {any}
* @api public
*/

async pipeline (input) {
for (const fn of this.values) {
input = await fn(input)
}
return input
}
}

AsyncOption.prototype.apply = AsyncOption.prototype.asyncApply
module.exports = AsyncOption
135 changes: 135 additions & 0 deletions packages/@vuepress/core/lib/plugin-api/abstract/Option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use strict'

/**
* Module dependencies.
*/

const { logger, chalk, compose, datatypes: { isFunction }} = require('@vuepress/shared-utils')

/**
* Expose synchronous option class.
*/

class Option {
constructor (key) {
this.key = key
this.items = []
}

/**
* Set value with name.
*
* @param {string} name
* @param {T} value
* @api public
*/

add (name, value) {
if (Array.isArray(value)) {
return this.items.push(...value.map(i => ({ value: i, name })))
}
this.items.push({ value, name })
}

/**
* Delete value with name.
*
* @param {string} name
* @api public
*/

delete (name) {
let index = this.items.findIndex(({ name: _name }) => _name === name)
while (index !== -1) {
this.items.splice(index, 1)
index = this.items.findIndex(({ name: _name }) => _name === name)
}
}

/**
* Clean option store
*
* @param {string} name
* @api public
*/

clear (name) {
this.items = []
}

/**
* Get values.
*
* @returns {any<T>}
* @api public
*/

get values () {
return this.items.map(item => item.value)
}

/**
* Get applied values
*
* @returns {Array|*|any[]}
* @api public
*/

get appliedValues () {
return this.appliedItems && this.appliedItems.map(item => item.value)
}

/**
* Get entries.
*
* @returns {any<T>}
* @api public
*/

get entries () {
return this.items.map(({ name, value }) => ([name, value]))
}

/**
* Synchronous running
*
* @param {Array<Function>} args
* @api public
*/

syncApply (...args) {
const rawItems = this.items
this.items = []
this.appliedItems = this.items

for (const { name, value } of rawItems) {
try {
this.add(
name,
isFunction(value)
? value(...args)
: value
)
} catch (error) {
logger.error(`${chalk.cyan(name)} apply ${chalk.cyan(this.key)} failed.`)
throw error
}
}

this.items = rawItems
}

/**
* Process a value via a pipeline.
* @param input
* @returns {*}
*/

pipeline (input) {
const fn = compose(this.values)
return fn(input)
}
}

Option.prototype.apply = Option.prototype.syncApply
module.exports = Option
33 changes: 33 additions & 0 deletions packages/@vuepress/core/lib/plugin-api/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict'

const PLUGIN_OPTION_META_MAP = {
// hooks
READY: { name: 'ready', types: [Function] },
COMPILED: { name: 'compiled', types: [Function] },
UPDATED: { name: 'updated', types: [Function] },
GENERATED: { name: 'generated', types: [Function] },
// options
CHAIN_WEBPACK: { name: 'chainWebpack', types: [Function] },
ENHANCE_DEV_SERVER: { name: 'enhanceDevServer', types: [Function] },
ENHANCE_APP_FILES: { name: 'enhanceAppFiles', types: [Array, Function] },
OUT_FILES: { name: 'outFiles', types: [Object] },
EXTEND_PAGE_DATA: { name: 'extendPageData', types: [Function] },
EXTEND_MARKDOWN: { name: 'extendMarkdown', types: [Function] },
CHAIN_MARKDOWN: { name: 'chainMarkdown', types: [Function] },
CLIENT_DYNAMIC_MODULES: { name: 'clientDynamicModules', types: [Function] },
CLIENT_ROOT_MIXIN: { name: 'clientRootMixin', types: [String] },
ADDITIONAL_PAGES: { name: 'additionalPages', types: [Function, Array] },
GLOBAL_UI_COMPONENTS: { name: 'globalUIComponents', types: [String, Array] },
DEFINE: { name: 'define', types: [Function, Object] },
ALIAS: { name: 'alias', types: [Function, Object] }
}

const PLUGIN_OPTION_MAP = {}
Object.keys(PLUGIN_OPTION_META_MAP).forEach(key => {
PLUGIN_OPTION_MAP[key] = Object.assign({ key }, PLUGIN_OPTION_META_MAP[key])
})

const OPTION_NAMES = Object.keys(PLUGIN_OPTION_META_MAP).map(key => PLUGIN_OPTION_META_MAP[key].name)

exports.PLUGIN_OPTION_MAP = PLUGIN_OPTION_MAP
exports.OPTION_NAMES = OPTION_NAMES
205 changes: 205 additions & 0 deletions packages/@vuepress/core/lib/plugin-api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
'use strict'

/**
* Module dependencies.
*/

const instantiateOption = require('./override/instantiateOption')
const { flattenPlugin, normalizePluginsConfig } = require('./util')
const { PLUGIN_OPTION_MAP } = require('./constants')
const {
shortcutPackageResolver: { resolvePlugin },
datatypes: { assertTypes },
env: { isDebug },
logger, chalk
} = require('@vuepress/shared-utils')

/**
* Expose PluginAPI class.
*/

module.exports = class PluginAPI {
constructor (context) {
this.options = {}
this._pluginContext = context
this._pluginQueue = []
this.initializeOptions(PLUGIN_OPTION_MAP)
}

/**
* Get enabled plugins
*
* @returns {array}
* @api public
*/

get enabledPlugins () {
return this._pluginQueue.filter(({ enabled }) => enabled)
}

/**
* Get disabled plugins
*
* @returns {array}
* @api public
*/

get disabledPlugins () {
return this._pluginQueue.filter(({ enabled }) => !enabled)
}

/**
* apply plugin.
*
* @api public
*/

apply () {
this._pluginQueue.forEach(plugin => {
if (plugin.enabled) {
this.applyPlugin(plugin)
} else {
logger.debug(`\n${chalk.gray(`[${plugin.name}]`)} disabled.`)
}
})
}

/**
* Normalize plugin and push it to the plugin queue.
*
* @param {object} pluginRaw
* @param {object} pluginOptions
* @returns {module.PluginAPI}
* @api public
*/

use (pluginRaw, pluginOptions = {}) {
let plugin = resolvePlugin(pluginRaw)
if (!plugin.module) {
console.warn(`[vuepress] cannot resolve plugin "${pluginRaw}"`)
return this
}
plugin = flattenPlugin(plugin, pluginOptions, this._pluginContext, this)
if (plugin.multiple !== true) {
const duplicateIndex = this._pluginQueue.findIndex(({ name }) => name === plugin.name)
if (duplicateIndex !== -1) {
this._pluginQueue.splice(duplicateIndex, 1)
}
}
this._pluginQueue.push(plugin)
return this
}

/**
* Use plugin by config.
*
* @param pluginsConfig
* @returns {module.PluginAPI}
* @api public
*/

useByPluginsConfig (pluginsConfig) {
pluginsConfig = normalizePluginsConfig(pluginsConfig)
pluginsConfig.forEach(([pluginRaw, pluginOptions]) => {
this.use(pluginRaw, pluginOptions)
})
return this
}

/**
* initialize plugin options.
*
* @api private
*/

initializeOptions () {
Object.keys(PLUGIN_OPTION_MAP).forEach(key => {
const option = PLUGIN_OPTION_MAP[key]
this.options[option.name] = instantiateOption(option.name)
})
}

/**
* Register plugin option.
*
* @param {string} key
* @param {any} value
* @param {string} pluginName
* @returns {module.PluginAPI}
* @api private
*/

registerOption (key, value, pluginName) {
const option = PLUGIN_OPTION_MAP[key]
const types = option.types
const { valid, warnMsg } = assertTypes(value, types)
if (valid) {
this.options[option.name].add(pluginName, value)
} else if (value !== undefined) {
logger.warn(
`${chalk.gray(pluginName)} ` +
`Invalid value for "option" ${chalk.cyan(option.name)}: ${warnMsg}`
)
}
return this
}

/**
* apply plugin.
*
* @api private
*/

applyPlugin ({
// info
name: pluginName,
shortcut,

// hooks
ready,
compiled,
updated,
generated,

// options
chainWebpack,
enhanceDevServer,
extendMarkdown,
chainMarkdown,
enhanceAppFiles,
outFiles,
extendPageData,
clientDynamicModules,
clientRootMixin,
additionalPages,
globalUIComponents,
define,
alias
}) {
const isInternalPlugin = pluginName.startsWith('@vuepress/internal-')
if (shortcut) {
logger.tip(`\nApply plugin ${chalk.magenta(shortcut)} ${chalk.gray(`(i.e. "${pluginName}")`)} ...`, !isInternalPlugin)
} else if (!isInternalPlugin || isDebug) {
logger.tip(`\nApply plugin ${chalk.magenta(pluginName)} ...`)
}

this
.registerOption(PLUGIN_OPTION_MAP.READY.key, ready, pluginName)
.registerOption(PLUGIN_OPTION_MAP.COMPILED.key, compiled, pluginName)
.registerOption(PLUGIN_OPTION_MAP.UPDATED.key, updated, pluginName)
.registerOption(PLUGIN_OPTION_MAP.GENERATED.key, generated, pluginName)
.registerOption(PLUGIN_OPTION_MAP.CHAIN_WEBPACK.key, chainWebpack, pluginName)
.registerOption(PLUGIN_OPTION_MAP.ENHANCE_DEV_SERVER.key, enhanceDevServer, pluginName)
.registerOption(PLUGIN_OPTION_MAP.EXTEND_MARKDOWN.key, extendMarkdown, pluginName)
.registerOption(PLUGIN_OPTION_MAP.CHAIN_MARKDOWN.key, chainMarkdown, pluginName)
.registerOption(PLUGIN_OPTION_MAP.EXTEND_PAGE_DATA.key, extendPageData, pluginName)
.registerOption(PLUGIN_OPTION_MAP.ENHANCE_APP_FILES.key, enhanceAppFiles, pluginName)
.registerOption(PLUGIN_OPTION_MAP.OUT_FILES.key, outFiles, pluginName)
.registerOption(PLUGIN_OPTION_MAP.CLIENT_DYNAMIC_MODULES.key, clientDynamicModules, pluginName)
.registerOption(PLUGIN_OPTION_MAP.CLIENT_ROOT_MIXIN.key, clientRootMixin, pluginName)
.registerOption(PLUGIN_OPTION_MAP.ADDITIONAL_PAGES.key, additionalPages, pluginName)
.registerOption(PLUGIN_OPTION_MAP.GLOBAL_UI_COMPONENTS.key, globalUIComponents, pluginName)
.registerOption(PLUGIN_OPTION_MAP.DEFINE.key, define, pluginName)
.registerOption(PLUGIN_OPTION_MAP.ALIAS.key, alias, pluginName)
}
}
23 changes: 23 additions & 0 deletions packages/@vuepress/core/lib/plugin-api/override/AliasOption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'

/**
* Module dependencies.
*/

const Option = require('../abstract/Option')

/**
* define option.
*/

module.exports = class DefineOption extends Option {
apply (config) {
super.syncApply()
const aliases = this.appliedValues
aliases.forEach((alias) => {
Object.keys(alias).forEach(key => {
config.resolve.alias.set(key, alias[key])
})
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict'

/**
* Module dependencies.
*/

const AsyncOption = require('../abstract/AsyncOption')

/**
* clientDynamicModules option.
*/

module.exports = class ClientDynamicModulesOption extends AsyncOption {
async apply (ctx) {
await super.asyncApply()

for (const { value, name: pluginName } of this.appliedItems) {
const { name, content, dirname = 'dynamic' } = value
await ctx.writeTemp(
`${dirname}/${name}`,
`
/**
* Generated by "${pluginName}"
*/
${content}\n\n
`.trim())
}
}
}
26 changes: 26 additions & 0 deletions packages/@vuepress/core/lib/plugin-api/override/DefineOption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict'

/**
* Module dependencies.
*/

const Option = require('../abstract/Option')

/**
* define option.
*/

module.exports = class DefineOption extends Option {
apply (config) {
super.syncApply()
const defines = this.appliedValues
defines.forEach(define => {
Object.keys(define).forEach(key => {
define[key] = JSON.stringify(define[key])
})
config.plugin('injections').tap(([options]) => [
Object.assign(options, define)
])
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use strict'

/**
* Module dependencies.
*/

const AsyncOption = require('../abstract/AsyncOption')
const {
fs,
chalk,
logger,
codegen: { pathsToModuleCode },
datatypes: { isPlainObject }
} = require('@vuepress/shared-utils')

/**
* enhanceAppFiles option.
*/

module.exports = class EnhanceAppFilesOption extends AsyncOption {
async apply (ctx) {
await super.asyncApply()

const manifest = []
let moduleId = 0

async function writeEnhancer (name, content, hasDefaultExport = true) {
return await ctx.writeTemp(
`app-enhancers/${name}.js`,
hasDefaultExport
? content
: content + '\nexport default {}'
)
}

// 1. write enhance app files.
for (const { value: enhanceAppFile, name: pluginName } of this.appliedItems) {
let destPath

// 1.1 dynamic code
if (isPlainObject(enhanceAppFile)) {
const { content } = enhanceAppFile
let { name } = enhanceAppFile
name = name.replace(/.js$/, '')

if (hasDefaultExport(content)) {
destPath = await writeEnhancer(name, content)
} else {
destPath = await writeEnhancer(name, content, false /* do not contain default export*/)
}
// 1.2 local file
} else {
if (fs.existsSync(enhanceAppFile)) {
const content = await fs.readFile(enhanceAppFile, 'utf-8')

if (hasDefaultExport(content)) {
destPath = await writeEnhancer(
moduleId++,
`export { default } from ${JSON.stringify(enhanceAppFile)}`
)
} else {
destPath = await writeEnhancer(
moduleId++,
`import ${JSON.stringify(enhanceAppFile)}`,
false /* do not contain default export*/
)
}
} else {
logger.debug(
chalk.gray(`[${pluginName}] `) +
`${chalk.cyan(enhanceAppFile)} Not Found.`
)
}
}

if (destPath) {
manifest.push(destPath)
}
}

// 2. write entry file.
await ctx.writeTemp('internal/app-enhancers.js', pathsToModuleCode(manifest))
}
}

function hasDefaultExport (content) {
return content.includes('export default') || content.includes('module.exports')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'

/**
* Module dependencies.
*/

const Option = require('../abstract/Option')

/**
* globalUIComponents option.
*/

module.exports = class GlobalUIComponentsOption extends Option {
async apply (ctx) {
await ctx.writeTemp(
`internal/global-ui.js`,
`export default ${JSON.stringify(this.values, null, 2)}`
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const EnhanceAppFilesOption = require('./EnhanceAppFilesOption')
const ClientDynamicModulesOption = require('./ClientDynamicModulesOption')
const GlobalUIComponentsOption = require('./GlobalUIComponentsOption')
const DefineOption = require('./DefineOption')
const AliasOption = require('./AliasOption')
const Option = require('../abstract/Option')
const { PLUGIN_OPTION_MAP } = require('../constants')

module.exports = function instantiateOption (name) {
switch (name) {
case PLUGIN_OPTION_MAP.ENHANCE_APP_FILES.name:
return new EnhanceAppFilesOption(name)

case PLUGIN_OPTION_MAP.CLIENT_DYNAMIC_MODULES.name:
return new ClientDynamicModulesOption(name)

case PLUGIN_OPTION_MAP.GLOBAL_UI_COMPONENTS.name:
return new GlobalUIComponentsOption(name)

case PLUGIN_OPTION_MAP.DEFINE.name:
return new DefineOption(name)

case PLUGIN_OPTION_MAP.ALIAS.name:
return new AliasOption(name)

default: return new Option(name)
}
}
Loading