前端工程化脚手架

154 阅读7分钟

一.使用脚手架原因

1.问题汇总

1659940027592.png

1659939857772.png 1659939922270.png

image.png 1659940250869.png

2.脚手架完整的实现流程

1659941423512.png

1659941618345.png

3.脚手架执行原理

vue/which/env/node本质都是脚手架

image.png

二.创建脚手架

1.脚手架的开发流程

  • 创建npm项目
//创建完项目在package.json中配置
"bin":{
    //imooc-ls这个key表示生成脚手架的名称
    //"./bin/index.js"这个value指向脚手架的源码路径
    "imooc-ls":"./bin/index.js"
}
  • 创建脚手架入口文件,最上方添加 #!/usr/bin/env node
//在入口的index.js文件最上方加上下面这段代码
#!/usr/bin/env node
  • 配置package.json,添加bin属性
  • 编写脚手架代码
  • 将脚手架发布到npm
//如果本地测试执行
//1.创建可执行文件的权限 终端执行 ./bin/index.js
//2. 执行 npm link 脚手架放到全局 执行本地测试
//3. 可在本地终端上  执行 imooc-ls

执行imooc-ls命令 出现此错误后 1665309913785.png 解决:

1Windows+R
2》输入PowerShell
3》运行 Set-ExecutionPolicy -Scope CurrentUser
4》出现ExecutionPolicy: ,填入RemoteSigned
5》输入Y
6》关闭PowerShel ,然后执行get-ExecutionPolicy,发现变成了RemoteSigned,就成功了

2.模拟实现 ls 查看文件列表功能

parseArgs.js文件

module.exports = function parse() {
  const args = process.argv.slice(2);
  let isAll = false;
  let isList = false;
  args.forEach((arg) => {
    if (arg.indexOf("a") >= 0) {
      isAll = true;
    }
    if (arg.indexOf("l") >= 0) {
      isList = true;
    }
  });
  return {
    args,
    isAll,
    isList,
  };
};

入口index.js文件

#!/usr/bin/env node
const fs = require("fs");
const parse = require("./parseArgs");
const dir = process.cwd(); //获取文件本地目录
const { args, isAll, isList } = parse();
//console.log(args, isAll, isList);
let files = fs.readdirSync(dir); //读取文件
let output = "";
if(!isAll){
  //去掉以点开头的文件
  files = files.filter((file) => file.indexOf(".") !== 0);
}
if (!isList) {
  // -a
  files.forEach((file) => (output += file + "\t"));
  console.log(output);
}else {
  //-l
  files.forEach((file,index) => {
    //最后一个元素不加 \n
    if(index === files.length - 1){
      output += file
    }else{
      output += file + "\n"
    }
  });
  console.log(output)
}

3.Macos(含linux)文件类型和存储原理

1665368789249.png

  • 3.1 nodejs 判断是不是文件夹
//-l
  files.forEach((file,index) => {

    const stat = fs.statSync(file)
    //! 第一种:node 给了stat.isDirectory()判断是不是文件夹类型
    //! 第二种:通过原理 isDir 与 操作老判断 isDir>0为文件夹 等于0则不是文件夹
    const mode = stat.mode
    const isDir = mode & fs.constants.S_IFDIR;
    //                 bin 16822 => 0100 0001 1011 0110
    //fs.constants.S_IFDIR 16384 => 0100 0000 0000 0000
     //bin目录对应的权限  0100 000 110 110 110
    //----------------  文件      rw-rw-rw-
    //两个二进制数进行 & 操作  两个都为1才为1
    console.log(mode,stat.isDirectory(),isDir>0,fs.constants.S_IFDIR)//bin文件夹 打印 16822 true true 16384
    //最后一个元素不加 \n
    if(index === files.length - 1){
      output += file
    }else{
      output += file + "\n"
    }
  });

1665370631680.png

 const auth = require('./auth')
 const getFileType = require('./getFileType')
  //在执行  -l  操作时
  files.forEach((file,index) => {
    const stat = fs.statSync(file)
    //! 2.通过原理 isDir 与 操作老判断 isDir>0为文件夹 等于0则不是文件夹
    const mode = stat.mode
    const authStr = auth(mode)
    const fileType = getFileType(mode)
    //最后一个元素不加 \n
    if(index === files.length - 1){
      output += fileType+authStr +"\t" + file
    }else{
      output += fileType+authStr +" \t" + file + "\n"
    }
  });
  console.log(output)

新建auth.js判断权限

const fs = require("fs");

module.exports = function auth(mode){
    let authString = ''
    // u 当前登陆用户的权限
    console.log(fs.constants)
    const canUserRead = mode & fs.constants.R_OK
    const canUserWrite = mode & fs.constants.W_OK
    const canUserExecute = mode & fs.constants.S_OK

    //依次类推 编写下面两种权限
    // 1.group 当前登录用户所在分组

    // 2.other 其他用户

    canUserRead ? authString += 'r' : authString += '-'
    canUserWrite ? authString += 'w' : authString += '-'
    canUserExecute ? authString += 'x' : authString += '-'
    return authString
}

新建getFileType.js 判断文件类型

const fs = require('fs')
module.exports = function getFileType(mode){
    const isDirectory = mode & fs.constants.S_IFDIR===fs.constants.S_IFDIR //判断是否为文件夹
    const isFile = mode & fs.constants.S_IFREG===fs.constants.S_IFREG //是否为文件
    const isLink = mode & fs.constants.S_IFLNK===fs.constants.S_IFLNK //

    if(isDirectory){
        return 'd'
    }else if(isFile){
        return '-'
    }else if(isLink){
        return 'l'
    }
}

4.commander的使用(完整的 node.js 命令行解决方案)

commander官网

npm install commander
//program方式创建脚手架  处理处理命令后面的参数
const { program } = require('commander')

program.option('--first')
        .option('-s,--separator <char>')
program.parse()

const options = program.opts()
const limit = options.first?1:undefined
//imooc-build --first a/b/c
console.log(program.args)//结果 ['a/b/c']
console.log(options.separator,"==")
//输入命令 imooc-build  a/b/c --first -s /   以 / 做分隔符  分割 a/b/c这个参数
console.log(program.args[0].split(options.separator,limit))// 结果 ['a']
//console.log(option)
  • 使用子命令并带有帮助描述的更完整的程序
    生成脚手架的帮助文档 imooc-build -h
    生成脚手架command的帮助文档 例如:下面的split
    imooc-build help split 或 imooc-build split -h
#!/usr/bin/env node
const { Command } = require('commander')
const program = new Command()
program
    .name('imooc-build') //脚手架名称
    .description('CLI to build javascript project') //描述 这个脚手架时干嘛的
    .version('0.0.1') //脚手架版本号

/*
脚手架默认支持两个命令
1. -h 帮助
2. -V 获取版本号
*/

//编写一个支持 split的功能
//! 通过创建 不同的command 从而让脚手架干不同的事
program
    .command('split')
    .description('Split string to array') //描述脚手架功能
    .argument('<arguments>','string to split')// 参数
    .option('--first','display just the first substring') 
    .option('-s,--separator <char>','separator char',',')//后面 , 表示默认以 , 作为分割符
    .action((args,options)=>{//在这里处理脚手架功能
        //执行命令 imooc-build split a/b/c --first -s / 
        console.log(args,options)// a/b/c   { separator: ',', first: true }
        const limit = options.first?1:undefined
        console.log(options.separator) // 结果为 /
        console.log(args.split(options.separator,limit))//['a']
        // imooc-build split a?b?c --first  上面打印 ['a']
        // imooc-build split a?b?c  上面打印 [ 'a', 'b', 'c' ]
    })
program.parse()
  • options四种调用方式
//当把下面的<char>换成[char]表示后面的字符可以不输入
1. -s <char> 例如 -s /
2. -s<char>  例如 -s/
3. --separator <char> 例如 --separator /
4. --separator=<char> 例如 --separator=/
  • option写法表示
//1.以这种写法 结果是boolean值
.option('-e,--ext','display just the first substring')
//2.以下面尖括号的写法 就是具体的value  而且必须要输入它
.option('-a,--add <string>','separator string',',')
//3.以下面中括号的写法 就是具体的value  非必须要填
.option('-a,--add [string]','separator string',',')
  • 定义全局脚手架option在所有脚手架里面都能用
//直接program.option()定义全局的 
program
  .option('-d, --debug', 'output extra debugging')
  .option('-s, --small', 'small pizza size')
  .option('-p, --pizza-type <type>', 'flavour of pizza');
  
 //可以用 program.opts()来获取这个option
const options = program.opts();
console.log(options);

const globalOption = program.optsWithGlobals()//和.opts()类似
console.log(globalOption)

//局部的option如上定义在command下  program.opts()获取不到

1665476344566.png

  • 常用选项options可以连写(细节可以参考文档关于options)

多个布尔短选项可以在破折号之后组合在一起,并且可以跟一个取值的单一选项。 例如 -d -s -p cheese 可以写成 -ds -p cheese 甚至 -dsp cheese

  • 必填的option .requiredOption() 可以添加默认值在后面再加上一个参数
program
  .requiredOption('-c, --cheese <type>', 'pizza must have cheese');
program.parse();

不填会报下面的错

$ pizza
error: required option '-c, --cheese <type>' not specified
  • Variadic option(同时输入多个参数)

定义选项时,可以通过使用...来设置参数为可变长参数。在命令行中,用户可以输入多个参数,解析后会以数组形式存储在对应属性字段中。在输入下一个选项前(---开头),用户输入的指令均会被视作变长参数。与普通参数一样的是,可以通过--标记当前命令的结束

program
  .option('-n, --number <numbers...>', 'specify numbers')
  .option('-l, --letter [letters...]', 'specify letters');

program.parse();

console.log('Options: ', program.opts());
console.log('Remaining arguments: ', program.args);
imooc-build -n 1 2 3 --letter a b c
Options:  { number: [ '1', '2', '3' ], letter: [ 'a', 'b', 'c' ] }
Remaining arguments:  []
imooc-build --letter=A -n80 operand
Options:  { number: [ '80' ], letter: [ 'A' ] }
Remaining arguments:  [ 'operand' ]
imooc-build --letter -n 1 -n 2 3 -- operand
Options:  { number: [ '1', '2', '3' ], letter: true }
Remaining arguments:  [ 'operand' ]
  • Version option(版本选项)参照官网
//执行以下  后 将-V改成 -v  并附加了说明 
program.version('0.0.1', '-v, --vers', 'output the current version');
  • More configuration(其他选项配置)参照官网
//测试 addOption
program
    .command('test')
    //imooc-build test -s
    .addOption(new Option('-s,--select','select something').hideHelp())//.hideHelp()隐藏掉-s的帮助文档
    //imooc-build test -t 10 表示延迟10秒执行
    .addOption(new Option('-t,--timeout <delay>','timeout seconds').default(60,'one minute'))//default默认60秒
    //选项功能 imooc-build test -c small 
    // -c后面只能输入['small','medium','large']这三个选项否则会报错
    .addOption(new Option('-c,--choice <choice>','you choice').choices(['small','medium','large']))
    //从环境变量里面取值  console.log(process.env) 打印出所有环境变量
    //手动指定80端口 imooc-build test -p80
    //PORT=80 TEST=1 imooc-build test 对PORT 和 TEST这两个环境变量赋值再输出  可以在git的命令行中试
    .addOption(new Option('-p,--port <number>','port number').env('PORT'))
    //对输入的结果处理 数量转换 默认支持boolean和string
    .addOption(new Option('--donate [amount]','donation in dollars').preset('20').argParser(parseFloat))
    //定义此命令和哪些命令产生冲突 互斥
    //--disable-server 这个命令和'port','choice'互斥
    .addOption(new Option('--disable-server', 'disables the server').conflicts(['port','choice']))
    .action((options,cmd)=>{
        console.log(cmd.optsWithGlobals())
    })
  • 自定义选项处理
#!/usr/bin/env node
const commander = require('commander')
const {Command,Option} = commander

function myParseInt(value){
    const intValue = parseInt(value,10)
    if(isNaN(intValue)){
        //commander自带的参数类型校验
        throw new commander.InvalidArgumentError('not a int Number')
    }
    return value
}
function increased(value,previous){
    const v = +value
    const p = previous || 0
    return v + p
}   
function collect (value,previous){
    return previous.concat([value])
    //return value.split(" ")
}
program
    .command('custom')
    //将输入的字符转成float类型
    .option('-f,--float <number>','float argument',parseFloat)
    .option('-i,--integer <number>','integer argument',myParseInt)
    //对单个参数做出累加 输入imooc-build custom --varbose 10 --varbose 20
    //结果:{ varbose: 30 }
    .option('--varbose <number>','increased',increased)
    //1.针对与集合处理 输入 imooc-build custom -c a -c b -c c
    //结果生成abc的数组:{ collect: [ 'a', 'b', 'c' ] }
    //2.输入imooc-build custom -c "a b c"要拿到上面的结果 需把collect函数注释的return打开
    .option('-c,--collect <value>','collect',collect,[])//默认[]
    .action((options,cmd)=>{
        console.log(cmd.optsWithGlobals())
    })
  • command讲解
    login的脚手架 1665562460512.png

  • .addCommand()讲解 1665563060108.png