The more I learn about JavaScript development, the more I’m exposed to JavaScript build and config tools. The tools such as babel and webpack, frustrate me. The config tools, the magic software of joy that sets up all of your build tools for you via magic pixie-dust or something so you can just write code, those fascinate me.
I wish we had more for WordPress and ones that did things exactly the way I think they should be done. I really dislike using templating languages for templating code. That’s what’s stopped me from building my own scaffolding tools before. I always build a plugin that did things the way I wanted my template to do things, then copied the plugin into the templates directory of my generated and then manually added substitution strings. Debugging was a pain.
My new goal is to keep a plugin that has a reference case for each thing I would want a plugin to do — composer.json, Gutenberg blocks, PHP direct injection container, etc. — and a scaffolding tool that can copy from that plugin.
I decided to write it in Node so that I could easily share it via npm, and be able to run it locally, in a Docker container or even in a Serverless app. It was a good excuse to learn more about Node’s file system module and npx.
Getting Comfortable With NPX
NPX is a tool that lets you run Node packages from anywhere without installing them first. It’s perfect for application scaffolding tools that you only occasionally use. Instead of keeping a copy installed locally, you run the version on npm. For example, to create a react app, you could install create-react-app globally on your computer, or you can use NPX create-react-app to accomplish the same thing and never worry about keeping it up to date or eating up storage space.
If you’re not familiar with NPX, which is included with npm, I recommend reading the introductory post about NPX.
In the introductory post, Kat shows how to execute JavaScript stored in a Github gist. As she notes, this is remote code execution, which is potentially highly unsafe. So step one for running this script is, read the source of the code you’re about to execute, step two is decide if you want to execute it and step three is to cause remote code execution.
Let’s do that. First, go to https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32 and look at the script. Last time I looked it just caused a console message to be logged. Make sure it hasn’t changed. If so it hasn’t:
That will cause that message to log.
Running the package with a local path. So this is cool, we can develop a script stored as a gist for our automation. But the experience of developing is going to be bad. If we have to push and pull code from the gist, then test, that will be slow. I’d rather test the code locally.
Yes, I could run the code directly with node, ie. switch to the local directory and run node index.js or whatever. What I really want is to use the development version in context. What if I could use npx ~/npx-is-cool from anywhere on my computer to use the local development version of npx-is-cool?
Turns out npx can use a local file path, so that is possible. Let’s walk through how it will show you some basics of how npx works.
First, download a copy of that gist. Put it in your main user directory and change the name of the directory to “npx-is-cool”. Then open up the index.js and you will see:
This is a pretty basic script, change it’s output to something more fun like “I am the Batman” so you can prove your local version is being used and because you are the Batman.
Now you can run this command from anywhere on your computer to use your own npx-is-cool
Now we have a basic model for developing the tool. Develop the npx package locally, using and debugging it in context. That last part is important, I’m making a development tool, I want to use it on the tools I am developing to see if it works or not and once it does so I have its capabilities available to me right away once they work.
Yes, I also want to push my changes to remote version control and make the package available via npm. But, when I’m developing, I do not want to think about managing releases.
Enter The Commander
Command that outputs just “Hi Roy”
Developer Automation With Commander
Using Node To Work With Local And Remote Files
For something practical, I created a command that copies the two files that copies the two PHP files I use to load assets for a React app in a WordPress plugin and change there namespace.
To accomplish this we will need four things:
- A function to download a file from a remote URL to the local.
- A function that uses the first function and then changes the namespace in the resulting file.
- A function that calls those functions will the right argument
- A command to call that function.
Let’s go through the list. I do not want to write a full tutorial on the node file system, but here’s your crash course.
In Node, we use the filesystem — read and write files for example — using “fs”. For example, to check if a file exists:
var fs = require('fs'); fs.existsSync(__dirname + '/hiRoy.txt' );
The fs module is pretty readable. This function is called “existsSync”. It checks — synchronously — if a file exists. We can write a file with fs, synchronously using fs.writeSync().
If you’re not used to require(). It brings the value of the module.exports from the file you specify into scope. For example, if you have the file “foo.js” and inside you have module.exports = function foo(){}; Then when you use const foo = require( ‘./foo’ ); from a file in the same directory that function is now in stored in the const foo. We can leave off the file path — require( ‘react’ ) — to access a module in the node_modules directory, in this case, the export of node_modules/react/index.js
Here is a module to handle downloading a file via https — using the https module — and writing it to file using the fs module:
Notice that on the last line — module.exports = download. The download is a reference to the function, so when we require this file, that function is usable. That’s how we use it in our next module.
Here is the module to download a file and change its PHP namespace.
const download = require( './download' ); const replace = require( 'replace-in-files' ); const path = require( 'path' ); /** * Download a file and change its namespace * * * @param {string} file Remote file to copy * @param {string } destPath Path to write file to * @param {string} nameSpace Namespace to use for new file */ function downloadPhpAndNameSpace(file,destPath,nameSpace){ download(file,destPath, () => { replace({ from: "/calderawp\\WordPressPlugin/g", to: nameSpace, files: [destPath] }); }); } module.exports = downloadPhpAndNameSpace;
If you’re still getting used to required() look at the difference between the required for download and for replace-in-files. The first starts with a file path ‘./’, so node looks for the file. The other does not, so it looks in node_modules.
None of these functions so work with the specific files we want to work with. They work with any files, which is good, these are common needs. But let’s start solving the problem at hand. We need to download two files and re-namespace them. With these two functions, we just need one more to set up the paths of where the files go.
Creating The Command
Now it’s time to wrap all of this up in a command so we can type npx caldera-former client-assets ~/my-plugin Vendor/Package and get our scripts copied to ~/my-plugin with the namespace Vendor/Package.
This is a sub-command of our package. I like this pattern vs having just one command with lots of options. It will make it easier to add additional commands and options later.
In the command() function, that words in square brackets become variables passed to the function “action”. The function action is bound to a simple callback that calls the function created in the last step.
Now You Take Command
I’m enjoying learning more about the parts of JavaScript that I do not normally get to work with due to only using JavaScript for front-end dev. Learning how Node modules are structured helped me understand more about what webpack is abstracting. I also learned more about the file system.
More importantly, I’ve been working on my npx app that should save me and my team time. If you think a tool like this could save your team time, fork it and make your own. At me on Twitter @josh412 or leave a comment with what you create.
No Comments