赞
踩
在之前 实现create-react-app packages 中的 create-react-app 这篇学习记录里头,七七八八实现了 create-react-app
主要流程,再往下就到了 script
命令执行,我们常用的两个命令就是 start
和 build
了,这两个命令也有点相似,我们就来看看 “非死不可” 是怎么实现的吧
react-scripts/package.json 中注册了一个命令。当我们在终端执行 react-scripts
,就会运行这个文件
"bin": {
"react-scripts": "./bin/react-scripts.js"
},
bin/react-scripts.js
的主要实现是这样的,其实也很简单,他相当于一个入口,根据终端提供的命令,直接去找对应的文件执行
#!/usr/bin/env node // 捕获未处理的异常 process.on('unhandledRejection', err => { throw err; }); const spawn = require('react-dev-utils/crossSpawn'); const args = process.argv.slice(2); // build,eject,start,test 只有这 4 个指令 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)) { // 开个子进程执行 scripts 文件下对应名称的文件 // 如: 执行 scripts/build.js const result = spawn.sync( process.execPath, nodeArgs .concat(require.resolve('../scripts/' + script)) .concat(args.slice(scriptIndex + 1)), { stdio: 'inherit' } ); // ...省略 SIGKILL 和 SIGTERM 提示,和主要流程无关,暂且不用管他 } 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' ); }
流程梳理:注册命令 + 根据命令找相对应的文件执行(一个命令的主要逻辑都封装在一个文件里头)
我们只需要分析对应文件的逻辑就可以了。
start
命令代码量也不多,主要逻辑就几十行代码,主要得流程就是
configFactory('development')
const compiler = webpack(config)
const serverConfig = createDevServerConfig()
const devServer = new WebpackDevServer(serverConfig, compiler);
openBrowser(urls.localUrlForBrowser);
如果不是很熟悉 webpack
配置的小伙伴,也可以去 react-scripts/config/webpack.config.js 看一下他的 webpack
配置,这里用的是工厂模式,module.exports = function (webpackEnv) {}
根据 webpackEnv
的不用,返回不同的配置信息。稍微整理了一下代码:
process.env.BABEL_ENV = 'development'; process.env.NODE_ENV = 'development'; // 捕获未处理的异常 process.on('unhandledRejection', err => { throw err; }); 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'); const useYarn = fs.existsSync(paths.yarnLockFile); const isInteractive = process.stdout.isTTY; // 检查是否有入口文件 if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { process.exit(1); } const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; const HOST = process.env.HOST || '0.0.0.0'; const { checkBrowsers } = require('react-dev-utils/browsersHelper'); checkBrowsers(paths.appPath, isInteractive) .then(() => { // 有默认端口,如果端口被占用,就用用户提供的端口 return choosePort(HOST, DEFAULT_PORT); }) .then(port => { if (port == null) { // We have not found a port. return; } // 有点工厂模式的感觉,development 和 production 生成两个对应的配置 const config = configFactory('development'); const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const appName = require(paths.appPackageJson).name; const useTypeScript = fs.existsSync(paths.appTsConfig); const urls = prepareUrls( protocol, HOST, port, paths.publicUrlOrPath.slice(0, -1) ); // Create a webpack compiler that is configured with custom messages. const compiler = createCompiler({ appName, config, urls, useYarn, useTypeScript, webpack, }); // 配置代理服务器 const proxySetting = require(paths.appPackageJson).proxy; const proxyConfig = prepareProxy( proxySetting, paths.appPublic, paths.publicUrlOrPath ); // 整合配置 const serverConfig = { ...createDevServerConfig(proxyConfig, urls.lanUrlForConfig), host: HOST, port, }; // 用 serverConfig 配置 WebpackDevServer 起服务 const devServer = new WebpackDevServer(serverConfig, compiler); devServer.startCallback(() => { if (isInteractive) { clearConsole(); } // ...版本更新提示 openBrowser(urls.localUrlForBrowser); }); // ...省略 SIGINT,SIGTERM 处理 和 环境变量处理 }) //...省略catch 处理,就是打印错误信息,退出程序
build
命令build
命令和 start
命令有一些相似的地方,我们梳理一下主要的流程:
webpack
的配置文件build
目录public
下面的静态文件到 build 目录webpack compiler
直接打包就行了const path = require('path'); const fs = require('fs-extra'); const webpack = require('webpack'); const configFactory = require('../config/webpack.config'); const paths = require('../config/paths'); const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild; const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; const useYarn = fs.existsSync(paths.yarnLockFile); // These sizes are pretty large. We'll warn for bundles exceeding them. const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; const isInteractive = process.stdout.isTTY; // 同样检查入口 if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { process.exit(1); } // 获取 webpack 配置 const config = configFactory('production'); // We require that you explicitly set browsers and do not fall back to // browserslist defaults. const { checkBrowsers } = require('react-dev-utils/browsersHelper'); checkBrowsers(paths.appPath, isInteractive) .then(() => { return measureFileSizesBeforeBuild(paths.appBuild); }) .then(previousFileSizes => { // 清空 build 文件夹 fs.emptyDirSync(paths.appBuild); // 复制 public 文件夹 copyPublicFolder(); // 开始打包 return build(previousFileSizes); }) .then( ({ stats, previousFileSizes, warnings }) => { // ...省略各种打印信息 // 打印打包后的信息 printFileSizesAfterBuild( stats, previousFileSizes, paths.appBuild, WARN_AFTER_BUNDLE_GZIP_SIZE, WARN_AFTER_CHUNK_GZIP_SIZE ); const appPackage = require(paths.appPackageJson); const publicUrl = paths.publicUrlOrPath; const publicPath = config.output.publicPath; const buildFolder = path.relative(process.cwd(), paths.appBuild); printHostingInstructions( appPackage, publicUrl, publicPath, buildFolder, useYarn ); }, // ...省略错误信息处理 ) // Create the production build and print the deployment instructions. function build(previousFileSizes) { console.log('Creating an optimized production build...'); const compiler = webpack(config); return new Promise((resolve, reject) => { compiler.run((err, stats) => { //...各种 warning 打印,和错误处理 }); }); } function copyPublicFolder() { fs.copySync(paths.appPublic, paths.appBuild, { dereference: true, // appHtml 有插件 htmlWebpackPlugin 处理 filter: file => file !== paths.appHtml, }); }
只要把 webpack
的配置信息配置好了,其他的就交给 webpack
的 complier
打包就行了,更多的可能就是要做交互的信息提示,以及异常处理而已。感觉还是比较简单的,我对后面开发自己的脚手架少了许多迷茫,多了许多信心。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。