create-react-app build 命令源码解析
准备工作
调试 cra build 源码步骤
- 打开源文件输入 debugger
- 点击 package.json 里 scripts 上的 debug 按钮, 选择 build。 (有很多方式,不一一列举)
入口文件 react-scripts.js
const args = process.argv.slice(2); // react-scripts build
process.argv 第一个是 node 可执行命令路径, 第二个是当前执行的文件, 第三个才是命令行参数 build, 如下。
1. '/Users/xxx/.nvm/versions/node/v18.12.1/bin/node',
2. '/Users/xxx/workspace/cra/node_modules/.bin/react-scripts'
3. 'build'
1. 读取用户命令行参数,找到对应执行文件
const scriptIndex = args.findIndex(
x => x === 'build' || x === 'eject' || x === 'start' || x === 'test'
); // 0
const script = scriptIndex === -1 ? args[0] : args[scriptIndex]; // build
const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : []; // []
if (['build', 'eject', 'start', 'test'].includes(script)) {
const result = spawn.sync(
process.execPath, // Node.js 进程的可执行文件的绝对路径名
nodeArgs
.concat(require.resolve('../scripts/' + script))
.concat(args.slice(scriptIndex + 1)),
{ stdio: 'inherit' }
);
}
spawn.sync(xxxx) 代码其实就是执行 ../scripts/build.js 文件, 如下。
node "User/xxx/cra/node_modules/react-scripts/scripts/build.js"
核心代码 build.js
2. 加载环境变量, 执行打包命令
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
// 设置环境变量
require('../config/env');
// 注入环境变量。下一节详解
const config = configFactory('production');
// configFactory 就是 webpack.config.js。里面有 cra 帮我们写好的 webpack 配置
fs.emptyDirSync(paths.appBuild);
// 清空 build 目录
copyPublicFolder()
build(previousFileSizes);
function build() {
const compiler = webpack(config); // 生成 compiler
return new Promise((resolve, reject) => {
compiler.run((err, stats) => { // 调用 compiler.run 方法开始打包
// do something
});
});
}
- 声明当前环境变量 production
- 注入 .env 文件变量
- configFactory 函数返回配置好的 webpack.config 配置文件
- 每次打包都清空 build 目录
- copyPublicFolder 函数把 cra 的 public 目录下的除了 index.html 外的所有文件迁移到打包的生成的 build 目录中, 在这里大家可能会有疑问。为什么排除index.html, 因为 index.html 是在 webpack.config.js 里用 html-webpack-plugin 生成的
- webpack(config) 获取 compiler。compiler.run 方法调用就会开启 webpack 打包。
补充工作
3. 注入环境变量 env.js
const dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`,
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
`${paths.dotenv}.${NODE_ENV}`,
paths.dotenv,
].filter(Boolean);
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
})
);
}
});
env文件加载也有优先级, 其顺序如下, 越靠前优先级越高
1. .env.(development|production).local
2. .env.local
3. .env.(development|production)
4. .env
4. 设置 NODE_PATH
const appDirectory = fs.realpathSync(process.cwd());
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder))
.map(folder => path.resolve(appDirectory, folder))
.join(path.delimiter);
- process.env.NODE_PATH 指定模块搜索路径。
- path.delimiter window下是;符号 mac下是:符号
核心代码都在这里了, 大家如果对一些方法感兴趣的话可自行去源码中查看。
总结
看源码只是为了空闲时学习, 方便以后自己遇到类似的需求时可以有思路。
相关文章
文档参考
cra 官方文档: create-react-app cra github: create-react-app
转载自:https://juejin.cn/post/7174797203725090877