{"id":19682714,"url":"https://github.com/zhenghui-su/handwriting-create-react-app","last_synced_at":"2026-05-08T03:04:32.536Z","repository":{"id":198259223,"uuid":"700420564","full_name":"zhenghui-su/handwriting-create-react-app","owner":"zhenghui-su","description":"手写实现create-react-app，start和build命令，内有教学md","archived":false,"fork":false,"pushed_at":"2023-10-05T10:22:54.000Z","size":154,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-10T06:11:37.653Z","etag":null,"topics":["javascript","lerna","lerna-monorepo","react","reactjs","webpack","webpack-dev-server","yarn-workspaces","yarnpkg"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zhenghui-su.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-04T15:05:24.000Z","updated_at":"2023-11-07T07:20:13.000Z","dependencies_parsed_at":"2024-11-11T18:12:08.999Z","dependency_job_id":"dbc0ab12-f896-4355-a79a-4b0220ad9d8b","html_url":"https://github.com/zhenghui-su/handwriting-create-react-app","commit_stats":null,"previous_names":["zhenghui-su/react-react-app","zhenghui-su/handwriting-create-react-app","zhenghui-su/create-react-app"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhenghui-su%2Fhandwriting-create-react-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhenghui-su%2Fhandwriting-create-react-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhenghui-su%2Fhandwriting-create-react-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhenghui-su%2Fhandwriting-create-react-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zhenghui-su","download_url":"https://codeload.github.com/zhenghui-su/handwriting-create-react-app/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240991098,"owners_count":19890010,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","lerna","lerna-monorepo","react","reactjs","webpack","webpack-dev-server","yarn-workspaces","yarnpkg"],"created_at":"2024-11-11T18:11:47.904Z","updated_at":"2026-05-08T03:04:32.486Z","avatar_url":"https://github.com/zhenghui-su.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 从零手写 create-react-app\n\n前置知识：\n\n+ yarn包管理\n+ workspace工作区知识概念\n+ Monorepo知识概念\n\n### 初始化\n\n一些知识科普讲解\n\n+ Lerna 是一个用于管理具有多个包（packages）的 JavaScript 项目的工具。在一个大型 JavaScript 项目中，通常会有多个独立的模块或包，这些包可能相互依赖，或者需要一起发布。\n\n+ 工作区（Workspace）通常是指在一个项目中，可以同时处理多个相关联的子项目（packages，modules等），而不需要将它们分别作为独立的项目来处理。在软件开发领域，工作区通常是指一个包含了多个子项目的项目容器，这些子项目可以共享一些配置、依赖关系和构建流程。\n+ Monorepo是 \"单一代码仓库\"（Monorepository）的缩写，它是一种软件开发的组织结构模式，其中所有项目或者库的代码都放在一个单一的版本控制仓库中。通常，这种仓库包含了多个相关的项目、库或者组件，这些项目可能共享某些代码、依赖关系或者配置。\n\n全局安装 lerna\n\n```bash\nnpm i lerna@3.22.1 -g\n```\n\n输入如下检测是否安装成功\n\n```bash\nlerna -v\n```\n\n在要创建的文件夹，打开终端，输入如下初始化\n\n```bash\nlerna init\n```\n\n打开后，这里我用 **yarn**管理，终端输入\n\n```bash\nyarn install\n```\n\n两个作用：安装lerna和它的依赖，在根目录的node_modules里面创建软链接，链向各个packages中的各个包\n\n\u003e yarn 支持workspace \tnpm只有在7版本以上支持\n\u003e\n\u003e yarn workspace    VS     lerna\n\u003e\n\u003e yarn重点在于包管理、处理依赖和软链\n\u003e\n\u003e lerna重点在于多个项目管理和发布\n\n然后创建 packages 文件夹（workspace），最终形成的初始文件目录如下\n\n![image-20231004232629874](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231004232629874.png)\n\n### 创建 package\n\n##### 创建 create-react-app\n\n在终端输入,然后在协议(license)改为 MIT，入口(main)改为`index.js`其它不变\n\n```bash\nlerna create create-react-app\n```\n\n然后进入，把一些无用的删除，最终形成如下\n\n![image-20231004233303465](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231004233303465.png)\n\n##### 创建react-scripts\n\n终端输入\n\n```bash\nlerna create react-scripts\n```\n\n##### 创建cra-template\n\n终端输入\n\n```bash\nlerna create cra-template\n```\n\n##### 查看工作包\n\n终端输入\n\n```bash\nyarn workspaces info\n```\n\n![image-20231004234845726](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231004234845726.png)\n\n最终形成的结构图如下\n\n![image-20231004234956700](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231004234956700.png)\n\n### 添加依赖\n\n在添加之前记得要设置为淘宝源，不然可能会安装很慢\n\n安装如下依赖\n\n```bash\nyarn add chalk cross-spawn fs-extra --ignore-workspace-root-check\n```\n\n\u003e - **chalk:** `chalk` 是一个用于在终端中添加颜色和样式的库。它允许你在命令行界面中使用不同的颜色和样式来输出文本，使得输出更加清晰和易读。\n\u003e - **cross-spawn:** `cross-spawn` 是一个用于跨平台（Windows、Linux、Mac 等）运行子进程的库。它解决了在不同操作系统下创建子进程的差异性，使得在 Node.js 环境中能够一致地运行子进程。\n\u003e - **fs-extra:** `fs-extra` 是 Node.js 的文件系统模块（`fs` 模块）的扩展，提供了更多的功能和便捷的方法，使得文件和目录的操作更加容易和灵活。\n\u003e - **--ignore-workspace-root-check**:这是 `yarn add` 命令的一个选项。当你在一个使用 Yarn 工作区（Workspace）的项目中执行 `yarn add` 命令时，默认情况下 Yarn 会检查你是否在工作区的根目录（root）中运行该命令。如果你使用了 `--ignore-workspace-root-check` 选项，Yarn 将忽略这个检查，允许你在工作区的任意位置执行 `yarn add` 命令。\n\n在lerna里面，packages里面的各个会在node_modules里面形成符号链接即软链，这样可以在别的package中访问另一个package\n\n![image-20231004235537950](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231004235537950.png)\n\n\n\n然后可以根据工作空间安装如下依赖\n\n```bash\nyarn workspace create-react-app add commander\n```\n\n\u003e `commander` 是一个用于构建命令行界面（CLI）的 Node.js 框架。它可以帮助开发者轻松地构建复杂和易用的命令行工具，提供了处理命令行参数、解析用户输入、显示帮助信息等功能\n\n### createReactApp.js文件\n\n在根目录`package.json`添加脚本命令\n\n```json\n\"scripts\": {\n    \"create-react-app\": \"node ./packages/create-react-app/index.js\"\n}\n```\n\n在`packages`中的`create-react-app`文件夹的`index.js`文件更改\n\n```js\nconst { init } = require('./createReactApp');\n\ninit();\n```\n\n在`create-react-app`文件夹中新建`createReactApp.js`文件\n\n\u003e 由于接下来我们需要用到刚刚安装的依赖，我们可以快速学习一下\n\n```js\nconst chalk = require('chalk');\nconst {Command} = require('commander');\nlet program = new Command('create-react-app');\nprogram\n    .version('1.0.0') // 设置版本号\n\t.arguments('\u003cmust1\u003e \u003cmust2\u003e [option1] [option2]')//设置命令行的参数格式 \u003c\u003e表示必选 []表示可选\n\t.usage(`${chalk.green(`\u003cmust1\u003e \u003cmust2\u003e [option1] [option2]`)}`) // 设置用法说明，改颜色\n    .action((must1,must2,option1,option2)=\u003e{ // 指令命令的行为\n    \n\t})\n```\n\n#### init函数\n\n然后我们开始写`createReactApp.js`文件\n\n```js\nconst { Command } = require('commander');\nconst packageJSON = require('./package.json');\nconst chalk = require('chalk');\n\nfunction init() {\n    let projectName;\n    new Command(packageJSON.name) // 项目名\n        .version(packageJSON.version) // 版本号\n        .arguments('\u003cproject-directory\u003e') // 项目的目录名\n        .usage(`${chalk.green('\u003cproject-directory\u003e')} [options]`)\n        .action(name =\u003e {\n            projectName = name;\n        })\n        .parse(process.argv); // [node完整路径,当前node脚本的路径,...其它参数]\n    console.log('projectName',projectName);\n}\n\nmodule.exports = {\n    init\n}\n```\n\n如果报错等，可以修改`package.json`文件的chalk，高版本可能有错\n\n```json\n\"dependencies\": {\n    \"chalk\": \"^4.1.2\",\n  }\n```\n\n在终端运行如下测试是否成功\n\n\u003e -- 后面代码传参\n\n```bash\nnpm run create-react-app -- myApp\n```\n\n此处我们传入项目名，最终终端会输出 myApp\n\n```bash\nprojectName myApp\n```\n\n#### createApp函数\n\n然后在`createReactApp.js`中创建createApp函数，在init中添加，并修改init为async\n\n```js\nasync function init() {\n    ... // 上面代码搬运下来\n    createApp(projectName)\n}\n```\n\n\u003e 这里用到了path模块和fs-extra模块\n\n```js\nasync function createApp(appName) { // projectName = appName\n    let root = path.resolve(appName); // 得到将生成项目绝对路径\n    fs.ensureDirSync(appName);// 保证此目录是存在的，如果不存在则创建\n    console.log(`Creating a new React app in ${chalk.green(root)}.`);\n    const packageJSON = {\n        name: appName,\n        version: '0.1.0',\n        private: true\n    }\n    // 写入\n    fs.writeFileSync(\n        path.join(root, 'package.json'),\n        JSON.stringify(packageJSON, null, 2)\n    );\n    const originalDirectory = process.cwd(); // 原始的命令工作目录\n    process.chdir(root); // change directory 改变工作目录\n    console.log('appName',appName);\n    console.log('root',root);\n    console.log('originalDirectory', originalDirectory); \n}\n```\n\n在终端执行,就会生成一个文件夹myApp并有一个package.json文件了\n\n```bash\nnpm run create-react-app -- myApp\n```\n\n![image-20231005013532488](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231005013532488.png)\n\n\u003e 扩展 JSON.stringify\n\u003e\n\u003e 如下代码\n\u003e\n\u003e ```js\n\u003e let obj = {name:'chenchen',age:20};\n\u003e let result = JSON.stringify(obj,replacer,2); // replacer 替换器 2 为缩进空格\n\u003e function replacer(key,value){\n\u003e     console.log(key,value);\n\u003e     if(key == 'age') return value*2;\n\u003e     return value;\n\u003e }\n\u003e console.log(result);\n\u003e ```\n\u003e\n\u003e 打印结果如下\n\u003e\n\u003e ```bash\n\u003e {name:'chenchen',age:20} //原始的obj\n\u003e name chenchen // replacer的console.log(key,value);\n\u003e age 10 // replacer的console.log(key,value);\n\u003e {\n\u003e \t\"name\":\"chenchen\", //前面的缩进就是上面定义的2空格\n\u003e \t\"age\":20\n\u003e } // 替换后结果\n\u003e ```\n\n#### run函数\n\n在createApp函数最后加上执行run函数\n\n```js\nasync function createApp(appName) {\n    ...//上面代码\n    await run(root, appName, originalDirectory);\n}\n```\n\n创建run函数\n\n```js\nasync function run(root, appName, originalDirectory) {\n    let scriptName = 'react-scripts'; // create生成的代码里 源文件编译，启动服务放在了react-scripts\n    let templateName = 'cra-template';\n    const allDependencies = ['react', 'react-dom', scriptName, templateName];\n    \n    console.log('Installing packages. This might take a couple of minutes.');\n    // Installing react, react-dom, and react-scripts with cra-template\n    console.log(\n        `Installing ${chalk.cyan('react')}, ${chalk.cyan(\n            'react-dom'\n        )}, and ${chalk.cyan(scriptName)}${` with ${chalk.cyan(templateName)}`}...`\n    );\n    await install(root,allDependencies);\n}\n```\n\n#### install函数\n\n\u003e 这里用到了cross-spawn模块，记得导入\n\n创建install函数\n\n```js\nasync function install(root,allDependencies) {\n    return new Promise(resolve =\u003e{\n        const command = 'yarnpkg'; //yarnpkg = yarn\n        const args = ['add', '--exact', ...allDependencies, '--cwd', root];\n        console.log(command,args);\n        const child = spawn(command, args, { stdio: 'inherit' });//stdio = 'inherit' 表示子进程的输出会直接打印到父进程中\n        child.on('close',resolve); // 回调执行\n    })\n}\n```\n\n\u003e `cross-spawn` 是一个用于跨平台（Windows、Linux 和 macOS）的 Node.js 包，用于跨平台地启动子进程。\n\u003e\n\u003e ```js\n\u003e const child = spawn('npm', ['install', 'some-package']);\n\u003e ```\n\u003e\n\u003e 换成这个就熟悉了吧\n\n#### executeNodeScript函数\n\n\u003e 拷贝模板的文件\n\n在run函数中补充\n\n```js\nasync function run(root, appName, originalDirectory) {\n    ...//上面代码\n    //项目根目录    项目名字   verbose是否显示详细信息 原始的目录 模板名称cra-template\n    let data = [root, appName, true, originalDirectory, templateName];\n    let source = `\n    const init = require('react-scripts/scripts/init.js');\n    init.apply(null, JSON.parse(process.argv[1]));\n    `;\n    await executeNodeScript({cwd:process.cwd()},data,source);\n    console.log('Done.');\n    process.exit(0); //退出\n}\n```\n\n创建executeNodeScript函数\n\n```js\nasync function executeNodeScript({ cwd }, data, source) {\n    return new Promise((resolve) =\u003e {\n        const child = spawn(\n            process.execPath,//node可执行文件的路径\n            ['-e', source, '--', JSON.stringify(data)],\n            { cwd, stdio: 'inherit' }\n        );\n        child.on('close', resolve);\n    });\n}\n```\n\n\u003e 上述简化的意思是通过node执行脚本\n\u003e\n\u003e 即把data里面的数据放到了node后面\n\u003e\n\u003e 如下，执行出来就是输出aaa\n\u003e\n\u003e ```bash\n\u003e node -e \"console.log('aaa')\" \n\u003e ```\n\u003e\n\u003e process.argv是一个包含命令行参数的数组\n\u003e\n\u003e ```bash\n\u003e node -e \"console.log(process.argv)\" -- a b c\n\u003e ```\n\u003e\n\u003e 如下输出 就会发现上面abc也带入了\n\u003e\n\u003e ```bash\n\u003e ['C:\\\\Progaram Files\\\\nodejs\\\\node.exe','a','b','c']\n\u003e ```\n\n此时基本完成，我们在终端输入\n\n```bash\nnpm run create-react-app -- bbb\n```\n\n安装上述的四个模块\n\n![image-20231005153831814](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231005153831814.png)\n\n拷贝模板文件到bbb文件夹下\n\n![image-20231005153929823](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231005153929823.png)\n\n然后会删除cra-template模块因为拷贝完了\n\n![image-20231005154009464](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231005154009464.png)\n\n最终安装成功，`cd bbb`切换到bbb文件夹，输入`yarn start`查看是否启动成功\n\n![image-20231005154209323](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231005154209323.png)\n\n查看bbb文件目录，发现和我们平时用cra创建的一样\n\n![image-20231005154332617](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231005154332617.png)\n\n**至此手写create-react-app已完成**\n\n\n\n### 手写react-scripts编译\n\n\u003e create-react-app流程\n\u003e\n\u003e + 执行命令\n\u003e + 创建了一个React项目\n\u003e + 安装依赖包\n\u003e + 初始化git，拷贝模板，安装模板依赖\n\u003e + 移除模板，成功\n\n![image-20231005160524479](https://gitee.com/dont-sleep-in-the-morning/pictures/raw/master/image-20231005160524479.png)\n\n#### react-scripts.js\n\n##### 初始化\n\n在package.json中配置\n\n```js\n\"bin\": {\n    \"react-scripts\": \"./bin/react-scripts.js\"\n  },\n```\n\n在根目录的package.json配置\n\n```js\n\"scripts\": {\n    \"create-react-app\": \"node ./packages/create-react-app/index.js\",\n    \"build\": \"cd packages/react-scripts \u0026\u0026 node bin/react-scripts.js build\"\n  }\n```\n\n在`react-scripts`文件夹创建bin文件夹，并创建`react-scripts.js`文件\n\n```js\nconsole.log(\"done\");\n```\n\n终端执行`npm run build`，输出`done`证明成功\n\n##### 书写\n\n在`react-scripts`文件夹下新建`scripts`文件夹，并创建`build.js`文件\n\n```js\nconsole.log('build.js')\n```\n\n书写`react-scripts.js`文件\n\n```js\n// 开启子进程\nconst spawn = require('cross-spawn');\n// 获取命令行参数 ['build']\nconst args = process.argv.slice(2);\nconst script = args[0]; // build\n// 以同步方式开始子进程执行scripts下面的build.js脚本\nspawn.sync(\n    process.execPath, // node的可执行文件路径\n    [require.resolve('../scripts/' + script)],// 执行对应命令的文件如build.js\n    { stdio: 'inherit' } // 让父进程和子进程共享输入输出\n)\n```\n\n根目录终端输入`npm run build`，输出`build.js`证明成功\n\n#### build.js\n\n在`react-scripts`文件夹下新建`config`文件夹，并创建`webpack.config.js`文件和`path.js`文件\n\n\u003e 在根目录下缺少什么依赖就安装什么依赖，如@babel/preset-react\n\n```js\n/**\n * 生产webpack配置文件工厂\n * @param {*} webpackEnv 环境信息 development production\n */\nconst path = require('path');\nconst paths = require('./paths');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nmodule.exports = function (webpackEnv) {\n    const isEnvDevelopment = webpackEnv === 'development'; // 是否是开发环境\n    const isEnvProduction = webpackEnv === 'production'; // 是否是生产环境\n    return {\n        mode: isEnvProduction ? 'production' : isEnvDevelopment \u0026\u0026 'development',\n        output: {\n            path: paths.appBuild\n        },\n        module: {\n            rules: [\n                {\n                    test: /\\.(js|jsx|ts|tsx)$/,\n                    include: paths.appSrc, //只转译src目录下面的文件\n                    use: [{\n                        loader: 'babel-loader',\n                        options: {\n                            presets: ['@babel/preset-react']\n                        }\n                    }]\n                }\n            ]\n        },\n        plugins: [\n            new HtmlWebpackPlugin({\n                inject: true,\n                template: paths.appHtml\n            })\n        ]\n    }\n}\n```\n\n```js\nconst path = require('path');\nconst appDirectory = process.cwd();//当前的工作目录\n// 接收一个相对路径，返回一个从应用目录出发的绝对路径\nconst resolveApp = relativePath =\u003e path.resolve(appDirectory, relativePath);\n\nmodule.exports = {\n    appBuild: resolveApp('build'),// 指向打包后的输出目录 webpack默认是dist\n    appHtml: resolveApp('public/index.html'), // html模板给html-webpack-plugin用的\n    appIndexJs: resolveApp('src/index.js'),// 默认的入口文件\n    appPublic: resolveApp('public')\n}\n```\n\n**书写build.js文件**\n\n```js\n// 1.设置环境变量为生产环境\nprocess.env.NODE_ENV = 'production'\nconst fs = require('fs-extra');//加强版的fs\nconst webpack = require('webpack');\n// 2.获取webpack的配置文件\nconst configFactory = require('../config/webpack.config');\nconst paths = require('../config/paths');\nconst chalk = require('chalk');\nconst config = configFactory('production');\n\n// 3.如果build目录不为空，要把build目录清空\nfs.emptyDirSync(paths.appBuild);\n// 4.拷贝public下面的静态文件到build目录\ncopyPublicFolder();\nbuild();\n\nfunction build() {\n    // compiler是总的编译对象\n    let compiler = webpack(config);\n    // 开始启动编译\n    compiler.run((err, stats) =\u003e {\n        console.log(err);\n        console.log(stats);//它是一个描述对象，描述本次打包出来的结果\n        console.log(chalk.green('Compiled successfully!'));\n    });\n}\n\nfunction copyPublicFolder() {\n    fs.copySync(paths.appPublic, paths.appBuild, {\n        filter: file =\u003e file !== paths.appHtml // index.html文件交由插件编译处理，它不需要拷贝\n    });\n}\n```\n\n在react-scripts目录随意创建public和src，public创建index.html并添加id为root的div，src创建index.js并输入如下\n\n```js\nimport React from 'react';\nimport ReactDom from 'react-dom';\n\nReactDom.render(\u003ch1\u003eHello World\u003c/h1\u003e, document.getElementById('root'));\n```\n\n在react-scripts中修改package.json,并安装\n\n```json\n\"dependencies\": {\n    \"@babel/core\": \"^7.16.0\",\n    \"@pmmmwh/react-refresh-webpack-plugin\": \"^0.5.3\",\n    \"@svgr/webpack\": \"^5.5.0\",\n    \"babel-jest\": \"^27.4.2\",\n    \"babel-loader\": \"^8.2.3\",\n    \"babel-plugin-named-asset-import\": \"^0.3.8\",\n    \"babel-preset-react-app\": \"^10.0.1\",\n    \"bfj\": \"^7.0.2\",\n    \"browserslist\": \"^4.18.1\",\n    \"camelcase\": \"^6.2.1\",\n    \"case-sensitive-paths-webpack-plugin\": \"^2.4.0\",\n    \"css-loader\": \"^6.5.1\",\n    \"css-minimizer-webpack-plugin\": \"^3.2.0\",\n    \"dotenv\": \"^10.0.0\",\n    \"dotenv-expand\": \"^5.1.0\",\n    \"eslint\": \"^8.3.0\",\n    \"eslint-config-react-app\": \"^7.0.1\",\n    \"eslint-webpack-plugin\": \"^3.1.1\",\n    \"file-loader\": \"^6.2.0\",\n    \"fs-extra\": \"^10.0.0\",\n    \"html-webpack-plugin\": \"^5.5.0\",\n    \"identity-obj-proxy\": \"^3.0.0\",\n    \"jest\": \"^27.4.3\",\n    \"jest-resolve\": \"^27.4.2\",\n    \"jest-watch-typeahead\": \"^1.0.0\",\n    \"mini-css-extract-plugin\": \"^2.4.5\",\n    \"postcss\": \"^8.4.4\",\n    \"postcss-flexbugs-fixes\": \"^5.0.2\",\n    \"postcss-loader\": \"^6.2.1\",\n    \"postcss-normalize\": \"^10.0.1\",\n    \"postcss-preset-env\": \"^7.0.1\",\n    \"prompts\": \"^2.4.2\",\n    \"react-app-polyfill\": \"^3.0.0\",\n    \"react-dev-utils\": \"^12.0.1\",\n    \"react-refresh\": \"^0.11.0\",\n    \"resolve\": \"^1.20.0\",\n    \"resolve-url-loader\": \"^4.0.0\",\n    \"sass-loader\": \"^12.3.0\",\n    \"semver\": \"^7.3.5\",\n    \"source-map-loader\": \"^3.0.0\",\n    \"style-loader\": \"^3.3.1\",\n    \"tailwindcss\": \"^3.0.2\",\n    \"terser-webpack-plugin\": \"^5.2.5\",\n    \"webpack\": \"^5.64.4\",\n    \"webpack-dev-server\": \"^4.6.0\",\n    \"webpack-manifest-plugin\": \"^4.0.2\",\n    \"workbox-webpack-plugin\": \"^6.4.1\"\n  },\n  \"devDependencies\": {\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\"\n  },\n  \"optionalDependencies\": {\n    \"fsevents\": \"^2.3.2\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"\u003e= 16\",\n    \"typescript\": \"^3.2.1 || ^4\"\n  },\n  \"peerDependenciesMeta\": {\n    \"typescript\": {\n      \"optional\": true\n    }\n  },\n  \"browserslist\": {\n    \"production\": [\n      \"\u003e0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n```\n\n随后终端输入`npm run build`，成功打包，生成build目录里面有所创建的压缩文件\n\n#### start命令\n\n根目录package.json配置,与build一样\n\n```js\n\"start\": \"cd packages/react-scripts \u0026\u0026 node bin/react-scripts.js start\"\n```\n\n在在react-scripts的config里新建webpackDevServer.config.js\n\n```js\nmodule.exports = function () {\n    return {\n        hot: true // 热更新\n    }\n}\n```\n\n在react-scripts的scripts里新建start.js\n\n```js\n// 1.设置环境变量为开发环境\nprocess.env.NODE_ENV = 'development';\n// 2.获取配置文件的工厂\nconst configFactory = require('../config/webpack.config');\nconst config = configFactory('development');\n//3.创建compiler编译对象\nconst chalk = require('chalk');\nconst webpack = require('webpack');\n// @ts-ignore\nconst compiler = webpack(config);\n// 4.获取devServer的配置对象\nconst createDevServerConfig = require('../config/webpackDevServer.config');\nconst serverConfig = createDevServerConfig();\nconst webpackDevServer = require('webpack-dev-server');\n/**\n * 1.内部会启动compiler的编译\n * 2.会启动一个http服务器并返回编译后的结果\n */\nconst devServer = new webpackDevServer(compiler, serverConfig);\n// 启动一个HTTP开发服务器，监听3000端口\ndevServer.listen(3000, 'localhost', () =\u003e {\n    console.log(chalk.cyan('Starting the development server...'))\n});\n```\n\n在终端输入`npm run start`就会看到我们上面创建index.js所要显示的Hello World\n\n**至此build和start命令我们都完成了，下面有源码讲解，如果上面有点吃力，建议再加深知识后来查看。**\n\n\n\n## 源码解析\n\n### react-scripts.js源码\n\n```js\n'use strict';\n\n// 使脚本在未处理的拒绝时崩溃，而不是静默\n// 忽略它们。将来，未处理的promise拒绝将使用非零退出代码终止Node.js进程。\nprocess.on('unhandledRejection', err =\u003e {\n  throw err;\n});\n\nconst spawn = require('react-dev-utils/crossSpawn');\nconst args = process.argv.slice(2);\n// 找到命令所在索引\nconst scriptIndex = args.findIndex(\n  x =\u003e x === 'build' || x === 'eject' || x === 'start' || x === 'test'\n);\nconst script = scriptIndex === -1 ? args[0] : args[scriptIndex];\nconst nodeArgs = scriptIndex \u003e 0 ? args.slice(0, scriptIndex) : [];\n\nif (['build', 'eject', 'start', 'test'].includes(script)) {\n  const result = spawn.sync(\n    process.execPath,\n    nodeArgs\n      .concat(require.resolve('../scripts/' + script))\n      .concat(args.slice(scriptIndex + 1)),\n    { stdio: 'inherit' }\n  );\n  if (result.signal) {\n    if (result.signal === 'SIGKILL') {\n      console.log(\n        'The build failed because the process exited too early. ' +\n          'This probably means the system ran out of memory or someone called ' +\n          '`kill -9` on the process.'\n      );\n    } else if (result.signal === 'SIGTERM') {\n      console.log(\n        'The build failed because the process exited too early. ' +\n          'Someone might have called `kill` or `killall`, or the system could ' +\n          'be shutting down.'\n      );\n    }\n    process.exit(1);\n  }\n  process.exit(result.status);\n} else {\n  console.log('Unknown script \"' + script + '\".');\n  console.log('Perhaps you need to update react-scripts?');\n  console.log(\n    'See: https://facebook.github.io/create-react-app/docs/updating-to-new-releases'\n  );\n}\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhenghui-su%2Fhandwriting-create-react-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhenghui-su%2Fhandwriting-create-react-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhenghui-su%2Fhandwriting-create-react-app/lists"}