As a HTML Panels Tips: #10 Packaging / ZXP Installers follow-up, in this tip I will show you how to automate (i.e. make less error prone) the utterly annoying job of building ZXP installers: i.e. with the help of Gulp.js, a task runner based on Node.js.
Gulp.js
If you come from Photoshop scripting like me, you might be pretty green when it comes to the plethora of tools that web developers are used to. Since they often need to perform repetitive tasks (such as compile LESS/SASS stylesheets, minify CSS, lint, uglify and concatenate JS, rename and move files, etc.) some tools known as build tools or task runners appeared - a couple of remarkable ones being Grunt and Gulp.
They do basically the same thing: Grunt is based on configuration, Gulp is (newer, and) code based. If the previous sentence makes no sense to you it doesn’t matter too much - if you want to learn more about task runners I strongly suggest you to watch this screencast made by the great and prolific @sayanee_ from the Build Podcast series (60 free videos about all kind of tech tools). Amazing resource, really.
I won’t dig deeper on Gulp here (also because I’m not a task runner expert myself), I will just share the particular code I’m using and that proved to work, explaining what each part does and trying to show a glimpse of the big picture. Hopefully, my workflow is simple enough for you to tweak it to fit your own needs.
Requisites
Gulp - like tons of useful stuff - is based on Node.js (a platform based on Google Chrome runtimes), so you’d better install it first downloading from the Node website if you haven’t already done that. Then, you need to know that there are about 88 thousands packages extending Node - which are managed by npm, the Node Package Manager. Npm is automatically installed alongside Node.js. You must have Gulp installed globally (i.e. not only in your project directory but available everywhere), so fire the Terminal and:
Enter your user password when requested (sudo
is a mandatory step on OSX as far as I know - I can’t say if it’s needed on Windows shell too).
Folders’ structure
I will package a hybrid extension - the very same I showed in HTML Panels Tips: #10 Packaging / ZXP Installers. That is, an installer that deploys an HTML Panel, a Mac and Windows specific plugin files and a shared Photoshop script. Please refer to the original post for information about the manual process - here I’ll just automate it. The folder hierarchy is as follows:
src/
contains all the source code;Âbuild/
 is empty and will contain just the final installer.- The source for the HTML panel is the
src/com.example.myExtension
folder. - The
src/MXI
folder gathers all the assets needed to pack the hybrid extension:src/MXI/com.example.myExtension.mxi
is the configuration file (info here).src/MXI/HTML
is empty but will contain the ZXP of the HTML panel.src/MXI/MAC
,Âsrc/MXI/WIN
andÂsrc/MXI/SCRIPT
are the plugins and scripts needed by the extension.
- Finally,
ucf.jar
,ZXPSignCmd
andmyCertificate.p12
are the command line tools and the signing certificate that you should well know about.
I’ve left alone the package.json
and gulpfile.js
files, which I will disclose in a moment.
Setting up the project
You can either create yourself the package.json
file with the following content:
Or let npm do it for you:Â cd
into the project directory (the root), and
The Node Package Manager will initialise a project asking you few questions; as follows my answers (when there’s none, I just hit enter to confirm the suggested defaults):
One way or the other, a package.json
file should be there. Now let’s install (locally – i.e. in this very folder) gulp:
We need two gulp plugins: gulp-clean is for deleting files:
while gulp-shell is a command line interface:
The --save-dev
flag instructs npm to add these ones as project’s dependencies, automatically modifying the json file for you:
You’ll see that npm has installed these dependencies in a node_modules
folder.
gulpfile.js
Here comes the fun: the gulpfile.js
is where you start defining tasks, that you’ll be calling from the Terminal. Before digging into the actual, entire workflow, let’s familiarize with tasks - here’s a simple one:
You first require
gulp, then call the gulp.task
 function. The first parameter is the task name as a string (here 'clean-all'
), the second is the function that will return a Stream (if you feel inclined, find here info about Streams). Don’t worry too much about that: what you need to know is just that you usually define a gulp source (here every zxp file from any subdirectory) and you pipe that to the clean
function - which deletes files. The { read: false }
 is a configuration object that instructs gulp not to read them, to speed up the process. How do you call that task? In the Terminal (root folder):
And presto! all the ZXPs that might have been around are gone. Here is another useful task - it uses the gulp-shell
plugin to call the ZXPSignCmd
 command line and actually pack files:
If you run in the Terminal:
it is like if you had typed the whole, long ZXPSignCmd string yourself. Hopefully you start appreciating the power of task runners now.
Combining tasks…
A task can rely on other tasks - dependencies are defined into an array of tasks:
In the above example, the 'clean-all'
task is performed first, then the 'pack-html'
task runs. If you have multiple dependencies (that is: your task needs to run after several other tasks) you can define them this way:
Mind you: while the 'test-task'
actually runs after 'first-dep'
and 'second-dep'
, you cannot be sure that 'first-dep'
and 'second-dep'
are executed in this very order! Dependencies are processed asynchronously:Â 'second-dep'
may finish before 'first-dep'
.
… synchronously!
But in the ZXP automation we need to be sure that a task is done before the next one starts - i.e. the whole thing must be synchronous. There are few ways to trick gulp into this behaviour: returning a stream, defining a callback, using promises and - the easiest one and therefore my preferred - chaining dependencies:
- Task #4 depends on Task #3
- Task #3 depends on Task #2
- Task #2 depends on Task #1
You run Task #4 and in fact the whole sequence is #1 > #2 > #3 > #4. There might be more elegant ways to get there, but this approach has the advantage of being easily testable: if the result isn’t what you’d expect, just run Task #3 to test the gulpfile up to that point; if it’s still broken, try Task #2, and so on, until you find the bugged task.
Complete gulpfile.js
Given the folder structure I’ve outlined, the entire gulpfile is as follows:
Hopefully the code and the comments are self-explanatory. Few caveats that might save you some search time on Google:
'./**/*.zxp'
is a way to mean: every zxp file in every subfolder of the root (being the root'./'
).- For a reason that I don’t know, a semicolon after the
shell.task()
breaks gulp. { read: false }
speeds up the process when you don’t need gulp to read files.{ base: './src/' }
 (as used in task #2) tells gulp to use./src/
as a base for the relative paths. If you omit it or set it to the root'./'
, you’ll end up with/src/MXI/HTML/src/com.example.myExtension.zxp
 (and extra/src/
) and notÂ/src/MXI/HTML/com.example.myExtension.zxp
which is what you actually want.
This tip just scratches the surface of gulp.js as a task runner for CC Extensions - I hope to have time to dig deeper and share my findings again - feel free to add yours in the comments!
The Photoshop HTML Panels Development Course
If you’re reading here, you might be interested in my Photoshop Panels development – so let me inform you that I’ve authored a full course:
- 300 pages PDF
- 3 hours of HD screencasts
- 28 custom Panels with commented code
Check it out! Please help me spread the news – I’ll be able to keep producing more exclusive content on this blog. Thank you!