Following our last post, we will discuss the architecture of ESLint. Architectural views, styles and design patterns will be discussed. Also, deployment and non-functional properties will be covered. This post gives an overview of the architecture of ESLint.
Relevant architectural views
In the book, Software Systems Architecture 1, Rozanski and Woods describe the concept of using viewpoints and views to aid in the architectural design process. For basic systems, it is possible to create a single architectural design for the complete system. However, for larger complex systems, this is not feasible. You can, however, model smaller parts of the system which together describe the architecture of the complete system. This is what Rozanski and Woods called an architecture view on your system.
“An architectural view is a way to portray those aspects or elements of the architecture that are relevant to the concerns the view intends to address—and, by implication, the stakeholders for whom those concerns are important.” - Software Systems Architecture by N. Rozanski and E. Woods 1
In the book of Rozanski and Woods, a couple of views for a system are presented, we will use these views for our analysis. However, every architect is, of course, free to choose what views to use for their system, First, we will describe these views shortly, based on the description given in the book 1:
- Functional: Describes the system’s functional elements, their responsibilities, interfaces, and primary interactions.
- Information: Describes the way that the architecture stores, manipulates, manages, and distributes information.
- Concurrency: Describes the concurrency structure of the system that identifies the parts of the system that can be executed concurrently and how this is coordinated and controlled.
- Development: Describes the architecture that supports the software development process.
- Deployment: Describes the environment into which the system will be deployed, including capturing the dependencies the system has on its runtime environment.
- Operational: Describes how the system will be operated, administered, and supported when it is running in its production environment.
For each of these views we will analyze how relevant these views are for ESLint:
- Functional: The functional view is an essential part of the architectural model. The functional view will always be important in systems, since the goal and use of the system should be clear to all.
- Information: ESLint is focussed on processing source code. It does not need expansive databases or distributed storage systems to function correctly. Therefore this view is a less important one for ESLint.
- Concurrency: This is an important view since a vast amount of files are to be scanned. If it was possible to be done in parallel, this could be a nice improvement. However, it is not detrimental if this is not done since it is not weird to expect a system that scans all source files to take a bit of time to complete. Therefore this view is relevant, up to a point.
- Development: Since the ESLint project is open-source, the development view is definitely one to be focussed on. ESLint provides excellent documentation for developers on how to contribute to the ESLint project.2
- Deployment: The deployment view is reasonably important since ESLint has to be easily usable. Without a good view on how and why users use ESLint, making decisions about the deployment of ESLint will be much harder.
- Operational: The operational view is less relevant for ESLint since it is deployed as a package. Once it is installed, users can update the package to a newer version, apart from that, it ‘just works’.
Architectural Style and Design Patterns
On the first inspection of the organization of the system, it is clear that ESLint is rigorously based on the architectural style of componentization. This is also documented by themselves3 and will be elaborated upon in the next section(s). The figure below shows the separation of components. Components such as
Source-Code do not have dependencies, because other components, like
Linter, tie these together. The isolated modules allow for better testing, higher code quality and lower the threshold for new contributors since they can get up to speed rapidly. These are important properties for a widely used system that is open-source.
If we venture a step lower, we can’t ignore the strategy design pattern4. This pattern solves the problem of adding and using new rules with custom linting behavior, without changing the other components such as the actual
Linter engine or source code parser
Source-Code. This is closely related to the open-closed principle, that states:
“software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”5
Overview of the ESLint system
In this section the development view will be discussed of ESLint. This describes the architecture that supports the software development process. The stakeholders who have the most use for this point of view are the software developers and testers.
First, we start by having a look at the required tools to develop in ESLint and the general structure of the modules in the repository. To develop in ESLint, 2 external tools are required:
The directory structure of the repository looks as follows6:
- bin: executable files that are available when ESLint is installed
- conf: default configuration information
- docs: documentation for the project
- lib: contains the source code
- formatters: all source files defining formatters
- rules: all source files defining rules
- tests: the main unit test folder
- lib: tests for the source code
- formatters: tests for the formatters
- rules: tests for the rules
- lib: tests for the source code
ESLint consists primarily of unit tests. For most files in the source folder there is a corresponding file in the test folder. To develop unit tests the npm module Mocha is required. Mocha provides a skeleton code for unit tests and thus are needed if developers intend to make contributions in ESLint. Finally the tests are run through npm.
ESLint architecture during run-time
ESLint provides a CLI interface that is used to execute ESLint [^eslintcli]. Once installed, this binary file, called
eslint can be found in the
./node_modules/bin/. The user uses this binary
eslint file to execute ESLint. Integrations with ESLint, like with text editors, also use this binary file to execute ESLint.
The flow of the ESLint project during run-time is as shown in the following diagram from the ESLint architecture documentation3:
We can see that this flow starts from the
eslint component. We will now explain the flow of ESLint and each of its components during run-time3, starting from the
eslintbinary with options and the patterns (a format that allows the user to specify certain files or folders) for all files. This file then calls
cli.jswhile passing on all arguments.
cli.jsparses all options and the patterns. Next, performs some logging when specified by the user and validates all options specified by the user. Next a
cli-engine` instance is created using the options and file patterns.
cli-engineiterates all files and uses a
linterinstance to get all the results. This
linterinstance is created using the options passed along to the
linteris the final ‘major’ component. The linter inspects the source code, execute rules on the source code and reports the results. If the user specified the code to be fixed, then the linter also fixes the problems in the source code.
Deployment of ESLint
As explained in the section “Relevant architectural views”, the deployment view describes the environment into which ESLint will be deployed including the dependencies it has on its runtime environment, according to Rozanski and Woods1. In this section, we will discuss how ESLint is deployed. Before deployment, the system should be tested and should be ready to go live. Otherwise, a potentially broken product will be delivered to the users.
First, we discuss the deployment requirements. The hardware requirements of ESLint are straightforward. A user needs a computer with internet connection that can run NodeJS (version
>=11.10.1, retrieved March 14th11). Apart from hardware, there are also third-party software requirements. The key player is the NPM packaging system, which is a software registry system that end-users can use to deploy packages into their projects. The ESLint team deploys a new package every week12, which is done by making the new changes available in their NPM package. NPM takes care of a large number of deployment concerns, such as the installation of the tool, storage and resolving potential dependency conflicts between ESLint and other packages13.
The complete deployment process is shown in the image below. After changes are made, Jenkins is used to scheduling a release build. Jenkins is an open-source automation server that helps to build, deploying and automating projects14. At some point, a six-digit 2FA code has to be entered from an authenticator app12. After deployment, the ESLint maintainers keep watching the release to verify that it functions as intended. The deployment is cross-platform, ESLint can be deployed to any operating system12.
ESLint itself is lightweight, runs on its own and does not have many requirements. For example, it does not need an internet connection to function properly. All it needs is a bit of storage space and processing power. Users can customize the configuration of ESLint to adapt the system to their needs15.
Nick Rozanski and Eoin Woods. Software Systems Architecture: Working with Stakeholders Using Viewpoints and Perspectives. Addison-Wesley, 2012, 2nd edition. ↩ ↩2 ↩3 ↩4
Meyer, B. (1988). “Object Oriented Software Construction”, Printice Hall. Inc., Upper Saddle River, NJ, USA. ↩