- PhantomJS Cookbook
- Rob Friesel
- 1068字
- 2021-07-16 11:37:57
Specifying a path for external scripts
In this recipe, we will introduce the libraryPath
property on the phantom
object and discuss how to use it to control the source of scripts that are loaded in the runtime via injectJs
.
Getting ready
To run this recipe, we will need at least one injectable script and the script that we want to run.
The script in this recipe is available in the downloadable code repository as recipe03.js
under chapter02
. If we run the provided example script, we must change to the root directory for the book's sample code.
How to do it…
Consider the following script:
console.log('Initial libraryPath: ' + phantom.libraryPath); phantom.libraryPath = phantom.libraryPath.replace(/chapter02$/, 'lib'); console.log('Updated libraryPath: ' + phantom.libraryPath); var isInjected = phantom.injectJs('hemingway.js'); if (isInjected) { console.log('Script was successfully injected.'); console.log('Give me some Fibonacci numbers! ' + fibonacci(Math.round(Math.random() * 10) + 1)); phantom.exit(); } else { console.log('Failed to inject script.'); phantom.exit(1); }
Given the preceding code block, enter the following at the command line:
phantomjs chapter02/recipe03.js
Our output should look like the following:
Initial libraryPath: /Users/robf/phantomjs-cookbook/chapter02 Updated libraryPath: /Users/robf/phantomjs-cookbook/lib Script was successfully injected. Give me some Fibonacci numbers! 0,1,1,2,3,5,8,13,21,34
How it works…
The injectJs
method on the phantom
object can take a script from the filesystem and inject it into the current execution context. It works by loading the specified file (relative to the current libraryPath
), interpreting it, and applying the interpreted script (global variables and all) to the current context; this operates in much the same way as JavaScript is imported onto a web page via a script
tag. We should use injectJs
to import scripts that do not conform to the CommonJS module proposal.
Tip
The CommonJS module proposal is a specification for loading code that minimizes pollution of the global scope or namespace in a JavaScript program by providing an exports
object (within the module) where we can attach our public properties and methods, and a global require
method that we can use to import those modules. For more information about the CommonJS module proposal, see http://wiki.commonjs.org/wiki/Modules.
Our preceding example script performs the following actions:
- We write the current base path for library scripts to the console using the
phantom.libraryPath
property. - We update the default
libraryPath
(the script's working directory) to be our intended target directory. Then, we write that to the console as well. - We import a script using
phantom.injectJs
, passing (in our case) only the filename to the method. The method returnstrue
if the script imports successfully andfalse
if it does not; this Boolean value is assigned to our variable,isInjected
. - If the script imports successfully, we call the
fibonacci
function that was imported from the script and write its results to the console. Then, we exit from PhantomJS. Otherwise, we exit PhantomJS with an error message.
There's more…
As mentioned in the preceding section, a call to phantom.injectJs
is functionally equivalent to a script
tag on a web page—code is read from the target, passed through the interpreter, and applied to the execution context. The libraryPath
property plays an important role here because it provides the path on the filesystem that will be used to resolve any relative paths requested through phantom.injectJs
.
Injectable scripts can be stored in any readable location on the filesystem. The key requirement of injectJs
is that it can resolve the provided path and interpret the target as a syntactically correct JavaScript file.
The libraryPath
property on the phantom
object is a simple one—it is a string that holds the value for the absolute path that will be used to resolve scripts that are injected into the execution context. The property on the phantom
object is read/write, so we can update it at any time during our script. However, it must be an absolute path. We can change libraryPath
to be a relative path, but methods that depend on it (for example, injectJs
) will fail. We can update libraryPath
through a simple assignment, for example:
phantom.libraryPath = '/path/to/libraries';
The injectJs
method on the phantom
object is one way that we can import external scripts into our current execution context. As mentioned in the discussion of our example script, calls to phantom.injectJs
take a string as an argument, and that string should be a reference to a file on the filesystem, either as a filename, a relative path, or an absolute path. The script path argument is consumed as follows:
- If PhantomJS can interpret the path as absolute, it will attempt to retrieve the script from that location on the filesystem.
- If PhantomJS cannot interpret the path as absolute, it will attempt to resolve that script as relative to the specified
libraryPath
.
The injectJs
method itself provides feedback about the loading operation in the form of its return value: true
if the script was successfully injected, and false
if it failed for any reason. If successfully injected, the interpreted contents of the script are applied to the outer space (and not within any webpage
objects) of the PhantomJS execution context.
It is important to consider the contents of any script before importing it with injectJs
. Just as scripts imported into a web page can easily "pollute" the global scope, so can scripts imported using injectJs
. Put another way, if we have a variable named foo
in our PhantomJS script, and then we use injectJs
to import a script that also has a variable named foo
, they will collide, and the most recently added value for foo
will take precedence.
Compare injectJs
with the global require
function that assumes the target files to be CommonJS modules. Although scripts imported with injectJs
have the potential to pollute the current execution context and clobber variables or function names, we have a lot more freedom to write these scripts in whatever style we choose. Contrast this with the require
function, which expects to resolve an exports
object with the exposed methods and properties. The require
method is a safer solution, but it comes at the cost of flexibility, and it cannot take advantage of phantom.libraryPath
; meanwhile, injectJs
can consume scripts that otherwise target more platforms but carry more risks associated with the PhantomJS global scope. It's up to us to consider those trade-offs when designing our library scripts and how we will import them.
See also
- The Reading a file from PhantomJS recipe
- The Loading custom modules in PhantomJS recipe