最近把 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 源码 https://github.com/facebook/create-react-app/blob/next/packages/create-react-app/createReactApp.js 加上注释总共 800 多行代码, 这里也不打算对它的源码进行逐一解读了, 如果想要对全部源码解读的, 可以先把这篇文章看完, 再去看源码, 应该会容易明白很多.
前面说了这么多废话, 现在该进入正题了. 那 create-react-app 到底是什么东东叱? 这里还是引用官方 readme 文件的第一句话解释:
Create React apps with no build configuration.
复制代码
嗯, 这解释得很清楚了: creact-react-app 可以让你零配置创建一个 React 应用! 为什么要强调零配置呢? 因为我们都知道, React 是分模块的组件化的框架, 这需要配置 webpack 打包吧? 还有使用了 JSX 和高大上的 ES6 新特性, 这需要配置 babel 了啊? 另外还需要对代码做静态检查, 需要配置 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 https://github.com/zhanyuzhang/simple-create-react-app.git 进行下载, 如果喜欢的欢迎 star 一下哈~
来源: https://juejin.im/post/5b56e84351882569fd2873ab