[前端工程化学习]-手写一个cli

153 阅读2分钟

手写一个cli笔记步骤

创建项目目录文件

初始化项目

  • npm init生成package.json
  • 在package.json中
  "bin": {
    "zmp": "./bin/zmp.js"
  },

  • 先安装一些依赖吧
npm install @babel/core @babel/preset-env @babel/preset-react babel-loader commander fs-extra git-promise html-webpack-plugin inquirer nanospinner webpack webpack-chain webpack-cli webpack-dev-server axios --save
  • 命令行执行npm link把指令链接起来

在项目目录下新建文件夹bin

  • bin文件夹下新建zmp.js文件
// zmp.js文件
#!/usr/bin/env node
// console.log('zmp------')
const pkg = require('../package.json')
// 新版本的写法
const { Command } = require('commander');
const program = new Command();
const cli = require('../cli')

// 设置当前脚手架版本号
program.version(pkg.version, '-v,--version')
.usage('<command> [options]');

program.command('init')
.description('创建项目')
.option('-t,--template [template]','JSON数据 HTTP的地址或者是文件的相对或绝对路径')
.action(options  => {
  // console.log('init', options)
  cli.exec('init', options)
})

program.command('dev')
.description('启动开发服务器')
.option('-t,--template [template]','JSON数据 HTTP的地址或者是文件的相对或绝对路径')
.action(options  => {
  // console.log('init', options)
  cli.exec('dev', options)
})
program.parse(process.argv);

在项目目录下新建文件夹cli

  • index.js
class ZMPScript {
  async exec(name,options) {
    await require(`./${name}`).setup(options)
  }
}
module.exports = new ZMPScript()

  • init.js下载git仓库的逻辑代码
const axios = require('axios')

const { createSpinner } = require('nanospinner')
const git = require('git-promise')
const fs = require('fs-extra')
const path = require('path')
class Init {
  templates = {}
  async checkTemplate(url) {
    const {
      data
    } = await axios.get(url)
    return data
  }
  async setup(options) {
    if (typeof options.template === 'string') {
      const templates = await this.checkTemplate(options.template);
      if (templates) {
        this.templates = templates
      }
    }
    await this.selectTemplate(this.templates)

  }
  async selectTemplate(templates) {
    console.log('this====templates', this.templates)
    const inquirer = await (await import('inquirer')).default
    const answers = await inquirer.prompt([
      {
        type: 'input',
        name: 'name',
        message: '请输入项目名称',
        default: function() {
          return 'zmp-project'
        }
      },
      {
        type:'list',
        name: 'template',
        message: '请选择模版',
        choices: Object.keys(templates)
      }
    ])
    // console.log('answer', answers)
    const gitRepo = this.templates[answers.template]
    await this.downloadRepo(gitRepo,answers.name)
  }
  async downloadRepo(repoPath,localPath) {
    const spinner = createSpinner().start()
    spinner.start({text:'downloading\n'})
    // 下载代码
    await git(`clone ${repoPath} ${localPath}`)
    spinner.success({
      text: `cd ${localPath} & npm install &npm run dev`
    })
  }
}
module.exports = new Init()
  • dev.js启动服务器的逻辑代码
const WebpackDevServer = require('webpack-dev-server')
const webpack = require('webpack')
const {getConfig} = require('../config')
class DevServer {
  async setup() {
    await this.startServer()
  }
  async startServer() {
    // const config = {devServer: {port: 1008}}
    // const config = {devServer: {}}
    const config = getConfig()
    const compiler = webpack(config)
    this.server = new WebpackDevServer(config.devServer,compiler)
    this.server.start()
  }
}
module.exports = new DevServer()

在项目目录下新建文件夹config,用来分析项目中的传入配置

  • index.js
const path = require('path')
const webpack = require('webpack')
const WebpackChain = require('webpack-chain')
const HtmlWebpackPlugin = require('html-webpack-plugin')


function processDefault(empConfig) {
  const devServer = empConfig.server || {}
  delete empConfig.server
  const mfOptions = {// 创建模块联邦选项对象
    filename: 'emp.js',// 当前容器为了对外提供模块联邦的服务,生成的单独的文件名
    ...empConfig.empShare
  }
  delete empConfig.empShare
  return {
    context: process.cwd(),
    mode: 'development',
    devtool: false,
    devServer,
    plugin: {
      html: {
        plugin: HtmlWebpackPlugin,
        args: [
          {
            template: path.join(__dirname, '../template/index.html')
          }
        ]
      },
      mf: {
        plugin: webpack.container.ModuleFederationPlugin,
        args: [mfOptions]
      }
    },
    module: {
      rule: {
        compile: {
          test: /\.js$/,
          exclude: [/node_modules/],
          use: {
            'babel-loader': {
              loader: require.resolve('babel-loader'),
              options: {
                presets: [
                  require.resolve('@babel/preset-env'),
                  require.resolve('@babel/preset-react')
                ]
              }
            }
          }
        }
      }
    },
    ...empConfig
  }
}


exports.getConfig = () => {
  const Config = new WebpackChain()
  const empConfigPath = path.resolve(process.cwd(), 'emp-config.js')
  const empConfig = require(empConfigPath)
  const afterConfig = processDefault(empConfig)
  Config.merge(afterConfig)
  console.log('config.toconfig',Config.toConfig())
  return Config.toConfig()
}

测试

zmp init -t https://static.zhufengpeixun.com/template_1680930323773.json
zmp dev

代码地址github.com/DDXY1230/zm…