create-react-app 脚本分析
scripts 文件夹
start.js
"use strict";
// Do this as the first thing so that any code reading it knows the right env.
/***** 定义环境变量 ***********************/
process.env.BABEL_ENV = "development";
process.env.NODE_ENV = "development";
/*******************************************************************************************************
* 使脚本在未处理的拒绝时崩溃,而不是沉默
* 忽略它们。在未来,未处理的承诺拒绝将会使用非零退出码终止Node.js进程。
*/
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on("unhandledRejection", (err) => {
throw err;
});
/*******************************************************************************************************
* Ensure environment variables are read.
* 确保环境变量被读取,若无环境变量 process·env·NODE_ENV ,则会抛错*
*/
// Ensure environment variables are read.
require("../config/env");
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 semver = require("semver");
const paths = require("../config/paths");
const configFactory = require("../config/webpack.config");
const createDevServerConfig = require("../config/webpackDevServer.config");
const getClientEnvironment = require("../config/env");
const react = require(require.resolve("react", { paths: [paths.appPath] }));
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
/*****判断是否使用yarn,依据是否有 yarn.lock 文件 ***********************/
const useYarn = fs.existsSync(paths.yarnLockFile);
/***** 确定 Nodejs 是否在终端上下文中运行的首选方法是检查 process·stdout·isTTY 属性的值是否为true ***********************/
const isInteractive = process.stdout.isTTY;
/***** 判断 public - index·html 与 src - index 文件是否存在***********************/
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
/***** 端口号与host ***********************/
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || "0.0.0.0";
if (process.env.HOST) {
console.log(
chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(
chalk.bold(process.env.HOST)
)}`
)
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
);
console.log(
`Learn more here: ${chalk.yellow("https://cra.link/advanced-config")}`
);
console.log();
}
/*******************************************************************************************************
* 我们要求你明确地设置浏览器,不要返回到browserslist默认配置
*/
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require("react-dev-utils/browsersHelper");
/*******************************************************************************************************
* 获取浏览器兼容性配置列表,例如:
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
*
*/
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
/*******************************************************************************************************
* 我们尝试使用默认端口,但如果它很忙,我们提供给用户
* 在另一个端口上运行 choosePort() 承诺解析到下一个空闲端口
* 使用webpack的工具方法choosePort,设置主机与端口号
*/
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
return choosePort(HOST, DEFAULT_PORT);
})
.then((port) => {
if (port == null) {
// We have not found a port.
return;
}
/*******************************************************************************************************
* configFactory:获取 webpack·config·js 开发模式的配置
* appName:获取 package·json 的app名称
* useTypeScript:查看文件中是否包含 tsconfig·json 文件
*/
const config = configFactory("development");
const protocol = process.env.HTTPS === "true" ? "https" : "http";
const appName = require(paths.appPackageJson).name;
const useTypeScript = fs.existsSync(paths.appTsConfig);
/*******************************************************************************************************
* urls = {
lanUrlForConfig:'192.168.20.65'
lanUrlForTerminal:'http://192.168.20.65:[1m8899[22m'
localUrlForBrowser:'http://localhost:8899'
localUrlForTerminal:'http://localhost:[1m8899[22m'
}
*/
const urls = prepareUrls(
protocol,
HOST,
port,
paths.publicUrlOrPath.slice(0, -1)
);
/*******************************************************************************************************
* 创建一个配置了自定义消息的webpack编译器
*/
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler({
appName,
config,
urls,
useYarn,
useTypeScript,
webpack,
});
/*******************************************************************************************************
* 加载代理配置
* proxySetting:获取package·json的proxy
* publicUrlOrPath:优先获取 process·env·PUBLIC_URL,再获取 package·json 的homepage
*/
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(
proxySetting,
paths.appPublic,
paths.publicUrlOrPath
);
/*******************************************************************************************************
* 通过web服务器提供由编译器生成的webpack资源。
* serverConfig:生成webpack·devServer的服务器配置
* devServer:通过webpack·devServer的服务器配置与编译器compiler生成一个开发服务器devServer的实例
*/
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = {
...createDevServerConfig(proxyConfig, urls.lanUrlForConfig),
host: HOST,
port,
};
const devServer = new WebpackDevServer(serverConfig, compiler);
/*******************************************************************************************************
* 启动webpack·devServer
*/
// Launch WebpackDevServer.
devServer.startCallback(() => {
// 如果在终端,清除终端控制台
if (isInteractive) {
clearConsole();
}
// react版本大于16.10才能进行快速刷新
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"));
// 打开浏览器进入localUrlForBrowser地址,localUrlForBrowser:'http://localhost:8899'
openBrowser(urls.localUrlForBrowser);
});
/*******************************************************************************************************
* SIGINT
* 产生方式: 键盘Ctrl+C
* 产生结果: 只对当前前台进程,和他的所在的进程组的每个进程都发送SIGINT信号,之后这些进程会执行信号处理程序再终止.
*
* SIGTERM
* 产生方式: 和任何控制字符无关,用kill函数发送
* 本质: 相当于shell> kill不加-9时 pid.
* 产生结果: 当前进程会收到信号,而其子进程不会收到.如果当前进程被kill(即收到SIGTERM),则其子进程的父进程将为init,即pid为1的进程.
*/
["SIGINT", "SIGTERM"].forEach(function (sig) {
process.on(sig, function () {
devServer.close();
process.exit();
});
});
if (process.env.CI !== "true") {
// 当stdin结束时优雅的退出
// Gracefully exit when stdin ends
process.stdin.on("end", function () {
devServer.close();
process.exit();
});
}
})
.catch((err) => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});