├── .babelrc ├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── package.json ├── src ├── generator.js ├── index.js └── prompts.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", { "targets": { "node": "8.9" } }]] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Additional context** 24 | Package version: X.X.X 25 | Vue version: X.X.X 26 | Vue CLI version: X.X.X 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless 74 | 75 | # Babel files output 76 | /*.js 77 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !README.md 3 | !LICENSE 4 | !/{generator,index,prompts}.js 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [120] 3 | } 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at solarliner@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | You are very welcome to contribute. In most cases a template will be provided to you when opening an issue or a PR; please follow it as strictly as possible in order to maximize information throughput. 4 | 5 | ## Pull Requests specifics 6 | 7 | Before submitting a pull request, it is preferable and strongly recommended to first open an issue, in order to discuss broadly on the implementation; the specifics can be put into the actual PR, where and when code will be reviewed. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nathan Graule 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **What**: 4 | 5 | **Why**: 6 | 7 | **How**: 8 | 9 | - [ ] I am working based off of `develop` 10 | - [ ] I have rebased my PR off of `develop` 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-cli-plugin-prerender-spa 2 | 3 | Add `prerender-spa-plugin` into your Vue application with zero configuration. 4 | 5 | ![](https://img.shields.io/david/solarliner/vue-cli-plugin-prerender-spa.svg) 6 | ![](https://img.shields.io/david/dev/solarliner/vue-cli-plugin-prerender-spa.svg) 7 | ![](https://img.shields.io/npm/v/vue-cli-plugin-prerender-spa.svg) 8 | ![](https://img.shields.io/github/commits-since/solarliner/vue-cli-plugin-prerender-spa/1.1.5.svg) 9 | 10 | **Looking for a co-maintainer**: I'm continuing to maintain this project, hoever 11 | I still would like help on some of the issues, and generally to help me keep this 12 | plugin going as it's getting more and more popular. If you think you can help, 13 | file and issue for maintainship! 14 | 15 | **Support requests**: Vue has a Discord server and I often lurk in there. And 16 | while there is a support label in the Issues, GitHub isn't the place for support 17 | requests and should be directed to me in the 18 | [Vue Land Discord server](https://vue-land.js.org). 19 | 20 | ## Install 21 | 22 | Add prerendering to your Vue application with: 23 | 24 | ```bash 25 | vue add prerender-spa 26 | ``` 27 | 28 | or by searching for `prerender-spa` in the Vue UI plugins. 29 | 30 | You'll be asked a few questions, detailed below, to which the default answers 31 | are the most common options. 32 | 33 | The main option to fit to your needs is the **list of routes to pre-render**. 34 | Specify them as a comma-separated list: 35 | 36 | ```bash 37 | ? Which routes to pre-render? (list them separated by a comma) /,/about,/contact 38 | ``` 39 | 40 | ## Options list 41 | 42 | ### Pre-rendered routes 43 | 44 | ```bash 45 | ? Which routes to pre-render? (list them separated by a comma) / 46 | ``` 47 | 48 | Specify a list of routes to pre-render. By default only the index page is pre- 49 | rendered, which should cover most SPAs. If your project uses vue-router, you 50 | can specify a list of routes that do not depend on dynamic content (like user 51 | uploaded data, public profiles, etc.). For example you can add your about page 52 | as well as a contact page - those will load faster, and will be indexed by bots 53 | who do not execute JavaScript, improving Search Engines rankings. 54 | 55 | Note that if you want to also pre-render user generated content, you _will_ 56 | have to switch to Server-Side Rendering, there are no other options. 57 | 58 | #### What it does to your project 59 | 60 | The list of routes is split into an array and passed into the Webpack plugin. 61 | The routes aren't checked for existence or even duplicates, just split into an 62 | array and sent to the `PrerenderSPAPlugin` instance. 63 | 64 | ### Event-triggered snapshot 65 | 66 | ```bash 67 | ? Use a render event to trigger the snapshot? Yes 68 | ``` 69 | 70 | Use a document event to signal `prerender-spa-plugin` to trigger a snapshot of 71 | the DOM and save it. By default the renderer waits until `DOMContentLoaded` to 72 | take a snapshot of the DOM. But it is still recommended that you control the 73 | snapshot trigger - no surprise waiting for hours for your build before 74 | realizing what's happening. 75 | 76 | #### What it does to your project 77 | 78 | When enabling the event-based snapshot trigger, it will tell 79 | `PrerenderSPAPlugin` to listen for an `x-app-rendered` event. Your main file 80 | is then modified to add a `mounted()` hook where the event will fire. Note that 81 | it doesn't check if the hook is already present, nor does it parses the file; 82 | it just looks for the line starting with `render:` (minus whitespaces) and 83 | inserts the `mounted()` hook below. If you already have the hook set up, or if 84 | your `render()` function on the main file is longer than one line, it will break 85 | your Vue entrypoint. A better injection routine is planned, but for now, it 86 | covers a vast majority of projects where the main file isn't touched. 87 | 88 | ### Use a headless browser for rendering 89 | 90 | ```bash 91 | ? Use a headless browser to render the application? (recommended) Yes 92 | ``` 93 | 94 | This option is there for debugging purposes, but **should be left enabled** 95 | otherwise. Not using a headless browser will open a Chrome window when building 96 | with your app running inside, then close once the snapshot has been taken. 97 | Since the plugin configuration object isn't available, it is available here. 98 | 99 | #### What it does to your project 100 | 101 | The `headless` value of the configuration object is set to the answer to the 102 | question. 103 | 104 | ### Only pre-render for production builds 105 | 106 | ```bash 107 | ? Only use prerendering for production builds? (recommended) Yes 108 | ``` 109 | 110 | Only load the pre-rendering plugin when building for production. This is 111 | strongly recommended as the plugin, spawning an instance of the Chrome browser, 112 | adds significant time to the build process. Development builds should be snappy 113 | and not memory-intensive; which is exactly what this plugin does to your build. 114 | 115 | However, there may be cases where you want to test the pre-rendering itself, 116 | and switching to a production build isn't the solution - you may then turn off 117 | that option. 118 | 119 | ### Indirect options 120 | 121 | #### Parallel / Mutli-threaded 122 | 123 | This option is configured from within the Vue CLI itself, but serves to a whole 124 | host of plugins to determine whether to turn on parallel jobs / multi-threading. 125 | 126 | This plugin uses it to tell `prerender-spa-plugin` to render pages concurrently 127 | (meaning in parallel) or not by setting the `maxConcurrentRoutes` parameter to 128 | either 1 or 4, if the build is respectively single-threaded or multi-threaded. 129 | 130 | ### Custom configuration 131 | 132 | After being invoked, the plugin saves a file named `.prerender-spa.json` in the 133 | root directory of the project; where you can specify custom options for the 134 | Puppeteer renderer. It will be merged, and its options will overwrite those set 135 | by the plugin itself. 136 | 137 | Other way to set custom configuration for the Puppeteer renderer is to use `customRendererConfig` 138 | dictionary of possible [Puppeteer launch options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions). 139 | 140 | Example configuration of debugging your site with Chrome DevTools opened automatically: 141 | 142 | ```js 143 | // vue.config.js 144 | 145 | module.exports = { 146 | pluginOptions: { 147 | prerenderSpa: { 148 | registry: undefined, 149 | renderRoutes: [ 150 | '/', 151 | '/about' 152 | ], 153 | useRenderEvent: true, 154 | onlyProduction: true, 155 | 156 | headless: false, // <- this could also be inside the customRendererConfig 157 | customRendererConfig: 158 | { 159 | args: ["--auto-open-devtools-for-tabs"] 160 | } 161 | } 162 | } 163 | } 164 | ``` 165 | 166 | ### User post processing function 167 | 168 | Pupeteer allows to postprocess the HTML after it's been snapshot, and the plugin 169 | allows you to provide your own function if you need to. 170 | 171 | Add a `postProcess` option into your `vue.config.js` file to provide a custom 172 | post-processing function to run on every build. 173 | 174 | Example configuration: 175 | 176 | ```js 177 | // vue.config.js 178 | 179 | module.exports = { 180 | pluginOptions: { 181 | prerenderSpa: { 182 | registry: undefined, 183 | renderRoutes: [ 184 | '/', 185 | '/about' 186 | ], 187 | useRenderEvent: true, 188 | headless: true, 189 | onlyProduction: true, 190 | postProcess: route => { 191 | // Defer scripts and tell Vue it's been server rendered to trigger hydration 192 | route.html = route.html 193 | .replace(/