Improving a Yeoman generator
For some time I've been wanting to write and publish an Angular directive that displays an editable single-entry ledger, or table, with itemized costs or fees. While I had written plenty of Angular directives for project I had worked on, I had yet to publish one as a component that could be used in other projects via Bower.
Automating the testing and deployment of even a simple Angular app can require a lot of pre-processing, which means that the build system will have a lot of moving parts. Here are just a few of the tasks we may want to automate:
Automating the testing and deployment of even a simple Angular app can require a lot of pre-processing, which means that the build system will have a lot of moving parts. Here are just a few of the tasks we may want to automate:
- A test runner than re-runs all tests every time a source or test file changes.
- For deployment, concatenate all Javascript files into a single file that can be linked to from the page.
- For development, insert a link to each of those files automatically into the page, in an order that will allow them to resolve module dependencies.
- Concatenate stylesheets into a single file that can be linked to.
- If we want to use Coffeescript, we need to run the coffee pre-processor that will translate each .coffee file into an equivalent .js file.
- If we want to use a stylesheet pre-processor like Sass or Less, we will need to automate that.
- We probably want to run a code-checker like JsLint or JsHint on our code each time a file changes.
- We probably want to be able to put our HTML templates in separate files which means that they will either have to be served as such, unless we can arrange for them to be automatically converted to Javascript strings and loaded into Angular's $templateCache when the page loads.
Then there is the matter of setting up your project's bower.json and package.json files and installing all the base packages your app will need, starting with Angular. While "bower init" and "npm init" will walk you through these processes and create files with all the basic fields you might need to publish your creation, I prefer to run one command to create both these files and prompt us only once for common fields like "keywords" and "description", and set default values for other fields, like "author", based on their known definitions in places like .gitconfig.
Unless you set all of this up from scratch every day, you will want to lean on someone else's experience and best practices. Yeoman has a small eco-system of generators that do all that. I've already had experience using angular-fullstack, gulp-angular, and ngbp to write complete single page apps (SPA) with and without a web-service API, previously I had not been able to find one that would allow me to create only one or more components (Angular service or directive factories) to be included in other projects through "bower install ...".
It would have been simple enough to look at some other component projects and use them as templates, but I prefer a solution that is repeatable, can be automated, and has hopefully already been tested. In previous searches I had missed it, but now I found generator-angular-package that seemed to do more or less what I wanted. Fortunately, it is Gulp-based. Grunt is still the workhorse of much of the nodejs and Angular ecosystem, but new projects seem to be using Gulp with greater frequency due to its superior speed, scalability, and overall ease of use.
While I am grateful to it's sponsor, InfinitePress, and it's author whose Git moniker is CoiChedid, there were a few things it didn't do that I still wanted:
- Pre-process Sass stylesheets (.scss) and concatenate them into one stylesheet to be linked to and loaded.
- Pre-process HTML templates into Javascript strings and generate code to load them at runtime into Angular's $templateCache.
- Copy only one min-fied, one non-min-fied, and one stylesheet into the dist/ directory.
- Prompt for a project description and keywords and define them in the bower.json and package.json fields.
- Provide a simple example.html file that can be used to demonstrate the component/s that this project builds.
- Provide a default LICENSE file containing the MIT license text.
- Add a few words to the README to explain how to run tests.
I have some time off over the winter holidays, so I decided to get down to it. The Yeoman API is surprisingly straightforward. At its center is a template-processor that will process templates and interpolate constructs like "<%= myVariable %>" as many of us are long familiar with from the days of Java servlets and JSP. It also has a prompting facility to define and validate console-based prompts and a facility to copy files as necessary to create a working project.
Likewise, CoiChedid's vision was straightforward. It prompts the user for a module name, then uses that name to name the project and the first Angular model, which may contain directives, services, filters and controllers. generator-angular-package contains several sub-generators to create new factory functions in this module or in a new module.
A few more changes I would like to make if I have time are:
- Initially prompt for a package name, then a separate name for the first module. A package might contain several modules so we might want to name it independently.
- Place test scripts in the same directory with the code they test. This seems to have become the preferred practice in test-driven development and it certainly makes it a lot more convenient for me to find these side-by-side in the same directory.
The biggest challenge I had while doing all this was to keep all these changes in separate branches so that I could submit several small pull-requests instead of one monolithic pull-request that might overwhelm the original repository's maintainer. Initially, I did everything in my master branch (an anti-pattern, I know) and wound up having to break this up into separate topic branches all rebased onto the same commit I forked so that it would be easier for base-repo maintainer to process. This turned out to be tedious and error-prone and took me the good part of an afternoon to accomplish.
The other thing I neglected to do is to write tests for all my new features. I wound up testing everything by npm-linking the generator and then repeatedly running it from a temp directory and checking the results. Not the most efficient method. I should practice what I preach and write tests first!