One, foreword
Scaffolding can quickly generate project templates, save the time to create and configure projects, and improve development efficiency; For example, Vue, Angular, and React each generate CLI tools for scaffolding. Recently, we used the architecture of micro-front-end to develop the project. Taking the micro-front-end application as an example, we developed a CLI tool to help us quickly generate the project template.
Project address: github.com/limzgiser/m…
Two, micro front-end CLI
1. Prepare templates
Find a few front-end framework templates from the web in advance, use different branches to manage them, and upload them to Github. A console query is then used to let the user enter commands to download templates from the specified branch.
I found a few test templates and uploaded them to my Github address: github.com/limzgiser/m…
2. Related plug-ins
-
art-template
- A template engine
- Replace placeholders in the template file with variables entered by the user.
-
chalk
- Node terminal style library
- Change the console output style
-
commander
- Node.js command line interface solution
-
download-git-repo
- Node downloads and extracts a Git repository (GitHub, GitLab, Bitbucket)
-
execa
Javascript libraries that call the shell and the local external program start the child process to execute
-
fs-extra
- Expand the node fs
-
inquirer
- User and command line interchange tool
-
lodash
- Js utility library, provide rich tool functions
-
ora
- Elegant terminal wheel
These plug-ins are the main ones to use. You can search NPM or Github to see how plug-ins are used.
3. Obtain user input parameters
Create the create command using the COMMANDER plug-in to get user input parameters. When creating a project, users are required to enter appType (create main application or child application), frameType (which framework is used to implement the application), install (whether project dependencies are automatically installed), pkG-took (NPM or YARN is used if dependencies are installed).
// index.ts import { Command } from 'commander'; import create from "./actions/create"; const program = new Command('mfc'); Program.version ('0.0.1') program.usage ('create <projectName> [options]').command('create <projectName>') .description(' Create item ').option('-a --appType [value]', 'Select application type: Main application (main) | child (child) '). Option (' -t - frameType [value] ', 'select frame type: Vue | react | presents'). Option (' -i - install ', 'whether installed automatically dependence, false). Option (' - pt - PKG - tool [value]'. 'npm or yarn?') .action(create); program.parse(process.argv)Copy the code
Action inputs a callback function, where we can retrieve the user’s input parameters. For example, enter a main application written in VUE in the console and automatically install the dependencies of the project using YARN once it is created.
Export default async function (projectName: string, options: CreateOptions) {console.log(' projectName: ', projectName); Console. log(' Input parameters: ', options); }Copy the code
4. Download the remote template
Download the project template for the specified branch to the remote repository based on the application type and framework type parameters entered by the user. The branch name is named “application type +’_ ‘+ framework type”. When the user creates a project, if the application type and frame type parameters are not specified, the user is asked in the console to select them.
Async function getTplParams(options: CreateOptions) {let appType = ", frameType = "; if (! Options.apptype) {// If no appType is entered, ask the user to select const answers = await inquirer. Prompt ([appTypeQues]); appType = answers.appType; } else { appType = options.appType; } if (! Options.frametype) {// Ask the user if no project name is entered, and let the user select const answers = await inquirer. Prompt ([frameTypeQues]); frameType = answers.frameType; } else { frameType = options.frameType; } return { appType, frameType } } // inquirers.ts const appTypeQues = { type: 'list', name: 'appType', choices: ['main', 'child'], default: 'NPM ', message:' Select the application type, main or child! ' } const frameTypeQues = { type: 'list', name: 'frameType', choices: ['vue', 'react', 'angular'], default: 'NPM ', message:' Select application framework! '}Copy the code
To get application type and framework type parameters, you can concatenate component names based on the parameters and download project templates from remote repositories.
let { appType, frameType } = await getTplParams(options); let branch = appType + "_" + frameType; Try {const spinner = ora(chalk. Blue (" initialization template... )).start(); await downloadTemplate( 'direct:https://github.com/limzgiser/mfc-cli.git#' + branch, projectName, { clone: true } ); Spinner. Info (' Template initialization succeeded '); } catch (error) { }Copy the code
5. Work with templates
How to change variables in the template dynamically, for example, replacing the name value of package.json with the project name entered when the project was created. First, recursively get the file path in the project directory and identify whether the current path is a folder. Extract the list of file paths, then iterate over the read file, using the template engine to replace variables in the file. Finally, the replaced content replaces the original content.
Function recursiveDir(sourceDir: string) {const res: FileItem[] = []; function traverse(dir: string) { readdirSync(dir).forEach((file: string) => { const pathname = `${dir}/${file}`; const isDir = statSync(pathname).isDirectory(); res.push({ file: pathname, isDir }); if (isDir) { traverse(pathname); } }) } traverse(sourceDir); return res; Function tplFile(projectName: string, files: Array<FileItem>) {files.foreach (item => {if (! item.file.includes('assets') && ! item.file.includes('public') ) { const content = template(process.cwd() + '/' + item.file, { projectName }); let dest = item.file; if (dest.includes('.art')) { unlinkSync(dest); dest = dest.replace(/\.art/, ''); } writeFileSync(dest, content); }}); }Copy the code
6. Install dependencies
If the user specified automatic installation dependencies when creating the project, go directly to what tool is used to install the dependencies, NPM or YARN? If the user does not specify an auto-install dependency, ask the user, do you want to install the dependency? If no, skip the installation. If yes, ask the user to use NPM or YARN. If yarn is selected, you can check whether yarn is installed. If no, you are prompted to install YARN first.
Spinner. Info (' Template initialization succeeded '); const cwd = './' + projectName; if (options.install) { installPkg(options.pkgTool, cwd); } else { const answers = await inquirer.prompt([ installQues, { ...pkgToolQues, when(currentAnswers) { return currentAnswers.install && !options.pkgTool; } } ]); if (answers.install) { installPkg(answers.pkgTool || options.pkgTool, cwd); } else {console.log(chalk. Green (' project created successfully ')); }} / / install depend on package async function installPkg (pkgTool: "NPM" | "yarn", CWD: string) {let tool = pkgTool; if (! tool) { const answers = await inquirer.prompt([pkgToolQues]); tool = answers.pkgTool; } if (tool === 'yarn' && ! HasYarn ()) {console.log(chalk. Red (' please install yarn')); } else {const spinner = ora(' setting dependency... ')).start(); await exec(tool + ' install', { cwd }); Spinner. Succeed (chalk. Green (' project created successfully ')); Function exec(command: string, options: execa.Options) { return new Promise((resolve, reject) => { const subProcess = execa.command(command, options); subProcess.stdout! .pipe(process.stdout); subProcess.stdout! .on('close', resolve); subProcess.stdout! .on('error', reject); }); } function hasYarn(): Boolean {try {execa.commandSync('yarn -v', {stdio: 'ignore'}); return true; } catch (error) { return false; }}Copy the code
7. Create subcommands
You can use the subcommand (add) to add components, directives, and other templates to the created project. Here is a command to create a component. Component templates do not need to be downloaded from a remote repository and can be defined in a local directory. You can create. Vue and. TSX components.
program.addCommand(childCommand(Command)); export default function (Command: CommandConstructor) { const generate = new Command('add'); Generate.command ('c <name>').description(' add a component ').option('-- TSX ', 'Is TSX ', false).action(addComponent); return generate; } import template from "art-template"; import { join } from "path"; import { outputFileSync } from "fs-extra"; import { kebabCase } from "lodash"; import chalk from "chalk"; export default function (name: string, options: { tsx: boolean; }) { let basePath = 'components'; let trueName = name; const data = name.split('/'); if (data.length > 1) { trueName = data.pop()! ; basePath = data.join('/'); } let suffix = '.vue'; if (options.tsx) { suffix = '.tsx'; } try { const content = template( join(__dirname, '.. /.. /templates', 'component' + suffix), { name: trueName, rootCls: kebabCase(trueName) } ); const dest = `src/${basePath}/${trueName}${suffix}`; outputFileSync(dest, content); Console. log(chalk. Green (' created successfully >>', dest)); } catch (e) {console.log(chalk. Red (' create failed ')); throw e; }}Copy the code
8. Compile TS
Use gulP to compile the project. Create the gulp.ts file in the root directory:
import { src, dest, series } from 'gulp';
import del from 'del';
import gts from 'gulp-typescript';
const outputDir = 'dist';
on clean() {
return del(outputDir);
}
function script() {
return src('src/**/*.ts', { base: 'src' })
.pipe(gts.createProject('tsconfig.json')())
.pipe(dest(outputDir));
}
export default series(clean, script);
Copy the code
Add script command to package.json to perform compilation of ts project.
"scripts": {
"build": "gulp"
},
npm run build
Copy the code
9. Test release scaffolding
Add bin to package.json.
"bin": {
"mfc": "index.js"
},
Copy the code
Create index.js in the root directory
#! /usr/bin/env node
require('./dist/index');
Copy the code
Console execution script
cnpm link
Copy the code
Test scaffold
mfc create child-vue
Copy the code
10. Release scaffolding
Specify the files included in the publication in package.json, and the items defined in the Files array are uploaded to NPM.
"files": [
"templates",
"dist",
"index.js",
"package.json",
"readme.md"
],
Copy the code
When THE CLI is installed as a library, the dependencies in devDependencies are not installed, so the plug-ins used after the CLI is published are explicitly development dependencies.
npm login
npm publish
Copy the code
Third, summary
Detailed can refer to [front-end automation master training camp], years ago, has not been in practice. We are currently developing projects using a microfront-end architecture. A CLI tool was written for this scenario and should be improved in the future.
Iv. Reference resources
Front end automation master training camp 】 【 www.bilibili.com/video/BV11K…