Skip to Content

面试导航 - 程序员面试题库大全 | 前端后端面试真题 | 面试

前端工程化NEWnpm start 原理

npm start 是一个常见的命令,通常用于启动 Node.js 项目或前端应用的开发服务器。要理解这个命令背后的原理,我们需要从几个角度来分析:Node.js、npm 脚本和项目的配置。

npm 和 npm script

npm 是 Node.js 的包管理工具,用于管理项目的依赖和任务。npm 支持通过脚本(scripts)来自动化执行常见的开发任务。

package.json 文件中,scripts 字段定义了不同的任务和命令。例如:

{ "scripts": { "start": "node server.js" } }

在这个例子中,npm start 将会执行 node server.js 命令。npm start 实际上是执行 scripts.start 字段中定义的命令。

React 项目中的 npm start

在 React 和 Vue 项目中执行 npm start 的机制虽然有些不同,但其原理都基于 npm 脚本配置以及开发工具链的工作。

React 项目通常使用 Create React App(CRA)工具来搭建。这是一个开箱即用的脚手架,自动化了很多开发流程,包括构建工具、开发服务器和热重载等。

首先我们来使用 CRA 脚手架来创建一个项目:

npx create-react-app start

对于 CRA 创建出来的 React 项目,npm start 执行的命令通常是:

"scripts": { "start": "react-scripts start" }

react-scriptsCRA 默认包含的一个工具包,包含了项目的启动、构建和测试等功能。当我们安装这个依赖包的时候,它会默认在 node_modules 上添加一个 .bin 目录:

20250219125638

react-scripts.cmd 和 react-scripts.ps1 这两个文件是与 React 项目开发相关的执行脚本,它们用于在不同操作系统上执行 react-script 命令:

  1. react-scripts.cmd 代码如下:
@ECHO off GOTO start :find_dp0 SET dp0=%~dp0 EXIT /b :start SETLOCAL CALL :find_dp0 IF EXIST "%dp0%\node.exe" ( SET "_prog=%dp0%\node.exe" ) ELSE ( SET "_prog=node" SET PATHEXT=%PATHEXT:;.JS;=;% ) endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\react-scripts\bin\react-scripts.js" %*
  1. react-scripts.ps1 代码如下:
#!/usr/bin/env pwsh $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent $exe="" if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { # Fix case when both the Windows and Linux builds of Node # are installed in the same directory $exe=".exe" } $ret=0 if (Test-Path "$basedir/node$exe") { # Support pipeline input if ($MyInvocation.ExpectingInput) { $input | & "$basedir/node$exe" "$basedir/../react-scripts/bin/react-scripts.js" $args } else { & "$basedir/node$exe" "$basedir/../react-scripts/bin/react-scripts.js" $args } $ret=$LASTEXITCODE } else { # Support pipeline input if ($MyInvocation.ExpectingInput) { $input | & "node$exe" "$basedir/../react-scripts/bin/react-scripts.js" $args } else { & "node$exe" "$basedir/../react-scripts/bin/react-scripts.js" $args } $ret=$LASTEXITCODE } exit $ret

当你运行 npm start 时,实际上是执行 package.json 文件中的 scripts.start 配置。例如,如果你的 package.json 配置如下:

"scripts": { "start": "react-scripts start" }

npm start 会运行 react-scripts start 命令。

react-scripts 实际上是通过 react-scripts.cmd 或 react-scripts.ps1 来执行的脚本,这两个文件分别用于 Windows 批处理和 PowerShell 环境。

react-scripts.cmd 的执行

在执行 npm start 时,首先会运行 react-scripts.cmd(如果是 Windows 系统)。其执行流程如下:

  1. 跳转到 find_dp0:这部分代码用于获取当前脚本所在的路径,并将其存储在 dp0 变量中。
:find_dp0 SET dp0=%~dp0 EXIT /b
  1. 设置 node.exe 路径:接着检查 dp0 路径下是否存在 node.exe。如果存在,则使用该 node.exe,否则使用全局的 node 命令。
IF EXIST "%dp0%\node.exe" ( SET "_prog=%dp0%\node.exe" ) ELSE ( SET "_prog=node" SET PATHEXT=%PATHEXT:;.JS;=;% )
  1. 执行 react-scripts.js:在确保 node.exe 的路径后,脚本将会执行 react-scripts 的 JavaScript 文件 react-scripts.js,并传递给它 npm start 时传入的参数(%*)。
"%_prog%" "%dp0%\..\react-scripts\bin\react-scripts.js" %*

这行命令启动了 React 项目的开发服务器或者执行构建相关的任务。

react-scripts.ps1 文件的执行和上面的逻辑也是差不多一样,这两个脚本都会定位到 node 执行程序,并运行 react-scripts/bin/react-scripts.js

react-scripts.js

在前面的内容中讲到执行 react-scripts.js 文件实际上就是执行下图的文件:

20250219135317

'use strict'; // 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; }); const spawn = require('react-dev-utils/crossSpawn'); 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]; 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' }, ); if (result.signal) { if (result.signal === 'SIGKILL') { console.log( 'The build failed because the process exited too early. ' + 'This probably means the system ran out of memory or someone called ' + '`kill -9` on the process.', ); } else if (result.signal === 'SIGTERM') { console.log( 'The build failed because the process exited too early. ' + 'Someone might have called `kill` or `killall`, or the system could ' + 'be shutting down.', ); } process.exit(1); } process.exit(result.status); } 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'); }

在这里的代码中,主要还是获取后面的参数来判断具体执行哪个文件,也就是区分不用的环境来执行不同的流程来启动 webpack:

const scriptIndex = args.findIndex( (x) => x === 'build' || x === 'eject' || x === 'start' || x === 'test', );

也就是执行这些不同的文件:

20250219135843

设置不同的环境变量来运行。

20250219140007

在这里就是将我们的 webpack 配置交给 webpack-dev-server 来进行处理并对输出的结果进行清除和优化,最终让开发者能看到好看的控制台输出。

20250219140128

这是 webpack 默认的返回的控制台信息,这是肯定不好看的,我们就需要先清空这些信息并提取关键信息来美化控制台:

20250219140544

总结

通过 npm start 启动应用的原理主要依赖于 npm 脚本和 package.json 中的配置。理解这一机制有助于你优化项目的启动流程、提高开发效率,并更容易进行跨平台开发。掌握它的工作原理后,你还可以自定义启动命令,集成其他工具,甚至通过调试信息优化项目的启动过程。

在上面的内容中只是讲到了 react 的,其实 vue 的脚手架也是差不多的执行原理。

如果你想自己实现一个,那么你可以看一下我们的目前正在研发的前端脚手架,就是差不多这个思路来实现的:

最后更新于:
Copyright © 2025Moment版权所有粤ICP备2025376666