create-react-app源码解读之为什么不搞个山寨版的create-react-app呢?
最近把 vue-cli@2.x 和 create-react-app 的源码都看了一遍。由于现在官方推荐使用 vue-cli@3.0 ,改动比较大,所以就不写关于 vue-cli 的了(据说是因为 vue-cli@2.x 创建项目时操作有点太复杂了,于是犹雨溪大大就借鉴了 create-react-app 的思想,搞出了个零配置的 vue-cli@3.0 ,有兴趣的小伙伴可以去自己看一下哈)。这篇随笔只讲解 create-react-app 的实现,但是,因为 create-react-app源码 加上注释总共800多行代码,这里也不打算对它的源码进行逐一解读了,如果想要对全部源码解读的,可以先把这篇文章看完、再去看源码,应该会容易明白很多。
前面说了这么多废话,现在该进入正题了。那 create-react-app 到底是什么东东叱?这里还是引用官方 readme 文件的第一句话解释:
Create React apps with no build configuration.
嗯,这解释得很清楚了:creact-react-app可以让你零配置创建一个React应用!为什么要强调零配置呢?因为我们都知道,React是分模块的组件化的框架,这需要配置webpack打包吧?还有使用了JSX和高大上的ES6新特性,这需要配置bable了啊?另外还需要对代码做静态检查,需要配置eslint吧?对于一个新手来说,能够成功的配置一个能运行React的环境,真的很有可能需要一两天时间的。所以零配置的意义就在于让小萌新在不懂配置的情况下,也能迅速的编写自己的第一个 react-hello-world,这是很有成就感的!
如果之前没有使用过create-react-app也没有关系 ,这里是它最简单的用法:
create-react-app my-app
等待数分钟,就会在当前目录下创建一个my-app的项目,然后进入这个根目录npm start就可以启动一个React项目了。记得要先全局安装好create-react-app。
介绍了create-react-app是什么,以及他的最简单的用法。现在我们就一起动手实现一个create-react-app山寨版吧。因为我们实现的是一个简化版的,去除了环境检查、版本检测、离线包安装等功能,代码就剩下100行左右,暂且就叫做simple-create-react-app。
在实现代码之前我们先梳理一上思路:
- 通过
commander获取项目名称; - 如果项目名称为空(实际上还要对包名进行有效性检查的,这里暂且忽略),则退出进程,并提示用户项目名称不能为空,否则进行步骤3;
- 在当前目录下创建一个子目录,目录名称就是用户输入的项目名,并在里面初始化一个
package.json文件; - 进入项目的根目录,安装
react,react-dom和react-scripts三个依赖; - 依赖安装完成后,调用
react-scripts的init方法初始化项目; - 结束;
按照上面的思路,开始编码吧!
先引入一些必要的依赖,对于这些依赖有什么作用这里就不展开了。 以及定义一个用来存放项目名称的变量projectName:
const commander = require('commander');
const chalk = require('chalk');
const spawn = require('cross-spawn');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const packageJson = require('./package.json');
let projectName; // 项目名称,通过命令行参数获取
接下来,就创建一个Commander的实例,获取用户输入的项目名称, 并判断是否为空。如果是空,则提示用户,并退出进程。
const program = new commander.Command(packageJson.name)
.version(packageJson.version)
.arguments('<project-directory>')
.usage(`${chalk.green('<project-directory>')} [options]`)
.action(name => {
projectName = name;
})
.parse(process.argv) // 格式化参数,必须要的
// 如果没有输入项目名称,则给出提示,并退出进程
if(typeof projectName === 'undefined') {
console.error('please specify the project directory');
console.log();
console.log('For examaple: ')
console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`)
console.log();
process.exit(1);
}
如果项目名称不为空,则开始创建一个空的项目,并且初始化一个packgae.json文件:
// 开始创建项目
createApp(projectName);
function createApp(name) {
const root = path.resolve(name);
fs.ensureDirSync(root); // 创建项目空目录
console.log(`Creating a new React app in ${chalk.green(root)}.`);
// 创建新项目的package.json
const packageJson = {
name: name,
version: '0.1.0',
private: true
};
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson, null, 2) + os.EOL);
// 将当前目录的路径存下来。因为下一步我们就要进入到新项目的目录了
// 后面可能还会用到当前的路径
const originalDirectory = process.cwd();
// 进入新创建的项目里面
process.chdir(root);
run(root, originalDirectory);
}
创建新项目之后,通过process.chdir(root);让进程的工作目录进入到新项目里面。然后开始安装依赖,等数分钟之后,安装依赖完成后,开始调用create-react-app的init方法初始化项目:
function run(root, originalDirectory) {
const allDependencies = ['react', 'react-dom', 'react-scripts'];
console.log('Installing packages. This migth take a couple of minutes...');
console.log(`Installing ${chalk.cyan('react')}, ${chalk.cyan('react-dom')}, and ${chalk.cyan('react-scripts')}...`);
console.log();
install(root, allDependencies)
.then(() => {
console.log();
console.log('Installing is success!');
console.log();
// 执行react-scripts模块下的init方法进行初始化项目
const scriptsPath = path.resolve(
process.cwd(),
'node_modules',
'react-scripts',
'scripts',
'init.js'
)
const init = require(scriptsPath);
init(root, projectName, null, originalDirectory);
})
.catch(reason => {
console.log();
console.log('Aborting installation.');
if(reason.command) {
console.log(` ${chalk.cyan(reason.command)} has failed.`);
} else {
console.log(chalk.red('Unexpected error!'), reason);
}
})
}
// 在指定目录下安装npm依赖
function install(root, dependencies) {
return new Promise((resolve, reject) => {
let command = 'yarnpkg';
const args = ['add'];
[].push.apply(args, dependencies);
let child = spawn(command, args, {stido: 'inherit'});
child.on('close', code => {
if(code !== 0) {
reject({
command: `${command} ${args.join(' ')}`
});
return;
}
resolve();
})
});
}
数了一下,代码总共100多行,就这么简单就实现了create-react-app的核心功能了。当然,实际上,还有环境检测、版本检测、离线安装等,我们这里忽略了的,如果有兴趣的,可以自己看一下官方的源码。
关于create-reate-app就写这么多了,源码可以到我的github进行下载,如果喜欢的欢迎star一下哈~
create-react-app源码解读之为什么不搞个山寨版的create-react-app呢?的更多相关文章
- APP源码集中打包大放送!十一个千万级别APP源码随意处置!
小伙伴们还在一个一个苦苦寻找各类APP源码吗?此贴集中打包最常用APP的源码,你想得到的APP,这里都有! 想做商城?这里有天猫! 想做同城服务?这里有大众点评! 想做外卖?这里有饿了么! 想做视频? ...
- React useEffect的源码解读
前言 对源码的解读有利于搞清楚Hooks到底做了什么,如果您觉得useEffect很"魔法",这篇文章也许对您有些帮助. 本篇博客篇幅有限,只看useEffect,力求简单明了,带 ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读_之SDWebImageDecoder
第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
- php-msf 源码解读【转】
php-msf: https://github.com/pinguo/php-msf 百度脑图 - php-msf 源码解读: http://naotu.baidu.com/file/cc7b5a49 ...
- 从koa-session源码解读session本质
前言 Session,又称为"会话控制",存储特定用户会话所需的属性及配置信息.存于服务器,在整个用户会话中一直存在. 然而: session 到底是什么? session 是存在 ...
- DRF(1) - REST、DRF(View源码解读、APIView源码解读)
一.REST 1.什么是编程? 数据结构和算法的结合. 2.什么是REST? 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下: /books/ /get_all_books/ 访问所 ...
- Webpack探索【16】--- 懒加载构建原理详解(模块如何被组建&如何加载)&源码解读
本文主要说明Webpack懒加载构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack懒加载构建原理. 本文使用的 ...
随机推荐
- SpringMVC云题库错题及答案汇总
试题分析:D,BeanNameViewResolver:这个视图解析器跟XmlViewResolver基本相同,它是通过把返回的逻辑视图名称去匹配定义好的视图bean对象 @ModelAttribut ...
- C语言编译数组中有的加code有的不加,有什么区别
像uchar code table[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};和uchar ds1302[]={0xfe,0xfd,0xfb,0xf7,0 ...
- Oracle启动和关闭数据库
本机只安装一个数据库的情况下sqlplus / as sysdba启动数据库startup关闭数据库shutdown immediate
- L345 大脑复生
Yale Researchers Build BrainEx Machine to Restore Brain Function After Death Researchers at Yale Uni ...
- URL和URL比较
浅谈URI和URL URI(Uniform Resource Identifier)字面上的意思是,统一资源标示符 URL(Uniform Resource Locator),统一资源定位符 光从字面 ...
- 通过配置文件添加MIME类型
在web.config配置文件中的configuration节点下添加如下节点: <system.webServer> <staticContent> <mimeMap ...
- "Loading a plug-in failed The plug-in or one of its prerequisite plug-ins may be missing or damaged and may need to be reinstalled"
The Unarchiver 虽好,但存在问题比我们在mac上zip打包一个软件xcode, 然后copy to another mac, 这时用The Unarchiver解压缩出来的xcode包不 ...
- Linux 环境下安装Mysql的步骤
一,以linux cent 6.9 安装mysql 5.6.39为例#下载安装包wget --no-check-certificate https://dev.mysql.com/get/Downlo ...
- Asp.net MVC Form认证,IIS改成集成模式后,FormsAuthentication.SetAuthCookie无效,Request.IsAuthenticated值,始终为false,页面提示HTTP 错误 401.0 - Unauthorized,您无权查看此目录或页面
最近公司领导要求,IIS网站要由经典模式改为集成模式,以提高性能.改完之后,登录成功跳转到主页之后,页面提示“”HTTP 错误 401.0 - Unauthorized“,“您无权查看此目录或页面”, ...
- 学习Spring中遇到关于BeanFactory及测试类的问题
最近在学习Spring,使用的是Spring 5.0.1 学习书本中使用的是4.0 学习书本中使用以下来加载配置文件及设置 Resource resource = new ClassPathResou ...