Minimal setup with markdown-centered project structure helps you focus on writing.
17 |Enjoy the dev experience of Vue + webpack, use Vue components in markdown, and develop custom themes with Vue.
21 |VuePress generates pre-rendered static HTML for each page, and runs as an SPA once a page is loaded.
25 |{{ 1 + 1 }}
{{ i }}
` tag, which will lead to hydration mismatch because `
` does not allow block elements to be placed inside it. 127 | ::: 128 | 129 | ### Using Components In Headers 130 | 131 | You can use Vue components in the headers, but note the difference between the following two ways: 132 | 133 | | markdown | Output HTML | Parsed Header | 134 | |--------|-------------|----------------| 135 | |
# text <Tag/>
| ` # text \`<Tag/>\`
| `<Tag/>
` will be displayed as is, only the HTML that is not wrapped will be parsed by Vue.
139 |
140 | ::: tip
141 |
142 | The output HTML is accomplished by [markdown-it](https://github.com/markdown-it/markdown-it), while the parsed headers are done by VuePress, and used for the [sidebar](../theme/default-theme-config.md#sidebar) and the document title.
143 | :::
144 |
145 | ## Using Pre-processors
146 |
147 | VuePress has built-in webpack config for the following pre-processors: `sass`, `scss`, `less`, `stylus` and `pug`. All you need to do is installing the corresponding dependencies. For example, to enable `sass`, install the following in your project:
148 |
149 | ``` bash
150 | yarn add -D sass-loader node-sass
151 | ```
152 |
153 | Now you can use the following in markdown and theme components:
154 |
155 | ``` vue
156 |
160 | ```
161 |
162 | Using `` requires installing `pug` and `pug-plain-loader`:
163 |
164 | ``` bash
165 | yarn add -D pug pug-plain-loader
166 | ```
167 |
168 | ::: tip
169 | If you are a Stylus user, you don't need to install `stylus` and `stylus-loader` in your project because VuePress uses Stylus internally.
170 |
171 | For pre-processors that do not have built-in webpack config support, you will need to [extend the internal webpack config](../config/README.md#configurewebpack) in addition to installing the necessary dependencies.
172 | :::
173 |
174 | ## Script & Style Hoisting
175 |
176 | Sometimes you may need to apply some JavaScript or CSS only to the current page. In those cases you can directly write root-level `
195 |
196 | ## Built-In Components
197 |
198 | ### OutboundLink
199 |
200 | It( ) is used to indicate that this is an external link. In VuePress this component have been followed by every external link.
201 |
202 | ### ClientOnly
203 |
204 | See [Browser API Access Restrictions](#browser-api-access-restrictions).
205 |
206 | ### Content
207 |
208 | - **Props**:
209 |
210 | - `custom` - boolean
211 |
212 | - **Usage**:
213 |
214 | The compiled content of the current `.md` file being rendered. This will be very useful when you use [Custom Layout](../theme/default-theme-config.md#custom-layout-for-specific-pages).
215 |
216 | ``` vue
217 |
218 | ```
219 |
220 | **Also see:**
221 |
222 | - [Custom Themes > Content Outlet](../theme/writing-a-theme.md#content-outlet)
223 |
224 |
225 | ### Badge
226 |
227 | - **Props**:
228 |
229 | - `text` - string
230 | - `type` - string, optional value: `"tip"|"warn"|"error"`, defaults to `"tip"`.
231 | - `vertical` - string, optional value: `"top"|"middle"`, defaults to `"top"`.
232 |
233 | - **Usage**:
234 |
235 | You can use this component in header to add some status for some API:
236 |
237 | ``` md
238 | ### Badge
239 | ```
240 |
241 | **Also see:**
242 |
243 | - [Using Components In Headers](#using-components-in-headers)
244 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require('puppeteer')
2 | const PDFMerge = require('easy-pdf-merge')
3 | const { join, dirname } = require('path')
4 | const { dev } = require('vuepress')
5 | const { fs, logger, chalk } = require('@vuepress/shared-utils')
6 | const { red, yellow, gray } = chalk
7 |
8 | // Keep silent before running custom command.
9 | logger.setOptions({ logLevel: 1 })
10 |
11 | module.exports = (options = {}, context) => ({
12 | name: 'vuepress-plugin-export',
13 |
14 | chainWebpack(config) {
15 | config.plugins.delete('bar')
16 | // TODO vuepress should give plugin the ability to remove this plugin
17 | config.plugins.delete('vuepress-log')
18 | },
19 |
20 | extendCli(cli) {
21 | cli
22 | .command('export [targetDir]', 'export current vuepress site to a PDF file')
23 | .allowUnknownOptions()
24 | .action(async (dir = '.') => {
25 | dir = join(process.cwd(), dir)
26 | try {
27 | const devContext = await dev({
28 | sourceDir: dir,
29 | clearScreen: false,
30 | theme: options.theme || '@vuepress/default'
31 | })
32 |
33 | await new Promise(resolve => {
34 | devContext.devProcess.server.compiler.hooks.done.tap('webpack-dev-server', () => {
35 | console.log('VuePress dev server compiler done')
36 | resolve()
37 | })
38 | })
39 |
40 | logger.setOptions({ logLevel: 3 })
41 | logger.info(`Start to generate current site to PDF ...`)
42 |
43 | try {
44 | await generatePDF(devContext, {
45 | port: devContext.devProcess.port,
46 | host: devContext.devProcess.displayHost, // See vuejs/vuepress@4d5c50e
47 | base: devContext.base,
48 | options
49 | })
50 | } catch (error) {
51 | logger.error(red(error))
52 | }
53 |
54 | devContext.devProcess.server.close()
55 | process.exit(0)
56 | } catch (error) {
57 | throw error
58 | }
59 | })
60 | }
61 | })
62 |
63 | const defineBundles = (options, exportPages) => {
64 | return (typeof options.bundles === 'function')
65 | ? options.bundles(exportPages)
66 | : Array.isArray(options.bundles)
67 | ? options.bundles
68 | : options.bundles
69 | ? [options.bundles]
70 | : {}
71 | }
72 |
73 | const applyFilter = (filter) => {
74 | if (typeof filter === 'function') {
75 | return (page) => filter(page.location, page)
76 | }
77 |
78 | return (page) => {
79 | return !filter || filter.test(page.location)
80 | }
81 | }
82 |
83 | const createOutputFilename = (dest, pluginConfig, fallback) => {
84 | if (typeof dest === 'function') {
85 | return dest(pluginConfig)
86 | }
87 |
88 | if (typeof dest === 'string') {
89 | return dest
90 | }
91 |
92 | return `${pluginConfig.title || String(fallback)}.pdf`
93 | }
94 |
95 | async function generatePDF(context, {
96 | port,
97 | host,
98 | base,
99 | options,
100 | }) {
101 | const { pages, tempPath, siteConfig } = context
102 | const tempDir = join(tempPath, 'pdf')
103 |
104 | fs.ensureDirSync(tempDir)
105 |
106 | const browser = await puppeteer.launch(options.puppeteer)
107 | const browserPage = await browser.newPage()
108 |
109 | // Generate all pages on bulk
110 | const exportPages = pages.slice(0).map(page => {
111 | return {
112 | url: page.path,
113 | title: page.title,
114 | location: `http://${host}:${port}${base || ''}${page.path.slice(1)}`,
115 | path: `${tempDir}/${page.key}.pdf`
116 | }
117 | })
118 |
119 | for (const exportPage of exportPages) {
120 | const {
121 | location,
122 | path: pagePath,
123 | url,
124 | title
125 | } = exportPage
126 |
127 | await browserPage.goto(
128 | location,
129 | { waitUntil: 'networkidle2' }
130 | )
131 |
132 | await browserPage.pdf({
133 | path: pagePath,
134 | format: 'A4'
135 | })
136 |
137 | logger.success(`Generated ${yellow(title)} ${gray(`${url}`)}`)
138 | }
139 |
140 | await browser.close()
141 |
142 | const bundles = defineBundles(options, exportPages)
143 |
144 | for (const bundle of bundles) {
145 | const files = exportPages
146 | .filter(applyFilter(bundle.filter))
147 | .sort(bundle.sorter)
148 | .map(({ path }) => path)
149 |
150 | const outputFile = createOutputFilename(bundle.dest, siteConfig, 'site')
151 | if (files.length === 0) {
152 | logger.warn('WARN. Found no files to export!')
153 | } else if (files.length === 1) {
154 | const [filename] = files
155 | fs.mkdirSync(dirname(outputFile), { recursive: true })
156 |
157 | fs.copyFileSync(filename, outputFile)
158 | logger.success(`Export ${yellow(outputFile)} file!`)
159 | } else {
160 | await new Promise(resolve => {
161 | fs.mkdirSync(dirname(outputFile), { recursive: true })
162 |
163 | PDFMerge(files, outputFile, error => {
164 | if (error) {
165 | throw error
166 | }
167 | logger.success(`Export ${yellow(outputFile)} file!`)
168 | resolve()
169 | })
170 | })
171 | }
172 | }
173 |
174 | // fs.removeSync(tempDir)
175 | }
176 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuepress-plugin-export",
3 | "version": "0.2.0",
4 | "description": "Export your VuePress site to a PDF file",
5 | "main": "index.js",
6 | "files": [
7 | "index.js"
8 | ],
9 | "licenses": "MIT",
10 | "scripts": {
11 | "export": "vuepress export example",
12 | "test": "npm run lint && jest",
13 | "lint": "xo",
14 | "release": "release-it"
15 | },
16 | "repository": {
17 | "url": "ulivz/vuepress-plugin-export",
18 | "type": "git"
19 | },
20 | "author": "ulivz",
21 | "license": "MIT",
22 | "dependencies": {
23 | "puppeteer": ">=1.17.0",
24 | "easy-pdf-merge": "^0.2.1"
25 | },
26 | "devDependencies": {
27 | "eslint-config-sherry": "0.0.1",
28 | "husky": "2.4.1",
29 | "lint-staged": "8.2.1",
30 | "jest": "24.8.0",
31 | "release-it": "^12.3.0",
32 | "conventional-changelog-cli": "^2.0.21",
33 | "vuepress": "^1.0.0"
34 | },
35 | "jest": {
36 | "testEnvironment": "node"
37 | },
38 | "xo": {
39 | "extends": [
40 | "sherry"
41 | ],
42 | "envs": [
43 | "jest"
44 | ]
45 | },
46 | "husky": {
47 | "hooks": {
48 | "pre-commit": "lint-staged"
49 | }
50 | },
51 | "lint-staged": {
52 | "*.{js}": [
53 | "xo --fix",
54 | "git add"
55 | ]
56 | },
57 | "peerDependencies": {
58 | "vuepress": ">=1.0.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/site.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulivz/vuepress-plugin-export/01418c8167f44b4eda638143010cda7a013a8fde/site.pdf
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | const vuepressPluginExport = require('../')
2 |
3 | test('main', () => {
4 | expect(typeof vuepressPluginExport).toBe('function')
5 | })
6 |
--------------------------------------------------------------------------------