├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── jest.config.js ├── package.json └── test ├── __snapshots__ └── index.spec.js.snap ├── error-cases ├── missing-source │ ├── expected-error.js │ ├── index.js │ └── webpack.config.js ├── non-function-export │ ├── expected-error.js │ ├── index.js │ └── webpack.config.js └── render-error │ ├── expected-error.js │ ├── index.js │ └── webpack.config.js ├── index.spec.js ├── success-cases ├── basic-promise │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── basic-sync │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── basic │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── compression │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── crawl-ignore │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── crawl-relative-iframes │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── crawl-relative │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── crawl-root-relative-iframes │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── crawl-root-relative │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── custom-file-names │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── default-entry-when-named │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── entry-via-file-name │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── entry-via-name │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── es-modules │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── ignore-inherited-locals │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── legacy-args │ ├── foo.js │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── multi-render-custom-file-names │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── multi-render │ ├── index.js │ ├── template.ejs │ └── webpack.config.js ├── no-options-provided │ ├── index.js │ └── webpack.config.js ├── require-ensure │ ├── foo.js │ ├── index.js │ ├── template.ejs │ └── webpack.config.js └── single-path │ ├── index.js │ ├── template.ejs │ └── webpack.config.js └── utils ├── dir-contents-to-object.js ├── directory-contains.js └── get-sub-dirs-sync.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | actual-output 3 | coverage 4 | *.log 5 | yarn.lock 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | after_script: 5 | - npm run coveralls 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mark Dalgleish 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/markdalgleish/static-site-generator-webpack-plugin/master.svg?style=flat-square)](http://travis-ci.org/markdalgleish/static-site-generator-webpack-plugin) [![Coverage Status](https://img.shields.io/coveralls/markdalgleish/static-site-generator-webpack-plugin/master.svg?style=flat-square)](https://coveralls.io/r/markdalgleish/static-site-generator-webpack-plugin) [![Dependency Status](https://img.shields.io/david/markdalgleish/static-site-generator-webpack-plugin.svg?style=flat-square)](https://david-dm.org/markdalgleish/static-site-generator-webpack-plugin) [![npm](https://img.shields.io/npm/v/static-site-generator-webpack-plugin.svg?style=flat-square)](https://npmjs.org/package/static-site-generator-webpack-plugin) 2 | 3 | # static site generator webpack plugin 4 | 5 | Minimal, unopinionated static site generator powered by webpack. 6 | 7 | Bring the world of server rendering to your static build process. Either provide an array of paths to be rendered, or *crawl your site automatically*, and a matching set of `index.html` files will be rendered in your output directory by executing your own custom, webpack-compiled render function. 8 | 9 | This plugin works particularly well with universal libraries like [React](https://github.com/facebook/react) and [React Router](https://github.com/rackt/react-router) since it allows you to pre-render your routes at build time, rather than requiring a Node server in production. 10 | 11 | ## Install 12 | 13 | ```bash 14 | $ npm install --save-dev static-site-generator-webpack-plugin 15 | ``` 16 | 17 | ## Usage 18 | 19 | Ensure you have webpack installed, e.g. `npm install -g webpack` 20 | 21 | ### webpack.config.js 22 | 23 | ```js 24 | const StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin'); 25 | 26 | module.exports = { 27 | 28 | entry: './index.js', 29 | 30 | output: { 31 | filename: 'index.js', 32 | path: 'dist', 33 | /* IMPORTANT! 34 | * You must compile to UMD or CommonJS 35 | * so it can be required in a Node context: */ 36 | libraryTarget: 'umd' 37 | }, 38 | 39 | plugins: [ 40 | new StaticSiteGeneratorPlugin({ 41 | paths: [ 42 | '/hello/', 43 | '/world/' 44 | ], 45 | locals: { 46 | // Properties here are merged into `locals` 47 | // passed to the exported render function 48 | greet: 'Hello' 49 | } 50 | }) 51 | ] 52 | 53 | }; 54 | ``` 55 | 56 | ### index.js 57 | 58 | Sync rendering: 59 | 60 | ```js 61 | module.exports = function render(locals) { 62 | return '' + locals.greet + ' from ' + locals.path + ''; 63 | }; 64 | ``` 65 | 66 | Async rendering via callbacks: 67 | 68 | ```js 69 | module.exports = function render(locals, callback) { 70 | callback(null, '' + locals.greet + ' from ' + locals.path + ''); 71 | }; 72 | ``` 73 | 74 | Async rendering via promises: 75 | 76 | ```js 77 | module.exports = function render(locals) { 78 | return Promise.resolve('' + locals.greet + ' from ' + locals.path + ''); 79 | }; 80 | ``` 81 | 82 | ## Multi rendering 83 | 84 | If you need to generate multiple files per render, or you need to alter the path, you can return an object instead of a string, where each key is the path, and the value is the file contents: 85 | 86 | ```js 87 | module.exports = function render() { 88 | return { 89 | '/': 'Home', 90 | '/hello': 'Hello', 91 | '/world': 'World' 92 | }; 93 | }; 94 | ``` 95 | 96 | Note that this will still be executed for each entry in your `paths` array in your plugin config. 97 | 98 | ## Default locals 99 | 100 | ```js 101 | // The path currently being rendered: 102 | locals.path; 103 | 104 | // An object containing all assets: 105 | locals.assets; 106 | 107 | // Advanced: Webpack's stats object: 108 | locals.webpackStats; 109 | ``` 110 | 111 | Any additional locals provided in your config are also available. 112 | 113 | ## Crawl mode 114 | 115 | Rather than manually providing a list of paths, you can use the `crawl` option to automatically crawl your site. This will follow all relative links and iframes, executing your render function for each: 116 | 117 | ```js 118 | module.exports = { 119 | 120 | ... 121 | 122 | plugins: [ 123 | new StaticSiteGeneratorPlugin({ 124 | crawl: true 125 | }) 126 | ] 127 | }; 128 | ``` 129 | 130 | Note that this can be used in conjunction with the `paths` option to allow multiple crawler entry points: 131 | 132 | ```js 133 | module.exports = { 134 | 135 | ... 136 | 137 | plugins: [ 138 | new StaticSiteGeneratorPlugin({ 139 | crawl: true, 140 | paths: [ 141 | '/', 142 | '/uncrawlable-page/' 143 | ] 144 | }) 145 | ] 146 | }; 147 | ``` 148 | 149 | ## Custom file names 150 | 151 | By providing paths that end in `.html`, you can generate custom file names other than the default `index.html`. Please note that this may break compatibility with your router, if you're using one. 152 | 153 | ```js 154 | module.exports = { 155 | 156 | ... 157 | 158 | plugins: [ 159 | new StaticSiteGeneratorPlugin({ 160 | paths: [ 161 | '/index.html', 162 | '/news.html', 163 | '/about.html' 164 | ] 165 | }) 166 | ] 167 | }; 168 | ``` 169 | 170 | ## Globals 171 | 172 | If required, you can provide an object that will exist in the global scope when executing your render function. This is particularly useful if certain libraries or tooling you're using assumes a browser environment. 173 | 174 | For example, when using Webpack's `require.ensure`, which assumes that `window` exists: 175 | 176 | ```js 177 | module.exports = { 178 | ..., 179 | plugins: [ 180 | new StaticSiteGeneratorPlugin({ 181 | globals: { 182 | window: {} 183 | } 184 | }) 185 | ] 186 | } 187 | ``` 188 | 189 | ## Asset support 190 | 191 | template.ejs 192 | ```ejs 193 | <% css.forEach(function(file){ %> 194 | 195 | <% }); %> 196 | 197 | <% js.forEach(function(file){ %> 198 | 199 | <% }); %> 200 | ``` 201 | 202 | index.js 203 | ```js 204 | if (typeof global.document !== 'undefined') { 205 | const rootEl = global.document.getElementById('outlay'); 206 | React.render( 207 | , 208 | rootEl, 209 | ); 210 | } 211 | 212 | export default (data) => { 213 | const assets = Object.keys(data.webpackStats.compilation.assets); 214 | const css = assets.filter(value => value.match(/\.css$/)); 215 | const js = assets.filter(value => value.match(/\.js$/)); 216 | return template({ css, js, ...data}); 217 | } 218 | ``` 219 | 220 | ## Specifying entry 221 | 222 | This plugin defaults to the first chunk found. While this should work in most cases, you can specify the entry name if needed: 223 | 224 | ```js 225 | module.exports = { 226 | ..., 227 | plugins: [ 228 | new StaticSiteGeneratorPlugin({ 229 | entry: 'main' 230 | }) 231 | ] 232 | } 233 | ``` 234 | 235 | ## Compression support 236 | 237 | Generated files can be compressed with [compression-webpack-plugin](https://github.com/webpack/compression-webpack-plugin), but first ensure that this plugin appears before compression-webpack-plugin in your plugins array: 238 | 239 | ```js 240 | const StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin'); 241 | const CompressionPlugin = require('compression-webpack-plugin'); 242 | 243 | module.exports = { 244 | ... 245 | 246 | plugins: [ 247 | new StaticSiteGeneratorPlugin(...), 248 | new CompressionPlugin(...) 249 | ] 250 | }; 251 | ``` 252 | 253 | ## Related projects 254 | 255 | - [react-router-to-array](https://github.com/alansouzati/react-router-to-array) - useful for avoiding hardcoded lists of routes to render 256 | - [gatsby](https://github.com/gatsbyjs/gatsby) - opinionated static site generator built on top of this plugin 257 | 258 | ## License 259 | 260 | [MIT License](http://markdalgleish.mit-license.org) 261 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var RawSource = require('webpack-sources/lib/RawSource'); 2 | var evaluate = require('eval'); 3 | var path = require('path'); 4 | var cheerio = require('cheerio'); 5 | var url = require('url'); 6 | var Promise = require('bluebird'); 7 | 8 | function StaticSiteGeneratorWebpackPlugin(options) { 9 | if (arguments.length > 1) { 10 | options = legacyArgsToOptions.apply(null, arguments); 11 | } 12 | 13 | options = options || {}; 14 | 15 | this.entry = options.entry; 16 | this.paths = Array.isArray(options.paths) ? options.paths : [options.paths || '/']; 17 | this.locals = options.locals; 18 | this.globals = options.globals; 19 | this.crawl = Boolean(options.crawl); 20 | } 21 | 22 | StaticSiteGeneratorWebpackPlugin.prototype.apply = function(compiler) { 23 | var self = this; 24 | 25 | addThisCompilationHandler(compiler, function(compilation) { 26 | addOptimizeAssetsHandler(compilation, function(_, done) { 27 | var renderPromises; 28 | 29 | var webpackStats = compilation.getStats(); 30 | var webpackStatsJson = webpackStats.toJson(); 31 | 32 | try { 33 | var asset = findAsset(self.entry, compilation, webpackStatsJson); 34 | 35 | if (asset == null) { 36 | throw new Error('Source file not found: "' + self.entry + '"'); 37 | } 38 | 39 | var assets = getAssetsFromCompilation(compilation, webpackStatsJson); 40 | 41 | var source = asset.source(); 42 | var render = evaluate(source, /* filename: */ self.entry, /* scope: */ self.globals, /* includeGlobals: */ true); 43 | 44 | if (render.hasOwnProperty('default')) { 45 | render = render['default']; 46 | } 47 | 48 | if (typeof render !== 'function') { 49 | throw new Error('Export from "' + self.entry + '" must be a function that returns an HTML string. Is output.libraryTarget in the configuration set to "umd"?'); 50 | } 51 | 52 | renderPaths(self.crawl, self.locals, self.paths, render, assets, webpackStats, compilation) 53 | .nodeify(done); 54 | } catch (err) { 55 | compilation.errors.push(err.stack); 56 | done(); 57 | } 58 | }); 59 | }); 60 | }; 61 | 62 | function renderPaths(crawl, userLocals, paths, render, assets, webpackStats, compilation) { 63 | var renderPromises = paths.map(function(outputPath) { 64 | var locals = { 65 | path: outputPath, 66 | assets: assets, 67 | webpackStats: webpackStats 68 | }; 69 | 70 | for (var prop in userLocals) { 71 | if (userLocals.hasOwnProperty(prop)) { 72 | locals[prop] = userLocals[prop]; 73 | } 74 | } 75 | 76 | var renderPromise = render.length < 2 ? 77 | Promise.resolve(render(locals)) : 78 | Promise.fromNode(render.bind(null, locals)); 79 | 80 | return renderPromise 81 | .then(function(output) { 82 | var outputByPath = typeof output === 'object' ? output : makeObject(outputPath, output); 83 | 84 | var assetGenerationPromises = Object.keys(outputByPath).map(function(key) { 85 | var rawSource = outputByPath[key]; 86 | var assetName = pathToAssetName(key); 87 | 88 | if (compilation.assets[assetName]) { 89 | return; 90 | } 91 | 92 | compilation.assets[assetName] = new RawSource(rawSource); 93 | 94 | if (crawl) { 95 | var relativePaths = relativePathsFromHtml({ 96 | source: rawSource, 97 | path: key 98 | }); 99 | 100 | return renderPaths(crawl, userLocals, relativePaths, render, assets, webpackStats, compilation); 101 | } 102 | }); 103 | 104 | return Promise.all(assetGenerationPromises); 105 | }) 106 | .catch(function(err) { 107 | compilation.errors.push(err.stack); 108 | }); 109 | }); 110 | 111 | return Promise.all(renderPromises); 112 | } 113 | 114 | var findAsset = function(src, compilation, webpackStatsJson) { 115 | if (!src) { 116 | var chunkNames = Object.keys(webpackStatsJson.assetsByChunkName); 117 | 118 | src = chunkNames[0]; 119 | } 120 | 121 | var asset = compilation.assets[src]; 122 | 123 | if (asset) { 124 | return asset; 125 | } 126 | 127 | var chunkValue = webpackStatsJson.assetsByChunkName[src]; 128 | 129 | if (!chunkValue) { 130 | return null; 131 | } 132 | // Webpack outputs an array for each chunk when using sourcemaps 133 | if (chunkValue instanceof Array) { 134 | // Is the main bundle always the first element? 135 | chunkValue = chunkValue.find(function(filename) { 136 | return /\.js$/.test(filename); 137 | }); 138 | } 139 | return compilation.assets[chunkValue]; 140 | }; 141 | 142 | // Shamelessly stolen from html-webpack-plugin - Thanks @ampedandwired :) 143 | var getAssetsFromCompilation = function(compilation, webpackStatsJson) { 144 | var assets = {}; 145 | for (var chunk in webpackStatsJson.assetsByChunkName) { 146 | var chunkValue = webpackStatsJson.assetsByChunkName[chunk]; 147 | 148 | // Webpack outputs an array for each chunk when using sourcemaps 149 | if (chunkValue instanceof Array) { 150 | // Is the main bundle always the first JS element? 151 | chunkValue = chunkValue.find(function(filename) { 152 | return /\.js$/.test(filename); 153 | }); 154 | } 155 | 156 | if (compilation.options.output.publicPath) { 157 | chunkValue = compilation.options.output.publicPath + chunkValue; 158 | } 159 | assets[chunk] = chunkValue; 160 | } 161 | 162 | return assets; 163 | }; 164 | 165 | function pathToAssetName(outputPath) { 166 | var outputFileName = outputPath.replace(/^(\/|\\)/, ''); // Remove leading slashes for webpack-dev-server 167 | 168 | if (!/\.(html?)$/i.test(outputFileName)) { 169 | outputFileName = path.join(outputFileName, 'index.html'); 170 | } 171 | 172 | return outputFileName; 173 | } 174 | 175 | function makeObject(key, value) { 176 | var obj = {}; 177 | obj[key] = value; 178 | return obj; 179 | } 180 | 181 | function relativePathsFromHtml(options) { 182 | var html = options.source; 183 | var currentPath = options.path; 184 | 185 | var $ = cheerio.load(html); 186 | 187 | var linkHrefs = $('a[href]') 188 | .map(function(i, el) { 189 | return $(el).attr('href'); 190 | }) 191 | .get(); 192 | 193 | var iframeSrcs = $('iframe[src]') 194 | .map(function(i, el) { 195 | return $(el).attr('src'); 196 | }) 197 | .get(); 198 | 199 | return [] 200 | .concat(linkHrefs) 201 | .concat(iframeSrcs) 202 | .map(function(href) { 203 | if (href.indexOf('//') === 0) { 204 | return null 205 | } 206 | 207 | var parsed = url.parse(href); 208 | 209 | if (parsed.protocol || typeof parsed.path !== 'string') { 210 | return null; 211 | } 212 | 213 | return parsed.path.indexOf('/') === 0 ? 214 | parsed.path : 215 | url.resolve(currentPath, parsed.path); 216 | }) 217 | .filter(function(href) { 218 | return href != null; 219 | }); 220 | } 221 | 222 | function legacyArgsToOptions(entry, paths, locals, globals) { 223 | return { 224 | entry: entry, 225 | paths: paths, 226 | locals: locals, 227 | globals: globals 228 | }; 229 | } 230 | 231 | function addThisCompilationHandler(compiler, callback) { 232 | if(compiler.hooks) { 233 | /* istanbul ignore next */ 234 | compiler.hooks.thisCompilation.tap('static-site-generator-webpack-plugin', callback); 235 | } else { 236 | compiler.plugin('this-compilation', callback); 237 | } 238 | } 239 | 240 | function addOptimizeAssetsHandler(compilation, callback) { 241 | if(compilation.hooks) { 242 | /* istanbul ignore next */ 243 | compilation.hooks.optimizeAssets.tapAsync('static-site-generator-webpack-plugin',callback); 244 | } else { 245 | compilation.plugin('optimize-assets', callback); 246 | } 247 | } 248 | 249 | module.exports = StaticSiteGeneratorWebpackPlugin; 250 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coveragePathIgnorePatterns: ["/node_modules/", "/test/"] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-site-generator-webpack-plugin", 3 | "version": "3.4.2", 4 | "description": "Minimal, unopinionated static site generator powered by webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest test", 8 | "coverage": "jest --coverage", 9 | "coveralls": "jest --coverage --coverageReporters=text-lcov | coveralls" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/markdalgleish/static-site-generator-webpack-plugin" 14 | }, 15 | "author": "Mark Dalgleish", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/markdalgleish/static-site-generator-webpack-plugin/issues" 19 | }, 20 | "homepage": "https://github.com/markdalgleish/static-site-generator-webpack-plugin", 21 | "dependencies": { 22 | "bluebird": "^3.0.5", 23 | "cheerio": "^0.22.0", 24 | "eval": "^0.1.0", 25 | "url": "^0.11.0", 26 | "webpack-sources": "^0.2.0" 27 | }, 28 | "devDependencies": { 29 | "async": "^2.0.1", 30 | "babel-core": "^6.2.1", 31 | "babel-loader": "^6.2.0", 32 | "babel-preset-es2015": "^6.1.18", 33 | "compression-webpack-plugin": "^0.3.1", 34 | "coveralls": "^3.0.2", 35 | "dir-compare": "^1.4.0", 36 | "ejs": "^2.3.4", 37 | "es6-promisify": "^6.0.0", 38 | "glob": "^7.0.3", 39 | "jest": "^23.6.0", 40 | "node-dir": "^0.1.17", 41 | "rimraf": "^2.4.4", 42 | "webpack": "^1.12.10", 43 | "webpack-stats-plugin": "^0.1.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Success cases basic should generate the expected files 1`] = ` 4 | Object { 5 | "foo/bar/index.html": " 6 | 7 |

/foo/bar

8 | 9 | 10 | ", 11 | "foo/index.html": " 12 | 13 |

/foo

14 | 15 | 16 | ", 17 | "index.html": " 18 | 19 |

/

20 | 21 | 22 | ", 23 | "index.js": "CONTENTS IGNORED IN SNAPSHOT TEST", 24 | "stats.json": "CONTENTS IGNORED IN SNAPSHOT TEST", 25 | } 26 | `; 27 | 28 | exports[`Success cases basic-promise should generate the expected files 1`] = ` 29 | Object { 30 | "foo/bar/index.html": " 31 | 32 |

/foo/bar

33 | 34 | 35 | ", 36 | "foo/index.html": " 37 | 38 |

/foo

39 | 40 | 41 | ", 42 | "index.html": " 43 | 44 |

/

45 | 46 | 47 | ", 48 | "index.js": "CONTENTS IGNORED IN SNAPSHOT TEST", 49 | "stats.json": "CONTENTS IGNORED IN SNAPSHOT TEST", 50 | } 51 | `; 52 | 53 | exports[`Success cases basic-sync should generate the expected files 1`] = ` 54 | Object { 55 | "foo/bar/index.html": " 56 | 57 |

/foo/bar

58 | 59 | 60 | ", 61 | "foo/index.html": " 62 | 63 |

/foo

64 | 65 | 66 | ", 67 | "index.html": " 68 | 69 |

/

70 | 71 | 72 | ", 73 | "index.js": "CONTENTS IGNORED IN SNAPSHOT TEST", 74 | "stats.json": "CONTENTS IGNORED IN SNAPSHOT TEST", 75 | } 76 | `; 77 | 78 | exports[`Success cases compression should generate the expected files 1`] = ` 79 | Object { 80 | "index.html": " 81 | 82 |

/

83 | 84 | 85 | ", 86 | "index.html.gz": "CONTENTS IGNORED IN SNAPSHOT TEST", 87 | "index.js": "CONTENTS IGNORED IN SNAPSHOT TEST", 88 | } 89 | `; 90 | 91 | exports[`Success cases crawl-ignore should generate the expected files 1`] = ` 92 | Object { 93 | "index.html": "

Don't crawl these:

94 | 100 | ", 101 | "index.js": "CONTENTS IGNORED IN SNAPSHOT TEST", 102 | } 103 | `; 104 | 105 | exports[`Success cases crawl-relative should generate the expected files 1`] = ` 106 | Object { 107 | "foo/bar/baz/index.html": "

/foo/bar/baz

108 | 109 | Crawl this 110 | ", 111 | "foo/bar/index.html": "

/foo/bar

112 | 113 | Crawl this 114 | ", 115 | "foo/index.html": "

/foo

116 | 117 | Crawl this 118 | ", 119 | "index.html": "

/

120 | 121 | Crawl this 122 | ", 123 | "index.js": "CONTENTS IGNORED IN SNAPSHOT TEST", 124 | } 125 | `; 126 | 127 | exports[`Success cases crawl-relative-iframes should generate the expected files 1`] = ` 128 | Object { 129 | "foo/bar/baz/index.html": "

/foo/bar/baz

130 | 131 |