快来跟我一起学 React(Day2)
Posted vv_小虫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快来跟我一起学 React(Day2)相关的知识,希望对你有一定的参考价值。
简介
继续我们的 React
的学习,上一节我们介绍了什么是 JSX
语法,并且从 Babel
源码角度分析了 JSX
语法的转换过程,最后我们还用 CDN
的形式搭建了一个简单的 React
项目,这一节我们研究一下 React
官方提供的脚手架create-react-app
。
知识点
- React 官方脚手架(create-react-app)
- react-scripts
- react 项目中的 webpack 配置
- start 命令
- build 命令
安装 React
小伙伴可以先看一下官网的描述:
- React 的安装:https://zh-hans.reactjs.org/docs/cdn-links.html
- create-react-app 官方文档:https://create-react-app.dev/docs
- create-react-app 开源地址:https://github.com/facebook/create-react-app
React
的安装方式有两种:
- CDN 链接。
- 使用React 官方脚手架(create-react-app)。
第一种我们上一节已经使用过了,接下来我们从源码角度介绍一下 create-react-app
。
你可以利用以下方式通过脚手架去创建 React
项目:
npx
npx create-react-app my-app
(npx 在 npm 5.2+ 才能使用,可以看这个 instructions for older npm versions)
npm
npm init react-app my-app
npm init
在 npm 6+ 才能使用
Yarn
yarn create react-app my-appe
yarn create
在 Yarn 0.25+ 才能使用
其实 npm init
和 yarn create
就是 npx
的简写(但是在 npm
和 yarn
中可以省略 create
字符串,直接 npm init react-app
或yarn create react-app
就可以了 ),工作流程大概是这样的:
- 首先会判断你本地有没有
create-react-app
依赖,如果没有的话就会去npm
官方下载。 - 找到
create-react-app
依赖,执行create-react-app
声明的bin
入口文件。
我们还是来测试一下吧。
测试
首先在本地找一个目录,然后执行以下命令(以 npm
为例),创建一个叫 react-demo1
的项目:
npm init react-app react-demo1
等执行完毕后会看到一个新创建好的文件夹 react-demo1
:
然后我们在 react-demo1
目录执行 npm start
命令就可以启动项目了:
npm start
可以看到,一个简单的 React
项目就被创建完毕并启动了。
React 官方脚手架(create-react-app)
我们从源码角度分析一下,当我们执行:
npm init react-app react-demo1
命令后,create-react-app
脚手架是如何帮我们创建项目的?
我们直接去官网下一份 create-react-app
的源码:
create-react-app 源码地址:https://github.com/facebook/create-react-app
可以看到,create-react-app
是一个用 lerna
管理的项目集合,所以接下来我们先安装依赖:
lerna bootstrap || yarn
本地没有安装 lerna
的话就直接用 yarn
去安装。
当我们执行:
npm init react-app react-demo1
命令后,首先执行的是 packages/create-react-app/index.js
文件(当前版本 4.0.3):
...
const init = require('./createReactApp');
init();
可以看到,直接执行了 ./createReactApp.js
文件的 init
方法:
function init()
const program = new commander.Command(packageJson.name)
...
.action(name =>
// 获取传递的项目名 react-demo1
projectName = name;
)
...
// 开始创建项目
createApp(
projectName, // 项目名
program.verbose, // 是否显示 npm 安装具体信息
program.scriptsVersion, // react-scripts 版本号
program.template, // 模版名称
program.useNpm, // 是否使用 npm
program.usePnp // 是否使用 pnp
);
...
init
方法中获取了一下传递进来的项目名,然后调用了 createApp
方法:
function createApp(name, verbose, version, template, useNpm, usePnp)
// 项目根目录
const root = path.resolve(name);
// 项目名
const appName = path.basename(root);
// 初始化项目 package.json 文件
const packageJson =
name: appName,
version: '0.1.0',
private: true,
;
fs.writeFileSync(
path.join(root, 'package.json'),
JSON.stringify(packageJson, null, 2) + os.EOL
);
// 开始创建
run(
root,
appName,
version,
verbose,
originalDirectory,
template,
useYarn,
usePnp
);
可以看到,初始化了我们项目的 package.json
文件,接着又执行了 run
方法:
function run(
root,
appName,
version,
verbose,
originalDirectory,
template,
useYarn,
usePnp
)
Promise.all([
// 获取 react-scripts 依赖基本信息
getInstallPackage(version, originalDirectory),
// 获取项目模版依赖基本信息,默认是 cra-templagte 模版
getTemplateInstallPackage(template, originalDirectory),
]).then(([packageToInstall, templateToInstall]) =>
...
.then(( isOnline, packageInfo, templateInfo ) =>
// 在项目根目录安装 react、react-dom、cra-tamplte 依赖
return install(
root,
useYarn,
usePnp,
allDependencies,
verbose,
isOnline
).then(() => (
packageInfo,
supportsTemplates,
templateInfo,
));
)
.then(async ( packageInfo, supportsTemplates, templateInfo ) =>
// 执行当前项目 react-demo1/node_modules/packageName/scripts/init.js 脚本文件
await executeNodeScript(
cwd: process.cwd(),
args: nodeArgs,
,
[root, appName, verbose, originalDirectory, templateName],
`
var init = require('$packageName/scripts/init.js');
init.apply(null, JSON.parse(process.argv[1]));
`
);
);
可以看到,run
方法主要是安装依赖,这些依赖是:
-
react:react api 基础库。
-
react-dom:react 核心库。
-
cra-template:react 项目模版。
因为我们在创建项目的时候没有指定项目模版,所以默认是官方的
cra-template
模版,官方中有两个模版:- cra-template:默认项目模版。
- cra-tamplate-typescript:ts 项目模版。
当然,还支持你传递自己的模版,可以为
file
、npm
、gitlab
类型,就不具体掩饰了。
接着执行了当前项目 react-demo1/node_modules/packageName/scripts/init.js 脚本文件:
// 初始化 git
function tryGitInit()
...
module.exports = function (
appPath,
appName,
verbose,
originalDirectory,
templateName
)
// 找到 react-demo1/nodule_modules/cra-template 目录,然后按照规则 copy 文件到当前 react-demo1 项目,最后删除 react-demo1/nodule_modules/cra-template 目录
console.log();
// 恭喜创建完毕
console.log('Happy hacking!');
;
到这,react-demo1
项目就算是创建完毕了。
start 命令
当我们在刚创建好的 react-demo1
项目中执行 npm start
命令的时候,会自动帮我们开启一个开发环境,并且打开入口页面:
npm start
ok,我们看一下当我们在项目根目录执行 npm start
命令到底干了什么?
首先是 react-demo1/package.json
文件中的 start
命令:
...
"scripts":
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
...
可以看到,执行了 react-scripts start
命令。
我们找到 react-scripts start
命令的源码 create-react-app/packages/react-scripts/scripts/start.js
:
// 设置当前环境变量为 development
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
// 开始项目中配置的环境变量
require('../config/env');
// 校验 typescript 的配置
verifyTypeScriptSetup();
...
// 校验入口文件跟入口 html 模版文件是否存在
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs]))
process.exit(1);
...
// 创建 webpack 的编译类
const compiler = createCompiler(
appName,
config,
devSocket,
urls,
useYarn,
useTypeScript,
tscCompileOnError,
webpack,
);
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(
proxySetting,
paths.appPublic,
paths.publicUrlOrPath
);
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
// 创建 WebpackDevServer 开启 webpack 服务
const devServer = new WebpackDevServer(compiler, serverConfig);
...
);
start
命令其实就是利用 webpack-dev-server
开启了一个 webpack
服务。(对 webpack
不熟的童鞋,强烈推荐我之前写的文章 来和 webpack 谈场恋爱吧:https://www.lanqiao.cn/courses/2893)
build 命令
build
命令就不用说了,直接就是 webpack
的打包操作,比如我们在 react-demo1
目录下执行 build
命令:
npm run build
可以看到,在 react-demo1/build
目录中输出了 webpack
打包过后的结果。
start
跟 build
都是利用的 webpack
进行编译打包操作的,只是环境不同 webpack
的配置也会不同,下面我们重点看一下在 development
模式与 production
模式中,React
脚手架对 webpack
的配置。
React 项目中的 Webpack 配置
我们直接找到源码 create-react-app/packages/react-scripts/config/webpack.config.js
文件:
...
// 是否生成 source-map 文件
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// webpack 客户端热载入口文件
const webpackDevClientEntry = require.resolve(
'react-dev-utils/webpackHotDevClient'
);
// 是否禁止 eslint 警告提示
const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === 'true';
// 是否禁止 eslint
const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true';
// 媒体文件字节限制,小于这个限制会打包成 base64 字符串,超出这个限制会导出文件
// 主要是指对 url-loader 的配置
const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
);
// 是否使用 ts
const useTypeScript = fs.existsSync(paths.appTsConfig);
// 根据环境返回不同的 webpack 配置,development 或者 production
module.exports = function (webpackEnv)
// 开发环境
const isEnvDevelopment = webpackEnv === 'development';
// 生产环境
const isEnvProduction = webpackEnv === 'production';
// 生成样式 loaders 主要是 sass、scss、css
const getStyleLoaders = (cssOptions, preProcessor) =>
const loaders = [
// 开发环境使用 style-loader(会生成内嵌样式)
isEnvDevelopment && require.resolve('style-loader'),
// 生产环境使用 MiniCssExtractPlugin.loader (生成外联样式)
isEnvProduction &&
loader: MiniCssExtractPlugin.loader,
options: paths.publicUrlOrPath.startsWith('.')
? publicPath: '../../'
: ,
,
// 配置 css-loader
loader: require.resolve('css-loader'),
options: cssOptions,
,
// 配置 postcss-loader
loader: require.resolve('postcss-loader'),
options:
postcssOptions:
plugins: [
// flex 布局兼容插件
require('postcss-flexbugs-fixes'),
[
// postcss env 插件集合
require('postcss-preset-env'),
// 自动添加样式兼容前缀
autoprefixer:
flexbox: 'no-2009',
,
stage: 3,
,
],
postcssNormalize(),
],
,
sourceMap: isEnvProduction && shouldUseSourceMap,
,
,
].filter(Boolean);
if (preProcessor)
// 添加根路径解析 loader,默认指向项目 src 目录
loaders.push(
loader: require.resolve('resolve-url-loader'),
options:
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
root: paths.appSrc,
,
,
// 添加 sass loader 等样式预加载器
loader: require.resolve(preProcessor),
options:
sourceMap: true,
,
);
return loaders;
;
return
// 设置 webpack 的模式
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// 设置 source-map 的生成方式
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
// 入口文件配置
entry:
isEnvDevelopment && !shouldUseReactRefresh
? [
// 测试环境并且允许热载刷新页面的时候
// 加载热载刷新入口文件
webpackDevClientEntry,
// 加载项目入口文件(默认 src/index.js)
paths.appIndexJs,
]
: paths.appIndexJs,
// 输出文件设置
output:
// 输出目录(默认是项目的 build 目录)
path: isEnvProduction ? paths.appBuild : undefined,
// 开发环境打开模块的 pathinfo 路径提示
pathinfo: isEnvDevelopment,
// 输出文 chunk、assets 名称设置
filename: isEnvProduction
...
;
太多了,就不一一分析了,小伙伴自己看一下源码文件哦(对 webpack
不熟的童鞋,强烈推荐我之前写的文章 来和 webpack 谈场恋爱吧:https://www.lanqiao.cn/courses/2893)。
那有小伙伴要问了,既然 React
脚手架帮我们内置了 webpack
的配置,如果我们需要自己修改 webpack
的一些配置该咋办呢?
比如我们需要修改以下配置:
修改输出的目录
从源码中我们可以知道,目前项目的输出文件的目录为 build
,比如我们需要改成 dist
,我们需要怎么做呢?
我们先看一下目前的配置文件 packages/react-scripts/config/webpack.config.js
:
const paths = require('./paths');
...
output:
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
可以看到,当为生产环境(production)的时候,path
的值为 paths.appBuild
。
我们找到 packages/react-scripts/config/paths.js
文件中的 appBuild
变量:
...
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
...
// 默认输出文件目录路径
const buildPath = process.env.BUILD_PATH || 'build';
module.exports =
...
appBuild: resolveApp(buildPath),
...
可以看到,我们可以通过 process.env.BUILD_PATH
变量去修改输出文件路径。
那么 process.env.BUILD_PATH
我们该怎么定义呢?
-
利用
cross-env
库,在执行命令的时候声明process.env.BUILD_PATH
变量。我们首先在
react-demo1
项目根目录安装cross-env
:yarn add -D cross-env
接着修改一下
package.json
中的build
命令:"scripts": "build": "cross-env BUILD_PATH=dist react-scripts build", ...
修改完毕后重新打包测试:
npm run build
可以看到,打包输出的目录变成了 dist
。
-
利用脚手架提供的环境变量文件
.env.[NODE_ENV].[local]
来修改,其中NODE_ENV
跟local
可选,表示根据环境来加载。我们在
react-demo1
项目根目录底下创建一个.env
文件,这样不管是development
模式还是production
模式,都会加载.env
文件中声明的变量:touch ./.env
然后在
.env
文件中声明BUILD_PATH
变量为dist
:## 修改项目的输出路径 BUILD_PATH=dist
修改完毕后重新打包测试,效果跟上面的一样,我就不演示了。
总结
这一节我们主要介绍了 React
官方提供的脚手架 create-react-app
,我们直接从源码的角度来分析了一个 React
项目创建的过程,其实无非就是对 Webpack
的一些配置而已,所以对 Webpack
不熟悉的小伙伴一定要加油补上哦,从create-react-app
官方文档上看,并没有提供 .env
配置文件的说明、怎么去修改 webpack
配置说明等等,还是需要你自己去看源码的,所以这就是看源码的重要性,其实从源码中我们可以知道,并不是所有的 webpack 配置都能修改的,那项目中我们又需要修改的话,该怎么办呢?那就只能抛弃脚手架了,所以这也算是 React
脚手架的一些不足吧,并没有像 vue-cli
一样,可以随意修改 webpack
的配置。
ok,后面我将会带大家脱离脚手架,利用 webpack
从 0
开始搭建一个 React
项目,大家敬请期待吧!
欢迎关注我的公众号,每天都有技术文章推送。
以上是关于快来跟我一起学 React(Day2)的主要内容,如果未能解决你的问题,请参考以下文章