React脚手架 react-scripts源码分析

文章首发地址React脚手架 react-scripts源码分析

前言

我们通过CRA在初始化一个 React 项目的时候,通过在终端执行 npm run start 运行项目,然后浏览器打开 https:localhost:3000 就可以直接运行我们的项目,背后的原理是什么呢?

入口文件

通过在 package.json 文件找到,我们运行 npm run start 背后是通过运行 react-scripts start 启动项目,我们执行命令行把项目下载到本地

sh 复制代码
git clone https://github.com/facebook/create-react-app.git 

下载完成后,打开文件 react-scripts/bin/react-scripts.js

该文件主要解析参数并执行对应的 .js 文件

js 复制代码
// 跨平台的spawn
const spawn = require('react-dev-utils/crossSpawn');

// 获取构建命令参数 如start、build、test、eject
const args = process.argv.slice(2);

const scriptIndex = args.findIndex(
  x => x === 'build' || x === 'eject' || x === 'start' || x === 'test'
);
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];

// 获取node命令的参数
const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];

// 根据构建参数执行对应的文件
if (['build', 'eject', 'start', 'test'].includes(script)) {
  const result = spawn.sync(
    process.execPath, // 绝对路径
    nodeArgs
      .concat(require.resolve('../scripts/' + script))
      .concat(args.slice(scriptIndex + 1)),
    { stdio: 'inherit' }
  );
  ...
} else {
  // 打印一些错误
  console.log('Unknown script "' + script + '".');
  console.log('Perhaps you need to update react-scripts?');
  console.log(
    'See: https://facebook.github.io/create-react-app/docs/updating-to-new-releases'
  );
}

分析文件

打开 scripts/start.js 文件

主要初始化 webpack 配置,通过 webpack-dev-server 本地启动一个node服务

js 复制代码
...
const fs = require('fs');
const chalk = require('react-dev-utils/chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const {
  choosePort,
  createCompiler,
  prepareProxy,
  prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths');
const configFactory = require('../config/webpack.config');
const createDevServerConfig = require('../config/webpackDevServer.config');

// 判断nodejs是否在终端运行
const isInteractive = process.stdout.isTTY;

// 校验入口文件
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
  process.exit(1);
}

// 设置默认的端口和HOST
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';

/**
 * checkBrowsers内部使用browserslist,会从can-i-use数据库判断css、js支持的版本
 * 会先校验package.json文件里面有没有browserslist字段
 * 1.如果有直接返回promise
 * 2.没有的话会在终端询问是否要添加browserslist
*/
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
  .then(() => {
    /**
     * detect-port-alt校验当前端口是否被占用
     * 如果被占用,提示是否使用另外的端口
    */
    return choosePort(HOST, DEFAULT_PORT);
  })
  .then(port => {
    // 返回当前端口
    if (port == null) {
      // We have not found a port.
      return;
    }

    /**
     * configFactory有以下功能
     * 初始化webpack配置
     * 1.定义入口文件、输出文件
     * 2.定义规则:处理图片、字体、css、jsx
     * 3.使用插件
     *  - HtmlWebpackPlugin 为html自动插入输出的js
     *  - MiniCssExtractPlugin css压缩插件
     *  - WebpackManifestPlugin 生成manifest.json
     *  - ESLintPlugin 配置一些eslint规则
    */
    const config = configFactory('development');
    const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
    const appName = require(paths.appPackageJson).name;

    // 判断是否使用ts
    const useTypeScript = fs.existsSync(paths.appTsConfig);
    // 通过协议、域名、端口组合成完成的地址字符串
    const urls = prepareUrls(
      protocol,
      HOST,
      port,
      paths.publicUrlOrPath.slice(0, -1)
    );
    // 内部通过调用webpack(config) 生成一个compiler实例
    const compiler = createCompiler({
      appName,
      config,
      urls,
      useYarn,
      useTypeScript,
      webpack,
    });
    // 获取package.json文件中的proxy字段
    const proxySetting = require(paths.appPackageJson).proxy;
    // 配置一些代理相关的信息
    const proxyConfig = prepareProxy(
      proxySetting,
      paths.appPublic,
      paths.publicUrlOrPath
    );
    // 配置WebpackDevServer参数
    // https://github.com/webpack/webpack-dev-server
    const serverConfig = {
      ...createDevServerConfig(proxyConfig, urls.lanUrlForConfig),
      host: HOST,
      port,
    };

    // 创建本地服务器
    const devServer = new WebpackDevServer(serverConfig, compiler);
    // 服务启动后的回调
    devServer.startCallback(() => {
      if (isInteractive) {
        clearConsole();
      }

      if (env.raw.FAST_REFRESH && semver.lt(react.version, '16.10.0')) {
        console.log(
          chalk.yellow(
            `Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.`
          )
        );
      }

      console.log(chalk.cyan('Starting the development server...\n'));
      openBrowser(urls.localUrlForBrowser);
    });
  })
...

总结

  • 如果想透彻的了解脚手架,必须读懂 react-dev-uilts
  • 内部原理为通过 webpack-dev-server创建一个 express 服务,然后和浏览器建立一个 webSocket 链接进行通讯。html-webpack-plugin 负责为本地的 html 文件注入 js
  • detect-port-alt会校验当前端口是否被占用
  • 在启动项目时,会检测 process.env.BROWSERSLIST、process.env.BROWSERSLIST_CONFIG、browserslist、.browserslistrc、package.json 文件中是否有 browserslist 信息,如果不存在会在 package.json 文件中自动添加默认值
json 复制代码
"browserslist": {
  "production": [">0.2%", "not dead", "not op_mini all"],
  "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version",
  ],
}
相关推荐
南囝coding7 小时前
React 19.2 重磅更新!这几个新特性终于来了
前端·react.js·preact
qq. 280403398413 小时前
react hooks
前端·javascript·react.js
PairsNightRain14 小时前
React Concurrent Mode 是什么?怎么使用?
前端·react.js·前端框架
小岛前端14 小时前
React 剧变!
前端·react.js·前端框架
teeeeeeemo14 小时前
Webpack 模块联邦(Module Federation)
开发语言·前端·javascript·笔记·webpack·node.js
用户479492835691517 小时前
面试官:讲讲这段react代码的输出(踩坑)
前端·javascript·react.js
GISer_Jing18 小时前
React中Element、Fiber、createElement和Component关系
前端·react.js·前端框架
lvchaoq1 天前
react 修复403页面无法在首页跳转问题
前端·javascript·react.js
郝开1 天前
6. React useState基础使用:useState修改状态的规则;useState修改对象状态的规则
前端·javascript·react.js
Codigger官方1 天前
Linux 基金会牵头成立 React 基金会:前端开源生态迎来里程碑式变革
linux·前端·react.js