美文网首页
企业级脚手架分析与调研

企业级脚手架分析与调研

作者: 这个前端不太冷 | 来源:发表于2022-05-21 22:09 被阅读0次
    e98439c2700d0181383771997.png

    大纲
    😁 什么是脚手架
    😁 企业级脚手架的原理
    😁 命令行工具的实现思路
    🏆 举个例子 create-react-app命令行工具
    😁 企业级脚手架模版的设计
    🏆 企业级脚手架模版的基本组成有哪些
    🏆 举个例子 UmiJS


    什么是脚手架

    编程领域中的“脚手架(Scaffolding)”指的是能够快速搭建项目“骨架”的一类工具。例如大多数的React项目都有src目录,public目录,webpack配置文件,babel配置等等,而src目录中又通常包含components目录,reducers目录等等。每次在新建项目时,你不得不手动创建这些固定的文件目录,繁琐而累赘。脚手架的作用就是帮助你完成这些重复性的工作,包括一键生成主要的目录结构、安装依赖等等。create-react-app、UmiJS就是著名的脚手架工具。

    企业级脚手架的原理

    我们一定使用过,create-react-app或vue-cli这些官方的脚手架。回忆下使用方式:

    create-react-app my-app
    vue create my-app
    

    在进入到my-app目录,从package.json文件中找到对应的命令,执行即可开发自己的项目了。
    这里我们把整个流程抽象一下:

    1. 先通过npm安装了一个包(脚手架)
    2. 这个包里有一个可执行文件,可以在全局执行对应的命令。
    3. 执行命令,会将用户输入的信息捕获,并与用户交互。
    4. 命令最终会将一个配置好的模板,拷贝到用户本地目录下,我们就可以直接用它来开发项目。
      逻辑如下:


      脚手架原理.png

    于是,可以把脚手架的实现分为2个主要部分:

    1. 命令行工具的开发;
    2. 不同场景脚手架模版的开发

    命令行工具的实现思路

    企业级脚手架命令行工具的开发,首先,我们要发布一个npm包。这样就用户就可以通过安装命令行工具的包选择相应的配置去github上拉取对应的模版代码。所以命令行工具的实现思路如下:

    1. 注册npm账户,用于后期发包
    2. 开发终端命令工具,进行命令解析,解析成功后执行相应测操作,比如create-react-app my-app,匹配到create-react-app命令后开始执行my-app工程的创建工作
    3. 命令识别成功之后,需要实现一个终端交互功能,来获取获取用户的输入信息,最终根据信息匹配出合适的模版
    4. 匹配到模版之后需要拷贝模板文件,从远程下载文件到本地->读取下载的内容->将文件中特殊标识提换成用户输入->生成对应的文件及目录->将原目录删除。

    以上思路有一些非常好用的工具可以帮助我们开发。学会将多种工具组合使用来达成自己的目标,也是一种能力。这里列出开发脚手架基础的包

    • commander:获取终端输入的参数
    • inquirer:交互式命令行工具
    • download-git-repo:下载模板
    • chalk:美化控制台显示
    • fs-extra:文件的操作,复制,粘贴,增加,删除,文件内容的新增,替换

    实现代码如下:
    1.终端命令工具命令实现:

    // 四种模板。对应我git仓库四个仓库地址
    const tempIndex = {
        react: 'reactTemplate', // react 模板路径
        vue: 'vueTemplate', // vue 模板路径
        h5: 'h5Template', // h5模板路径
        dva: 'dvaTemplate', // dva模板路径
    };
    
    let templateName; // 模板名称
    let inputIndex; // 除了拷贝模板,还支持自定义模板路径下载,
    const program = new commander.Command(packageJson.name)
        .version('v' + packageJson.version, '-v, --version')
        .arguments('<templateName>')
        .option('-f, --force', 'force delete the exist director')
        .option('-d, --directly', 'copy the not specified template')
        .alias('cp')
        .description('create-blue-cli react myProject')
        .action(function (index,name) {
            inputIndex = index;
            // 允许目标项目名和要复制的模板类型名顺序颠倒
            if (tempIndex[index] || tempIndex[name]) {
                if (tempIndex[index]) {
                    templateName = tempIndex[index];
                } else {
                    templateName = tempIndex[name];
                }
            }
            if (program.directly) {
                templateName = index;
            }
        });
    program.parse(process.argv)
    // 没有输入任何参数,报语法错误,并打印help
    if (program.args.length === 0) {
        console.log(chalk.red('syntax error'));
        program.help()
    }
    
    if (templateName) {
        inquire(templateName, program.force);
    } else {
        console.log(`the template ${inputIndex} you want download do not exist`);
    }
    
    1. 终端交互功能的实现
    const inquire = (templateName, force) => {
        inquirer.prompt([{
            type: 'list', // 开发环境
            name: 'latype',
            prefix: '****',
            suffix: '****',
            message: '请选择脚手架模版:',
            choices: [
                'react',
                'vue',
                'h5',
                'dva'
            ],
            default: 0
        }]).then(answers => {
            create(templateName, tempIndex[answers.latype], force);
        });
    }
    
    1. 拷贝模板文件:
       async function create(temp, renameFile, force = false) {
        tempName = temp;
        forceDel = force;
        const file = process.cwd().replace(/\\/g, '/') + '/';
        try {
            // 检测项目文件夹是否已存在, 若存在,抛出错误
            const res = await fs.pathExists(file);
            if (res) {
                if (forceDel) {
                    console.log(green('force remove the exist directory'));
                    await fs.remove(file);
                    downloadByGit(renameFile, tempName);
                } else {
                    // 抛出错误,并提示可以使用-f参数来强制删除已存在的项目
                    console.log(chalk.red('Error, In this directory, the project name already exsits !'));
                    console.log(chalk.green('you can use option -f to force delete the directory !'));
                }
                return;
            }
            // 若不存在,直接从git下载
            downloadByGit(renameFile, tempName);
        } catch (err) {
            console.error(red(err));
        }
    }
    

    关于chalk,这是一款颜色标记插件,将要打印的文字用不同的颜色标记出来,像下面这样:

    image.png
    1. git文件下载:
      function downloadByGit(callback, template) {
        console.log(green('start download'));
        console.log(`git@github.com:closertb/${template}.git`);
        const result = spawn(
            'git',
            ['clone', `git@github.com:closertb/${template}.git`],
            { stdio: 'inherit' }
        );
        const error = result.error;
        if (error) {
            console.log(red(error));
            return;
        }
        // 定义回调;
        callback && callback();
    }
    

    主要就这4段代码,就实现了命令的解析,和从git源端拷贝模板到本地。

    举个例子 create-react-app命令行工具

    企业级脚手架架模版的设计

    企业级脚手架的基本组成有哪些
    前端脚手架.png
    举个例子 UmiJS

    umi,中文可发音为乌米,是一个可插拔的企业级 react 应用框架,默认开启约定式路由、按需加载配置.
    umi跟create-react-app有什么区别呢?大多数前端同学都试过用cra来搭react项目吧,每次总是要花很多时间来配置typescript/less/css modules,还有配套的react router/redux等,搭建一个基础项目就能花上好几天,而umi这个企业级的前端应用框架,则已经帮我们内置了很多前端开发常用的功能,让我们节省了很多时间去配置,做到真正的开箱即用。

    执行npx @umijs/create-umi-app初始化出来的react项目,天然就支持typecsript、less、css modules,并整合了antd、dva(阿里自研的数据流方案),提供国际化、权限、数据流、配置式路由等开发者常用的功能,能够节省大量的初始化项目时间。

    抄个架构图

    Umijs.png
    约定化的思想

    在使用umi框架的时候,很容易就发现它很多东西都是约定式的。所谓约定式就是指,按照约定好的方式开发,就能达到某种效果,中间的过程由框架帮我们完成,特别适合我们这种懒懒的开发。

    • 建一个 locales 目录,就拥有了国际化
    • 建一个 models 目录,就拥有了数据流
    • 建一个 mock 目录,就拥有了数据 mock
    • 建一个 access.ts 文件,就拥有了权限策略
    • ...

    这看起来是非常黑盒非常酷的,用这种方式其实对于团队代码风格的统一是非常有好处的,直接在框架层面就约束了大家的目录组织模式,便于团队维护。

    但是缺点也是挺明显的,灵活性不如配置式的高,因为只能按特定的模式来开发,如果原本约定的方式不满足业务需求,就需要额外开发umi插件来魔改原本的功能。而且约定式的开发是相对其他框架来说很特别的一点,对新上手的同学来说需要时间去通读官网文档了解约定式的规则。

    约定式路由

    约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过src/pages目录和文件及其命名分析出路由配置, 也就是让umi根据约定好的目录结构帮我们生成路由配置文件。

    比如以下文件结构:

      └── pages
        ├── index.tsx
        └── users.tsx
        └── setting
           └── index.tsx
    

    会得到以下路由配置,

    [
      { exact: true, path: '/', component: '@/pages/index' },
      { exact: true, path: '/users', component: '@/pages/users' },
      { exact: true, path: '/setting', component: '@/pages/setting/index' }
    ]
    
    

    需要注意的是,满足以下任意规则的文件不会被注册为路由,

    • 以.或_开头的文件或目录
    • 以d.ts结尾的类型定义文件
    • 以test.ts、spec.ts、e2e.ts结尾的测试文件(适用于.js、.jsx和.tsx文件)
    • components和component目录
    • utils和util目录
    • 不是.js、.jsx、.ts或.tsx文件
    • 文件内容不包含 JSX 元素

    动态路由

    src/pages/users/[id].tsx 生成的对应path /users/:id

    嵌套路由

    └── pages
        └── users
            ├── _layout.tsx
            ├── index.tsx
            └── list.tsx
    

    生成的对应path

    [
      { exact: false, path: '/users', component: '@/pages/users/_layout',
        routes: [
          { exact: true, path: '/users', component: '@/pages/users/index' },
          { exact: true, path: '/users/list', component: '@/pages/users/list' },
        ]
      }
    ]
    

    全局Layout

    src/layouts/index.tsx

    [
      { exact: false, path: '/', component: '@/layouts/index',
        routes: [
          { exact: true, path: '/', component: '@/pages/index' },
          { exact: true, path: '/users', component: '@/pages/users' },
        ],
      },
    ]
    
    

    404路由

    src/pages/404.tsx

    生成的对应path

    [
      { exact: true, path: '/', component: '@/pages/index' },
      { exact: true, path: '/users', component: '@/pages/users' },
      { component: '@/pages/404' },
    ]
     
    

    项目结构

    ├── /dist/           # 项目输出目录
    ├── /mock/           # 数据mock
    ├── /public/         # 公共文件,编译时copy至dist目录
    ├── /src/            # 项目源码目录
    │ ├── /components/   # UI组件及UI相关方法
    │ ├── /layouts/      # 全局组件
    │ │ └── index.js     # 入口文件
    │ ├── /models/       # 数据模型
    │ ├── /pages/        # 页面组件
    │ │ └── index.js     # html模版
    │ ├── /services/     # 数据接口
    │ ├── /themes/       # 项目样式
    │ │ ├── default.less # 全局样式
    │ │ └── vars.less    # 全局样式变量
    │ ├── /utils/        # 工具函数
    │ │ ├── config.js    # 项目常规配置
    │ │ ├── menu.js      # 菜单及面包屑配置
    │ │ ├── config.js    # 项目常规配置
    │ │ ├── request.js   # 异步请求函数(axios)
    │ │ └── theme.js     # 项目需要在js中使用到样式变量
    ├── package.json     # 项目信息
    ├── .eslintrc        # Eslint配置
    └── .umirc.js        # umi配置
    └── .umirc.mock.js   # mock配置
    └── .theme.config.js # 主题less编译配置
    

    .umirc.js

    alias: { // webpack 额外配置一些文件夹的快速访问
        themes: resolve(__dirname, './src/themes'),
        components: resolve(__dirname,"./src/components"),
        utils: resolve(__dirname,"./src/utils"),
        config: resolve(__dirname,"./src/utils/config"),
        enums: resolve(__dirname,"./src/utils/enums"),
        services: resolve(__dirname,"./src/services"),
        models: resolve(__dirname,"./src/models"),
        routes: resolve(__dirname,"./src/routes"),
    }
    

    约定式路由,建立文件夹就生产路由
    类 next.js 的约定式路由,无需再维护一份冗余的路由配置,支持权限、动态路由、嵌套路由等等。

    云端打包会把package.json ^ 开头的包都拉当下大版本最新的包安装来打包

    相关文章

      网友评论

          本文标题:企业级脚手架分析与调研

          本文链接:https://www.haomeiwen.com/subject/plecprtx.html