How San CLI Works Under the Hood: Core Modules, Workflow, and Plugin Architecture
This article dissects San CLI's internal architecture, detailing its core modules, the distinction between main and service processes, the step‑by‑step execution of the san init command, the TaskList implementation that drives sequential tasks, and the design of Command and Service plugins that enable extensibility.
Core Modules
san-cli: orchestrates the main workflow and core functionality. san-cli-service: Service layer that handles Webpack‑related logic. san-cli-command-init: implements the san init command as a Command plugin. san-cli-plugin-*: generic Service plugins. san-cli-utils: utility library usable by plugins. san-cli-webpack: shared Webpack build and dev‑server logic, including custom plugins.
Core Concepts
The architecture is built around two concepts: processes (main process and Service process) and plugins (Command plugins and Service plugins).
The main process is defined in san-cli/index.js. When a user runs a command such as san init, san serve or san build, the corresponding handler is invoked. Some handlers delegate to the Service process via san-cli-service.
The Service process, defined in san-cli-service/Service.js, mainly handles Webpack build logic.
Overall Workflow
Check Node version.
Check San CLI version.
Instantiate a command object via san-cli/lib/Commander.js and add global options and middleware (e.g., set logLevel, NODE_ENV, enrich argv with logging utilities).
Load built‑in commands ( init, serve, build, inspect, ui, etc.).
Load custom commands declared in package.json and sanrc.json.
Trigger the selected command’s handler to start execution.
san init Command
The san init command creates a new project by cloning a scaffold repository (or using a local template) and processing the files with vinyl-fs, the core of Gulp.
Its workflow consists of four serial tasks:
Check the target directory and offline package status.
Download the scaffold from a remote repository (e.g., GitHub) into a cache.
Generate the project directory structure by streaming files from the cache to the target directory using vinyl-fs.
Prompt the developer to install dependencies listed in package.json.
TaskList Implementation
The command uses a TaskList class to run the four tasks sequentially. A simplified usage example:
const taskList = [checkStatus, download, generator, installDep];
const tasks = new TaskList(taskList);
tasks.run();Each task receives a task object with a complete method. When a task finishes, it calls task.complete(), which advances the index and runs the next task. The simplified implementation is:
class TaskList {
constructor(taskList) {
this._taskList = taskList;
this._index = 0;
}
run() {
const currentTask = this._taskList[this._index];
currentTask.complete = () => this.next();
currentTask(currentTask);
}
next() {
this._index++;
if (this._index >= this._taskList.length) return;
this.run();
}
}This design makes adding new tasks trivial—just append a function to the task array.
Plugin Mechanism
Command Plugins
Command plugins extend the yargs command system. A typical Command plugin exports a command name, a builder for options, a desc, and a handler function. The plugin is declared in package.json. When any San command runs, San CLI reads the declared commands, registers them with yargs, and finally invokes the matching handler.
exports.command = 'hello';
exports.builder = { name: { type: 'string' } };
exports.desc = 'Greet the given name warmly';
exports.handler = argv => {
console.log(`${argv.name}, hello!`);
};Service Plugins
Service plugins are similar to Vue CLI’s Service plugins but with differences. They are loaded from built‑in plugins, sanrc.json, and san.config.js. The Service class loads these plugins, creates a PluginAPI instance for each, and calls the plugin’s apply method, allowing the plugin to modify the Webpack configuration via methods like configWebpack.
class Service {
constructor(plugins) {
this.plugins = this.loadPlugin(plugins);
}
run() {
const projectOptions = this.loadProjectOptions();
const morePlugins = this.loadPlugin(projectOptions.plugins);
this.plugins = [...this.plugins, ...morePlugins];
this.plugins.forEach(plugin => {
const pluginApi = new PluginAPI();
plugin.apply(pluginApi);
});
}
// loadPlugin, loadProjectOptions omitted for brevity
}The PluginAPI provides helper methods such as configWebpack(fn) for plugins to adjust the Webpack config.
class PluginAPI {
configWebpack(fn) {
// implementation omitted – registers a function to modify the webpack config
}
}Service Flow
Parse the command line (e.g., san serve) and locate the corresponding handler in san-cli/commands/serve.
The handler instantiates Service with the list of built‑in Service plugins and those declared in sanrc.json. Service.run() loads environment variables, reads san.config.js for project‑specific options, merges built‑in and project plugins, and executes each plugin’s apply method via a PluginAPI instance.
During execution, plugins can modify the Webpack configuration, add webpack chain callbacks, or perform other build‑time tasks.
Repository
https://github.com/ecomfe/san-cli
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
