Alfred - A Modular Toolchain for JavaScript
What is Alfred? TL;DR
- An alternative to boilerplates / starter kits
- Better tooling, out of the box
- A solution to brittle and complex JS infrastructure
The Background
Maintaining over 200 open source JavaScript projects for the last 6 years has exposed me to the best and worst parts of JavaScript infrastructure. JavaScript infrastructure shines when it comes to flexibility. They make few assumptions about the user of the tools, but they don't assume you'll be using other tools. Making no assumptions is nice because it allows users to use whatever tools they want. Infrastructure usually doesn't come with integration for other tools out of the box support so integration is usually added by 3rd party plugins. This model for infrastructure is convenient because it allows developers to write plugins which extend the functionality of the tools they use, therefore allowing the tools to be used in a much wider range of scenarios than anticipated by maintainers. The downside of this customizability is that the tools usually don’t work out of the box. This comes at a huge cost to the user experience of beginners who have never used that tool before.
The JS ecosystem responds to this complexity by creating ‘boilerplates’, or ‘starter kits’, which are essentially template projects that have their infrastructure preconfigured for the tools the template project supports. For example, electron-react-boilerplate
is a boilerplate that configures electron, react, eslint, webpack, and jest. Boilerplates solve the out-of-the-box problem but they sacrifice extensibility because they don’t expect users to change the tools they come with or even change the configurations of those tools.
Alfred proposes a new solution that allows each tool to configure itself with respect to other tools. To better understand Alfred’s solution, it is important to dive deeper into the current problems of JS infrastructure.
The Problem
JS Tooling is Brittle
When trying new tools, newcomers often spend a significant amount of time configuring tools. The difficulty of configuring a tool or library can discourage the user from using the it alltogether. Even users of widely adopted libraries and tools tend to experience issues related to configuration. The tweet below describes the situation well:
"Looking at the issues on storybooks/storybook
:
- 2,479 total issues
- 732 mention "webpack" (30%)
- 428 mention "babel" (17%)
That's crazy! Other keywords that come up that often would be treated as requiring architectural change, but those are just for configuration."
It's a little mind-boggling how many issues are purely for dealing with Babel or Webpack configuration. And those are by far some of the most frustrating issues to debug when you do run into them. So much time wasted. Makes you really understand the "zero config" movement.
Incorrect, Suboptimal Infrastructure
One of the great strengths of JS tooling is its customizability. This customizability allows JS tools to be used in a wide different use cases. But leaving the configuration of tools up to users allows for the possibility of misconfiguring tools, which often results in tools that are used sub-optimally or even incorrectly. For example, it is common for libraries to ship with polyfills or compiled code. This is considered an anti pattern because this makes it harder for applications to optimize apps which use the libraries (tree shaking) and it increases the bundle sizes of apps that use the libraries.
The Solution
Alfred aims to solve these problems by enabling tools to configure themselves out of the box. Each tool should know how to configure itself so that it can be compatible with other tools the user is using. Alfred achieves this 'out of the box' solution by generating minimal configurations for the user's tools. Advanced users can override or extend generated configurations. Alfred tests each combination of tools before publishing new versions.
Skills
A skill is an abstraction over a tool that allows it to configure itself with respect to other tools. For example, a babel ‘skill’ which wants to add react support would add the babel-preset-react
preset to its config if the user is using the react skill.
Here is an example of a skill:
For more on skills, see the skills section of the docs.
Alfred comes with skills out of the box but it also allows users to use 3rd party skills as well. Users can customize configs through Alfred's configs:
Here is an example of what an Alfred config looks like:
Entrypoints
Alfred formalizes the concept of entrypoints, which are files that determine the project type and platform a project will run on. For example, the entrypoint src/app.browser.js
will be built as a browser app, app being the project type and 'browser' being the platform. Entrypoints determine which skills should be used to act on the entrypoint for a specific subcommand. Running the build
subcommand on a project that has a ./src/lib.browser.js
entrypoint should build the entrypoint with rollup, a bundler that is optimal for libraries.
Skills can declare which project types, platforms, and environments they support. Here's how the parcel skill defines which environments, platforms, and projects it supports:
Tasks
Tasks determine which skill should be used when a certain subcommand is called. For example, when alfred run build
is called, either the parcel, webpack, or rollup skill could be used. They also specify how skills are called and provide information about the task. Below is an example of a task:
Skills can then implement certain tasks:
Tasks allow for skills to be interchanged while maintaining a consistent developer workflow. For example, all skills that lint a user’s project will use the @alfred/task-lint
task so all of these skills are invoked through the lint
subcommand that the task registers.
Alfred comes with the following tasks built-in: build
, start
, lint
, format
, and test
.
Files and Directories
Sometimes, adding or changing configuration may not be enough to add support for a certain tool or library. Redux, for example, requires configureStore.js
, root.js
, and other files. To allow skills to fully add out of the box support for tools they wrap, Alfred allows them to define files and directories which are added to the user's project. Similar to configs, files can also be modified by skill transforms. Below is an example of how the redux skill transforms the configureStore.prod.js
file to be compatible with typescript:
Files can be transformed by either applying diffs to files or by replacing strings that match a regular expression.
Getting Started with Alfred
To get started with alfred,
Here is an example of what an Alfred config looks like:
For more details on how to use Alfred, see the docs.
Directory Structure
The following is an example of the directory structure of an Alfred browser app project:
Exciting Opportunities
Alfred creates some exciting new oppertunities for workflows and tooling integration. Expect to see the oppertunities below in future releases!
Entrypoint specific commands
Sometimes, it is useful to run subcommands for a specific entrypoint. Alfred will allow user's to do so through the CLI:
This will make maintaining apps with multiple entrypoints much easier.
Publishing/Deploying with Alfred
Alfred integration with publishing and deploying can significantly simplify the deployment process for many web developers. Ideally, developers can deploy to their platform of choice just by learning a skill:
Documentation Tooling
It can be said that one of the most undervalued pieces of JS tooling is its documentation tooling. There is much to be learned from the success of docs.rs, the standard for documentation generation for Rust. A JS equivalent of docs.rs might be of much use to the JS ecosystem.
Plugins for Alfred
It is very common for JS tools to allow plugins to add extra functionality to tools. Take, for example, a simple rollup plugin that prints the sizes of chunks after bundling:
A useful plugin indeed! But if we want to use this plugin across all our bundlers of all our entrypoints (parcel or webpack if you have an app entrypoint) we would need to create a new plugin for both webpack and parcel.
Alfred can reduce the duplication of plugins with skills and tasks. Skills can return metadata from the tools they wrap. Tasks can provide an interface which skills should conform their metadata responses to.
Here is an example of what a task may look like:
Below is an example of a skill that takes rollup's AST and return an object that conforms to the interface defined by the task.
A plugin can now receive this metadata from any skill that uses the build
task:
Shared ASTs
Sharing AST's between tools is an interesting area for investigation that can provide significantly improve the quality of JS tooling. This can improve the performance and developer experience of all JS tooling. Rome is already doing this!
Acknowledgements
Prior Art
- Cargo
- NPM, Yarn
- Yeoman
- Rome
- create-react-app
- react-boilerplate, electron-react-boilerplate, and many many other boilerplates