likes
comments
collection
share

💯 面试官:来说说你的脚手架是做模板扩展的?

作者站长头像
站长
· 阅读数 68

因为最近一直在做一个脚手架,所以在简历上自然也就把它放到了项目中的最前位置。在关于这个脚手架中被问到了一个这样的问题:你的脚手架是怎么样做模板扩展的,就类似于我要添加 axios 或者 router 这些?

其实这个问题的解法有很多中,那么接下来我们就来看看如何实现这个问题,在这篇文章中我们就来看看常见的两种方法,学完之后你们也可以来尝试一下。

create-react-app 中是这样做的

使用过这个脚手架的小伙伴都知道,create-react-app 脚手架除了提供一个基础模板之外,还提供了一个 redux 模板,但是查看 cra 源码,只提供了两个源码,那么它们是怎么实现的呢?

💯 面试官:来说说你的脚手架是做模板扩展的?

create-react-app 整个流程是这样的,首先,它会把上面图片中的 create-template 或者下面那个当成 npm 包来下载,我们查看 npm 仓库中也确实存在这个包:

💯 面试官:来说说你的脚手架是做模板扩展的?

那么它下载这个包是要干嘛呢? 答案是他们想要对包里面的文件进行 copy,我们打开包里面的源代码来看看 template 目录下的文件是啥:

💯 面试官:来说说你的脚手架是做模板扩展的?

是不是好熟悉呀,没错,这就是我们使用 create-react-app 脚手架里创建的文件,那么他是怎么做到的呢?

首先他会使用 fs 模块对其从 node_modules 文件中进行 copy,当 copy 完成之后,会对其进行 uninstall 这个时候,刚才被下载下来的模板包也就被删除了,至于上面那个图片中的文件中缺少了 package.json 文件,这个是脚手架那边做的事情,这里就不多讲了,看看移除模板包的代码实现:

  let command;
  let remove;
  let args;

  if (useYarn) {
    command = 'yarnpkg';
    remove = 'remove';
    args = ['add'];
  } else {
    command = 'npm';
    remove = 'uninstall';
    args = [
      'install',
      '--no-audit', // https://github.com/facebook/create-react-app/issues/11174
      '--save',
      verbose && '--verbose',
    ].filter(e => e);
  }

// Remove template
  console.log(`Removing template package using ${command}...`);
  console.log();

  const proc = spawn.sync(command, [remove, templateName], {
    stdio: 'inherit',
  });
  if (proc.status !== 0) {
    console.error(`\`${command} ${args.join(' ')}\` failed`);
    return;
  }

那么如果要添加 redux 或者 router 这些呢,那也是创建一个模板吗?

当然不是啦,那不傻逼吗,又 ts 或者 js 的那模板不就是 10 几个了,而且模板重复了。

如何添加额外的功能

如果继续按照这个模式继续开发下去,你可以再封装一个只存在 redux 模块的包,该包中只存在 redux 相关配置的包,因为 redux 需要全局引入,你需要在原来的基础上添加特定的 redux 配置文件,并对整个 react 入口文件进行重写,你可以在脚手架端进行重写,又或者你可以直接在 redux 文件附带 react 入口文件直接进行覆盖。但是如果同时想要 reduxrouter 呢,因为这两个都要对 react 文件进行重写的,所以个人认为这个还是要在脚手架端进行重写。

create-react-app 为什么会这么慢

看到这里,如果我前面想说的内容有表达清除了,你应该就了解到这个脚手架初始化项目的时候为什么会这么慢了。

你不慢谁慢啊,本来 npm 就慢了,你还要用它来下载模板,等下载完模板之后,又删除又啥的,,一系列操作,而且下载所有依赖包的都是默认使用 npm 下载,等你把这个项目创建完成,我都喝完一杯咖啡了。

来聊聊我是实现下载模板并支持多个模板的

首先我还是会像上面那样创建模板,把他写成一个 npm 包,但不同的是,我不会像上面那样使用 npm 命令来进行下载,有看过我之前文章的可能知道,一个 npm 会经历过很多步骤,具体请看下面文章: 你真的了解npm install和npx原理吗

一个 npm install 命令就经历了这么多流程,它能不慢吗?

首先,每一个 npm 依赖包都有一个用于存放它们的仓库,在 package-lock.json 文件中有一个 resolved 字段中,这个值就是仓库中具体的源代码位置,如下示例所示:

"docusaurus/website/node_modules/react": {
    "version": "16.14.0",
    "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
},

其中把他拆分开来它是这样的形式:

const packageName = "react";
const packageVersion = "18.2.0";

const packageURL = `https://registry.npmjs.org/${packageName}/-/${packageName}-${packageVersion}.tgz`;

既然我们有了这个仓库地址可以获取源代码,我们可以通过 axios 发送网络请求的方式对其 tgz 文件进行下载,然后使用 tar 依赖包对其进行解压到特定目录,具体代码实现如下:

import axios from "axios";
import fs from "node:fs";
import path from "node:path";
import tar from "tar";

const packageName = "react";
const packageVersion = "18.2.0";

const packageURL = `https://registry.npmjs.org/${packageName}/-/${packageName}-${packageVersion}.tgz`;

// 下载 .tgz 包
axios
  .get(packageURL, { responseType: "arraybuffer" })
  .then((response) => {
    // 获取当前终端目录
    const currentDir = process.cwd();

    // 保存 .tgz 包为文件
    const tgzPath = path.join(
      currentDir,
      `${packageName}-${packageVersion}.tgz`
    );
    fs.writeFileSync(tgzPath, response.data);

    // 解压缩 .tgz 包到当前终端目录
    tar.extract({
      file: tgzPath,
      cwd: currentDir,
      sync: true,
    });

    // 删除临时的 .tgz 文件
    fs.unlinkSync(tgzPath);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

此时代码目录如下所示:

├───📁 node_modules/
│   ├───📁 .pnpm/
│   │   └───...
│   └───📄 .modules.yaml
├───📄 index.js
├───📄 package.json
└───📄 pnpm-lock.yaml

执行 node index.js,会创建一个名为 package 的目录,并生成对应的文件,如下图所示:

💯 面试官:来说说你的脚手架是做模板扩展的?

这不正是我们平时通过 npm 下载的依赖包吗,这样通过网络连接直接下载的方式省去了很多 npm install 的步骤,而且还省去了删除的步骤。

而通过这种方式再去扩展 redux 或者 router 这些模板的时候就更加方便, 而不需要又 install 之后再 uninstall 了。

以上便是我对于如何实现的一些见解,当然,你也可以使用 git 的方式通过远程拉取相对应的模板。

总结

实现的方式有很多中,但当你要对某个项目或者某个功能点进行加速的时候,你懂其原理,就能很快找到问题的根源。

本来打算能在这个月的 15 号能完成的,但是写着写着发现还差太多太多东西了,因此我便去学了 rollup 打包工具计划对一些包进行重构,把整个构想完成估计要等到 8 月初了。

以下是我对这未来一两个月想做的事情的一些构想:

  • 编写一个 eslint 配置库,把一些常用的配置写成一个 eslint 插件;
  • 使用 rollup 构建一个 Typescript 库,无需配置,直接可以编写 react 组件库,开箱即用;
  • cli 进行重构;

如果对你有帮助或者你也对脚手架感兴趣,不妨给我的脚手架一个star,不胜感激。

转载自:https://juejin.cn/post/7241406861143097399
评论
请登录