Я был 23 января этого года на
segmentfault
Опубликовал эту статью на . Случайные мысли сегодняcreate-react-app
Исходник статьи, нашел много похожих статей в наггетсах... Очень хочу выложить картинку, а если поменять два слова, то будет оригинально (хотя вроде бы так), ну и пусть, что еще могу сказать!
Эта статья изначально перемещаетсяновый адрес блога,перепечатайте и укажите источник автора,спасибо!!!Если считаете что это ваше изменить слово-два,просто ТМ как будто я этого не говорил.
предисловие
За это время дел в компании стало меньше, а свободного времени много.Как начинающий программист, только что закончивший школу и пришедший на работу, совсем не смею расслабляться. быть всеми и хочу делать что-то для сообщества открытого исходного кода. Я внес небольшой вклад, но у меня не было четких целей. Недавно я усердно работал, чтобы пройти через это.react
, плюсreact
Инструмент для строительных лесовcreate-react-app
Уже очень зрелый, инициализируйтеreact
Проект не может видеть, как он построил эту среду разработки для меня, и как это сделать.Я все еще хочу знать, поэтому я вытащил его.
Если в тексте есть ошибки или исправления, пожалуйста, дайте нам больше советов и вместе двигайтесь вперед.
Инструкции по применению
Как я сказал в начале, чтобы изучить новую вещь, нужно сначала научиться ею пользоваться, а потом посмотреть, как она реализована.create-react-app
В конце концов, это то, что, в двух словах, должно обеспечить официальное быстрое создание новогоreact
Инструменты для строительных лесов для проектов, похожие наvue
изvue-cli
а такжеangular
изangular-cli
, а почему бы и не позвонитьreact-cli
Это наводящий на размышления вопрос... хахаха, смешно!
Не говори ерунды, выкладывай фото и смотри прямоcreate-react-app
Команда помогите.
Краткое описание
Ведь это уже очень навороченный инструмент, да и инструкция тоже совершенная, в которой основное внимание уделяется--scripts-version
Only <project-directory> is required
create-react-app
При заказе за ним должно следовать название вашего проекта.На самом деле здесь оно не точное, типа--version --info --help
Этим трем параметрам не требуется имя проекта, подробности см. ниже:
-
create-react-app -V(or --version)
: Эта опция может использоваться отдельно или в печатной версии, каждый инструмент имеет основное право? -
create-react-app --info
: Эту опцию также можно использовать отдельно для печати текущей системы иreact
Соответствующие параметры среды разработки, то есть что такое операционная система?Node
версии и т. д., вы можете попробовать сами. -
create-react-app -h(or --help)
: Это точно можно использовать в одиночку, иначе как распечатать справочную информацию, а то скринов выше не будет.
То есть, в дополнение к трем указанным выше параметрам, их можно использовать отдельно от имени требуемого параметра проекта, поскольку все они инициализируются вами.react
Проекту делать нечего, тогда остальные параметры надо инициализироватьreact
Проект настроен, то есть три параметра могут появляться одновременно, давайте посмотрим на их соответствующие функции:
-
create-react-app <my-project> --verbose
: Посмотрите на картинку выше, распечатайте локальный лог, на самом деле онnpm
а такжеyarn
Опция, которую можно добавить для установки внешних зависимостей, которые могут печатать информацию при возникновении ошибки в установке. -
create-react-app <my-project> --scripts-version
: Поскольку он разделяет этап инициализации каталога создания и команду управления, он используется для управленияreact
Разработка, упаковка и тестирование проекта находятся наreact-scripts
Внутри, так что вы можете настроить параметры управления отдельно здесь, возможно, вы не очень хорошо это понимаете, я подробно объясню это ниже. -
create-react-app <my-project> --use-npm
: Вы можете увидеть значение этой опции,create-react-app
Использовать по умолчаниюyarn
для установки, запустите, если вы не используетеyarn
Вам может понадобиться эта конфигурация, укажитеnpm
.
о--scripts-version
Хочу сказать еще немного, собственно, на скриншоте выше мы уже видим,create-react-app
Варианты среди них я уже объяснял.Всего четыре случая.По одному я их не пробовал,потому что это довольно хлопотно.Если они будут использоваться в дальнейшем,то я их восполню.Позвольте мне сначала примерно предположить, что они означают:
- Указана версия 0.8.2
- существует
npm
опубликовать свой собственныйreact-scripts
- Настройте его на своем веб-сайте
.tgz
скачать пакет - Настройте его на своем веб-сайте
.tar.gz
скачать пакет
Это видно из вышеизложенногоcreate-react-app
Он по-прежнему очень дружелюбен к разработчикам, многие вещи вы можете определить сами, если вы не хотите так метаться, он также предоставляет стандартныеreact-scripts
Для использования разработчиками, мне всегда это было любопытно, а про официальный стандарт я потом расскажу отдельноreact
Как производится настройка.
Анализ каталогов
С итерацией его версии исходники обязательно изменятся, вот что я скачалv1.1.0
Мы можем само по себеgithub
Загрузите эту версию, не могу найтитыкать ссылку.
основное описание
Давайте посмотрим на его структуру каталогов.
├── .github
├── packages
├── tasks
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── .yarnrc
├── appveyor.cleanup-cache.txt
├── appveyor.yml
├── CHANGELOG-0.x.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── lerna.json
├── LICENSE
├── package.json
├── README.md
└── screencast.svg
-
.github
: Вот где вы упомянули, когда упомянули в этом проектеissue
а такжеpr
Когда спецификации -
packages
: Дословный смысл - пакеты..... Временно сзади подробно описано ---->фокус -
tasks
: Буквально означает задания ..... временная помощь, деталь позже сказать ---->фокус -
.eslintignore
:eslint
Игнорировать файлы при проверке -
.eslintrc
:eslint
Проверьте файл конфигурации -
.gitignore
:git
Игнорировать файл при отправке -
.travis.yml
:travis
конфигурационный файл -
.yarnrc
:yarn
конфигурационный файл -
appveyor.cleanup-cache.txt
: в нем есть строчкаEdit this file to trigger a cache rebuild
Редактирование этого файла приводит к срабатыванию кеша, для чего он нужен? -
appveyor.yml
:appveyor
конфигурационный файл -
CHANGELOG-0.x.md
: начиная с версии 0.X изменить документацию CHANGELOG.md
-
CODE_OF_CONDUCT.md
:facebook
-
CONTRIBUTING.md
: Основное описание проекта -
lerna.json
:lerna
конфигурационный файл -
LICENSE
: Лицензия с открытым исходным кодом -
package.json
: файл конфигурации проекта -
README.md
: Инструкция по использованию проекта -
screencast.svg
:картина...
Прочитав столько документов, вы отступили? Хахахаха, ладно, ладно, давайте к делу, на самом деле вышеизложенное полезно нам только для чтения исходного кодаpackages
,tasks
,package.json
Всего три файла, и все, что можно использовать в этой статье, этоpackages
а такжеpackage.json
Не пытается ударить меня ..... Я просто хочу сказать вам, что такое использование этих файлов, все они имеют свою собственную роль, если не понимают, обратитесь к следующим справочным ссылкам.
Ссылка на ссылку
eslint
Связанный:официальный сайт эслинта
travis
Связанный:официальный сайт Трэвиса Начало работы с Travis
yarn
Связанный:официальный сайт пряжи
appveyor
Связанный:официальный сайт приложения
lerna
Связанный:официальный сайт лерны
Инструменты для самостоятельного понимания, в документе говорится только о соответствующем исходном кодеpackages
,package.json
.
найти вход
Большинство текущих фронтенд-проектов имеют множество других зависимостей, не похожих на исходные.javascript
Библиотека инструментов, получите файл исходного кода, вы можете начать читать, напримерjQuery
,underscore
Подождите, один-два файла содержат все его содержимое, хотя есть и фреймворки, которые будутumd
Файлы спецификаций можно читать напрямую, напримерbetter-scroll
Подождите, но на самом деле, когда он писал исходный код, он все равно разбил его на множество частей и, наконец, интегрировал вместе с инструментом упаковки. но, какcreate-react-app
Кажется, что такие инструменты скаффолдинга нельзя рассматривать так же, как раньше, нужно найти вход всей программы и постепенно пробиться, поэтому первым инструментом должен быть поиск входа.
начать следить
Когда мы получим проект, с какого файла мы должны начать? пока он основан наnpm
Управляется, рекомендую оба отpackage.json
Файл начинает смотреть, это вводной файл проекта, вы его не смотрите.
Теоретически он должен иметь некоторую описательную информацию, такую как имя, версия и т. д., но это бесполезно.Посмотрите на несколько важных конфигураций.
"workspaces": [
"packages/*"
],
оworkspaces
Сначала я былnpm
Документ описания не найден, хотя из буквального значения можно догадаться, что это означает, что фактический рабочий каталогpackages
, то я проверилyarn
Что внутри, подробнее см.эта статья, для локального тестирования не обращайте на это внимания, просто отсюда мы знаем, что реально рабочие файлы все тамpackages
в.
Фокус
Из вышеизложенного мы знаем, что теперь действительно нужно сосредоточиться на содержании.packages
Внутрь смотрим, что там внутри штучки:
├── babel-preset-react-app --> 暂不关注
├── create-react-app
├── eslint-config-react-app --> 暂不关注
├── react-dev-utils --> 暂不关注
├── react-error-overlay --> 暂不关注
└── react-scripts --> 核心啊,还是暂不关注
create-react-app
create-react-app
create-react-app
, в этом каталоге также естьpackage.json
файл, теперь мы разделяем эти 6 файлов на 6 проектов для анализа, как упоминалось выше, сначала посмотрите на проектpackage.json
файл, найдите в нем ключевые моменты:
"bin": {
"create-react-app": "./index.js"
}
понял суть,package.json
в файлеbin
Это команда, которую можно запустить в командной строке, то есть мы выполняемcreate-react-app
когда команда выполняетсяcreate-react-app
в каталогеindex.js
документ.
скажи больше
оpackage.json
серединаbin
Вариант, по сути, основан наnode
create-react-app
После этого выполнитьcreate-react-app
Эквивалентnode index.js
.
После серии просмотров выше мы, наконец, нашли жесткийcreate-react-app
packages/create-react-app
目录,仔细一瞅,噢哟,只有四个文件,四个文件我们还搞不定吗? Кромеpackage.json
,README.md
Осталось увидеть только два файла, давайте посмотрим на эти два файла.
index.js
Поскольку вы виделиpackages/create-react-app/package.json
оbin
Настройка выполненаindex.js
файл, мы начинаем сindex.js
Начните, начните смотреть исходный код, в конце концов есть некоторые креветки.
За исключением длинного списка комментариев, код на самом деле очень маленький, и он весь опубликован:
var chalk = require('chalk');
var currentNodeVersion = process.versions.node; // 返回Node版本信息,如果有多个版本返回多个版本
var semver = currentNodeVersion.split('.'); // 所有Node版本的集合
var major = semver[0]; // 取出第一个Node版本信息
// 如果当前版本小于4就打印以下信息并终止进程
if (major < 4) {
console.error(
chalk.red(
'You are running Node ' +
currentNodeVersion +
'.\n' +
'Create React App requires Node 4 or higher. \n' +
'Please update your version of Node.'
)
);
process.exit(1); // 终止进程
}
// 没有小于4就引入以下文件继续执行
require('./createReactApp');
На первый взгляд, вы можете понять, что это значит... ПроверитьNode.js
версия, не более4
Он не будет выполняться, давайте посмотрим на него отдельно, здесь он использует библиотекуchalk
, это не очень сложно, разбор построчно.
-
chalk
: фактический эффект этого кода заключается в изменении цвета выходной информации в командной строке. Это также приводит к роли этой библиотеки в изменении стиля вывода информации в командной строке.адрес нпм
несколько из нихNode
собственныйAPI
:
-
process.versions
Возвращает объект, содержащийNode
И его информация о зависимости -
process.exit
конецNode
обработать,1
index.js
createReactApp.js
, и продолжайте читать ниже.
createReactApp.js
когда наш местныйNode
версия больше, чем4
Когда мы должны продолжить работу с этим файлом, откройте файл, кода также много, вероятно700
Давай, мы будем разбирать его медленно.
Вот небольшая хитрость.При чтении исходного кода можно открыть окно для написания кода и написать его снова.Исполняемый код можно удалить сначала в исходном файле, чтобы
700行
код, когда вы читаете200行
, исходные файлы только500行
Теперь у вас есть не только чувство выполненного долга, чтобы продолжить чтение, но и удалить логику, которая не выполняется первой, чтобы она не повлияла на ваше чтение в другом месте.
const validateProjectName = require('validate-npm-package-name');
const chalk = require('chalk');
const commander = require('commander');
const fs = require('fs-extra');
const path = require('path');
const execSync = require('child_process').execSync;
const spawn = require('cross-spawn');
const semver = require('semver');
const dns = require('dns');
const tmp = require('tmp');
const unpack = require('tar-pack').unpack;
const url = require('url');
const hyperquest = require('hyperquest');
const envinfo = require('envinfo');
const packageJson = require('./package.json');
Откройте ряд зависимости в коде, с толку .... Я не могу проверить, для чего используются зависимости, верно? Поэтому мое предложение - это игнорировать его сейчас, вернуться, чтобы увидеть, что он делает, когда вы используете его, понять его более тщательно и продолжать смотреть вниз.
let projectName; // 定义了一个用来存储项目名称的变量
const program = new commander.Command(packageJson.name)
.version(packageJson.version) // 输入版本信息,使用`create-react-app -v`的时候就用打印版本信息
.arguments('<project-directory>') // 使用`create-react-app <my-project>` 尖括号中的参数
.usage(`${chalk.green('<project-directory>')} [options]`) // 使用`create-react-app`第一行打印的信息,也就是使用说明
.action(name => {
projectName = name; // 此处action函数的参数就是之前argument中的<project-directory> 初始化项目名称 --> 此处影响后面
})
.option('--verbose', 'print additional logs') // option配置`create-react-app -[option]`的选项,类似 --help -V
.option('--info', 'print environment debug info') // 打印本地相关开发环境,操作系统,`Node`版本等等
.option(
'--scripts-version <alternative-package>',
'use a non-standard version of react-scripts'
) // 这我之前就说过了,指定特殊的`react-scripts`
.option('--use-npm') // 默认使用`yarn`,指定使用`npm`
.allowUnknownOption() // 这个我没有在文档上查到,直译就是允许无效的option 大概意思就是我可以这样`create-react-app <my-project> -la` 其实 -la 并没有定义,但是我还是可以这么做而不会保存
.on('--help', () => {
// 此处省略了一些打印信息
}) // on('--help') 用来定制打印帮助信息 当使用`create-react-app -h(or --help)`的时候就会执行其中的代码,基本都是些打印信息
.parse(process.argv); // 这个就是解析我们正常的`Node`进程,可以这么理解没有这个东东,`commander`就不能接管`Node`
В приведенном выше коде я опустил не относящуюся к делу информацию о печати.Этот код является ключевой точкой входа в этот файл.new
взял одинcommander
node_modules
npm
-
commander
: Контур,Node
командный интерфейс, то есть его можно использовать для размещенияNode
Заказ.адрес нпм
Вышеупомянутое толькоcommander
Реализация использования, особо сказать нечего, я так понимаюcommander
Это не сложно, определения здесь - это те вещи, которые мы видим в командной строке, такие как параметры, такие как печать информации и т.д., мы продолжаем спускаться.
// 判断在命令行中执行`create-react-app <name>` 有没有name,如果没有就继续
if (typeof projectName === 'undefined') {
// 当没有传name的时候,如果带了 --info 的选项继续执行下列代码,这里配置了--info时不会报错
if (program.info) {
// 打印当前环境信息和`react`、`react-dom`, `react-scripts`三个包的信息
envinfo.print({
packages: ['react', 'react-dom', 'react-scripts'],
noNativeIDE: true,
duplicates: true,
});
process.exit(0); // 正常退出进程
}
// 在没有带项目名称又没带 --info 选项的时候就会打印一堆错误信息,像--version 和 --help 是commander自带的选项,所以不用单独配置
console.error('Please specify the project directory:');
console.log(
` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`
);
console.log();
console.log('For example:');
console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`);
console.log();
console.log(
`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
);
process.exit(1); // 抛出异常退出进程
}
Помните вышесказанноеcreate-react-app <my-project>
Имя проекта в заданномprojectName
Переменная? Роль здесь заключается в том, чтобы увидеть, загрузил ли пользователь это<my-project>
Параметр, если его нет, будет сообщено об ошибке и будет показана некоторая справочная информация.Здесь используется другая внешняя зависимость.envinfo
.
-
envinfo
: Вы можете распечатать текущую среду операционной системы и информацию об указанном пакете.адрес нпм
Я собираюсь разглагольствовать здесь
segmentfault
's editor... Я открываю вид и одновременно редактирую карточку... cover face.png!
Здесь я оставил одну вещь или придумать это:
const hiddenProgram = new commander.Command()
.option(
'--internal-testing-template <path-to-template>',
'(internal usage only, DO NOT RELY ON THIS) ' +
'use a non-standard application template'
)
.parse(process.argv);
create-react-app
При инициализации проекта генерируется стандартная папка, есть скрытый вариант--internal-testing-template
, шаблон, используемый для изменения каталога инициализации, как он сказал здесь, для внутреннего использования, он должен использоваться разработчиками при разработке, поэтому не рекомендуется всем использовать эту опцию.
Продолжаем смотреть вниз, там есть несколько предопределенных функций, нам все равно, сразу находим первую функцию для выполнения:
createApp(
projectName,
program.verbose,
program.scriptsVersion,
program.useNpm,
hiddenProgram.internalTestingTemplate
);
ОдинcreateAPP
-
projectName
: воплощать в жизньcreate-react-app <name>
-
program.verbose
commander
изoption
true
, иначе этоfalse
, то есть если добавить--verbose
, то этот параметр равенtrue
, что касаетсяverbose
что, как я уже говорил, вyarn
илиnpm
Распечатывать локальную информацию при установке, то есть, если при установке возникнет ошибка, мы можем найти дополнительную информацию. -
program.scriptsVersion
: Так же, как и выше, укажитеreact-scripts
Версия -
program.useNpm
: Так же, как и выше, укажите, следует ли использоватьnpm
, который используется по умолчаниюyarn
-
hiddenProgram.internalTestingTemplate
: Этого, я должен был дать ему опустить, я уже дополнил, указал инициализацию шаблона, они сказали, что внутреннее использование мы можем игнорировать, пришло время для использования и тестирования шаблона для использования.
Найдена первая функция для выполненияcreateApp
, Давайте посмотримcreateApp
Что именно делает функция?
createApp()
function createApp(name, verbose, version, useNpm, template) {
const root = path.resolve(name); // 获取当前进程运行的位置,也就是文件目录的绝对路径
const appName = path.basename(root); // 返回root路径下最后一部分
checkAppName(appName); // 执行 checkAppName 函数 检查文件名是否合法
fs.ensureDirSync(name); // 此处 ensureDirSync 方法是外部依赖包 fs-extra 而不是 node本身的fs模块,作用是确保当前目录下有指定文件名,没有就创建
// isSafeToCreateProjectIn 函数 判断文件夹是否安全
if (!isSafeToCreateProjectIn(root, name)) {
process.exit(1); // 不合法结束进程
}
// 到这里打印成功创建了一个`react`项目在指定目录下
console.log(`Creating a new React app in ${chalk.green(root)}.`);
console.log();
// 定义package.json基础内容
const packageJson = {
name: appName,
version: '0.1.0',
private: true,
};
// 往我们创建的文件夹中写入package.json文件
fs.writeFileSync(
path.join(root, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
// 定义常量 useYarn 如果传参有 --use-npm useYarn就是false,否则执行 shouldUseYarn() 检查yarn是否存在
// 这一步就是之前说的他默认使用`yarn`,但是可以指定使用`npm`,如果指定使用了`npm`,`useYarn`就是`false`,不然执行 shouldUseYarn 函数
// shouldUseYarn 用于检测本机是否安装了`yarn`
const useYarn = useNpm ? false : shouldUseYarn();
// 取得当前node进程的目录,之前还懂为什么要单独取一次,之后也明白了,下一句代码将会改变这个值,所以如果我后面要用这个值,后续其实取得值将不是这个
// 所以这里的目的就是提前存好,免得我后续使用的时候不好去找,这个地方就是我执行初始化项目的目录,而不是初始化好的目录,是初始化的上级目录,有点绕..
const originalDirectory = process.cwd();
// 修改进程目录为底下子进程目录
// 在这里就把进程目录修改为了我们创建的目录
process.chdir(root);
// 如果不使用yarn 并且checkThatNpmCanReadCwd()函数 这里之前说的不是很对,在重新说一次
// checkThatNpmCanReadCwd 这个函数的作用是检查进程目录是否是我们创建的目录,也就是说如果进程不在我们创建的目录里面,后续再执行`npm`安装的时候就会出错,所以提前检查
if (!useYarn && !checkThatNpmCanReadCwd()) {
process.exit(1);
}
// 比较 node 版本,小于6的时候发出警告
// 之前少说了一点,小于6的时候指定`react-scripts`标准版本为0.9.x,也就是标准的`react-scripts@1.0.0`以上的版本不支持`node`在6版本之下
if (!semver.satisfies(process.version, '>=6.0.0')) {
console.log(
chalk.yellow(
`You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
`Please update to Node 6 or higher for a better, fully supported experience.\n`
)
);
// Fall back to latest supported react-scripts on Node 4
version = 'react-scripts@0.9.x';
}
// 如果没有使用yarn 也发出警告
// 这里之前也没有说全,还判断了`npm`的版本是不是在3以上,如果没有依然指定安装`react-scripts@0.9.x`版本
if (!useYarn) {
const npmInfo = checkNpmVersion();
if (!npmInfo.hasMinNpm) {
if (npmInfo.npmVersion) {
console.log(
chalk.yellow(
`You are using npm ${npmInfo.npmVersion} so the project will be boostrapped with an old unsupported version of tools.\n\n` +
`Please update to npm 3 or higher for a better, fully supported experience.\n`
)
);
}
// Fall back to latest supported react-scripts for npm 3
version = 'react-scripts@0.9.x';
}
}
// 传入这些参数执行run函数
// 执行完毕上述代码以后,将执行`run`函数,但是我还是先把上述用到的函数全部说完,在来下一个核心函数`run`
run(root, appName, version, verbose, originalDirectory, template, useYarn);
}
Позвольте мне сначала подытожить, что делает эта функция, а затем взглянуть на зависимости, которые она использует.Давайте поговорим о том, что она сделала.Создайте каталог проекта в нашем каталоге и проверьте правильность имени каталога, является ли этот каталог безопасным и затем написалpackage.json
файл и решить, какой из них следует использовать в текущей среде.react-scripts
версии, а затем выполняетсяrun
функция. Мы посмотрим, что эта функция с внешними зависимостями:
После этого я подробно разберу функциональные зависимости функции, за исключением нескольких очень простых функций, а затем давайте взглянем на функциональные зависимости этой функции:
-
checkAppName()
: используется для проверки правильности имени файла, -
isSafeToCreateProjectIn()
: Используется для проверки безопасности папки. -
shouldUseYarn()
: для обнаруженияyarn
Достаточно ли установлено устройство -
checkThatNpmCanReadCwd()
: для обнаруженияnpm
Он выполняется в правильном каталоге -
checkNpmVersion()
: для обнаруженияnpm
Он уже установлен на этой машине?
checkAppName()
function checkAppName(appName) {
// 使用 validateProjectName 检查包名是否合法返回结果,这个validateProjectName是外部依赖的引用,见下面说明
const validationResult = validateProjectName(appName);
// 如果对象中有错继续,这里就是外部依赖的具体用法
if (!validationResult.validForNewPackages) {
console.error(
`Could not create a project called ${chalk.red(
`"${appName}"`
)} because of npm naming restrictions:`
);
printValidationResults(validationResult.errors);
printValidationResults(validationResult.warnings);
process.exit(1);
}
// 定义了三个开发依赖的名称
const dependencies = ['react', 'react-dom', 'react-scripts'].sort();
// 如果项目使用了这三个名称都会报错,而且退出进程
if (dependencies.indexOf(appName) >= 0) {
console.error(
chalk.red(
`We cannot create a project called ${chalk.green(
appName
)} because a dependency with the same name exists.\n` +
`Due to the way npm works, the following names are not allowed:\n\n`
) +
chalk.cyan(dependencies.map(depName => ` ${depName}`).join('\n')) +
chalk.red('\n\nPlease choose a different project name.')
);
process.exit(1);
}
}
Эта функция на самом деле довольно проста, она использует внешнюю зависимость для проверки совпадения имени файла.npm
Спецификация для пакетных файлов, затем определяет три недоброжелательных именаreact
,react-dom
,react-scripts
-
validate-npm-package-name
: Внешние зависимости, проверьте название пакета легитимно.адрес нпм
При этом функциональная зависимость:
printValidationResults()
isSafeToCreateProjectIn()
function isSafeToCreateProjectIn(root, name) {
// 定义了一堆文件名
// 我今天早上仔细的看了一些,以下文件的来历就是我们这些开发者在`create-react-app`中提的一些文件
const validFiles = [
'.DS_Store',
'Thumbs.db',
'.git',
'.gitignore',
'.idea',
'README.md',
'LICENSE',
'web.iml',
'.hg',
'.hgignore',
'.hgcheck',
'.npmignore',
'mkdocs.yml',
'docs',
'.travis.yml',
'.gitlab-ci.yml',
'.gitattributes',
];
console.log();
// 这里就是在我们创建好的项目文件夹下,除了上述文件以外不包含其他文件就会返回true
const conflicts = fs
.readdirSync(root)
.filter(file => !validFiles.includes(file));
if (conflicts.length < 1) {
return true;
}
// 否则这个文件夹就是不安全的,并且挨着打印存在哪些不安全的文件
console.log(
`The directory ${chalk.green(name)} contains files that could conflict:`
);
console.log();
for (const file of conflicts) {
console.log(` ${file}`);
}
console.log();
console.log(
'Either try using a new directory name, or remove the files listed above.'
);
// 并且返回false
return false;
}
Его функция также относительно проста, то есть определить, содержит ли созданный каталог помимо вышеперечисленныхvalidFiles
Файлы внутри, откуда взялись файлы внутри, то естьcreate-react-app
В разработке пока что предложили разработчики.
shouldUseYarn()
function shouldUseYarn() {
try {
execSync('yarnpkg --version', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
Всего три строчки... из которыхexecSync
Кnode
Сам модульchild_process
Ссылка используется для выполнения команды, эта функция предназначена для выполненияyarnpkg --version
чтобы определить, правильно ли мы его установилиyarn
, если не правильно установилyarn
если,useYarn
ещеfalse
, независимо от того, указано или нет--use-npm
.
-
execSync
: Цитата изchild_process.execSync
, используется для выполнения дочернего процесса, который необходимо выполнить
checkThatNpmCanReadCwd()
function checkThatNpmCanReadCwd() {
const cwd = process.cwd(); // 这里取到当前的进程目录
let childOutput = null; // 定义一个变量来保存`npm`的信息
try {
// 相当于执行`npm config list`并将其输出的信息组合成为一个字符串
childOutput = spawn.sync('npm', ['config', 'list']).output.join('');
} catch (err) {
return true;
}
// 判断是否是一个字符串
if (typeof childOutput !== 'string') {
return true;
}
// 将整个字符串以换行符分隔
const lines = childOutput.split('\n');
// 定义一个我们需要的信息的前缀
const prefix = '; cwd = ';
// 去整个lines里面的每个line查找有没有这个前缀的一行
const line = lines.find(line => line.indexOf(prefix) === 0);
if (typeof line !== 'string') {
return true;
}
// 取出后面的信息,这个信息大家可以自行试一试,就是`npm`执行的目录
const npmCWD = line.substring(prefix.length);
// 判断当前目录和执行目录是否是一致的
if (npmCWD === cwd) {
return true;
}
// 不一致就打印以下信息,大概意思就是`npm`进程没有在正确的目录下执行
console.error(
chalk.red(
`Could not start an npm process in the right directory.\n\n` +
`The current directory is: ${chalk.bold(cwd)}\n` +
`However, a newly started npm process runs in: ${chalk.bold(
npmCWD
)}\n\n` +
`This is probably caused by a misconfigured system terminal shell.`
)
);
// 这里他对windows的情况作了一些单独的判断,没有深究这些信息
if (process.platform === 'win32') {
console.error(
chalk.red(`On Windows, this can usually be fixed by running:\n\n`) +
` ${chalk.cyan(
'reg'
)} delete "HKCU\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n` +
` ${chalk.cyan(
'reg'
)} delete "HKLM\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n\n` +
chalk.red(`Try to run the above two lines in the terminal.\n`) +
chalk.red(
`To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/`
)
);
}
return false;
}
Раньше я неправильно размещал эту функцию, мне очень жаль. Раньше я не понимал смысла этой функции, сегодня, когда я смотрю на нее снова, становится ясно, что это означает, что приведенный выше код был разобран, и используется внешняя зависимость:
-
cross-spawn
: Я упоминал это раньше? забыл, используется для выполненияnode
обработать.адрес нпм
Зачем использовать отдельную внешнюю зависимость вместо использованияnode
А как насчет собственного? посмотриcross-spawn
собственное описание самого себя,Node
Кроссплатформенное решение, которое решаетwindows
различные вопросы.
checkNpmVersion()
function checkNpmVersion() {
let hasMinNpm = false;
let npmVersion = null;
try {
npmVersion = execSync('npm --version')
.toString()
.trim();
hasMinNpm = semver.gte(npmVersion, '3.0.0');
} catch (err) {
// ignore
}
return {
hasMinNpm: hasMinNpm,
npmVersion: npmVersion,
};
}
Это может сказать, что это относительно маленький, взгляд, чтобы знать, что это значит, возвращает объект, есть две пары объектов выше, этоnpm
Номер версии, есть ли минимумnpm
Ограничения версии, одна из внешних зависимостей, однаNode
Про собственный API я уже говорил ранее, так что не буду.
посмотреть здесьcreateApp()
Зависимости и выполнение функции закончились, а затем выполнениеrun()
Функция, мы продолжаем смотреть наrun()
Какие функции, хочу еще раз пожаловаться, забудь, помалкивай! ! !
run()
функционировать вcreateApp()
После того, как все содержимое функции выполнено, она выполняется, она получает 7 параметров.Давайте сначала посмотрим.
-
root
: абсолютный путь к каталогу, который мы создали -
appName
: имя каталога, которое мы создали -
version
;react-scripts
версия -
verbose
: продолжить входящиеverbose
,существуетcreateApp
не используется в -
originalDirectory
: Исходный каталог, о котором упоминалось ранее, вrun
полезно в функции -
tempalte
: Шаблон, этот параметр также сказал раньше, не используйте снаружи -
useYarn
yarn
run()
функция.
run()
function run(
root,
appName,
version,
verbose,
originalDirectory,
template,
useYarn
) {
// 这里对`react-scripts`做了大量的处理
const packageToInstall = getInstallPackage(version, originalDirectory); // 获取依赖包信息
const allDependencies = ['react', 'react-dom', packageToInstall]; // 所有的开发依赖包
console.log('Installing packages. This might take a couple of minutes.');
getPackageName(packageToInstall) // 获取依赖包原始名称并返回
.then(packageName =>
// 检查是否离线模式,并返回结果和包名
checkIfOnline(useYarn).then(isOnline => ({
isOnline: isOnline,
packageName: packageName,
}))
)
.then(info => {
// 接收到上述的包名和是否为离线模式
const isOnline = info.isOnline;
const packageName = info.packageName;
console.log(
`Installing ${chalk.cyan('react')}, ${chalk.cyan(
'react-dom'
)}, and ${chalk.cyan(packageName)}...`
);
console.log();
// 安装依赖
return install(root, useYarn, allDependencies, verbose, isOnline).then(
() => packageName
);
})
.then(packageName => {
// 检查当前`Node`版本是否支持包
checkNodeVersion(packageName);
// 检查`package.json`的开发依赖是否正常
setCaretRangeForRuntimeDeps(packageName);
// `react-scripts`脚本的目录
const scriptsPath = path.resolve(
process.cwd(),
'node_modules',
packageName,
'scripts',
'init.js'
);
// 引入`init`函数
const init = require(scriptsPath);
// 执行目录的拷贝
init(root, appName, verbose, originalDirectory, template);
// 当`react-scripts`的版本为0.9.x发出警告
if (version === 'react-scripts@0.9.x') {
console.log(
chalk.yellow(
`\nNote: the project was boostrapped with an old unsupported version of tools.\n` +
`Please update to Node >=6 and npm >=3 to get supported tools in new projects.\n`
)
);
}
})
// 异常处理
.catch(reason => {
console.log();
console.log('Aborting installation.');
// 根据命令来判断具体的错误
if (reason.command) {
console.log(` ${chalk.cyan(reason.command)} has failed.`);
} else {
console.log(chalk.red('Unexpected error. Please report it as a bug:'));
console.log(reason);
}
console.log();
// 出现异常的时候将删除目录下的这些文件
const knownGeneratedFiles = [
'package.json',
'npm-debug.log',
'yarn-error.log',
'yarn-debug.log',
'node_modules',
];
// 挨着删除
const currentFiles = fs.readdirSync(path.join(root));
currentFiles.forEach(file => {
knownGeneratedFiles.forEach(fileToMatch => {
if (
(fileToMatch.match(/.log/g) && file.indexOf(fileToMatch) === 0) ||
file === fileToMatch
) {
console.log(`Deleting generated file... ${chalk.cyan(file)}`);
fs.removeSync(path.join(root, file));
}
});
});
// 判断当前目录下是否还存在文件
const remainingFiles = fs.readdirSync(path.join(root));
if (!remainingFiles.length) {
console.log(
`Deleting ${chalk.cyan(`${appName} /`)} from ${chalk.cyan(
path.resolve(root, '..')
)}`
);
process.chdir(path.resolve(root, '..'));
fs.removeSync(path.join(root));
}
console.log('Done.');
process.exit(1);
});
}
react-script
react-script
сам имеетnode
зависит от версии и используетcreate-react-app init <project>
Инициировать проект, может быть указанreact-script
версия или внешний самоопределяемый материал.
онrun()
Используются ссылки в функцияхPromise
Метод обратного вызова выполнен, от моего официального контактаNode
Привыкайте к этому с самого началаasync/await
, так даPromise
Я действительно не знаком, и я восполнил это. Давайте разберем каждое предложение и функцию каждой функции. Давайте сначала посмотрим на использование внешних зависимостей или тех, которые не упоминались ранее. Давайте посмотрим на список функций:
-
getInstallPackage()
: получить тот, чтобы установитьreact-scripts
версия или определяется разработчикомreact-scripts
-
getPackageName()
: получить официальнуюreact-scripts
имя пакета -
checkIfOnline()
: проверьте, нормально ли работает сетевое соединение. -
install()
: установить зависимости разработки -
checkNodeVersion()
:экзаменNode
Информация о версии -
setCaretRangeForRuntimeDeps()
: проверьте, правильно ли установлены зависимости разработки и правильная ли версия -
init()
: Скопируйте предварительно определенные файлы каталога в мой проект.
Зная общую идею, давайте разберем роль каждой функции по порядку:
getInstallPackage()
function getInstallPackage(version, originalDirectory) {
let packageToInstall = 'react-scripts'; // 定义常量 packageToInstall,默认就是标准`react-scripts`包名
const validSemver = semver.valid(version); // 校验版本号是否合法
if (validSemver) {
packageToInstall += `@${validSemver}`; // 合法的话执行,就安装指定版本,在`npm install`安装的时候指定版本为加上`@x.x.x`版本号,安装指定版本的`react-scripts`
} else if (version && version.match(/^file:/)) {
// 不合法并且版本号参数带有`file:`执行以下代码,作用是指定安装包为我们自身定义的包
packageToInstall = `file:${path.resolve(
originalDirectory,
version.match(/^file:(.*)?$/)[1]
)}`;
} else if (version) {
// 不合法并且没有`file:`开头,默认为在线的`tar.gz`文件
// for tar.gz or alternative paths
packageToInstall = version;
}
// 返回最终需要安装的`react-scripts`的信息,或版本号或本地文件或线上`.tar.gz`资源
return packageToInstall;
}
Этот метод принимает два параметраversion
номер версии,originalDirectory
Оригинальный каталог, основная функция - судитьreact-scripts
здесьcreate-react-app
Сама обеспечивает установкуreact-scripts
из трех механизмов можно указать элементы, инициализированные в началеreact-scripts
Версия заказывается настраиваемой, поэтому он предоставит эти механизмы здесь, что использует внешнюю зависимость только однуsemver
, я уже говорил это раньше, нечего сказать.
getPackageName()
function getPackageName(installPackage) {
// 函数进来就根据上面的那个判断`react-scripts`的信息来安装这个包,用于返回正规的包名
// 此处为线上`tar.gz`包的情况
if (installPackage.match(/^.+\.(tgz|tar\.gz)$/)) {
// 里面这段创建了一个临时目录,具体它是怎么设置了线上.tar.gz包我没试就不乱说了
return getTemporaryDirectory()
.then(obj => {
let stream;
if (/^http/.test(installPackage)) {
stream = hyperquest(installPackage);
} else {
stream = fs.createReadStream(installPackage);
}
return extractStream(stream, obj.tmpdir).then(() => obj);
})
.then(obj => {
const packageName = require(path.join(obj.tmpdir, 'package.json')).name;
obj.cleanup();
return packageName;
})
.catch(err => {
console.log(
`Could not extract the package name from the archive: ${err.message}`
);
const assumedProjectName = installPackage.match(
/^.+\/(.+?)(?:-\d+.+)?\.(tgz|tar\.gz)$/
)[1];
console.log(
`Based on the filename, assuming it is "${chalk.cyan(
assumedProjectName
)}"`
);
return Promise.resolve(assumedProjectName);
});
// 此处为信息中包含`git+`信息的情况
} else if (installPackage.indexOf('git+') === 0) {
return Promise.resolve(installPackage.match(/([^/]+)\.git(#.*)?$/)[1]);
// 此处为只有版本信息的时候的情况
} else if (installPackage.match(/.+@/)) {
return Promise.resolve(
installPackage.charAt(0) + installPackage.substr(1).split('@')[0]
);
// 此处为信息中包含`file:`开头的情况
} else if (installPackage.match(/^file:/)) {
const installPackagePath = installPackage.match(/^file:(.*)?$/)[1];
const installPackageJson = require(path.join(installPackagePath, 'package.json'));
return Promise.resolve(installPackageJson.name);
}
// 什么都没有直接返回包名
return Promise.resolve(installPackage);
}
Цель его функции вернуть нормальное имя зависимого пакета, например, мы возвращаемся без ничегоreact-scripts
, например, если мы определим свой собственный пакет, мы вернемmy-react-scripts
, продолжайте более важную функцию, получитеinstallPackage
параметр, который используется с самого начала этой функцииPromise
Метод обратного вызова выполняется до конца Давайте посмотрим, что делает эта функция, и посмотрим комментарии к каждой строке выше.
Суммируйте его, роль этой функции - вернуться к нормальному имени пакета, без какого-либо знака, чтобы увидеть его внешнюю зависимость:
-
hyperquest
: используется для потоковой передачи HTTP-запросов.адрес нпм
У него тоже есть функциональные зависимости.Я не буду говорить об этих двух функциональных зависимостях отдельно.Смысл функции очень легко понять.Насчет того,зачем я это делаю,я пока не разобрался:
-
getTemporaryDirectory()
: Не сложно, сам он является callback-функцией, используемой для создания временного каталога. -
extractStream()
: в основном используетсяnode
Сам поток, а вот я действительно не понял, почему препарат переходит на поток, чтобы не выразить свои взгляды, на самом деле, я все еще не мог понять, чтобы понять, действительно понять, что собирается попробовать, но очень немного неприятностей, не хочу сосредоточиться на.
checkIfOnline()
function checkIfOnline(useYarn) {
if (!useYarn) {
return Promise.resolve(true);
}
return new Promise(resolve => {
dns.lookup('registry.yarnpkg.com', err => {
let proxy;
if (err != null && (proxy = getProxy())) {
dns.lookup(url.parse(proxy).hostname, proxyErr => {
resolve(proxyErr == null);
});
} else {
resolve(err == null);
}
});
});
}
Сама функция должна ли получитьyarn
Параметры для определения того, следует ли последующее наблюдение, если используетсяnpm
просто вернисьtrue
Теперь, почему эта функция связана сyarn
Существует функция, называемая автономной установкой.Эта функция используется для определения необходимости установки в автономном режиме, которая использует внешние зависимости:
-
dns
: Используется для проверки возможности запроса указанного адреса.адрес нпм
install()
function install(root, useYarn, dependencies, verbose, isOnline) {
// 封装在一个回调函数中
return new Promise((resolve, reject) => {
let command; // 定义一个命令
let args; // 定义一个命令的参数
// 如果使用yarn
if (useYarn) {
command = 'yarnpkg'; // 命令名称
args = ['add', '--exact']; // 命令参数的基础
if (!isOnline) {
args.push('--offline'); // 此处接上面一个函数判断是否是离线模式
}
[].push.apply(args, dependencies); // 组合参数和开发依赖 `react` `react-dom` `react-scripts`
args.push('--cwd'); // 指定命令执行目录的地址
args.push(root); // 地址的绝对路径
// 在使用离线模式时候会发出警告
if (!isOnline) {
console.log(chalk.yellow('You appear to be offline.'));
console.log(chalk.yellow('Falling back to the local Yarn cache.'));
console.log();
}
// 不使用yarn的情况使用npm
} else {
// 此处于上述一样,命令的定义 参数的组合
command = 'npm';
args = [
'install',
'--save',
'--save-exact',
'--loglevel',
'error',
].concat(dependencies);
}
// 因为`yarn`和`npm`都可以带这个参数,所以就单独拿出来了拼接到上面
if (verbose) {
args.push('--verbose');
}
// 这里就把命令组合起来执行
const child = spawn(command, args, { stdio: 'inherit' });
// 命令执行完毕后关闭
child.on('close', code => {
// code 为0代表正常关闭,不为零就打印命令执行错误的那条
if (code !== 0) {
reject({
command: `${command} ${args.join(' ')}`,
});
return;
}
// 正常继续往下执行
resolve();
});
});
}
Это более важное место, внимательно посмотрите на каждую строку комментариев кода, функция здесь состоит в том, чтобы объединитьyarn
илиnpm
Команда установки устанавливает эти модули в папку проекта, а используемые в них внешние зависимостиcross-spawn
Я уже говорил это раньше, поэтому не буду говорить.
Фактически реализовано здесь,create-react-app
Каталог был создан для нас,package.json
и установил все зависимости,react
,react-dom
а такжеreact-scrpts
, сложная часть позади, продолжайте.
checkNodeVersion()
function checkNodeVersion(packageName) {
// 找到`react-scripts`的`package.json`路径
const packageJsonPath = path.resolve(
process.cwd(),
'node_modules',
packageName,
'package.json'
);
// 引入`react-scripts`的`package.json`
const packageJson = require(packageJsonPath);
// 在`package.json`中定义了一个`engines`其中放着`Node`版本的信息,大家可以打开源码`packages/react-scripts/package.json`查看
if (!packageJson.engines || !packageJson.engines.node) {
return;
}
// 比较进程的`Node`版本信息和最小支持的版本,如果比他小的话,会报错然后退出进程
if (!semver.satisfies(process.version, packageJson.engines.node)) {
console.error(
chalk.red(
'You are running Node %s.\n' +
'Create React App requires Node %s or higher. \n' +
'Please update your version of Node.'
),
process.version,
packageJson.engines.node
);
process.exit(1);
}
}
Node
react-scrpts
зависит отNode
версия, то есть более низкая версияNode
Не поддерживается.На самом деле внешние зависимости тоже прежние,так что сказать нечего.
setCaretRangeForRuntimeDeps()
function setCaretRangeForRuntimeDeps(packageName) {
const packagePath = path.join(process.cwd(), 'package.json'); // 取出创建项目的目录中的`package.json`路径
const packageJson = require(packagePath); // 引入`package.json`
// 判断其中`dependencies`是否存在,不存在代表我们的开发依赖没有成功安装
if (typeof packageJson.dependencies === 'undefined') {
console.error(chalk.red('Missing dependencies in package.json'));
process.exit(1);
}
// 拿出`react-scripts`或者是自定义的看看`package.json`中是否存在
const packageVersion = packageJson.dependencies[packageName];
if (typeof packageVersion === 'undefined') {
console.error(chalk.red(`Unable to find ${packageName} in package.json`));
process.exit(1);
}
// 检查`react` `react-dom` 的版本
makeCaretRange(packageJson.dependencies, 'react');
makeCaretRange(packageJson.dependencies, 'react-dom');
// 重新写入文件`package.json`
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
}
Я не хочу много говорить об этой функции, ее функция не такая уж и большая, она используется для определения того, были ли написаны ранее установленные нами зависимости.package.json
Внутри обнаруживаются и зависимые версии, одна из которых является функциональной зависимостью:
-
makeCaretRange()
: используется для определения версии зависимостей
Отдельно подфункции не разбирал, так как не думаю, что это сложно, да и на основную ветку мало влияет, не хочу много постить.
сюдаcreateReactApp.js
Исходный код внутри был проанализирован, эй! Вы можете сказать, что не говорилиinit()
Функция, хахаха, видя это показывает, что вы серьезны,init()
функция помещается вpackages/react-scripts/script
каталог, но я все равно должен ему сказать, потому что на самом деле это связано сreact-scripts
Контакта с упаковкой не так много, это простоcopy
Он определяет функцию структуры каталогов шаблона.
init()
он сам получает5
Параметры:
-
appPath
:предыдущийroot
, абсолютный путь к проекту -
appName
: название проекта -
verbose
: я говорил об этом параметре раньше,npm
originalDirectory
template
// 当前的包名,也就是这个命令的包
const ownPackageName = require(path.join(__dirname, '..', 'package.json')).name;
// 当前包的路径
const ownPath = path.join(appPath, 'node_modules', ownPackageName);
// 项目的`package.json`
const appPackage = require(path.join(appPath, 'package.json'));
// 检查项目中是否有`yarn.lock`来判断是否使用`yarn`
const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock'));
appPackage.dependencies = appPackage.dependencies || {};
// 定义其中`scripts`的
appPackage.scripts = {
start: 'react-scripts start',
build: 'react-scripts build',
test: 'react-scripts test --env=jsdom',
eject: 'react-scripts eject',
};
// 重新写入`package.json`
fs.writeFileSync(
path.join(appPath, 'package.json'),
JSON.stringify(appPackage, null, 2)
);
// 判断项目目录是否有`README.md`,模板目录中已经定义了`README.md`防止冲突
const readmeExists = fs.existsSync(path.join(appPath, 'README.md'));
if (readmeExists) {
fs.renameSync(
path.join(appPath, 'README.md'),
path.join(appPath, 'README.old.md')
);
}
// 是否有模板选项,默认为当前执行命令包目录下的`template`目录,也就是`packages/react-scripts/tempalte`
const templatePath = template
? path.resolve(originalDirectory, template)
: path.join(ownPath, 'template');
if (fs.existsSync(templatePath)) {
// 拷贝目录到项目目录
fs.copySync(templatePath, appPath);
} else {
console.error(
`Could not locate supplied template: ${chalk.green(templatePath)}`
);
return;
}
create-react-app
С нуля полагаться на каталог установки исходного кода было проанализировано, но это всего лишь инициализация каталога и зависимость, которая существует в коде управляющей среды.react-scripts
В середине это на самом деле немного далеко от ключевых мест, которые я хочу знать, но эта статья уже очень длинная, не предназначена для того, чтобы говорить это сейчас, и вы потерпите.
Надеюсь, эта статья будет вам полезна.
многословный
я собирался поставить этоcreate-react-app
Все исходящие разговоры, которые включают в себяwebpack
конфигурация,eslint
конфигурация,babel
Конфигурация ах... Подождите, но это многовато, он сам ставит инициализацию управления и управленияreact
Команды окружения разделены наpackages/create-react-app
а такжеpackages/react-script
С обеих сторон это пространство толькоpackages/create-react-app
законченный, более сложныйpackages/react-script
В разговоре об этом не знаю, сколько там места, поэтому опустошусь, написав отдельную статью оpackages/react-script