💯 面试官:来说说你的脚手架是做模板扩展的?
因为最近一直在做一个脚手架,所以在简历上自然也就把它放到了项目中的最前位置。在关于这个脚手架中被问到了一个这样的问题:你的脚手架是怎么样做模板扩展的,就类似于我要添加 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
入口文件直接进行覆盖。但是如果同时想要 redux
和 router
呢,因为这两个都要对 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,不胜感激。
- 仓库地址: github.com/xun082/reac…
转载自:https://juejin.cn/post/7241406861143097399