html-snapshots 0.18.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +4 -5
- package/.github/workflows/verify.yml +2 -2
- package/HISTORY.md +42 -0
- package/README.md +109 -256
- package/examples/README.md +149 -0
- package/examples/custom/myFilter.js +2 -2
- package/examples/custom/package-lock.json +2021 -5
- package/examples/custom/package.json +1 -1
- package/examples/custom/snapshot.js +8 -10
- package/examples/debug-phantomjs/package-lock.json +6 -6
- package/examples/debug-phantomjs/package.json +1 -1
- package/examples/debug-phantomjs/snapshot.js +13 -14
- package/examples/debug-puppeteer/README.md +18 -0
- package/examples/debug-puppeteer/package-lock.json +2804 -0
- package/examples/debug-puppeteer/package.json +12 -0
- package/examples/debug-puppeteer/snapshot.js +30 -0
- package/examples/html5rocks/package-lock.json +1306 -5
- package/examples/html5rocks/package.json +1 -1
- package/examples/html5rocks/snapshot.js +14 -23
- package/examples/process-limit/package-lock.json +1306 -5
- package/examples/process-limit/package.json +1 -1
- package/examples/process-limit/snapshot.js +8 -10
- package/examples/simple-promise/package-lock.json +1306 -5
- package/examples/simple-promise/package.json +1 -1
- package/examples/simple-promise/snapshot.js +6 -8
- package/examples/sitemap-index/package-lock.json +1306 -5
- package/examples/sitemap-index/package.json +1 -1
- package/examples/sitemap-index/snapshot.js +6 -6
- package/examples/utils/index.js +2 -4
- package/examples/verbose/package-lock.json +1306 -5
- package/examples/verbose/package.json +1 -1
- package/examples/verbose/snapshot.js +12 -14
- package/lib/async/index.js +0 -1
- package/lib/common/index.js +1 -1
- package/lib/common/sitemap/index.js +16 -0
- package/lib/common/sitemap/sitemap-collection.js +94 -0
- package/lib/common/sitemap/sitemap-index.js +68 -0
- package/lib/common/{sitemap.js → sitemap/sitemap.js} +68 -57
- package/lib/html-snapshots.js +87 -5
- package/lib/input-generators/_base.js +7 -1
- package/lib/input-generators/array.js +0 -1
- package/lib/input-generators/robots.js +98 -58
- package/lib/input-generators/sitemap-index.js +3 -124
- package/lib/input-generators/sitemap.js +3 -7
- package/lib/input-generators/textfile.js +2 -6
- package/lib/puppeteer/index.js +98 -0
- package/lib/puppeteer/removeScripts.js +8 -0
- package/package.json +9 -4
- package/test/helpers/options.js +4 -2
- package/test/mocha/browsers/puppeteer.js +106 -0
- package/test/mocha/browsers/server/index.html +9 -0
- package/test/mocha/browsers/test.js +11 -0
- package/test/mocha/html-snapshots/basics.js +60 -47
- package/test/mocha/html-snapshots/phantomjs-options.js +54 -60
- package/test/mocha/html-snapshots/process-limit.js +126 -120
- package/test/mocha/html-snapshots/puppeteer.js +47 -0
- package/test/mocha/html-snapshots/robots.js +156 -121
- package/test/mocha/html-snapshots/server/public/page-sitemap.xml +16 -0
- package/test/mocha/html-snapshots/server/test_robots.txt +13 -0
- package/test/mocha/html-snapshots/server/test_robots_sitemap.txt +15 -0
- package/test/mocha/html-snapshots/server/test_robots_sitemap.xml +22 -0
- package/test/mocha/html-snapshots/sitemap-index.js +100 -90
- package/test/mocha/html-snapshots/sitemap.js +75 -31
- package/test/mocha/html-snapshots/snapshot-scripts.js +150 -145
- package/test/mocha/html-snapshots/test.js +23 -14
- package/test/mocha/html-snapshots/test_robots.txt +0 -2
- package/test/mocha/html-snapshots/use-jquery.js +65 -64
- package/test/mocha/html-snapshots/utils.js +29 -30
- package/test/mocha/input-generators/server/test_robots.txt +0 -2
- package/test/mocha/input-generators/server/test_robots_bad.txt +0 -2
- package/test/mocha/input-generators/server/test_robots_sitemap.txt +17 -0
- package/test/mocha/input-generators/server/test_robots_sitemap_bad.txt +17 -0
- package/test/mocha/input-generators/server/test_robots_sitemap_multi.txt +18 -0
- package/test/mocha/input-generators/server/test_sitemap_index_empty.xml +3 -0
- package/test/mocha/input-generators/server/test_sitemap_index_malformed.xml +2 -0
- package/test/mocha/input-generators/sitemap-index.js +65 -9
- package/test/mocha/input-generators/test.js +276 -280
- package/test/mocha/input-generators/test_robots.txt +0 -2
- package/test/mocha/input-generators/test_robots_bad.txt +0 -2
- package/test/mocha/input-generators/test_robots_sitemap.txt +17 -0
- package/test/mocha/input-generators/test_robots_sitemap_bad.txt +17 -0
- package/test/mocha/input-generators/test_robots_sitemap_multi.txt +18 -0
- package/lib/common/node.js +0 -26
- package/test/mocha/common/node.js +0 -100
package/.eslintrc.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"parserOptions": {
|
|
3
|
-
"ecmaVersion":
|
|
4
|
-
"
|
|
5
|
-
"impliedStrict": true
|
|
6
|
-
}
|
|
3
|
+
"ecmaVersion": 2020,
|
|
4
|
+
"sourceType": "module"
|
|
7
5
|
},
|
|
8
6
|
"env": {
|
|
9
|
-
"node": true
|
|
7
|
+
"node": true,
|
|
8
|
+
"es2020": true
|
|
10
9
|
},
|
|
11
10
|
"extends": [
|
|
12
11
|
"eslint:recommended"
|
|
@@ -18,12 +18,12 @@ jobs:
|
|
|
18
18
|
steps:
|
|
19
19
|
- uses: actions/checkout@v3
|
|
20
20
|
- name: Use Node.js ${{ matrix.node-version }}
|
|
21
|
-
uses: actions/setup-node@v3
|
|
21
|
+
uses: actions/setup-node@v3
|
|
22
22
|
with:
|
|
23
23
|
node-version: ${{ matrix.node-version }}
|
|
24
24
|
- run: npm ci
|
|
25
25
|
- name: Lint, Test, And Coverage
|
|
26
|
-
run: npm run lint &&
|
|
26
|
+
run: npm run lint && xvfb-run -a npm run test:cover
|
|
27
27
|
- name: Coverage Upload
|
|
28
28
|
if: ${{ success() }}
|
|
29
29
|
uses: coverallsapp/github-action@master
|
package/HISTORY.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# History
|
|
2
|
+
An overview of the [Breaking Changes](#breaking-changes) and [Node Support](#node-support-tags).
|
|
3
|
+
|
|
4
|
+
## Breaking Changes
|
|
5
|
+
|
|
6
|
+
### Introduced in v1.0.0
|
|
7
|
+
Puppeteer process introduced and used as the default browser, `browser` option added to switch back ("puppeteer" vs "phantomjs").
|
|
8
|
+
|
|
9
|
+
### Introduced in v0.19.x
|
|
10
|
+
#### Robots Input
|
|
11
|
+
Robots.txt files are now searched for `Sitemap` directive(s) **first** for sitemap/sitemapIndex files. If those directives are found, those directives are used to drive the crawl of the site alone. If no Sitemap directives are found, htmlSnapshots reverts back to using `Allow` directives. If no `Sitemap` directive is found, this is a non-breaking change.
|
|
12
|
+
|
|
13
|
+
### Introduced in v0.18.x
|
|
14
|
+
#### Dropped support for Node 10 & 12.
|
|
15
|
+
|
|
16
|
+
### Introduced in v0.17.x
|
|
17
|
+
#### Dropped support for Node 8.
|
|
18
|
+
|
|
19
|
+
### Introduced in v0.16.x
|
|
20
|
+
#### Dropped support for Node 6.
|
|
21
|
+
|
|
22
|
+
### Introduced in v0.15.x
|
|
23
|
+
#### Dropped support for Node 4.
|
|
24
|
+
|
|
25
|
+
### Introduced in v0.14.x
|
|
26
|
+
#### Run method return value
|
|
27
|
+
The library `run` method no longer returns a boolean value indicating a successful start. Instead, it returns a Promise that resolves to an array of file paths to completed snapshots, or error on failure. The `run` method's second argument, a completion callback, is now **optional** and provided for compatibility only. If you supply one, it will be called, but the Promise will also resolve, so it is not needed.
|
|
28
|
+
#### Dropped support for Node <= 0.12
|
|
29
|
+
|
|
30
|
+
### Introduced in v0.6.x
|
|
31
|
+
jQuery selectors are no longer supported by default. To restore the previous behavior, set the `useJQuery` option to `true`.
|
|
32
|
+
The upside is jQuery is no longer required to be loaded by the page being snapshotted. However, if you use jQuery selectors, or selectors not supported by [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/document.querySelector), the page being snapshotted must load jQuery.
|
|
33
|
+
|
|
34
|
+
## Node Support Tags
|
|
35
|
+
`v0.13.2 ` Node 0.12 (or less)
|
|
36
|
+
`v0.14.16` Node 4+
|
|
37
|
+
`v0.15.x ` Node 6+
|
|
38
|
+
`v0.16.x ` Node 8+
|
|
39
|
+
`v0.17.x ` Node 10+
|
|
40
|
+
`v0.18.x ` Node 14+
|
|
41
|
+
`v0.19.x ` Node 14+
|
|
42
|
+
`v1.0.x ` Node 14+
|
package/README.md
CHANGED
|
@@ -11,20 +11,8 @@
|
|
|
11
11
|
+ [Getting Started](#getting-started)
|
|
12
12
|
+ [Grunt Task](https://github.com/localnerve/grunt-html-snapshots)
|
|
13
13
|
+ [More Information](#more-information)
|
|
14
|
-
+ [Breaking Changes](#breaking-changes)
|
|
15
14
|
+ [API Reference](#api)
|
|
16
15
|
+ [Example Usage](#example-usage)
|
|
17
|
-
+ [Per-page Selectors](#example---per-page-selectors-and-timeouts)
|
|
18
|
-
+ [Per-page Output Paths](#example---per-page-special-output-paths)
|
|
19
|
-
+ [Per-page jQuery](#example---per-page-selectors-and-jquery)
|
|
20
|
-
+ [Array Input](#example---array)
|
|
21
|
-
+ [Array Input DRY](/examples/html5rocks)
|
|
22
|
-
+ [Sitemap Index](/examples/sitemap-index)
|
|
23
|
-
+ [Process Limit](/examples/process-limit)
|
|
24
|
-
+ [Script Removal](#example---remote-robotstxt-remove-script-tags-from-html-snapshots)
|
|
25
|
-
+ [Custom Filters](/examples/custom)
|
|
26
|
-
+ [Debug PhantomJS w/Verbose Output](/examples/verbose)
|
|
27
|
-
+ [Debug PhantomJS w/Attach](/examples/debug-phantomjs)
|
|
28
16
|
+ [Option Reference](#options)
|
|
29
17
|
+ [Input Options](#input-control-options)
|
|
30
18
|
+ [Output Options](#output-control-options)
|
|
@@ -35,9 +23,9 @@
|
|
|
35
23
|
+ [License](#license)
|
|
36
24
|
|
|
37
25
|
## Overview
|
|
38
|
-
html-snapshots is a flexible html snapshot library that uses
|
|
26
|
+
html-snapshots is a flexible html snapshot library that uses a headless browser to take html snapshots of your webpages served from your site. A snapshot is only taken when a specified selector is detected visible in the output html. This tool is useful when your site is largely ajax content, or an SPA, and you want your dynamic content indexed by search engines.
|
|
39
27
|
|
|
40
|
-
html-snapshots gets urls to process from either a robots.txt or sitemap.xml. Alternatively, you can supply an array with completely arbitrary urls, or a line delimited textfile with arbitrary host-relative paths.
|
|
28
|
+
html-snapshots gets urls to process from either a robots.txt, sitemap.xml, or sitemap-index.xml. Alternatively, you can supply an array with completely arbitrary urls, or a line delimited textfile with arbitrary host-relative paths.
|
|
41
29
|
|
|
42
30
|
## Getting Started
|
|
43
31
|
|
|
@@ -56,40 +44,10 @@ Here are some [background and other notes](/docs/notes.md) regarding this projec
|
|
|
56
44
|
+ [Why Does This Library Exist?](/docs/notes.md#why-does-this-exist)
|
|
57
45
|
+ [How To Use Without Knowing About Page Content](/docs/notes.md#what-if-i-dont-know-about-the-rendered-page-content)
|
|
58
46
|
+ [Caveats](/docs/notes.md#caveats)
|
|
47
|
+
+ [Support History](HISTORY.md)
|
|
59
48
|
|
|
60
49
|
### Process Model
|
|
61
|
-
html-snapshots takes snapshots in parallel, each page getting its own
|
|
62
|
-
|
|
63
|
-
### Node Support Tags
|
|
64
|
-
`v0.13.2 ` Node 0.12 (or less)
|
|
65
|
-
`v0.14.16` Node 4+
|
|
66
|
-
`v0.15.x ` Node 6+
|
|
67
|
-
`v0.16.x ` Node 8+
|
|
68
|
-
`v0.17.x ` Node 10+
|
|
69
|
-
`v0.18.x ` Node 14+
|
|
70
|
-
|
|
71
|
-
### Breaking Changes
|
|
72
|
-
|
|
73
|
-
#### Introduced in v0.18.x
|
|
74
|
-
##### Dropped support for Node 10 & 12.
|
|
75
|
-
|
|
76
|
-
#### Introduced in v0.17.x
|
|
77
|
-
##### Dropped support for Node 8.
|
|
78
|
-
|
|
79
|
-
#### Introduced in v0.16.x
|
|
80
|
-
##### Dropped support for Node 6.
|
|
81
|
-
|
|
82
|
-
#### Introduced in v0.15.x
|
|
83
|
-
##### Dropped support for Node 4.
|
|
84
|
-
|
|
85
|
-
#### Introduced in v0.14.x
|
|
86
|
-
##### Run method return value
|
|
87
|
-
The library `run` method no longer returns a boolean value indicating a successful start. Instead, it returns a Promise that resolves to an array of file paths to completed snapshots, or error on failure. The `run` method's second argument, a completion callback, is now **optional** and provided for compatibility only. If you supply one, it will be called, but the Promise will also resolve, so it is not needed.
|
|
88
|
-
##### Dropped support for Node <= 0.12
|
|
89
|
-
|
|
90
|
-
#### Introduced in v0.6.x
|
|
91
|
-
jQuery selectors are no longer supported by default. To restore the previous behavior, set the `useJQuery` option to `true`.
|
|
92
|
-
The upside is jQuery is no longer required to be loaded by the page being snapshotted. However, if you use jQuery selectors, or selectors not supported by [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/document.querySelector), the page being snapshotted must load jQuery.
|
|
50
|
+
html-snapshots takes snapshots in parallel, each page getting its own browser process. Each browser process dies after snapshotting one page. You can limit the number of browser processes that can ever run at once with the `processLimit` option. This effectively sets up a process pool for browser instances. The default processLimit is 4 browser instances. When a browser process dies, and another snapshot needs to be taken, a new browser process is spawned to take the vacant slot. This continues until a `processLimit` number of processes are running at once.
|
|
93
51
|
|
|
94
52
|
## API
|
|
95
53
|
The api is just one `run` method that returns a Promise.
|
|
@@ -98,13 +56,13 @@ The api is just one `run` method that returns a Promise.
|
|
|
98
56
|
A method that takes [options](#options) and an optional callback. Returns a Promise.
|
|
99
57
|
**Syntax:**
|
|
100
58
|
```javascript
|
|
101
|
-
|
|
59
|
+
const htmlSnapshots = require('html-snapshots');
|
|
102
60
|
|
|
103
|
-
htmlSnapshots.run
|
|
104
|
-
.then(
|
|
61
|
+
htmlSnapshots.run(options[, callback])
|
|
62
|
+
.then(completed => {
|
|
105
63
|
// `completed` is an array of paths to the completed snapshots.
|
|
106
64
|
})
|
|
107
|
-
.catch(
|
|
65
|
+
.catch(errorObject => {
|
|
108
66
|
// `errorObject` is an instance of Error
|
|
109
67
|
// `errorObject.completed` is an array of paths to the snapshots that did successfully complete.
|
|
110
68
|
// `errorObject.notCompleted` is an array of paths to files that DID NOT successfully complete.
|
|
@@ -120,173 +78,37 @@ callback (errorObject, arrayOfPathsToCompletedSnapshots)
|
|
|
120
78
|
*For the callback, in the error case, the errorObject does not have the new extra properties `completed` and `notCompleted`. However, `arrayOfPathsToCompletedSnapshots` is supplied, and contains the paths to the snapshots that successfully completed.*
|
|
121
79
|
|
|
122
80
|
## Example Usage
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
A growing showcase of runnable examples can be found [here](/examples).
|
|
126
|
-
|
|
127
|
-
An older (version 0.13.2), more in depth usage example is located in this [article](/docs/example-heroku-redis.md) that includes explanation and code of a real usage featuring dynamic app routes, ExpressJS, Heroku, and more.
|
|
128
|
-
|
|
129
|
-
### Simple example
|
|
130
|
-
```javascript
|
|
131
|
-
var htmlSnapshots = require('html-snapshots');
|
|
132
|
-
htmlSnapshots.run({
|
|
133
|
-
source: "/path/to/robots.txt",
|
|
134
|
-
hostname: "exampledomain.com",
|
|
135
|
-
outputDir: "./snapshots",
|
|
136
|
-
outputDirClean: true,
|
|
137
|
-
selector: "#dynamic-content"
|
|
138
|
-
})
|
|
139
|
-
.then(function (completed) {
|
|
140
|
-
// completed is an array of full file paths to the completed snapshots.
|
|
141
|
-
})
|
|
142
|
-
.catch(function (error) {
|
|
143
|
-
// error is an Error instance.
|
|
144
|
-
// error.completed is an array of snapshot file paths that were completed.
|
|
145
|
-
// error.notCompleted is an array of file paths that did NOT complete.
|
|
146
|
-
});
|
|
147
|
-
```
|
|
148
|
-
This reads the urls from your robots.txt and produces snapshots in the ./snapshots directory. In this example, a selector named "#dynamic-content" appears in all pages across the site. Once this selector is visible in a page, the html snapshot is taken.
|
|
149
|
-
|
|
150
|
-
### Example - Per page selectors and timeouts
|
|
151
|
-
```javascript
|
|
152
|
-
var htmlSnapshots = require('html-snapshots');
|
|
153
|
-
htmlSnapshots.run({
|
|
154
|
-
input: "sitemap",
|
|
155
|
-
source: "/path/to/sitemap.xml",
|
|
156
|
-
outputDir: "./snapshots",
|
|
157
|
-
outputDirClean: true,
|
|
158
|
-
selector: {
|
|
159
|
-
"http://mysite.com": "#home-content",
|
|
160
|
-
"__default": "#dynamic-content"
|
|
161
|
-
},
|
|
162
|
-
timeout: {
|
|
163
|
-
"http://mysite.com/superslowpage": 20000,
|
|
164
|
-
"__default": 10000
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
.then(function (completed) {
|
|
168
|
-
// completed is an array of full file paths to the completed snapshots.
|
|
169
|
-
})
|
|
170
|
-
.catch(function (error) {
|
|
171
|
-
// error is an Error instance.
|
|
172
|
-
// error.completed is an array of snapshot file paths that were completed.
|
|
173
|
-
// error.notCompleted is an array of file paths that did NOT complete.
|
|
174
|
-
});
|
|
175
|
-
```
|
|
176
|
-
This reads the urls from your sitemap.xml and produces snapshots in the ./snapshots directory. In this example, a selector named "#dynamic-content" appears in all pages across the site except the home page, where "#home-content" appears \(the appearance of a selector in the output triggers the snapshot\). Finally, a default timeout of 10000 ms is set on all pages except http://mysite.com/superslowpage, where it waits 20000 ms.
|
|
177
|
-
|
|
178
|
-
### Example - Per page special output paths
|
|
179
|
-
```javascript
|
|
180
|
-
var htmlSnapshots = require('html-snapshots');
|
|
181
|
-
htmlSnapshots.run({
|
|
182
|
-
input: "sitemap",
|
|
183
|
-
source: "/path/to/sitemap.xml",
|
|
184
|
-
outputDir: "./snapshots",
|
|
185
|
-
outputDirClean: true,
|
|
186
|
-
outputPath: {
|
|
187
|
-
"http://mysite.com/services/?page=1": "services/page/1",
|
|
188
|
-
"http://mysite.com/services/?page=2": "services/page/2"
|
|
189
|
-
},
|
|
190
|
-
selector: "#dynamic-content"
|
|
191
|
-
})
|
|
192
|
-
.then(function (completed) {
|
|
193
|
-
// completed is an array of full file paths to the completed snapshots.
|
|
194
|
-
})
|
|
195
|
-
.catch(function (error) {
|
|
196
|
-
// error is an Error instance.
|
|
197
|
-
// error.completed is an array of snapshot file paths that were completed.
|
|
198
|
-
// error.notCompleted is an array of file paths that did NOT complete.
|
|
199
|
-
});
|
|
200
|
-
```
|
|
201
|
-
This example implies there are a couple of pages with query strings in sitemap.xml, and we don't want html-snapshots to create directories with query string characters in the names. We would also have a rewrite rule that reflects this same mapping when `_escaped_fragment_` shows up in the querystring of a request so we serve the snapshot from the appropriate directory.
|
|
202
|
-
|
|
203
|
-
### Example - Per page selectors and jQuery
|
|
204
|
-
```javascript
|
|
205
|
-
var htmlSnapshots = require('html-snapshots');
|
|
206
|
-
htmlSnapshots.run({
|
|
207
|
-
source: "/path/to/robots.txt",
|
|
208
|
-
hostname: "mysite.com",
|
|
209
|
-
outputDir: "./snapshots",
|
|
210
|
-
outputDirClean: true,
|
|
211
|
-
selector: {
|
|
212
|
-
"__default": "#dynamic-content",
|
|
213
|
-
"/jqpage": "A-Selector-Not-Supported-By-querySelector"
|
|
214
|
-
},
|
|
215
|
-
useJQuery: {
|
|
216
|
-
"/jqpage": true,
|
|
217
|
-
"__default": false
|
|
218
|
-
}
|
|
219
|
-
})
|
|
220
|
-
.then(function (completed) {
|
|
221
|
-
// completed is an array of full file paths to the completed snapshots.
|
|
222
|
-
})
|
|
223
|
-
.catch(function (error) {
|
|
224
|
-
// error is an Error instance.
|
|
225
|
-
// error.completed is an array of snapshot file paths that were completed.
|
|
226
|
-
// error.notCompleted is an array of file paths that did NOT complete.
|
|
227
|
-
});
|
|
228
|
-
```
|
|
229
|
-
This reads the urls from your robots.txt and produces snapshots in the ./snapshots directory. In this example, a selector named "#dynamic-content" appears in all pages across the site except in "/jqpage", where a selector not supported by [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/document.querySelector) is used. Further, "/jqpage" loads jQuery itself \(required\). All the other pages don't need to use special selectors, so the default is set to `false`. Notice that since a robots.txt input is used, full URLs are **not** used to match selectors. Instead, paths \(and QueryStrings and any Hashes\) are used, just as specified in the robots.txt file itself.
|
|
81
|
+
This example reads the pages from a mix of sitemap or sitemap-index files found in the robots.txt and produces snapshots in the ./snapshots directory. In this example, a selector named "#dynamic-content" appears in all pages across the site. Once this selector is visible in a page, the html snapshot is taken and saved to ./snapshots.
|
|
230
82
|
|
|
231
|
-
### Example
|
|
83
|
+
### Quick Example
|
|
232
84
|
```javascript
|
|
233
|
-
|
|
85
|
+
const htmlSnapshots = require('html-snapshots');
|
|
234
86
|
htmlSnapshots.run({
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
outputDir:
|
|
238
|
-
outputDirClean: true
|
|
239
|
-
selector: "#dynamic-content"
|
|
87
|
+
source: 'https://host.domain/robots.txt',
|
|
88
|
+
selector: '#dynamic-content',
|
|
89
|
+
outputDir: './snapshots',
|
|
90
|
+
outputDirClean: true
|
|
240
91
|
})
|
|
241
|
-
.then(
|
|
92
|
+
.then(completed => {
|
|
242
93
|
// completed is an array of full file paths to the completed snapshots.
|
|
243
94
|
})
|
|
244
|
-
.catch(
|
|
95
|
+
.catch(error => {
|
|
245
96
|
// error is an Error instance.
|
|
246
|
-
// error.completed is an array of snapshot file paths that
|
|
97
|
+
// error.completed is an array of snapshot file paths that did complete.
|
|
247
98
|
// error.notCompleted is an array of file paths that did NOT complete.
|
|
248
99
|
});
|
|
249
100
|
```
|
|
250
|
-
Generates snapshots for "/", "/contact", and "/special" from mysite.com. "/special" uses port 82. All use http protocol. Array input can be powerful, check out a [simple example](/examples/simple-promise), or a more [complex example](/examples/html5rocks).
|
|
251
101
|
|
|
252
|
-
|
|
253
|
-
```javascript
|
|
254
|
-
var assert = require("assert");
|
|
255
|
-
var fs = require("fs");
|
|
256
|
-
var htmlSnapshots = require("html-snapshots");
|
|
102
|
+
More examples can be found in [this document](/examples/README.md). Also, A showcase of runnable examples can be found [here](/examples).
|
|
257
103
|
|
|
258
|
-
|
|
259
|
-
source: "http://localhost/robots.txt",
|
|
260
|
-
hostname: "localhost",
|
|
261
|
-
outputDir: "./snapshots",
|
|
262
|
-
outputDirClean: true,
|
|
263
|
-
selector: "#dynamic-content",
|
|
264
|
-
snapshotScript: {
|
|
265
|
-
script: "removeScripts"
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
.then(function (completed) {
|
|
269
|
-
completed.forEach(function (snapshotFile) {
|
|
270
|
-
var content = fs.readFileSync(snapshotFile, { encoding: "utf8"});
|
|
271
|
-
assert.equal(false, /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi.test(content));
|
|
272
|
-
});
|
|
273
|
-
// It didn't throw b/c there are no script tags in the html snapshots
|
|
274
|
-
console.log('stripped all script tags as expected');
|
|
275
|
-
})
|
|
276
|
-
.catch(function (error) {
|
|
277
|
-
// error is an Error instance.
|
|
278
|
-
// error.completed is an array of snapshot file paths that were completed.
|
|
279
|
-
// error.notCompleted is an array of file paths that did NOT complete.
|
|
280
|
-
});
|
|
281
|
-
```
|
|
282
|
-
Removes all script tags from the output of the html snapshot. Custom filters are also supported, see the customFilter Example in the explanation of the `snapshotScript` option. Also, check out the concrete [example](/examples/custom).
|
|
104
|
+
An older (version 0.13.2), more in depth usage example is located in this [article](/docs/example-heroku-redis.md) that includes explanation and code of a real usage featuring dynamic app routes, ExpressJS, Heroku, and more.
|
|
283
105
|
|
|
284
106
|
## Options
|
|
285
107
|
> Every option has a default value except `outputDir`.
|
|
286
108
|
|
|
287
109
|
### Input Control Options
|
|
288
110
|
|
|
289
|
-
|
|
111
|
+
* **input** {String}
|
|
290
112
|
+ default: `"robots"`
|
|
291
113
|
+ Specifies the input generator to be used to produce the urls.
|
|
292
114
|
|
|
@@ -295,18 +117,19 @@ Removes all script tags from the output of the html snapshot. Custom filters are
|
|
|
295
117
|
+ `"sitemap"` Supply urls from a local or remote sitemap.xml file. Gzipped sitemaps are supported.
|
|
296
118
|
+ `"sitemap-index"` Supply urls from a local or remote sitemap-index.xml file. Gzipped sitemap indexes are supported.
|
|
297
119
|
+ `"array"`, supply arbitrary urls from a javascript array.
|
|
298
|
-
+ `"robots"` Supply urls from a local or remote robots.txt file. Robots.txt
|
|
120
|
+
+ `"robots"` Supply urls from a local or remote robots.txt file. Robots.txt is first scanned for `Sitemap` directives. If found, those are used to drive the crawl. Otherwise, `Allow` directives are used in conjunction with [origin options](#origin-options).
|
|
299
121
|
+ `"textfile"` Supply urls from a local line-oriented text file in the style of robots.txt
|
|
300
122
|
|
|
301
|
-
|
|
123
|
+
* **source** {String|Array}
|
|
302
124
|
+ default: `"./robots.txt"`, `"./sitemap.xml"`, `"./sitemap-index.xml"`, `"./line.txt"`, or `[]`, depending on the input generator.
|
|
303
|
-
+ Specifies the input source. This must be a valid array or the location of a robots, text, or sitemap file for the corresponding input generator.
|
|
125
|
+
+ Specifies the input source. This must be a valid array or the location (local or remote) of a robots, text, or sitemap file for the corresponding input generator. For the array input generator, it must be an array of urls.
|
|
304
126
|
|
|
305
|
-
##### Sitemap
|
|
127
|
+
##### Sitemap Only Input Options
|
|
128
|
+
> Options that apply to robots.txt with Sitemap directives, sitemaps, and sitemap-index input
|
|
306
129
|
|
|
307
|
-
|
|
130
|
+
* **sitemapPolicy** {Boolean}
|
|
308
131
|
+ default: `false`
|
|
309
|
-
+ For use only with the sitemap and sitemap-index input generators. When true, lastmod and/or changefreq sitemap url child elements can be used to determine if a snapshot needs to be taken. Here are the possibilities for usage:
|
|
132
|
+
+ For use only with the robots, sitemap, and sitemap-index input generators. When true, lastmod and/or changefreq sitemap url child elements can be used to determine if a snapshot needs to be taken. Here are the possibilities for usage:
|
|
310
133
|
+ Both lastmod and changefreq tags are specified alongside loc tags in the sitemap. In this case, both of these tags are used to determine if the url is out-of-date and needs a snapshot.
|
|
311
134
|
+ Only a lastmod tag is specified alongside loc tags in the sitemap. In this case, if an output file from a previous run is found for the url loc, then the file modification time is compared against the lastmod value to see if the url is out-of-date and needs a snapshot.
|
|
312
135
|
+ Only a changefreq tag is specified alongside loc tags in the sitemap. In this case, if an output file from a previous run is found for the url loc, then the last file modification time is used as a timespan \(from now\) and compared against the given changefreq to see if the url is out-of-date and needs a snapshot.
|
|
@@ -315,42 +138,43 @@ Removes all script tags from the output of the html snapshot. Custom filters are
|
|
|
315
138
|
|
|
316
139
|
Not all url elements in a sitemap have to have lastmod and/or changefreq \(those tags are optional, unlike loc\), but the urls you want to be able to skip \(if they are current\) must make use of those tags. You can intermix usage of these tags, as long as the requirements are met for making an age determination. If a determination on age cannot be made for any reason, the url is processed normally. For more info on sitemap tags and acceptable values, read the [wikipedia](http://en.wikipedia.org/wiki/Sitemaps) page.
|
|
317
140
|
|
|
318
|
-
|
|
141
|
+
* **sitemapOutputDir** {String}
|
|
319
142
|
+ default: `_sitemaps_`
|
|
320
143
|
+ For use only with the sitemap-index input generator, this option directs the storage of sitemaps locally. It is a string that defines the name of the subdirectory under the `outputDir` where sitemaps are stored.
|
|
321
144
|
Locally stored sitemaps are used for age determinations with incoming lastmod tags. If this option is falsy, it will prevent sitemap storage and thereby disable sitemapPolicy for sitemaps referenced in a sitemap-index.
|
|
322
145
|
|
|
323
146
|
The [examples](/examples) directory contains [sitemap-index](/examples/sitemap-index) and [sitemap](/examples/custom) usage examples.
|
|
324
147
|
|
|
325
|
-
#####
|
|
148
|
+
##### Origin Options
|
|
149
|
+
> Origin options are only useful for Robots.txt files that use `Allow` directives and Textfile input types.
|
|
326
150
|
|
|
327
|
-
|
|
151
|
+
* **hostname** {String}
|
|
328
152
|
+ default: `"localhost"`
|
|
329
153
|
+ Specifies the hostname to use for paths found in a robots.txt or textfile. Applies to all pages. This option is ignored if you are using the sitemap or array input generators.
|
|
330
154
|
|
|
331
|
-
|
|
155
|
+
* **port** {Number}
|
|
332
156
|
+ default: 80
|
|
333
157
|
+ Specifies the port to use for all paths found in a robots.txt or textfile. This option is ignored if you are using the sitemap or array input generators.
|
|
334
158
|
|
|
335
|
-
|
|
159
|
+
* **auth** {String}
|
|
336
160
|
+ default: none
|
|
337
161
|
+ Specifies the old-school authentication portion of the url. Applies to all path found in a robots.txt or textfile.
|
|
338
162
|
|
|
339
|
-
|
|
163
|
+
* **protocol** {String}
|
|
340
164
|
+ default: `"http"`
|
|
341
165
|
+ Specifies the protocol to use for all paths found in a robots.txt or textfile. This option is ignored if you are using the sitemap or array input generators.
|
|
342
166
|
|
|
343
167
|
### Output Control Options
|
|
344
168
|
|
|
345
|
-
|
|
169
|
+
* **outputDir** {String}
|
|
346
170
|
+ default: none
|
|
347
171
|
+ **Required** \(you must specify a value\). Specifies the root output directory to put all the snapshot files in. Paths to the snapshot files in the output directory are defined by the paths in the urls themselves. The snapshot files are always named "index.html".
|
|
348
172
|
|
|
349
|
-
|
|
173
|
+
* **outputDirClean** {Boolean}
|
|
350
174
|
+ default: `false`
|
|
351
175
|
+ Specifies if html-snapshots should clean the output directory before it creates the snapshots. If you are using sitemapPolicy and only specifying one of lastmod or changefreq in your sitemap \(thereby relying on file modification times on output files from a previous run\) this value must be false.
|
|
352
176
|
|
|
353
|
-
|
|
177
|
+
* **outputPath** {Object|Function}
|
|
354
178
|
+ default: none
|
|
355
179
|
+ Specifies per url overrides to the generated snapshot output path. The default output path for a snapshot file, while rooted at outputDir, is simply an echo of the input path - plus any arguments. Depending on your urls, your `_escaped_fragment_` rewrite rule (see below), or the characters allowed in directory names in your environment, it might be necessary to use this option to change the output paths.
|
|
356
180
|
|
|
@@ -366,9 +190,9 @@ Removes all script tags from the output of the html snapshot. Custom filters are
|
|
|
366
190
|
|
|
367
191
|
### Snapshot Control Options
|
|
368
192
|
|
|
369
|
-
|
|
193
|
+
* **selector** {String|Object|Function}
|
|
370
194
|
+ default: `"body"`
|
|
371
|
-
+
|
|
195
|
+
+ The selector to wait for in the output that triggers a snapshot to be taken.
|
|
372
196
|
|
|
373
197
|
The value can be one of these *javascript types*:
|
|
374
198
|
|
|
@@ -380,61 +204,83 @@ Removes all script tags from the output of the html snapshot. Custom filters are
|
|
|
380
204
|
|
|
381
205
|
NOTE: By default, selectors must conform to [this spec](http://www.w3.org/TR/selectors-api/#grammar), as they are used by [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/document.querySelector). If you need selectors not supported by this, you must specify the `useJQuery` option, and load jQuery in your page.
|
|
382
206
|
|
|
383
|
-
|
|
384
|
-
+ default: `
|
|
385
|
-
+ Specifies
|
|
386
|
-
|
|
387
|
-
The value can be one of these *javascript types*:
|
|
388
|
-
|
|
389
|
-
`"boolean"` If the value is a boolean, it is used for every page. Note that if it is any scalar type such as "string" or "number", it will be interpreted as a boolean using javascript rules. Coerced string values "true", "yes", and "1" are specifically true, all others are false.
|
|
390
|
-
|
|
391
|
-
`"object"` If the value is an object, it is interpreted as key/value pairs where the key must match the url (or path in the case of robots.txt style) found by the input generator. This allows you to specify the use of jQuery for individual pages. The reserved key "__default" allows you to specify a default jQuery usage so you don't have to specify usage for every individual page.
|
|
392
|
-
|
|
393
|
-
`"function"` If the value is a function, it is called for every page and passed a single argument that is the url (or path in the case of robots.txt style) found in the input. The function must return a value to use for this option for the page it is given. The value returned for a given page must be a boolean.
|
|
394
|
-
|
|
395
|
-
NOTE: You do not *have to* use this option if your page uses jQuery. You only need this if your selector is not supported by [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/document.querySelector). However, if you do use this option, the page being snapshotted must load jQuery itself.
|
|
396
|
-
|
|
397
|
-
+ `snapshotScript`
|
|
398
|
-
+ default: This library's [default](/lib/phantom/default.js) snapshot script. This script runs in PhantomJS and takes the snapshot when the supplied selector becomes visible.
|
|
399
|
-
+ Specifies the PhantomJS script to run to actually produce the snapshot. The script supplied in this option is run per url (or path) by html-snapshots in a separate PhantomJS process. Applies to all pages.
|
|
207
|
+
* **snapshotScript** {String|Object}
|
|
208
|
+
+ default: This library's default snapshot script. Which one is used is determined by the [`browser`](#process-control-options) option.
|
|
209
|
+
+ Specifies the browser script to run to actually produce the snapshot. The script supplied in this option is run per url (or path) by html-snapshots in a separate browser process. Applies to all pages.
|
|
400
210
|
|
|
401
211
|
The value can be one of these *javascript types*:
|
|
402
212
|
|
|
403
|
-
`"string"` If the value is a string, it must an absolute path to a custom
|
|
213
|
+
`"string"` If the value is a string, it must an absolute path to a custom script you supply.
|
|
214
|
+
|
|
215
|
+
+ `browser: "phantomjs"`:
|
|
216
|
+
html-snapshots will spawn a separate phantomjs process to run your snapshot script and give it the following [arguments](http://phantomjs.org/api/system/property/args.html):
|
|
404
217
|
+ `system.args[0]` The path to your PhantomJS script.
|
|
405
|
-
+ `system.args[1]` The output path.
|
|
218
|
+
+ `system.args[1]` The output file path.
|
|
406
219
|
+ `system.args[2]` The url to snapshot.
|
|
407
220
|
+ `system.args[3]` The selector to watch for to signal page completion.
|
|
408
221
|
+ `system.args[4]` The overall timeout \(milliseconds\).
|
|
409
222
|
+ `system.args[5]` The interval \(milliseconds\) to watch for the selector.
|
|
410
223
|
+ `system.args[6]` A flag indicating jQuery selectors should be supported.
|
|
411
224
|
+ `system.args[7]` A flag indicating verbose output is desired.
|
|
412
|
-
+ `system.args[8]` A custom module to load.
|
|
413
|
-
|
|
414
|
-
`"
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
+ `
|
|
225
|
+
+ `system.args[8]` A custom module to load.
|
|
226
|
+
|
|
227
|
+
+ `browser: "puppeteer"`:
|
|
228
|
+
html-snapshots will spawn your script as a separate process and give it the following arguments:
|
|
229
|
+
+ `process.argv[0]` The path to NodeJS.
|
|
230
|
+
+ `process.argv[1]` The path to your snapshot script.
|
|
231
|
+
+ `process.argv[2]` The output file path.
|
|
232
|
+
+ `process.argv[3]` The url to snapshot.
|
|
233
|
+
+ `process.argv[4]` The selector to wait for to signal page completion.
|
|
234
|
+
+ `process.argv[5]` The overall timeout \(milliseconds\).
|
|
235
|
+
+ `process.argv[6]` The path to a custom NodeJS module that returns a filter function.
|
|
236
|
+
+ `process.argv[7]` A debug flag to kick the browser into headed, devtools mode.
|
|
237
|
+
+ `process.argv[8]` A slowMo time \(milliseconds\) to slow the browser down.
|
|
238
|
+
|
|
239
|
+
`"object"` If an object is supplied, it has the following properties:
|
|
240
|
+
+ `script` This must be one of the following values:
|
|
241
|
+
+ `"removeScripts"` This runs the default snapshot script with an output filter that removes all script tags are removed from the html snapshot before it is saved.
|
|
242
|
+
+ `"customFilter"` This runs the default snapshot script, but allows you to supply any output filter.
|
|
243
|
+
+ `module` This property is required only if you supplied a value of `"customFilter"` for the `script` property. This must be an absolute path to a PhantomJS module you supply. Your module will be `require`d and called as a function to filter the html snapshot output. Your module's function will receive the entire raw html content as a single input string, and must return the filtered html content.
|
|
419
244
|
|
|
420
245
|
customFilter Example:
|
|
421
246
|
```javascript
|
|
422
247
|
// option snippet showing snapshotScript object with "customFilter":
|
|
423
248
|
{
|
|
424
249
|
snapshotScript: {
|
|
425
|
-
script:
|
|
426
|
-
module:
|
|
250
|
+
script: 'customFilter',
|
|
251
|
+
module: '/path/to/myFilter.js'
|
|
427
252
|
}
|
|
428
253
|
}
|
|
429
254
|
|
|
430
255
|
// in myFilter.js:
|
|
431
256
|
module.exports = function(content) {
|
|
432
|
-
return content.replace(/someregex/g,
|
|
257
|
+
return content.replace(/someregex/g, 'somereplacement'); // remove or replace anything
|
|
433
258
|
}
|
|
434
259
|
```
|
|
435
|
-
A more complete example using custom options is available [here](/examples/custom).
|
|
260
|
+
A more complete example using custom options is available [here](/examples/custom).
|
|
261
|
+
|
|
262
|
+
* **debug** {Object}
|
|
263
|
+
> This options is only supported with the puppeteer browser script.
|
|
264
|
+
+ default: `{ flag: false, slowMo: 500 }`
|
|
265
|
+
+ Setting the `debug.flag` to true starts chrome in headed mode with devtools open. `debug.slowMo` is a time in milliseconds to reduce browser processing speed (larger numbers slows down chrome more). Recommended use is with a single problem page input using an Array source.
|
|
266
|
+
|
|
267
|
+
* **useJQuery** {Boolean|Object|Function}
|
|
268
|
+
> This option is only supported with the phantomjs browser script.
|
|
269
|
+
+ default: `false`
|
|
270
|
+
+ Specifies to use jQuery selectors to detect when to snapshot a page. Please note that you cannot use these selectors if the page to be snapshotted does not load jQuery itself. To return to the behavior prior to v0.6.x, set this to `true`.
|
|
271
|
+
|
|
272
|
+
The value can be one of these *javascript types*:
|
|
436
273
|
|
|
437
|
-
|
|
274
|
+
`"boolean"` If the value is a boolean, it is used for every page. Note that if it is any scalar type such as "string" or "number", it will be interpreted as a boolean using javascript rules. Coerced string values "true", "yes", and "1" are specifically true, all others are false.
|
|
275
|
+
|
|
276
|
+
`"object"` If the value is an object, it is interpreted as key/value pairs where the key must match the url (or path in the case of robots.txt style) found by the input generator. This allows you to specify the use of jQuery for individual pages. The reserved key "__default" allows you to specify a default jQuery usage so you don't have to specify usage for every individual page.
|
|
277
|
+
|
|
278
|
+
`"function"` If the value is a function, it is called for every page and passed a single argument that is the url (or path in the case of robots.txt style) found in the input. The function must return a value to use for this option for the page it is given. The value returned for a given page must be a boolean.
|
|
279
|
+
|
|
280
|
+
NOTE: You do not *have to* use this option if your page uses jQuery. You only need this if your selector is not supported by [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/document.querySelector). However, if you do use this option, the page being snapshotted must load jQuery itself.
|
|
281
|
+
|
|
282
|
+
* **verbose** {Boolean|Object|Function}
|
|
283
|
+
> This option is only used with the phantomjs browser script
|
|
438
284
|
+ default: `false`
|
|
439
285
|
+ Specifies to turn on extended console output in the PhantomJS process for debugging purposes. Can be applied to all pages, or just specific page(s). It is recommended to do this one page at a time, as the output can be large, and interleaved with parallel processes. See following explanation of types for how to debug just one page, and also [this example](/examples/verbose).
|
|
440
286
|
|
|
@@ -448,7 +294,11 @@ Removes all script tags from the output of the html snapshot. Custom filters are
|
|
|
448
294
|
|
|
449
295
|
### Process Control Options
|
|
450
296
|
|
|
451
|
-
|
|
297
|
+
* **browser** {String}
|
|
298
|
+
+ default: `"puppeteer"`
|
|
299
|
+
+ Specifies which browser process to use in the crawl. Can be one of "phantomjs" or "puppeteer".
|
|
300
|
+
|
|
301
|
+
* **timeout** {Number|Object|Function}
|
|
452
302
|
+ default: 10000 \(milliseconds\)
|
|
453
303
|
+ Specifies the time to wait for the selector to become visible.
|
|
454
304
|
|
|
@@ -460,19 +310,21 @@ Removes all script tags from the output of the html snapshot. Custom filters are
|
|
|
460
310
|
|
|
461
311
|
`"function"` If the value is a function, it is called for every page and passed a single argument that is the url (or path in the case of robots.txt style) found in the input. The function must return a value to use for this option for the page it is given. The value returned for a given page must be a number.
|
|
462
312
|
|
|
463
|
-
|
|
313
|
+
* **processLimit** {Number}
|
|
464
314
|
+ default: 4
|
|
465
315
|
+ Limits the number of child PhantomJS processes that can ever be actively running in parallel. A value of 1 effectively forces the snapshots to be taken in series (only one at a time). Useful if you need to limit the number of processes spawned by this library. Experiment with what works best. One guideline suggests about [4 per CPU](http://stackoverflow.com/questions/9961254/how-to-manage-a-pool-of-phantomjs-instances).
|
|
466
316
|
|
|
467
|
-
|
|
317
|
+
* **pollInterval** {Number}
|
|
318
|
+
+ default: 500 \(milliseconds\)
|
|
319
|
+
+ Specifies the rate at which html-snapshots checks to see if a browser script has completed. Applies to all pages.
|
|
320
|
+
|
|
321
|
+
* **checkInterval** {Number}
|
|
322
|
+
> This option is only used with the phantomjs browser script
|
|
468
323
|
+ default: 250 (milliseconds)
|
|
469
324
|
+ Specifies the rate at which the PhantomJS script checks to see if the selector is visible yet. Applies to all pages.
|
|
470
325
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
+ Specifies the rate at which html-snapshots checks to see if a PhantomJS script has completed. Applies to all pages.
|
|
474
|
-
|
|
475
|
-
+ `phantomjsOptions`
|
|
326
|
+
* **phantomjsOptions** {String|Array|Object|Function}
|
|
327
|
+
> This option is only used with the phantomjs browser script
|
|
476
328
|
+ default: ""
|
|
477
329
|
+ Specifies options to give to PhantomJS. Can specify per page or for all pages. Since PhantomJS instances run per page, it is possible to specify different PhantomJS options per page. Useful for debugging PhantomJS scripts on a specific page.
|
|
478
330
|
For PhantomJS options syntax, checkout the [current options](http://phantomjs.org/api/command-line.html).
|
|
@@ -505,7 +357,8 @@ Removes all script tags from the output of the html snapshot. Custom filters are
|
|
|
505
357
|
```
|
|
506
358
|
An example demonstrating how to **debug** a PhantomJS script is available [here](/examples/debug-phantomjs). It also demonstrates per-page option usage.
|
|
507
359
|
|
|
508
|
-
|
|
360
|
+
* **phantomjs** {String}
|
|
361
|
+
> This option is only used with the phantomjs browser script
|
|
509
362
|
+ default: A package local reference to PhantomJS.
|
|
510
363
|
+ Specifies the PhantomJS executable to run. Applies to all pages. Override this if you want to supply a path to a different version of PhantomJS. To reference PhantomJS globally in your environment, just use the value, "phantomjs". Remember, it must be found in your environment path to execute.
|
|
511
364
|
See [PhantomJS](http://phantomjs.org/) for more information.
|