Repository: chrisvfritz/prerender-spa-plugin
Branch: master
Commit: 60e400472a8d
Files: 77
Total size: 107.8 KB
Directory structure:
gitextract_ep0hv056/
├── .babelrc
├── .eslintrc.js
├── .gitignore
├── LICENSE.md
├── README.md
├── assets/
│ └── logo.gvdesign
├── es5-autogenerated/
│ └── index.js
├── es6/
│ └── index.js
├── examples/
│ ├── angular-cli-eject/
│ │ ├── .angular-cli.json
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── e2e/
│ │ │ ├── app.e2e-spec.ts
│ │ │ ├── app.po.ts
│ │ │ └── tsconfig.e2e.json
│ │ ├── karma.conf.js
│ │ ├── package.json
│ │ ├── protractor.conf.js
│ │ ├── src/
│ │ │ ├── app/
│ │ │ │ ├── app.component.css
│ │ │ │ ├── app.component.html
│ │ │ │ ├── app.component.spec.ts
│ │ │ │ ├── app.component.ts
│ │ │ │ └── app.module.ts
│ │ │ ├── assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── environments/
│ │ │ │ ├── environment.prod.ts
│ │ │ │ └── environment.ts
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ ├── polyfills.ts
│ │ │ ├── styles.css
│ │ │ ├── test.ts
│ │ │ ├── tsconfig.app.json
│ │ │ ├── tsconfig.spec.json
│ │ │ └── typings.d.ts
│ │ ├── tsconfig.json
│ │ ├── tslint.json
│ │ └── webpack.config.js
│ ├── create-react-app/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── config-overrides.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ ├── index.html
│ │ │ └── manifest.json
│ │ └── src/
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── serviceWorker.js
│ ├── vanilla-simple/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── main.js
│ │ │ └── static/
│ │ │ └── index.html
│ │ └── webpack.config.js
│ ├── vue2-webpack-router/
│ │ ├── .babelrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ └── main.js
│ │ └── webpack.config.js
│ └── vue2-webpack-simple/
│ ├── .babelrc
│ ├── .editorconfig
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── src/
│ │ ├── App.vue
│ │ └── main.js
│ └── webpack.config.js
├── index.js
├── package.json
└── test/
├── assets/
│ └── single-route/
│ └── index.html
├── index.js
└── single-route.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
["env", {
"targets": {
"node": 4,
}
}],
"stage-2"
],
"plugins": [
["transform-runtime", {
"helpers": false,
"polyfill": false,
"regenerator": true,
"moduleName": "babel-runtime"
}]
]
}
================================================
FILE: .eslintrc.js
================================================
module.exports = {
root: true,
parser: 'babel-eslint',
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0
}
}
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io/api/node,macos,linux,windows,visualstudiocode
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
================================================
FILE: LICENSE.md
================================================
## MIT License
Copyright (c) 2017 Chris Fritz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<h1 align="center">[DEPRECATED]</h1>
<h2 align="center">Prerender SPA Plugin</h2>
<p align="center">
<em>Flexible, framework-agnostic static site generation for sites and SPAs built with webpack.</em>
</p>
<p align="center"><img width="200" src="https://raw.githubusercontent.com/chrisvfritz/prerender-spa-plugin/master/assets/logo.png?raw=true"></p>
---
<div align="center">
[]()
[]()
[]()
[](https://standardjs.com/)
[]()
</div>
---
<div align="center">
[](https://nodei.co/npm/prerender-spa-plugin/)
</div>
## About prerender-spa-plugin
**:point_right: This is the stable `3.x` version of `prerender-spa-plugin` based on puppeteer. If you're looking for the (now-deprecated) `2.x` version, based on PhantomJS, take a look at the `v2` branch.**
The goal of this plugin is to provide a simple prerendering solution that is easily extensible and usable for any site or single-page-app built with webpack.
Plugins for other task runners and build systems are planned.
## Examples
Framework-specific examples can be found in the `examples/` directory.
- [Vanilla JS](examples/vanilla-simple)
- [Vue.js 2 Simple](examples/vue2-webpack-simple)
- [Vue.js 2 Router](examples/vue2-webpack-router)
- [React (Create React App)](examples/create-react-app)
- [Angular (Angular CLI + Eject)](examples/angular-cli-eject)
### Basic Usage (`webpack.config.js`)
```js
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
module.exports = {
plugins: [
...
new PrerenderSPAPlugin({
// Required - The path to the webpack-outputted app to prerender.
staticDir: path.join(__dirname, 'dist'),
// Required - Routes to render.
routes: [ '/', '/about', '/some/deep/nested/route' ],
})
]
}
```
### Advanced Usage (`webpack.config.js`)
```js
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
module.exports = {
plugins: [
...
new PrerenderSPAPlugin({
// Required - The path to the webpack-outputted app to prerender.
staticDir: path.join(__dirname, 'dist'),
// Optional - The path your rendered app should be output to.
// (Defaults to staticDir.)
outputDir: path.join(__dirname, 'prerendered'),
// Optional - The location of index.html
indexPath: path.join(__dirname, 'dist', 'index.html'),
// Required - Routes to render.
routes: [ '/', '/about', '/some/deep/nested/route' ],
// Optional - Allows you to customize the HTML and output path before
// writing the rendered contents to a file.
// renderedRoute can be modified and it or an equivelant should be returned.
// renderedRoute format:
// {
// route: String, // Where the output file will end up (relative to outputDir)
// originalRoute: String, // The route that was passed into the renderer, before redirects.
// html: String, // The rendered HTML for this route.
// outputPath: String // The path the rendered HTML will be written to.
// }
postProcess (renderedRoute) {
// Ignore any redirects.
renderedRoute.route = renderedRoute.originalRoute
// Basic whitespace removal. (Don't use this in production.)
renderedRoute.html = renderedRoute.html.split(/>[\s]+</gmi).join('><')
// Remove /index.html from the output path if the dir name ends with a .html file extension.
// For example: /dist/dir/special.html/index.html -> /dist/dir/special.html
if (renderedRoute.route.endsWith('.html')) {
renderedRoute.outputPath = path.join(__dirname, 'dist', renderedRoute.route)
}
return renderedRoute
},
// Optional - Uses html-minifier (https://github.com/kangax/html-minifier)
// To minify the resulting HTML.
// Option reference: https://github.com/kangax/html-minifier#options-quick-reference
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
// Server configuration options.
server: {
// Normally a free port is autodetected, but feel free to set this if needed.
port: 8001
},
// The actual renderer to use. (Feel free to write your own)
// Available renderers: https://github.com/Tribex/prerenderer/tree/master/renderers
renderer: new Renderer({
// Optional - The name of the property to add to the window object with the contents of `inject`.
injectProperty: '__PRERENDER_INJECTED',
// Optional - Any values you'd like your app to have access to via `window.injectProperty`.
inject: {
foo: 'bar'
},
// Optional - defaults to 0, no limit.
// Routes are rendered asynchronously.
// Use this to limit the number of routes rendered in parallel.
maxConcurrentRoutes: 4,
// Optional - Wait to render until the specified event is dispatched on the document.
// eg, with `document.dispatchEvent(new Event('custom-render-trigger'))`
renderAfterDocumentEvent: 'custom-render-trigger',
// Optional - Wait to render until the specified element is detected using `document.querySelector`
renderAfterElementExists: 'my-app-element',
// Optional - Wait to render until a certain amount of time has passed.
// NOT RECOMMENDED
renderAfterTime: 5000, // Wait 5 seconds.
// Other puppeteer options.
// (See here: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions)
headless: false // Display the browser window when rendering. Useful for debugging.
})
})
]
}
```
### v2.x Compability
Most usages of `prerender-spa-plugin` v2.x should be compatible with v3.x.
The exception being advanced configuration options that controlled PhantomJS. These have been replaced by pluggable renderers with their own specific configuration options.
If you use this format, you will be greeted with a warning prompting you to migrate to the new object-based configuration format, but it should still function for the time being.
```js
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
module.exports = {
// ...
plugins: [
new PrerenderSPAPlugin(
// (REQUIRED) Absolute path to static root
path.join(__dirname, 'relative/path/to/static/root'),
// (REQUIRED) List of routes to prerender
[ '/', '/about', '/contact' ],
// (OPTIONAL) Compatible options from v2.
{
// NOTE: Unless you are relying on asynchronously rendered content,
// such as after an Ajax request, none of these options should be
// necessary. All synchronous scripts are already executed before
// capturing the page content.
// Wait until a specific event is fired on the document.
captureAfterDocumentEvent: 'custom-post-render-event',
// This is how you would trigger this example event:
// document.dispatchEvent(new Event('custom-post-render-event'))
// Wait until a specific element is detected with
// document.querySelector.
captureAfterElementExists: '#content',
// Wait until a number of milliseconds has passed after scripts
// have been executed. It's important to note that this may
// produce unreliable results when relying on network
// communication or other operations with highly variable timing.
captureAfterTime: 5000,
// path of index file. By default it's index.html in static root.
indexPath: path.resolve('/dist/path/to/index.html'),
// Manually transform the HTML for each page after prerendering,
// for example to set the page title and metadata in edge cases
// where you cannot handle this via your routing solution.
//
// The function's context argument contains two properties:
//
// - html :: the resulting HTML after prerendering)
// - route :: the route currently being processed
// e.g. "/", "/about", or "/contact")
//
// Whatever is returned will be printed to the prerendered file.
// NOTE: this has been deprecated in favor of the `postProcess` option.
// See the documentation below.
postProcessHtml: function (context) {
var titles = {
'/': 'Home',
'/about': 'Our Story',
'/contact': 'Contact Us'
}
return context.html.replace(
/<title>[^<]*<\/title>/i,
'<title>' + titles[context.route] + '</title>'
)
}
}
)
]
}
```
#### Additional Changes
- It is no longer possible to use multiple `renderAfterX` (`captureAfterX`) options at the same time. Only one may be selected. The reason for this removal is to prevent ambiguity.
- The recommended configuration format has changed from `new PrerenderSPAPlugin(staticDir: String, routes: Array<String>, config: Object)` to
```javascript
new PrerenderSPAPlugin({
staticDir: String,
routes: String,
...
})
```
in order to reduce ambiguity. The old format still works for the time being.
- The default renderer is no longer PhantomJS. It has been replaced with [puppeteer](https://github.com/GoogleChrome/puppeteer). It is fairly simple to develop your own renderer as well. An alternate [jsdom](https://github.com/tmpvar/jsdom)-based renderer is available at [@prerenderer/renderer-jsdom](https://www.npmjs.com/package/@prerenderer/renderer-jsdom).
- `prerender-spa-plugin` is now based on [prerenderer](https://github.com/Tribex/prerenderer). Accordingly, most bugs should be reported in that repository.
## What is Prerendering?
Recently, SSR (Server Side Rendering) has taken the JavaScript front-end world by storm. The fact that you can now render your sites and apps on the server before sending them to your clients is an absolutely *revolutionary* idea (and totally not what everyone was doing before JS client-side apps got popular in the first place...)
However, the same criticisms that were valid for PHP, ASP, JSP, (and such) sites are valid for server-side rendering today. It's slow, breaks fairly easily, and is difficult to implement properly.
Thing is, despite what everyone might be telling you, you probably don't *need* SSR. You can get almost all the advantages of it (without the disadvantages) by using **prerendering.** Prerendering is basically firing up a headless browser, loading your app's routes, and saving the results to a static HTML file. You can then serve it with whatever static-file-serving solution you were using previously. It *just works* with HTML5 navigation and the likes. No need to change your code or add server-side rendering workarounds.
In the interest of transparency, there are some use-cases where prerendering might not be a great idea.
- **Tons of routes** - If your site has hundreds or thousands of routes, prerendering will be really slow. Sure you only have to do it once per update, but it could take ages. Most people don't end up with thousands of static routes, but just in-case...
- **Dynamic Content** - If your render routes that have content that's specific to the user viewing it or other dynamic sources, you should make sure you have placeholder components that can display until the dynamic content loads on the client-side. Otherwise it might be a tad weird.
## Available Renderers
- `@prerenderer/renderer-puppeteer` - Uses [puppeteer](https://github.com/GoogleChrome/puppeteer) to render pages in headless Chrome.
- `@prerenderer/renderer-jsdom` - Uses [jsdom](https://npmjs.com/package/jsdom). Extremely fast, but unreliable and cannot handle advanced usages. May not work with all front-end frameworks and apps.
### Which renderer should I use?
**Use `@prerenderer/renderer-puppeteer` if:** You're prerendering up to a couple hundred pages and want accurate results (bye-bye RAM!).
**Use `@prerenderer/renderer-jsdom` if:** You need to prerender thousands upon thousands of pages, but quality isn't all that important, and you're willing to work around issues for more advanced cases. (Programmatic SVG support, etc.)
## Documentation
### Plugin Options
| Option | Type | Required? | Default | Description |
|-------------|-------------------------------------------|-----------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| staticDir | String | Yes | None | The root path to serve your app from. |
| outputDir | String | No | None | Where the prerendered pages should be output. If not set, defaults to staticDir. |
| indexPath | String | No | `staticDir/index.html` | The index file to fall back on for SPAs. |
| postProcess | Function(Object context): [Object \| Promise] | No | None | See the [Using the postProcess Option](#using-the-postprocess-option) section. |
| minify | Object | No | None | Minifies the resulting HTML using [html-minifier](https://github.com/kangax/html-minifier). Full list of options available [here](https://github.com/kangax/html-minifier#options-quick-reference). |
| server | Object | No | None | App server configuration options (See below) |
| renderer | Renderer Instance or Configuration Object | No | `new PuppeteerRenderer()` | The renderer you'd like to use to prerender the app. It's recommended that you specify this, but if not it will default to `@prerenderer/renderer-puppeteer`. |
#### Server Options
| Option | Type | Required? | Default | Description |
|--------|---------|-----------|----------------------------|----------------------------------------|
| port | Integer | No | First free port after 8000 | The port for the app server to run on. |
| proxy | Object | No | No proxying | Proxy configuration. Has the same signature as [webpack-dev-server](https://github.com/webpack/docs/wiki/webpack-dev-server#proxy) |
#### Using The postProcess Option
The `postProcess(Object context): Object | Promise` function in your renderer configuration allows you to adjust the output of `prerender-spa-plugin` before writing it to a file. It is called once per rendered route and is passed a `context` object in the form of:
```javascript
{
// The prerendered route, after following redirects.
route: String,
// The original route passed, before redirects.
originalRoute: String,
// The resulting HTML for the route.
html: String,
// The path to write the rendered HTML to.
// This is null (automatically calculated after postProcess)
// unless explicitly set.
outputPath: String || null
}
```
You can modify `context.html` to change what gets written to the prerendered files and/or modify `context.route` or `context.outputPath` to change the output location.
You are expected to adjust those properties as needed, then return the context object, or a promise that resolves to it like so:
```javascript
postProcess(context) {
// Remove /index.html from the output path if the dir name ends with a .html file extension.
// For example: /dist/dir/special.html/index.html -> /dist/dir/special.html
if (context.route.endsWith('.html')) {
context.outputPath = path.join(__dirname, 'dist', context.route)
}
return context
}
postProcess(context) {
return someAsyncProcessing(context.html)
.then((html) => {
context.html = html;
return context;
});
}
```
#### Vue.js Notes
If you are having issues prerendering with Vue.js, try adding the [`data-server-rendered="true"`](https://ssr.vuejs.org/guide/hydration.html) attribute to your root app element. This will cause Vue to treat your current page as an already-rendered app and update it rather than completely rerendering the whole tree. You can add the attribute using `postProcess` or by manipulating the DOM with JavaScript prior prerendering with `renderAfterDocumentEvent`.
---
### `@prerenderer/renderer-puppeteer` options
| Option | Type | Required? | Default | Description |
|------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|-----------|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| maxConcurrentRoutes | Number | No | 0 (No limit) | The number of routes allowed to be rendered at the same time. Useful for breaking down massive batches of routes into smaller chunks. |
| inject | Object | No | None | An object to inject into the global scope of the rendered page before it finishes loading. Must be `JSON.stringifiy`-able. The property injected to is `window['__PRERENDER_INJECTED']` by default. |
| injectProperty | String | No | `__PRERENDER_INJECTED` | The property to mount `inject` to during rendering. |
| renderAfterDocumentEvent | String | No | None | Wait to render until the specified event is fired on the document. (You can fire an event like so: `document.dispatchEvent(new Event('custom-render-trigger'))` |
| renderAfterElementExists | String (Selector) | No | None | Wait to render until the specified element is detected using `document.querySelector` |
| renderAfterTime | Integer (Milliseconds) | No | None | Wait to render until a certain amount of time has passed. |
| skipThirdPartyRequests | Boolean | No | `false` | Automatically block any third-party requests. (This can make your pages load faster by not loading non-essential scripts, styles, or fonts.) |
| consoleHandler | function(route: String, message: [ConsoleMessage](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-consolemessage)) | No | None | Allows you to provide a custom console.* handler for pages. Argument one to your function is the route being rendered, argument two is the [Puppeteer ConsoleMessage](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-consolemessage) object. |
| [[Puppeteer Launch Options]](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) | ? | No | None | Any additional options will be passed to `puppeteer.launch()`, such as `headless: false`. |
---
### `@prerenderer/renderer-jsdom` options
| Option | Type | Required? | Default | Description |
|--------------------------|------------------------|-----------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| maxConcurrentRoutes | Number | No | 0 (No limit) | The number of routes allowed to be rendered at the same time. Useful for breaking down massive batches of routes into smaller chunks. |
| inject | Object | No | None | An object to inject into the global scope of the rendered page before it finishes loading. Must be `JSON.stringifiy`-able. The property injected to is `window['__PRERENDER_INJECTED']` by default. |
| injectProperty | String | No | `__PRERENDER_INJECTED` | The property to mount `inject` to during rendering. |
| renderAfterDocumentEvent | String | No | None | Wait to render until the specified event is fired on the document. (You can fire an event like so: `document.dispatchEvent(new Event('custom-render-trigger'))` |
| renderAfterElementExists | String (Selector) | No | None | Wait to render until the specified element is detected using `document.querySelector` |
| renderAfterTime | Integer (Milliseconds) | No | None | Wait to render until a certain amount of time has passed. |
---
## Tips & Troubleshooting
### JS not firing before prerender?
If you have code that relies on the existence of `<body>` (and you almost certainly do), simply run it in a callback to the `DOMContentLoaded` event:
*(Otherwise you'll find that `prerender-spa-plugin` will output the contents of your page before your JS runs.)*
```js
document.addEventListener('DOMContentLoaded', function () {
// your code
})
```
For example, if you're using Vue.js and mounting to a `<div id="app">` in `<body>`:
``` js
const root = new Vue({
// ...
})
document.addEventListener('DOMContentLoaded', function () {
root.$mount('#app')
})
```
### Inline Styles
If you rely on inline CSS, i.e. you do not extract CSS from your bundle and, thus, experience duplicate CSS style tags, consider using [extract-text-webpack-plugin](https://github.com/webpack-contrib/extract-text-webpack-plugin) to extract CSS into a separate file and then either inject CSS back into a `template.html` file using [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) or just call it as an external CSS file.
Either way, there will not be any unnecessary styles inside JS.
### Caveats
- For obvious reasons, `prerender-spa-plugin` only works for SPAs that route using the HTML5 history API. `index.html#/hash/route` URLs will unfortunately not work.
- Whatever client-side rendering library you're using should be able to at least replace any server-rendered content or diff with it.
- For **Vue.js 1** use [`replace: false`](http://vuejs.org/api/#replace) on root components.
- For **Vue.js 2** Ensure your root component has the same id as the prerendered element it's replacing. Otherwise you'll end up with duplicated content.
---
## Alternatives
- [react-snap](https://github.com/stereobooster/react-snap) - Zero-configuration framework-agnostic prerendering. Does not depend on webpack. Handles a variety of edge-cases.
- [snapshotify](https://github.com/errorception/snapshotify) - An experimental prerenderer that performes a number of speed optimizations.
- [presite](https://github.com/egoist/presite) - Minimal-configuration framework-agnostic prerendering.
- [prerenderer](https://github.com/tribex/prerenderer) - Pluggable prerendering library that `prerender-spa-plugin` `v3+` is based on.
## License (MIT)
```
Copyright (c) 2017 Chris Fritz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## Maintainers
<table>
<tbody>
<tr>
<td align="center">
<a href="https://github.com/chrisvfritz">
<img width="150" height="150" src="https://github.com/chrisvfritz.png?v=3&s=150">
</br>
Chris Fritz
</a>
</td>
<td align="center">
<a href="https://github.com/drewlustro">
<img width="150" height="150" src="https://github.com/drewlustro.png?v=3&s=150">
</br>
Drew Lustro
</a>
</td>
<td align="center">
<a href="https://github.com/tribex">
<img width="150" height="150" src="https://github.com/tribex.png?v=3&s=150">
</br>
Joshua Bemenderfer
</a>
</td>
</tr>
<tbody>
</table>
================================================
FILE: es5-autogenerated/index.js
================================================
'use strict';
var path = require('path');
var Prerenderer = require('@prerenderer/prerenderer');
var PuppeteerRenderer = require('@prerenderer/renderer-puppeteer');
var _require = require('html-minifier'),
minify = _require.minify;
function PrerenderSPAPlugin() {
var _this = this;
var rendererOptions = {}; // Primarily for backwards-compatibility.
this._options = {};
// Normal args object.
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (args.length === 1) {
this._options = args[0] || {};
// Backwards-compatibility with v2
} else {
console.warn("[prerender-spa-plugin] You appear to be using the v2 argument-based configuration options. It's recommended that you migrate to the clearer object-based configuration system.\nCheck the documentation for more information.");
var staticDir = void 0,
routes = void 0;
args.forEach(function (arg) {
if (typeof arg === 'string') staticDir = arg;else if (Array.isArray(arg)) routes = arg;else if (typeof arg === 'object') _this._options = arg;
});
staticDir ? this._options.staticDir = staticDir : null;
routes ? this._options.routes = routes : null;
}
// Backwards compatiblity with v2.
if (this._options.captureAfterDocumentEvent) {
console.warn('[prerender-spa-plugin] captureAfterDocumentEvent has been renamed to renderAfterDocumentEvent and should be moved to the renderer options.');
rendererOptions.renderAfterDocumentEvent = this._options.captureAfterDocumentEvent;
}
if (this._options.captureAfterElementExists) {
console.warn('[prerender-spa-plugin] captureAfterElementExists has been renamed to renderAfterElementExists and should be moved to the renderer options.');
rendererOptions.renderAfterElementExists = this._options.captureAfterElementExists;
}
if (this._options.captureAfterTime) {
console.warn('[prerender-spa-plugin] captureAfterTime has been renamed to renderAfterTime and should be moved to the renderer options.');
rendererOptions.renderAfterTime = this._options.captureAfterTime;
}
this._options.server = this._options.server || {};
this._options.renderer = this._options.renderer || new PuppeteerRenderer(Object.assign({}, { headless: true }, rendererOptions));
if (this._options.postProcessHtml) {
console.warn('[prerender-spa-plugin] postProcessHtml should be migrated to postProcess! Consult the documentation for more information.');
}
}
PrerenderSPAPlugin.prototype.apply = function (compiler) {
var _this2 = this;
var compilerFS = compiler.outputFileSystem;
// From https://github.com/ahmadnassri/mkdirp-promise/blob/master/lib/index.js
var mkdirp = function mkdirp(dir, opts) {
return new Promise(function (resolve, reject) {
compilerFS.mkdirp(dir, opts, function (err, made) {
return err === null ? resolve(made) : reject(err);
});
});
};
var afterEmit = function afterEmit(compilation, done) {
var PrerendererInstance = new Prerenderer(_this2._options);
PrerendererInstance.initialize().then(function () {
return PrerendererInstance.renderRoutes(_this2._options.routes || []);
})
// Backwards-compatibility with v2 (postprocessHTML should be migrated to postProcess)
.then(function (renderedRoutes) {
return _this2._options.postProcessHtml ? renderedRoutes.map(function (renderedRoute) {
var processed = _this2._options.postProcessHtml(renderedRoute);
if (typeof processed === 'string') renderedRoute.html = processed;else renderedRoute = processed;
return renderedRoute;
}) : renderedRoutes;
})
// Run postProcess hooks.
.then(function (renderedRoutes) {
return _this2._options.postProcess ? Promise.all(renderedRoutes.map(function (renderedRoute) {
return _this2._options.postProcess(renderedRoute);
})) : renderedRoutes;
})
// Check to ensure postProcess hooks returned the renderedRoute object properly.
.then(function (renderedRoutes) {
var isValid = renderedRoutes.every(function (r) {
return typeof r === 'object';
});
if (!isValid) {
throw new Error('[prerender-spa-plugin] Rendered routes are empty, did you forget to return the `context` object in postProcess?');
}
return renderedRoutes;
})
// Minify html files if specified in config.
.then(function (renderedRoutes) {
if (!_this2._options.minify) return renderedRoutes;
renderedRoutes.forEach(function (route) {
route.html = minify(route.html, _this2._options.minify);
});
return renderedRoutes;
})
// Calculate outputPath if it hasn't been set already.
.then(function (renderedRoutes) {
renderedRoutes.forEach(function (rendered) {
if (!rendered.outputPath) {
rendered.outputPath = path.join(_this2._options.outputDir || _this2._options.staticDir, rendered.route, 'index.html');
}
});
return renderedRoutes;
})
// Create dirs and write prerendered files.
.then(function (processedRoutes) {
var promises = Promise.all(processedRoutes.map(function (processedRoute) {
return mkdirp(path.dirname(processedRoute.outputPath)).then(function () {
return new Promise(function (resolve, reject) {
compilerFS.writeFile(processedRoute.outputPath, processedRoute.html.trim(), function (err) {
if (err) reject(`[prerender-spa-plugin] Unable to write rendered route to file "${processedRoute.outputPath}" \n ${err}.`);else resolve();
});
});
}).catch(function (err) {
if (typeof err === 'string') {
err = `[prerender-spa-plugin] Unable to create directory ${path.dirname(processedRoute.outputPath)} for route ${processedRoute.route}. \n ${err}`;
}
throw err;
});
}));
return promises;
}).then(function (r) {
PrerendererInstance.destroy();
done();
}).catch(function (err) {
PrerendererInstance.destroy();
var msg = '[prerender-spa-plugin] Unable to prerender all routes!';
console.error(msg);
compilation.errors.push(new Error(msg));
done();
});
};
if (compiler.hooks) {
var plugin = { name: 'PrerenderSPAPlugin' };
compiler.hooks.afterEmit.tapAsync(plugin, afterEmit);
} else {
compiler.plugin('after-emit', afterEmit);
}
};
PrerenderSPAPlugin.PuppeteerRenderer = PuppeteerRenderer;
module.exports = PrerenderSPAPlugin;
================================================
FILE: es6/index.js
================================================
const path = require('path')
const Prerenderer = require('@prerenderer/prerenderer')
const PuppeteerRenderer = require('@prerenderer/renderer-puppeteer')
const { minify } = require('html-minifier')
function PrerenderSPAPlugin (...args) {
const rendererOptions = {} // Primarily for backwards-compatibility.
this._options = {}
// Normal args object.
if (args.length === 1) {
this._options = args[0] || {}
// Backwards-compatibility with v2
} else {
console.warn("[prerender-spa-plugin] You appear to be using the v2 argument-based configuration options. It's recommended that you migrate to the clearer object-based configuration system.\nCheck the documentation for more information.")
let staticDir, routes
args.forEach(arg => {
if (typeof arg === 'string') staticDir = arg
else if (Array.isArray(arg)) routes = arg
else if (typeof arg === 'object') this._options = arg
})
staticDir ? this._options.staticDir = staticDir : null
routes ? this._options.routes = routes : null
}
// Backwards compatiblity with v2.
if (this._options.captureAfterDocumentEvent) {
console.warn('[prerender-spa-plugin] captureAfterDocumentEvent has been renamed to renderAfterDocumentEvent and should be moved to the renderer options.')
rendererOptions.renderAfterDocumentEvent = this._options.captureAfterDocumentEvent
}
if (this._options.captureAfterElementExists) {
console.warn('[prerender-spa-plugin] captureAfterElementExists has been renamed to renderAfterElementExists and should be moved to the renderer options.')
rendererOptions.renderAfterElementExists = this._options.captureAfterElementExists
}
if (this._options.captureAfterTime) {
console.warn('[prerender-spa-plugin] captureAfterTime has been renamed to renderAfterTime and should be moved to the renderer options.')
rendererOptions.renderAfterTime = this._options.captureAfterTime
}
this._options.server = this._options.server || {}
this._options.renderer = this._options.renderer || new PuppeteerRenderer(Object.assign({}, { headless: true }, rendererOptions))
if (this._options.postProcessHtml) {
console.warn('[prerender-spa-plugin] postProcessHtml should be migrated to postProcess! Consult the documentation for more information.')
}
}
PrerenderSPAPlugin.prototype.apply = function (compiler) {
const compilerFS = compiler.outputFileSystem
// From https://github.com/ahmadnassri/mkdirp-promise/blob/master/lib/index.js
const mkdirp = function (dir, opts) {
return new Promise((resolve, reject) => {
compilerFS.mkdirp(dir, opts, (err, made) => err === null ? resolve(made) : reject(err))
})
}
const afterEmit = (compilation, done) => {
const PrerendererInstance = new Prerenderer(this._options)
PrerendererInstance.initialize()
.then(() => {
return PrerendererInstance.renderRoutes(this._options.routes || [])
})
// Backwards-compatibility with v2 (postprocessHTML should be migrated to postProcess)
.then(renderedRoutes => this._options.postProcessHtml
? renderedRoutes.map(renderedRoute => {
const processed = this._options.postProcessHtml(renderedRoute)
if (typeof processed === 'string') renderedRoute.html = processed
else renderedRoute = processed
return renderedRoute
})
: renderedRoutes
)
// Run postProcess hooks.
.then(renderedRoutes => this._options.postProcess
? Promise.all(renderedRoutes.map(renderedRoute => this._options.postProcess(renderedRoute)))
: renderedRoutes
)
// Check to ensure postProcess hooks returned the renderedRoute object properly.
.then(renderedRoutes => {
const isValid = renderedRoutes.every(r => typeof r === 'object')
if (!isValid) {
throw new Error('[prerender-spa-plugin] Rendered routes are empty, did you forget to return the `context` object in postProcess?')
}
return renderedRoutes
})
// Minify html files if specified in config.
.then(renderedRoutes => {
if (!this._options.minify) return renderedRoutes
renderedRoutes.forEach(route => {
route.html = minify(route.html, this._options.minify)
})
return renderedRoutes
})
// Calculate outputPath if it hasn't been set already.
.then(renderedRoutes => {
renderedRoutes.forEach(rendered => {
if (!rendered.outputPath) {
rendered.outputPath = path.join(this._options.outputDir || this._options.staticDir, rendered.route, 'index.html')
}
})
return renderedRoutes
})
// Create dirs and write prerendered files.
.then(processedRoutes => {
const promises = Promise.all(processedRoutes.map(processedRoute => {
return mkdirp(path.dirname(processedRoute.outputPath))
.then(() => {
return new Promise((resolve, reject) => {
compilerFS.writeFile(processedRoute.outputPath, processedRoute.html.trim(), err => {
if (err) reject(`[prerender-spa-plugin] Unable to write rendered route to file "${processedRoute.outputPath}" \n ${err}.`)
else resolve()
})
})
})
.catch(err => {
if (typeof err === 'string') {
err = `[prerender-spa-plugin] Unable to create directory ${path.dirname(processedRoute.outputPath)} for route ${processedRoute.route}. \n ${err}`
}
throw err
})
}))
return promises
})
.then(r => {
PrerendererInstance.destroy()
done()
})
.catch(err => {
PrerendererInstance.destroy()
const msg = '[prerender-spa-plugin] Unable to prerender all routes!'
console.error(msg)
compilation.errors.push(new Error(msg))
done()
})
}
if (compiler.hooks) {
const plugin = { name: 'PrerenderSPAPlugin' }
compiler.hooks.afterEmit.tapAsync(plugin, afterEmit)
} else {
compiler.plugin('after-emit', afterEmit)
}
}
PrerenderSPAPlugin.PuppeteerRenderer = PuppeteerRenderer
module.exports = PrerenderSPAPlugin
================================================
FILE: examples/angular-cli-eject/.angular-cli.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "angular",
"ejected": true
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json",
"exclude": "**/node_modules/**"
},
{
"project": "src/tsconfig.spec.json",
"exclude": "**/node_modules/**"
},
{
"project": "e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"component": {
}
}
}
================================================
FILE: examples/angular-cli-eject/.editorconfig
================================================
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
================================================
FILE: examples/angular-cli-eject/.gitignore
================================================
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/dist-server
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db
================================================
FILE: examples/angular-cli-eject/README.md
================================================
# Ejected Angular CLI App - Prerender SPA Example
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.0.
## Build
```bash
npm install
npm run build
```
Now check the new `build` directory for your prerendered static files!
To view the rendered files, install [http-server](https://www.npmjs.com/package/http-server) (`npm install -g http-server`) if you haven't already and run it in the dist directory.
Now visit the following route in your browser (note the trailing slash):
- [http://localhost:8000/](http://localhost:8000/)
If all went well, it should load without JavaScript.
## Development
To edit the `prerender-spa-plugin` configuration, look for `new PrerenderSPAPlugin` in the plugins section of `webpack.config.js`.
If you're using a router or have more than one page to prerender, edit the `routes` array under that config object.
```bash
npm install
npm start
```
================================================
FILE: examples/angular-cli-eject/e2e/app.e2e-spec.ts
================================================
import { AppPage } from './app.po';
describe('angular App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});
================================================
FILE: examples/angular-cli-eject/e2e/app.po.ts
================================================
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}
================================================
FILE: examples/angular-cli-eject/e2e/tsconfig.e2e.json
================================================
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
================================================
FILE: examples/angular-cli-eject/karma.conf.js
================================================
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
================================================
FILE: examples/angular-cli-eject/package.json
================================================
{
"name": "prerender-spa-plugin-example-angular-cli-eject",
"version": "1.0.0",
"private": true,
"scripts": {
"ng": "ng",
"start": "webpack-dev-server --port=4200",
"build": "webpack",
"test": "karma start ./karma.conf.js",
"lint": "ng lint",
"e2e": "protractor ./protractor.conf.js",
"pree2e": "webdriver-manager update --standalone false --gecko false --quiet"
},
"dependencies": {
"@angular/animations": "^5.2.0",
"@angular/common": "^5.2.0",
"@angular/compiler": "^5.2.0",
"@angular/core": "^5.2.0",
"@angular/forms": "^5.2.0",
"@angular/http": "^5.2.0",
"@angular/platform-browser": "^5.2.0",
"@angular/platform-browser-dynamic": "^5.2.0",
"@angular/router": "^5.2.0",
"core-js": "^2.4.1",
"rxjs": "^5.5.6",
"zone.js": "^0.8.19"
},
"devDependencies": {
"@angular-devkit/core": "0.3.1",
"@angular/cli": "~1.7.0",
"@angular/compiler-cli": "^5.2.0",
"@angular/language-service": "^5.2.0",
"@ngtools/webpack": "1.10.0",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"autoprefixer": "^7.2.3",
"circular-dependency-plugin": "^4.2.1",
"codelyzer": "^4.0.1",
"copy-webpack-plugin": "~4.4.1",
"file-loader": "^1.1.5",
"html-webpack-plugin": "^2.29.0",
"istanbul-instrumenter-loader": "^3.0.0",
"jasmine-core": "~2.8.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"less-loader": "^4.0.5",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.10",
"postcss-url": "^7.1.2",
"prerender-spa-plugin": "^3.0.0",
"protractor": "~5.1.2",
"raw-loader": "^0.5.1",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.1",
"stylus-loader": "^3.0.1",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",
"typescript": "~2.5.3",
"uglifyjs-webpack-plugin": "^1.1.8",
"url-loader": "^0.6.2",
"webpack": "~3.11.0",
"webpack-dev-server": "~2.11.0"
}
}
================================================
FILE: examples/angular-cli-eject/protractor.conf.js
================================================
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
================================================
FILE: examples/angular-cli-eject/src/app/app.component.css
================================================
================================================
FILE: examples/angular-cli-eject/src/app/app.component.html
================================================
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
</li>
</ul>
================================================
FILE: examples/angular-cli-eject/src/app/app.component.spec.ts
================================================
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});
================================================
FILE: examples/angular-cli-eject/src/app/app.component.ts
================================================
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
}
================================================
FILE: examples/angular-cli-eject/src/app/app.module.ts
================================================
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
================================================
FILE: examples/angular-cli-eject/src/assets/.gitkeep
================================================
================================================
FILE: examples/angular-cli-eject/src/environments/environment.prod.ts
================================================
export const environment = {
production: true
};
================================================
FILE: examples/angular-cli-eject/src/environments/environment.ts
================================================
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
};
================================================
FILE: examples/angular-cli-eject/src/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
================================================
FILE: examples/angular-cli-eject/src/main.ts
================================================
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
================================================
FILE: examples/angular-cli-eject/src/polyfills.ts
================================================
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';
/**
* Required to support Web Animations `@angular/platform-browser/animations`.
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
================================================
FILE: examples/angular-cli-eject/src/styles.css
================================================
/* You can add global styles to this file, and also import other style files */
================================================
FILE: examples/angular-cli-eject/src/test.ts
================================================
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
================================================
FILE: examples/angular-cli-eject/src/tsconfig.app.json
================================================
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
================================================
FILE: examples/angular-cli-eject/src/tsconfig.spec.json
================================================
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"baseUrl": "./",
"module": "commonjs",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}
================================================
FILE: examples/angular-cli-eject/src/typings.d.ts
================================================
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}
================================================
FILE: examples/angular-cli-eject/tsconfig.json
================================================
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
]
}
}
================================================
FILE: examples/angular-cli-eject/tslint.json
================================================
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs",
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}
================================================
FILE: examples/angular-cli-eject/webpack.config.js
================================================
const fs = require('fs');
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const rxPaths = require('rxjs/_esm5/path-mapping');
const autoprefixer = require('autoprefixer');
const postcssUrl = require('postcss-url');
const postcssImports = require('postcss-import');
const { NoEmitOnErrorsPlugin, SourceMapDevToolPlugin, NamedModulesPlugin } = require('webpack');
const { NamedLazyChunksWebpackPlugin, BaseHrefWebpackPlugin, PostcssCliResources } = require('@angular/cli/plugins/webpack');
const { CommonsChunkPlugin } = require('webpack').optimize;
const { AngularCompilerPlugin } = require('@ngtools/webpack');
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
const nodeModules = path.join(process.cwd(), 'node_modules');
const realNodeModules = fs.realpathSync(nodeModules);
const genDirNodeModules = path.join(process.cwd(), 'src', '$$_gendir', 'node_modules');
const entryPoints = ["inline","polyfills","sw-register","styles","vendor","main"];
const hashFormat = {"chunk":"","extract":"","file":".[hash:20]","script":""};
const baseHref = "";
const deployUrl = "";
const projectRoot = process.cwd();
const maximumInlineSize = 10;
const postcssPlugins = function (loader) {
return [
postcssImports({
resolve: (url, context) => {
return new Promise((resolve, reject) => {
let hadTilde = false;
if (url && url.startsWith('~')) {
url = url.substr(1);
hadTilde = true;
}
loader.resolve(context, (hadTilde ? '' : './') + url, (err, result) => {
if (err) {
if (hadTilde) {
reject(err);
return;
}
loader.resolve(context, url, (err, result) => {
if (err) {
reject(err);
}
else {
resolve(result);
}
});
}
else {
resolve(result);
}
});
});
},
load: (filename) => {
return new Promise((resolve, reject) => {
loader.fs.readFile(filename, (err, data) => {
if (err) {
reject(err);
return;
}
const content = data.toString();
resolve(content);
});
});
}
}),
postcssUrl({
filter: ({ url }) => url.startsWith('~'),
url: ({ url }) => {
const fullPath = path.join(projectRoot, 'node_modules', url.substr(1));
return path.relative(loader.context, fullPath).replace(/\\/g, '/');
}
}),
postcssUrl([
{
// Only convert root relative URLs, which CSS-Loader won't process into require().
filter: ({ url }) => url.startsWith('/') && !url.startsWith('//'),
url: ({ url }) => {
if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) {
// If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
return `${deployUrl.replace(/\/$/, '')}${url}`;
}
else if (baseHref.match(/:\/\//)) {
// If baseHref contains a scheme, include it as is.
return baseHref.replace(/\/$/, '') +
`/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
}
else {
// Join together base-href, deploy-url and the original URL.
// Also dedupe multiple slashes into single ones.
return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
}
}
},
{
// TODO: inline .cur if not supporting IE (use browserslist to check)
filter: (asset) => {
return maximumInlineSize > 0 && !asset.hash && !asset.absolutePath.endsWith('.cur');
},
url: 'inline',
// NOTE: maxSize is in KB
maxSize: maximumInlineSize,
fallback: 'rebase',
},
{ url: 'rebase' },
]),
PostcssCliResources({
deployUrl: loader.loaders[loader.loaderIndex].options.ident == 'extracted' ? '' : deployUrl,
loader,
filename: `[name]${hashFormat.file}.[ext]`,
}),
autoprefixer({ grid: true }),
];
};
module.exports = {
"resolve": {
"extensions": [
".ts",
".js"
],
"symlinks": true,
"modules": [
"./src",
"./node_modules"
],
"alias": rxPaths(),
"mainFields": [
"browser",
"module",
"main"
]
},
"resolveLoader": {
"modules": [
"./node_modules"
],
"alias": rxPaths()
},
"entry": {
"main": [
"./src/main.ts"
],
"polyfills": [
"./src/polyfills.ts"
],
"styles": [
"./src/styles.css"
]
},
"output": {
"path": path.join(process.cwd(), "dist"),
"filename": "[name].bundle.js",
"chunkFilename": "[id].chunk.js",
"crossOriginLoading": false
},
"module": {
"rules": [
{
"test": /\.html$/,
"loader": "raw-loader"
},
{
"test": /\.(eot|svg|cur)$/,
"loader": "file-loader",
"options": {
"name": "[name].[hash:20].[ext]",
"limit": 10000
}
},
{
"test": /\.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
"loader": "url-loader",
"options": {
"name": "[name].[hash:20].[ext]",
"limit": 10000
}
},
{
"exclude": [
path.join(process.cwd(), "src/styles.css")
],
"test": /\.css$/,
"use": [
{
"loader": "raw-loader"
},
{
"loader": "postcss-loader",
"options": {
"ident": "embedded",
"plugins": postcssPlugins,
"sourceMap": true
}
}
]
},
{
"exclude": [
path.join(process.cwd(), "src/styles.css")
],
"test": /\.scss$|\.sass$/,
"use": [
{
"loader": "raw-loader"
},
{
"loader": "postcss-loader",
"options": {
"ident": "embedded",
"plugins": postcssPlugins,
"sourceMap": true
}
},
{
"loader": "sass-loader",
"options": {
"sourceMap": true,
"precision": 8,
"includePaths": []
}
}
]
},
{
"exclude": [
path.join(process.cwd(), "src/styles.css")
],
"test": /\.less$/,
"use": [
{
"loader": "raw-loader"
},
{
"loader": "postcss-loader",
"options": {
"ident": "embedded",
"plugins": postcssPlugins,
"sourceMap": true
}
},
{
"loader": "less-loader",
"options": {
"sourceMap": true
}
}
]
},
{
"exclude": [
path.join(process.cwd(), "src/styles.css")
],
"test": /\.styl$/,
"use": [
{
"loader": "raw-loader"
},
{
"loader": "postcss-loader",
"options": {
"ident": "embedded",
"plugins": postcssPlugins,
"sourceMap": true
}
},
{
"loader": "stylus-loader",
"options": {
"sourceMap": true,
"paths": []
}
}
]
},
{
"include": [
path.join(process.cwd(), "src/styles.css")
],
"test": /\.css$/,
"use": [
"style-loader",
{
"loader": "raw-loader"
},
{
"loader": "postcss-loader",
"options": {
"ident": "embedded",
"plugins": postcssPlugins,
"sourceMap": true
}
}
]
},
{
"include": [
path.join(process.cwd(), "src/styles.css")
],
"test": /\.scss$|\.sass$/,
"use": [
"style-loader",
{
"loader": "raw-loader"
},
{
"loader": "postcss-loader",
"options": {
"ident": "embedded",
"plugins": postcssPlugins,
"sourceMap": true
}
},
{
"loader": "sass-loader",
"options": {
"sourceMap": true,
"precision": 8,
"includePaths": []
}
}
]
},
{
"include": [
path.join(process.cwd(), "src/styles.css")
],
"test": /\.less$/,
"use": [
"style-loader",
{
"loader": "raw-loader"
},
{
"loader": "postcss-loader",
"options": {
"ident": "embedded",
"plugins": postcssPlugins,
"sourceMap": true
}
},
{
"loader": "less-loader",
"options": {
"sourceMap": true
}
}
]
},
{
"include": [
path.join(process.cwd(), "src/styles.css")
],
"test": /\.styl$/,
"use": [
"style-loader",
{
"loader": "raw-loader"
},
{
"loader": "postcss-loader",
"options": {
"ident": "embedded",
"plugins": postcssPlugins,
"sourceMap": true
}
},
{
"loader": "stylus-loader",
"options": {
"sourceMap": true,
"paths": []
}
}
]
},
{
"test": /\.ts$/,
"loader": "@ngtools/webpack"
}
]
},
"plugins": [
new NoEmitOnErrorsPlugin(),
new CopyWebpackPlugin([
{
"context": "src",
"to": "",
"from": {
"glob": "assets/**/*",
"dot": true
}
},
{
"context": "src",
"to": "",
"from": {
"glob": "favicon.ico",
"dot": true
}
}
], {
"ignore": [
".gitkeep",
"**/.DS_Store",
"**/Thumbs.db"
],
"debug": "warning"
}),
new ProgressPlugin(),
new CircularDependencyPlugin({
"exclude": /(\\|\/)node_modules(\\|\/)/,
"failOnError": false,
"onDetected": false,
"cwd": projectRoot
}),
new NamedLazyChunksWebpackPlugin(),
new HtmlWebpackPlugin({
"template": "./src/index.html",
"filename": "./index.html",
"hash": false,
"inject": true,
"compile": true,
"favicon": false,
"minify": false,
"cache": true,
"showErrors": true,
"chunks": "all",
"excludeChunks": [],
"title": "Webpack App",
"xhtml": true,
"chunksSortMode": function sort(left, right) {
let leftIndex = entryPoints.indexOf(left.names[0]);
let rightindex = entryPoints.indexOf(right.names[0]);
if (leftIndex > rightindex) {
return 1;
}
else if (leftIndex < rightindex) {
return -1;
}
else {
return 0;
}
}
}),
new BaseHrefWebpackPlugin({}),
new CommonsChunkPlugin({
"name": [
"inline"
],
"minChunks": null
}),
new CommonsChunkPlugin({
"name": [
"vendor"
],
"minChunks": (module) => {
return module.resource
&& (module.resource.startsWith(nodeModules)
|| module.resource.startsWith(genDirNodeModules)
|| module.resource.startsWith(realNodeModules));
},
"chunks": [
"main"
]
}),
new SourceMapDevToolPlugin({
"filename": "[file].map[query]",
"moduleFilenameTemplate": "[resource-path]",
"fallbackModuleFilenameTemplate": "[resource-path]?[hash]",
"sourceRoot": "webpack:///"
}),
new CommonsChunkPlugin({
"name": [
"main"
],
"minChunks": 2,
"async": "common"
}),
new NamedModulesPlugin({}),
new AngularCompilerPlugin({
"mainPath": "main.ts",
"platform": 0,
"hostReplacementPaths": {
"environments/environment.ts": "environments/environment.ts"
},
"sourceMap": true,
"tsConfigPath": "src/tsconfig.app.json",
"skipCodeGeneration": true,
"compilerOptions": {}
}),
// == PRERENDER SPA PLUGIN == //
new PrerenderSPAPlugin({
// Index.html is in the root directory.
staticDir: path.join(__dirname, 'dist'),
routes: [ '/' ],
// Optional minification.
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
renderer: new Renderer({
renderAfterTime: 500
})
})
],
"node": {
"fs": "empty",
"global": true,
"crypto": "empty",
"tls": "empty",
"net": "empty",
"process": true,
"module": false,
"clearImmediate": false,
"setImmediate": false
},
"devServer": {
"historyApiFallback": true
}
};
================================================
FILE: examples/create-react-app/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: examples/create-react-app/README.md
================================================
# Create-React-App - Prerender SPA Example
This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app) and uses [react-app-rewired](https://github.com/timarney/react-app-rewired) to allow modification of the webpack configuration.
## Build
```bash
npm install
npm run build
```
Now check the new `build` directory for your prerendered static files!
To view the rendered files, install [http-server](https://www.npmjs.com/package/http-server) (`npm install -g http-server`) if you haven't already and run it in the `build` directory.
Now visit the following route in your browser (note the trailing slash):
- [http://localhost:8000/](http://localhost:8000/)
If all went well, it should load without JavaScript.
## Development
To edit the `prerender-spa-plugin` configuration, look for `new PrerenderSPAPlugin` in `config-overrides.js`.
If you're using a router or have more than one page to prerender, edit the `routes` array under that config object.
```bash
npm install
npm start
```
================================================
FILE: examples/create-react-app/config-overrides.js
================================================
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const path = require('path');
module.exports = (config, env) => {
if (env === 'production') {
config.plugins = config.plugins.concat([
new PrerenderSPAPlugin({
routes: ['/'],
staticDir: path.join(__dirname, 'build'),
}),
]);
}
return config;
};
================================================
FILE: examples/create-react-app/package.json
================================================
{
"name": "prerender-spa-plugin-example-create-react-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-scripts": "2.1.3"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"devDependencies": {
"prerender-spa-plugin": "3.4.0",
"react-app-rewired": "2.0.2"
}
}
================================================
FILE: examples/create-react-app/public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
================================================
FILE: examples/create-react-app/public/manifest.json
================================================
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
================================================
FILE: examples/create-react-app/src/App.css
================================================
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
================================================
FILE: examples/create-react-app/src/App.js
================================================
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
}
export default App;
================================================
FILE: examples/create-react-app/src/App.test.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
================================================
FILE: examples/create-react-app/src/index.css
================================================
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
================================================
FILE: examples/create-react-app/src/index.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
================================================
FILE: examples/create-react-app/src/serviceWorker.js
================================================
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read http://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit http://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
================================================
FILE: examples/vanilla-simple/.gitignore
================================================
node_modules/
dist/
================================================
FILE: examples/vanilla-simple/README.md
================================================
# Vanilla JS - Prerender SPA Example
Demonstrates usage of `prerender-spa-plugin` with Vanilla JS and Webpack 3. Build will generate 3 static routes at the following paths:
- `/`
- `/about`
- `/some/deep/nested/route`
## Build
```bash
npm install
npm run build
```
Now check the new `dist` directory for your prerendered static files!
To view the rendered files, install [http-server](https://www.npmjs.com/package/http-server) (`npm install -g http-server`) if you haven't already and run it in the dist directory.
Now visit the following routes in your browser (note the trailing slash):
- [http://localhost:8000/](http://localhost:8000/)
- [http://localhost:8000/about/](http://localhost:8000/about/)
- [http://localhost:8000/some/deep/nested/route/](http://localhost:8000/some/deep/nested/route/)
You should notice that the TODOs are rendered and populated already.
## Development
To edit the `prerender-spa-plugin` configuration, look for `new PrerenderSPAPlugin` in the plugins section of `webpack.config.js`.
If you're using a router or have more than one page to prerender, edit the `routes` array under that config object.
================================================
FILE: examples/vanilla-simple/package.json
================================================
{
"name": "prerender-spa-plugin-example-vanilla-simple",
"version": "3.0.0",
"private": true,
"scripts": {
"build": "webpack --progress --hide-modules"
},
"devDependencies": {
"copy-webpack-plugin": "^4.4.1",
"prerender-spa-plugin": "^3.0.0",
"webpack": "^3.11.0"
}
}
================================================
FILE: examples/vanilla-simple/src/main.js
================================================
// -----
// STATE
// -----
var todos = [
'Do the dishes',
'Make the bed',
'Take out the trash'
]
// --------
// ELEMENTS
// --------
var newTodoInput = document.getElementById('new-todo')
var todosContainer = document.getElementById('todos')
// ------
// RENDER
// ------
function render () {
todosContainer.innerHTML = '<ul>' +
todos.map(function (todo, index) {
return '<li class="item">' +
todo +
' <button class="remove-todo" data-index="' + index + '">X</button>' +
'</li>'
}).join('') +
'</ul>'
document.dispatchEvent(new Event('render-event'))
}
render()
// ------
// EVENTS
// ------
newTodoInput.addEventListener('keyup', function (event) {
if (event.which === 13) {
todos.push(event.target.value)
newTodoInput.value = ''
render()
}
})
todosContainer.addEventListener('click', function (event) {
var clickedElement = event.target
if (clickedElement.className === 'remove-todo') {
todos.splice(clickedElement.dataset.index, 1)
render()
}
})
document.body.innerHTML += `<p>Injected: ${JSON.stringify(window['__PRERENDER_INJECTED'])}</p>`
================================================
FILE: examples/vanilla-simple/src/static/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>Generic & Simple Prerendering Demo</title>
</head>
<body>
<input id="new-todo" placeholder="New todo">
<div id="todos"></div>
<script src="/main.js"></script>
</body>
</html>
================================================
FILE: examples/vanilla-simple/webpack.config.js
================================================
const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
module.exports = {
entry: [ './src/main.js' ],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
plugins: [
new CopyWebpackPlugin([{
from: 'src/static',
to: '.'
}]),
// == PRERENDER SPA PLUGIN == //
new PrerenderSPAPlugin({
// Index.html is in the root directory.
staticDir: path.join(__dirname, 'dist'),
routes: [ '/', '/about', '/some/deep/nested/route' ],
// Optional minification.
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
renderer: new Renderer({
inject: {
foo: 'bar'
},
renderAfterDocumentEvent: 'render-event'
})
})
]
}
================================================
FILE: examples/vue2-webpack-router/.babelrc
================================================
{
"presets": ["@babel/preset-env"]
}
================================================
FILE: examples/vue2-webpack-router/.gitignore
================================================
node_modules/
dist/
================================================
FILE: examples/vue2-webpack-router/README.md
================================================
# Vue.js 2.0 + vue-router Prerender SPA Example
Demonstrates usage of Vuejs 2.0 with Vue Router and Webpack 3. Build will generate 3 static routes at the following paths:
- `/`
- `/about`
- `/contact`
## Build
```bash
npm install
npm run build
```
Now check the new `dist` directory for your prerendered static files!
To view the rendered files, install [http-server](https://www.npmjs.com/package/http-server) (`npm install -g http-server`) if you haven't already and run it in the dist directory.
Now visit the following routes in your browser (note the trailing slash):
- [http://localhost:8000/](http://localhost:8000/)
- [http://localhost:8000/about/](http://localhost:8000/about/)
- [http://localhost:8000/contact/](http://localhost:8000/contact/)
## Development
To edit the `prerender-spa-plugin` configuration, look for `new PrerenderSPAPlugin` in the plugins section of `webpack.config.js`.
If you're using a router or have more than one page to prerender, edit the `routes` array under that config object.
```bash
npm install
npm run dev
```
================================================
FILE: examples/vue2-webpack-router/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
================================================
FILE: examples/vue2-webpack-router/package.json
================================================
{
"name": "prerender-spa-plugin-example-vue2-webpack-router",
"version": "1.0.0",
"private": true,
"author": "Drew Lustro <drewlustro@gmail.com>",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"serve": "http-server dist"
},
"dependencies": {
"vue": "^2.6.7",
"vue-router": "^3.0.2"
},
"devDependencies": {
"@babel/core": "^7.3.3",
"@babel/preset-env": "^7.3.1",
"babel-loader": "^8.0.5",
"cross-env": "^5.2.0",
"css-loader": "^2.1.0",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"http-server": "^0.11.1",
"prerender-spa-plugin": "^3.4.0",
"vue-loader": "^15.6.4",
"vue-template-compiler": "^2.6.7",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.0"
}
}
================================================
FILE: examples/vue2-webpack-router/src/App.vue
================================================
<template>
<div id="app">
<img src="./assets/logo.png">
<h1>{{ msg }}</h1>
<p>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/contact">Contact</router-link>
</p>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
msg: 'Welcome to your prerender-spa-plugin Vuejs 2.0 demo app!'
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
================================================
FILE: examples/vue2-webpack-router/src/main.js
================================================
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
Vue.use(VueRouter)
const Home = { template: '<div><h2>Home Page</h2></div>' }
const About = { template: '<div><h2>About Page</h2></div>' }
const Contact = { template: '<div><h2>Contact Page</h2></div>' }
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/contact', component: Contact }
]
const router = new VueRouter({
routes,
mode: 'history'
})
new Vue({
el: '#app',
router,
render: h => h(App),
mounted () {
// You'll need this for renderAfterDocumentEvent.
document.dispatchEvent(new Event('render-event'))
}
})
================================================
FILE: examples/vue2-webpack-router/webpack.config.js
================================================
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
mode: process.env.NODE_ENV,
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: false,
},
devtool: '#eval-source-map',
plugins: [
new VueLoaderPlugin(),
]
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new HtmlWebpackPlugin({
title: 'PRODUCTION prerender-spa-plugin',
template: 'index.html',
filename: path.resolve(__dirname, 'dist/index.html'),
favicon: 'favicon.ico'
}),
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: [ '/', '/about', '/contact' ],
renderer: new Renderer({
inject: {
foo: 'bar'
},
headless: true,
renderAfterDocumentEvent: 'render-event'
})
})
])
} else {
// NODE_ENV === 'development'
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"'
}
}),
new HtmlWebpackPlugin({
title: 'DEVELOPMENT prerender-spa-plugin',
template: 'index.html',
filename: 'index.html',
favicon: 'favicon.ico'
}),
])
}
================================================
FILE: examples/vue2-webpack-simple/.babelrc
================================================
{
"presets": ["@babel/preset-env"]
}
================================================
FILE: examples/vue2-webpack-simple/.editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: examples/vue2-webpack-simple/.gitignore
================================================
.DS_Store
node_modules/
dist/
npm-debug.log
yarn-error.log
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
================================================
FILE: examples/vue2-webpack-simple/README.md
================================================
# Vue.js 2.0 Webpack Simple Template - Prerender SPA Example
Demonstrates usage of Vuejs 2.0 with Webpack 3. Build will populate `dist/index.html` with the prerendered page.
## Build
```bash
npm install
npm run build
```
Now check the new `dist` directory for your prerendered static files!
To view the rendered files, install [http-server](https://www.npmjs.com/package/http-server) (`npm install -g http-server`) if you haven't already and run it in the dist directory.
Now visit the following route in your browser (note the trailing slash):
- [http://localhost:8000/](http://localhost:8000/)
## Development
To edit the `prerender-spa-plugin` configuration, look for `new PrerenderSPAPlugin` in the plugins section of `webpack.config.js`.
If you're using a router or have more than one page to prerender, edit the `routes` array under that config object.
```bash
npm install
npm run dev
```
================================================
FILE: examples/vue2-webpack-simple/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
================================================
FILE: examples/vue2-webpack-simple/package.json
================================================
{
"name": "prerender-spa-plugin-example-vue2-webpack-simple",
"version": "1.0.0",
"author": "Joshua Bemenderfer <tribex10@gmail.com>",
"private": true,
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
"serve": "http-server ./dist"
},
"dependencies": {
"vue": "^2.6.7"
},
"devDependencies": {
"@babel/core": "^7.3.3",
"@babel/preset-env": "^7.3.1",
"babel-loader": "^8.0.5",
"cross-env": "^5.2.0",
"css-loader": "^2.1.0",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"http-server": "^0.11.1",
"prerender-spa-plugin": "^3.4.0",
"vue-loader": "^15.6.4",
"vue-template-compiler": "^2.6.7",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.0"
}
}
================================================
FILE: examples/vue2-webpack-simple/src/App.vue
================================================
<template>
<div id="app">
<img src="./assets/logo.png">
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
<ul>
<li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
</ul>
<h2>Ecosystem</h2>
<ul>
<li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
<li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
<li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
================================================
FILE: examples/vue2-webpack-simple/src/main.js
================================================
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App),
mounted () {
// You'll need this for renderAfterDocumentEvent.
document.dispatchEvent(new Event('render-event'))
}
})
================================================
FILE: examples/vue2-webpack-simple/webpack.config.js
================================================
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
mode: process.env.NODE_ENV,
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: false,
},
devtool: '#eval-source-map',
plugins: [
new VueLoaderPlugin(),
]
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new HtmlWebpackPlugin({
title: 'PRODUCTION prerender-spa-plugin',
template: 'index.html',
filename: path.resolve(__dirname, 'dist/index.html')
}),
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: [ '/', '/about', '/contact' ],
renderer: new Renderer({
inject: {
foo: 'bar'
},
headless: true,
renderAfterDocumentEvent: 'render-event'
})
})
])
} else {
// NODE_ENV === 'development'
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"'
}
}),
new HtmlWebpackPlugin({
title: 'DEVELOPMENT prerender-spa-plugin',
template: 'index.html',
filename: 'index.html'
}),
])
}
================================================
FILE: index.js
================================================
/**
* @file Just a super-simple wrapper for determining whether to load the original ES6 version
* of the code or the ES5 version at runtime.
* @author Joshua Bemenderfer <tribex10@gmail.com>
*/
// Is there a better way to check versions? Haven't really looked into it.
if (parseInt(process.versions.node.split('.')[0]) >= 8) {
// Native (Node 8+) ES6. (Requires async / await.)
module.exports = require('./es6/index.js')
} else {
// Transpiled through babel to target Node 4+.
module.exports = require('./es5-autogenerated/index.js')
}
================================================
FILE: package.json
================================================
{
"name": "prerender-spa-plugin",
"version": "3.4.0",
"description": "Flexible, framework-agnostic static site generation for sites and SPAs built with webpack.",
"license": "MIT",
"main": "index.js",
"scripts": {
"build": "babel es6 --out-dir es5-autogenerated",
"prepublishOnly": "npm run build"
},
"files": [
"es6/*",
"es5-autogenerated/*"
],
"keywords": [
"prerender",
"spa",
"ssr",
"serverside-rendering"
],
"author": {
"name": "Chris Fritz",
"email": "chrisvfritz@gmail.com",
"url": "https://twitter.com/chrisvfritz"
},
"contributors": [
{
"name": "Chris Fritz",
"email": "chrisvfritz@gmail.com",
"url": "https://twitter.com/chrisvfritz"
},
{
"name": "Drew Lustro",
"url": "https://drewlustro.com/"
},
{
"name": "Joshua Bemenderfer",
"email": "tribex10@gmail.com",
"url": "https://joshderf.com/"
},
{
"name": "qkdreyer",
"email": "quentin.dreyer@gmail.com"
}
],
"repository": "chrisvfritz/prerender-spa-plugin",
"bugs": {
"url": "https://github.com/chrisvfritz/prerender-spa-plugin/issues",
"email": "chrisvfritz@gmail.com"
},
"dependencies": {
"@prerenderer/prerenderer": "^0.7.2",
"@prerenderer/renderer-puppeteer": "^0.2.0",
"html-minifier": "^3.5.16"
},
"devDependencies": {
"ava": "^3.9.0",
"babel-cli": "^6.24.1",
"babel-eslint": "^8.2.3",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-2": "^6.24.1",
"eslint": "^4.19.1",
"eslint-config-standard": "^5.3.5",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^1.3.2",
"eslint-plugin-standard": "^1.3.3"
},
"engines": {
"node": ">=4.0.0"
}
}
================================================
FILE: test/assets/single-route/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>Single Route Rendering Test</title>
</head>
<body>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.body.innerHTML = 'Render Successful!'
})
</script>
</body>
</html>
================================================
FILE: test/index.js
================================================
================================================
FILE: test/single-route.js
================================================
module.exports = {
name: 'Render Single Route',
description: '',
webpackConfig: {
},
isValid (t) {
}
}
gitextract_ep0hv056/
├── .babelrc
├── .eslintrc.js
├── .gitignore
├── LICENSE.md
├── README.md
├── assets/
│ └── logo.gvdesign
├── es5-autogenerated/
│ └── index.js
├── es6/
│ └── index.js
├── examples/
│ ├── angular-cli-eject/
│ │ ├── .angular-cli.json
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── e2e/
│ │ │ ├── app.e2e-spec.ts
│ │ │ ├── app.po.ts
│ │ │ └── tsconfig.e2e.json
│ │ ├── karma.conf.js
│ │ ├── package.json
│ │ ├── protractor.conf.js
│ │ ├── src/
│ │ │ ├── app/
│ │ │ │ ├── app.component.css
│ │ │ │ ├── app.component.html
│ │ │ │ ├── app.component.spec.ts
│ │ │ │ ├── app.component.ts
│ │ │ │ └── app.module.ts
│ │ │ ├── assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── environments/
│ │ │ │ ├── environment.prod.ts
│ │ │ │ └── environment.ts
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ ├── polyfills.ts
│ │ │ ├── styles.css
│ │ │ ├── test.ts
│ │ │ ├── tsconfig.app.json
│ │ │ ├── tsconfig.spec.json
│ │ │ └── typings.d.ts
│ │ ├── tsconfig.json
│ │ ├── tslint.json
│ │ └── webpack.config.js
│ ├── create-react-app/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── config-overrides.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ ├── index.html
│ │ │ └── manifest.json
│ │ └── src/
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── serviceWorker.js
│ ├── vanilla-simple/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── main.js
│ │ │ └── static/
│ │ │ └── index.html
│ │ └── webpack.config.js
│ ├── vue2-webpack-router/
│ │ ├── .babelrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.vue
│ │ │ └── main.js
│ │ └── webpack.config.js
│ └── vue2-webpack-simple/
│ ├── .babelrc
│ ├── .editorconfig
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── src/
│ │ ├── App.vue
│ │ └── main.js
│ └── webpack.config.js
├── index.js
├── package.json
└── test/
├── assets/
│ └── single-route/
│ └── index.html
├── index.js
└── single-route.js
SYMBOL INDEX (19 symbols across 13 files)
FILE: es5-autogenerated/index.js
function PrerenderSPAPlugin (line 10) | function PrerenderSPAPlugin() {
FILE: es6/index.js
function PrerenderSPAPlugin (line 6) | function PrerenderSPAPlugin (...args) {
FILE: examples/angular-cli-eject/e2e/app.po.ts
class AppPage (line 3) | class AppPage {
method navigateTo (line 4) | navigateTo() {
method getParagraphText (line 8) | getParagraphText() {
FILE: examples/angular-cli-eject/protractor.conf.js
method onPrepare (line 22) | onPrepare() {
FILE: examples/angular-cli-eject/src/app/app.component.ts
class AppComponent (line 8) | class AppComponent {
FILE: examples/angular-cli-eject/src/app/app.module.ts
class AppModule (line 18) | class AppModule { }
FILE: examples/angular-cli-eject/src/typings.d.ts
type NodeModule (line 3) | interface NodeModule {
FILE: examples/create-react-app/src/App.js
class App (line 5) | class App extends Component {
method render (line 6) | render() {
FILE: examples/create-react-app/src/serviceWorker.js
function register (line 23) | function register(config) {
function registerValidSW (line 57) | function registerValidSW(swUrl, config) {
function checkValidServiceWorker (line 101) | function checkValidServiceWorker(swUrl, config) {
function unregister (line 129) | function unregister() {
FILE: examples/vanilla-simple/src/main.js
function render (line 22) | function render () {
FILE: examples/vue2-webpack-router/src/main.js
method mounted (line 26) | mounted () {
FILE: examples/vue2-webpack-simple/src/main.js
method mounted (line 7) | mounted () {
FILE: test/single-route.js
method isValid (line 9) | isValid (t) {
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (121K chars).
[
{
"path": ".babelrc",
"chars": 272,
"preview": "{\n \"presets\": [\n [\"env\", {\n \"targets\": {\n \"node\": 4,\n }\n }],\n \"stage-2\"\n ],\n \"plugins\": [\n "
},
{
"path": ".eslintrc.js",
"chars": 282,
"preview": "module.exports = {\n root: true,\n parser: 'babel-eslint',\n // https://github.com/feross/standard/blob/master/RULES.md#"
},
{
"path": ".gitignore",
"chars": 2094,
"preview": "# Created by https://www.gitignore.io/api/node,macos,linux,windows,visualstudiocode\n\n### Linux ###\n*~\n\n# temporary files"
},
{
"path": "LICENSE.md",
"chars": 1071,
"preview": "## MIT License\n\nCopyright (c) 2017 Chris Fritz\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 29582,
"preview": "<h1 align=\"center\">[DEPRECATED]</h1>\n\n<h2 align=\"center\">Prerender SPA Plugin</h2>\n<p align=\"center\">\n <em>Flexible, fr"
},
{
"path": "es5-autogenerated/index.js",
"chars": 6600,
"preview": "'use strict';\n\nvar path = require('path');\nvar Prerenderer = require('@prerenderer/prerenderer');\nvar PuppeteerRenderer "
},
{
"path": "es6/index.js",
"chars": 6278,
"preview": "const path = require('path')\nconst Prerenderer = require('@prerenderer/prerenderer')\nconst PuppeteerRenderer = require('"
},
{
"path": "examples/angular-cli-eject/.angular-cli.json",
"chars": 1268,
"preview": "{\n \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n \"project\": {\n \"name\": \"angular\",\n \"ejected\""
},
{
"path": "examples/angular-cli-eject/.editorconfig",
"chars": 245,
"preview": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = "
},
{
"path": "examples/angular-cli-eject/.gitignore",
"chars": 529,
"preview": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/ou"
},
{
"path": "examples/angular-cli-eject/README.md",
"chars": 935,
"preview": "# Ejected Angular CLI App - Prerender SPA Example\n\nThis project was generated with [Angular CLI](https://github.com/angu"
},
{
"path": "examples/angular-cli-eject/e2e/app.e2e-spec.ts",
"chars": 289,
"preview": "import { AppPage } from './app.po';\n\ndescribe('angular App', () => {\n let page: AppPage;\n\n beforeEach(() => {\n page"
},
{
"path": "examples/angular-cli-eject/e2e/app.po.ts",
"chars": 208,
"preview": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n navigateTo() {\n return browser.get('/');"
},
{
"path": "examples/angular-cli-eject/e2e/tsconfig.e2e.json",
"chars": 235,
"preview": "{\n \"extends\": \"../tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../out-tsc/e2e\",\n \"baseUrl\": \"./\",\n \"modu"
},
{
"path": "examples/angular-cli-eject/karma.conf.js",
"chars": 923,
"preview": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-fi"
},
{
"path": "examples/angular-cli-eject/package.json",
"chars": 2161,
"preview": "{\n \"name\": \"prerender-spa-plugin-example-angular-cli-eject\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"scripts\": {\n "
},
{
"path": "examples/angular-cli-eject/protractor.conf.js",
"chars": 722,
"preview": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib"
},
{
"path": "examples/angular-cli-eject/src/app/app.component.css",
"chars": 0,
"preview": ""
},
{
"path": "examples/angular-cli-eject/src/app/app.component.html",
"chars": 1141,
"preview": "<!--The content below is only a placeholder and can be replaced.-->\n<div style=\"text-align:center\">\n <h1>\n Welcome t"
},
{
"path": "examples/angular-cli-eject/src/app/app.component.spec.ts",
"chars": 986,
"preview": "import { TestBed, async } from '@angular/core/testing';\nimport { AppComponent } from './app.component';\ndescribe('AppCom"
},
{
"path": "examples/angular-cli-eject/src/app/app.component.ts",
"chars": 207,
"preview": "import { Component } from '@angular/core';\n\n@Component({\n selector: 'app-root',\n templateUrl: './app.component.html',\n"
},
{
"path": "examples/angular-cli-eject/src/app/app.module.ts",
"chars": 316,
"preview": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\n\n\nimport { AppCompo"
},
{
"path": "examples/angular-cli-eject/src/assets/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "examples/angular-cli-eject/src/environments/environment.prod.ts",
"chars": 51,
"preview": "export const environment = {\n production: true\n};\n"
},
{
"path": "examples/angular-cli-eject/src/environments/environment.ts",
"chars": 387,
"preview": "// The file contents for the current environment will overwrite these during build.\n// The build system defaults to the "
},
{
"path": "examples/angular-cli-eject/src/index.html",
"chars": 294,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>Angular</title>\n <base href=\"/\">\n\n <meta nam"
},
{
"path": "examples/angular-cli-eject/src/main.ts",
"chars": 370,
"preview": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynami"
},
{
"path": "examples/angular-cli-eject/src/polyfills.ts",
"chars": 3114,
"preview": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfi"
},
{
"path": "examples/angular-cli-eject/src/styles.css",
"chars": 80,
"preview": "/* You can add global styles to this file, and also import other style files */\n"
},
{
"path": "examples/angular-cli-eject/src/test.ts",
"chars": 642,
"preview": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/"
},
{
"path": "examples/angular-cli-eject/src/tsconfig.app.json",
"chars": 211,
"preview": "{\n \"extends\": \"../tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../out-tsc/app\",\n \"baseUrl\": \"./\",\n \"modu"
},
{
"path": "examples/angular-cli-eject/src/tsconfig.spec.json",
"chars": 283,
"preview": "{\n \"extends\": \"../tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../out-tsc/spec\",\n \"baseUrl\": \"./\",\n \"mod"
},
{
"path": "examples/angular-cli-eject/src/typings.d.ts",
"chars": 104,
"preview": "/* SystemJS module definition */\ndeclare var module: NodeModule;\ninterface NodeModule {\n id: string;\n}\n"
},
{
"path": "examples/angular-cli-eject/tsconfig.json",
"chars": 363,
"preview": "{\n \"compileOnSave\": false,\n \"compilerOptions\": {\n \"outDir\": \"./dist/out-tsc\",\n \"sourceMap\": true,\n \"declarati"
},
{
"path": "examples/angular-cli-eject/tslint.json",
"chars": 3012,
"preview": "{\n \"rulesDirectory\": [\n \"node_modules/codelyzer\"\n ],\n \"rules\": {\n \"arrow-return-shorthand\": true,\n \"callable"
},
{
"path": "examples/angular-cli-eject/webpack.config.js",
"chars": 15007,
"preview": "const fs = require('fs');\nconst path = require('path');\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\nconst "
},
{
"path": "examples/create-react-app/.gitignore",
"chars": 310,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "examples/create-react-app/README.md",
"chars": 1046,
"preview": "# Create-React-App - Prerender SPA Example\n\nThis project was bootstrapped with [Create React App](https://github.com/fac"
},
{
"path": "examples/create-react-app/config-overrides.js",
"chars": 349,
"preview": "const PrerenderSPAPlugin = require('prerender-spa-plugin');\nconst path = require('path');\n\nmodule.exports = (config, env"
},
{
"path": "examples/create-react-app/package.json",
"chars": 628,
"preview": "{\n \"name\": \"prerender-spa-plugin-example-create-react-app\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"dependencies\": "
},
{
"path": "examples/create-react-app/public/index.html",
"chars": 1586,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"shortcut icon\" href=\"%PUBLIC_URL%/"
},
{
"path": "examples/create-react-app/public/manifest.json",
"chars": 306,
"preview": "{\n \"short_name\": \"React App\",\n \"name\": \"Create React App Sample\",\n \"icons\": [\n {\n \"src\": \"favicon.ico\",\n "
},
{
"path": "examples/create-react-app/src/App.css",
"chars": 468,
"preview": ".App {\n text-align: center;\n}\n\n.App-logo {\n animation: App-logo-spin infinite 20s linear;\n height: 40vmin;\n}\n\n.App-he"
},
{
"path": "examples/create-react-app/src/App.js",
"chars": 636,
"preview": "import React, { Component } from 'react';\nimport logo from './logo.svg';\nimport './App.css';\n\nclass App extends Componen"
},
{
"path": "examples/create-react-app/src/App.test.js",
"chars": 248,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nit('renders without crashing', ()"
},
{
"path": "examples/create-react-app/src/index.css",
"chars": 380,
"preview": "body {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n "
},
{
"path": "examples/create-react-app/src/index.js",
"chars": 451,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport * as "
},
{
"path": "examples/create-react-app/src/serviceWorker.js",
"chars": 4948,
"preview": "// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the ap"
},
{
"path": "examples/vanilla-simple/.gitignore",
"chars": 20,
"preview": "node_modules/\ndist/\n"
},
{
"path": "examples/vanilla-simple/README.md",
"chars": 1144,
"preview": "# Vanilla JS - Prerender SPA Example\n\nDemonstrates usage of `prerender-spa-plugin` with Vanilla JS and Webpack 3. Build "
},
{
"path": "examples/vanilla-simple/package.json",
"chars": 298,
"preview": "{\n \"name\": \"prerender-spa-plugin-example-vanilla-simple\",\n \"version\": \"3.0.0\",\n \"private\": true,\n \"scripts\": {\n \""
},
{
"path": "examples/vanilla-simple/src/main.js",
"chars": 1133,
"preview": "// -----\n// STATE\n// -----\n\nvar todos = [\n 'Do the dishes',\n 'Make the bed',\n 'Take out the trash'\n]\n\n// --------\n// "
},
{
"path": "examples/vanilla-simple/src/static/index.html",
"chars": 240,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>Generic & Simple Prerendering Demo</title>\n </head>\n <body>\n <input "
},
{
"path": "examples/vanilla-simple/webpack.config.js",
"chars": 1018,
"preview": "const path = require('path')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst PrerenderSPAPlugin = require"
},
{
"path": "examples/vue2-webpack-router/.babelrc",
"chars": 38,
"preview": "{\n \"presets\": [\"@babel/preset-env\"]\n}"
},
{
"path": "examples/vue2-webpack-router/.gitignore",
"chars": 20,
"preview": "node_modules/\ndist/\n"
},
{
"path": "examples/vue2-webpack-router/README.md",
"chars": 1064,
"preview": "# Vue.js 2.0 + vue-router Prerender SPA Example\n\nDemonstrates usage of Vuejs 2.0 with Vue Router and Webpack 3. Build wi"
},
{
"path": "examples/vue2-webpack-router/index.html",
"chars": 175,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title><%= htmlWebpackPlugin.options.title %></title>"
},
{
"path": "examples/vue2-webpack-router/package.json",
"chars": 907,
"preview": "{\n \"name\": \"prerender-spa-plugin-example-vue2-webpack-router\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"author\": \"Dr"
},
{
"path": "examples/vue2-webpack-router/src/App.vue",
"chars": 842,
"preview": "<template>\n <div id=\"app\">\n <img src=\"./assets/logo.png\">\n <h1>{{ msg }}</h1>\n <p>\n <router-link to=\"/\">H"
},
{
"path": "examples/vue2-webpack-router/src/main.js",
"chars": 679,
"preview": "import Vue from 'vue'\nimport VueRouter from 'vue-router'\nimport App from './App.vue'\n\nVue.use(VueRouter)\n\nconst Home = {"
},
{
"path": "examples/vue2-webpack-router/webpack.config.js",
"chars": 2318,
"preview": "var path = require('path')\nvar webpack = require('webpack')\nvar HtmlWebpackPlugin = require('html-webpack-plugin')\nconst"
},
{
"path": "examples/vue2-webpack-simple/.babelrc",
"chars": 38,
"preview": "{\n \"presets\": [\"@babel/preset-env\"]\n}"
},
{
"path": "examples/vue2-webpack-simple/.editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": "examples/vue2-webpack-simple/.gitignore",
"chars": 127,
"preview": ".DS_Store\nnode_modules/\ndist/\nnpm-debug.log\nyarn-error.log\n\n# Editor directories and files\n.idea\n*.suo\n*.ntvs*\n*.njsproj"
},
{
"path": "examples/vue2-webpack-simple/README.md",
"chars": 905,
"preview": "# Vue.js 2.0 Webpack Simple Template - Prerender SPA Example\n\nDemonstrates usage of Vuejs 2.0 with Webpack 3. Build will"
},
{
"path": "examples/vue2-webpack-simple/index.html",
"chars": 174,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title><%= htmlWebpackPlugin.options.title %></title>"
},
{
"path": "examples/vue2-webpack-simple/package.json",
"chars": 886,
"preview": "{\n \"name\": \"prerender-spa-plugin-example-vue2-webpack-simple\",\n \"version\": \"1.0.0\",\n \"author\": \"Joshua Bemenderfer <t"
},
{
"path": "examples/vue2-webpack-simple/src/App.vue",
"chars": 1345,
"preview": "<template>\n <div id=\"app\">\n <img src=\"./assets/logo.png\">\n <h1>{{ msg }}</h1>\n <h2>Essential Links</h2>\n <u"
},
{
"path": "examples/vue2-webpack-simple/src/main.js",
"chars": 228,
"preview": "import Vue from 'vue'\nimport App from './App.vue'\n\nnew Vue({\n el: '#app',\n render: h => h(App),\n mounted () {\n // "
},
{
"path": "examples/vue2-webpack-simple/webpack.config.js",
"chars": 2257,
"preview": "var path = require('path')\nvar webpack = require('webpack')\nvar HtmlWebpackPlugin = require('html-webpack-plugin')\nconst"
},
{
"path": "index.js",
"chars": 550,
"preview": "/**\n * @file Just a super-simple wrapper for determining whether to load the original ES6 version\n * of the code or the "
},
{
"path": "package.json",
"chars": 1850,
"preview": "{\n \"name\": \"prerender-spa-plugin\",\n \"version\": \"3.4.0\",\n \"description\": \"Flexible, framework-agnostic static site gen"
},
{
"path": "test/assets/single-route/index.html",
"chars": 247,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>Single Route Rendering Test</title>\n</head>\n<body>\n <script>\n document.addEve"
},
{
"path": "test/index.js",
"chars": 0,
"preview": ""
},
{
"path": "test/single-route.js",
"chars": 119,
"preview": "module.exports = {\n name: 'Render Single Route',\n description: '',\n\n webpackConfig: {\n\n },\n\n isValid (t) {\n\n }\n}\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the chrisvfritz/prerender-spa-plugin GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (107.8 KB), approximately 28.3k tokens, and a symbol index with 19 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.