前端脚手架原理和实战

83 阅读4分钟

我正在参加「掘金·启航计划」

什么是脚手架?

操作系统的可执行文件,可以通过java、python等语音编写

脚手架执行流程

以vue举例

1.在终端输入 vue create my-app

2.在环境变量 $PATH 中查询 vue 命令(相当于执行 which vue)

3.查询实际链接文件

4.通过 /usr/bin/env node 执行文件

脚手架执行原理

  1. 在终端输入 vue create my-app
  2. 终端解析出 vue 命令
  3. 终端在环境变量中找到 vue 命令
  4. 终端根据 vue 命令 链接到实际文件 vue.js
  5. 终端利用 node 执行 vue.js
  6. vue.js 解析 command/options
  7. vue.js执行 command
  8. 执行完毕,退出执行

脚手架开发入门

脚手架开发流程

  1. 创建 npm 项目
  2. 创建脚手架入口文件,最上方添加
#!/usr/bin/env node

3.配置 package.json, 添加 bin 属性

4.编写脚手架代码

5.将脚手架发布到 Npm

脚手架搭建和本地调试

1.新建npm 项目

mkdir cf-ls
cd cf-ls
npm init -y

2.配置 package.json

"bin": {
    "cf-ls": "./bin/index.js"
},

3.新建入口文件

#!/usr/bin/env node

console.log('hello world')

4.通过link方式进行本地调试,在终端输入npm link

npm link

5.在终端输入 cf-ls,就能看到本地的效果, 也可以看到软链接的指向。

实现 ls 的功能

参数解析

在Node中 通过 process.argv获取参数

#!/usr/bin/env node

console.log(process.argv)

编写 封装 参数 解析方法

// parseArgs.js
module.exports = function parse(){
    let isAll = false; // -a
    let isList = false; // -l
    const args = process.argv.slice(2)
    console.log(args)

    args.forEach(arg => {
        if(arg.indexOf('a') >= 0){
            isAll = true;
        }
        
        if(arg.indexOf('l') >= 0){
            isList = true;
        }
    })
    return {
        isAll,
        isList,
        args
    }
}

实现 ls 的效果

#!/usr/bin/env node

const fs = require('fs')
const parse = require('./parseArgs')

const dir = process.cwd();

const { isAll, isList, args} = parse()

if(!isAll && !isList){
    let files = fs.readdirSync(dir);
    // 过滤 以 . 开头的文件
    files = files.filter(file => file.indexOf('.') !== 0)

    // 格式化
    let output = '';
    files.forEach(file => output+=file + '\t')
    // 打印
    console.log(output)
}

看下效果

实现 ls -a

#!/usr/bin/env node

const fs = require('fs')
const parse = require('./parseArgs')

const dir = process.cwd();

const { isAll, isList, args} = parse()

let files = fs.readdirSync(dir);
let output = '';
if(!isAll && !isList){
    // 过滤 以 . 开头的文件
    files = files.filter(file => file.indexOf('.') !== 0)
    // 格式化
    files.forEach(file => output+=file + '\t')
}else if(isAll && !isList){
    // 实现 ls -a
    files.forEach(file => output+=file + '\t')
}
// 打印
console.log(output)

实现 ls -l

#!/usr/bin/env node

const fs = require('fs')
const parse = require('./parseArgs')

const dir = process.cwd();

const { isAll, isList, args} = parse()

let files = fs.readdirSync(dir);
let output = '';
if(!isAll){
// 过滤 以 . 开头的文件
files = files.filter(file => file.indexOf('.') !== 0)
}
if(!isList){
    // 格式化
    files.forEach(file => output+=file + '\t')
}else{
    // ls -l
    files.forEach((file, index) => {
        if(index === files.length - 1){
            output+=file
        }else{
            output+=file + '\n'
        }
    })
}
// 打印
console.log(output)

文件系统

bin: rwx r-x r-x
package.json: rw- r-- r--

r: 访问,w: 编辑 x: 执行
分三组:
第一组:u:当前登录用户
第二组:g:当前登录用户所在分组
第三组:o:其他用户


修改权限:
chmod +r file

\

Unix存储文件类型和权限

Unix使用32位二进制存储文件类型和权限

0000 0000 0000 0000

分组:
0000(文件类型), 000(特殊权限), 000(用户权限), 000(分组权限), 000(其他权限)

实现ls -l显示读写权限

// auth.js
const fs = require('fs')
module.exports = function auth (mode){
    let authStr = ''
    // user
    const canUserRead = mode & fs.constants.S_IRUSR;
    const canUserWrite = mode & fs.constants.S_IWUSR;
    const canUserExecute = mode & fs.constants.S_IXUSR;

    // group
    const canGroupRead = mode & fs.constants.S_IRGRP;
    const canGroupWrite = mode & fs.constants.S_IWGRP;
    const canGroupExecute = mode & fs.constants.S_IXGRP;

    // other
    const canOtherRead = mode & fs.constants.S_IROTH;
    const canOtherWrite = mode & fs.constants.S_IWOTH;
    const canOtherExecute = mode & fs.constants.S_IXOTH;

    canUserRead ? authStr+= 'r' : authStr+='-'
    canUserWrite ? authStr+= 'w' : authStr+='-'
    canUserExecute ? authStr+= 'x' : authStr+='-'


    canGroupRead ? authStr+= 'r' : authStr+='-'
    canGroupWrite ? authStr+= 'w' : authStr+='-'
    canGroupExecute ? authStr+= 'x' : authStr+='-'

    canOtherRead ? authStr+= 'r' : authStr+='-'
    canOtherWrite ? authStr+= 'w' : authStr+='-'
    canOtherExecute ? authStr+= 'x' : authStr+='-'

    return authStr;
}

在index.js中引入auth.js

const auth = require('./auth')

const authStr = auth(mode)

if(index === files.length - 1){
            output+= authStr + '\t' + file
        }else{
            output+= authStr + '\t' + file + '\n'
        }

效果:

实现 ls -l 显示是否文件夹、文件、链接

// getFileType
const fs = require('fs')
module.exports = function getFileType(mode){
    const isDir = mode & fs.constants.S_IFDIR;
    const isFile= mode & fs.constants.S_IFREG;
    const isLink= mode & fs.constants.S_IFLNK;
    

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

在Index.js中引入

const  getFileType = require('./getFileType')


 const fileType = getFileType(mode)

        if(index === files.length - 1){
            output+= fileType + authStr + '\t' + file
        }else{
            output+= fileType + authStr + '\t' + file + '\n'
        }

效果:

使用id获取当前用户信息

在终端输入id,可以显示当前用户的权限信息,如下:

从上图看出,当前uid是502,在终端输入 id -un 502, 就能获取当前用户名

在终端输入 id -gn 502,就能获取当前所在组

在终端输入 id -Gn 502 获取所有组

因为是在另一个进程打印,所以需要使用Nodejs提供的child_process来实现在另外一个子进程获取这些信息。

const cp = require('child_process');

module.exports = function getFileUser(stat){
    const { uid, gid } = stat;
    let userName = cp.execSync('id -un ' + uid).toString().trim();
    const groupIdsStr = cp.execSync('id -G ' + uid).toString().trim();
    const groupIds = groupIdsStr.split(' ');
    const groupIdsNameStr = cp.execSync('id -Gn ' + uid).toString().trim();
    const groupIdsName = groupIdsNameStr.split(' ');
    const index= groupIds.findIndex(id => +id === + gid)
    const groupName = groupIdsName[index];
     
    return userName+  ' ' + groupName;
}

在index.js中引入

const  getFileUser = require('./getFileUser')


const fileUser = getFileUser(stat)
        if(index === files.length - 1){
            output+= fileType + authStr + '\t' + fileUser + '\t' + file
        }else{
            output+= fileType + authStr + '\t' + fileUser + '\t' + file + '\n'
        }

效果:

实现 ls -l 显示创建时间和文件容量

// getFileSizeDate.js
module.exports = function getFileSizeAndDate(stat){
    const { mtimeMs, size  } = stat;
    const birthTime = new Date(mtimeMs)
    const month = birthTime.getMonth() + 1;
    const date = birthTime.getDate();
    const hour = birthTime.getHours();
    const minute = birthTime.getMinutes();

    return size + ' '+month + '月  ' + date + ' ' + hour + ' ' + minute;
}

在index.js中引入

const  getFileSizeAndDate = require('./getFileSizeAndDate')

const fileSizeDate  = getFileSizeAndDate(stat);

        if(index === files.length - 1){
            output+= fileType + authStr + '\t' + fileUser + '  '+ fileSizeDate + '  ' + file
        }else{
            output+= fileType + authStr + '\t' + fileUser + '  ' + fileSizeDate + '  ' + file + '\n'
        }

效果:

实现 文件夹下级文件数量

// index.js
let size = 1;
        if(isDir){
            // 下级文件
            const subDir = fs.readdirSync(file)
            size = subDir.length;
        }


if(index === files.length - 1){
            output+= fileType + authStr + ' ' + size  + '\t' + fileUser + '  '+ fileSizeDate + '  ' + file
        }else{
            output+= fileType + authStr  + ' ' + size+ '\t' + fileUser + '  ' + fileSizeDate + '  ' + file + '\n'
        }

效果:

增加 自动化测试

安装 mocha

npm install mocha --save-dev

在根目录创建test文件夹

举个例子:新建test.js

const assert = require('assert');

describe('Array', function(){
    describe('#indexOf()', function(){
        it('should return -1 when the value is not present', function(){
            assert.equal([1,2,3].indexOf(4), -1)
        })
    })
})

配置scripts

"scripts": {
    "test": "mocha test/test.js"
  },

执行 npm run test

测试通过