Ссылка на эту статью:JSON в 1993.GitHub.IO/2018/05/пока горячо…
Предыдущая статьяМы говорили оcreate-react-app
создать внутриpackage.json
Такие шаги, как установка зависимостей и копирование работоспособной демоверсии.портал
Давайте поговорим об этомcreate-react-app
Службы запуска и другие части внутри обычно после установки зависимостей запускают службы разработки:npm start
. В этой части слишком много всего о вебпаке и настройке, а в первой статье чувствуется, что описание слишком избыточно~ Так что эта статья не будет вдаваться в подробности, а примерно распишет логическую идею его работы, и конкретный исходный код будут предоставлять порталы.
Всем рекомендую прочитать первую частьИнициализация проектаа такжеотладка точки остановачасть, которая не будет здесь повторяться.Раздел отладки точки останова инициализации проекта портала
Этап подготовки
Версия приложения create-реагировать, которую мы здесь обсуждаем, по-прежнемуv1.1.4
Поскольку эта статья в основном посвященаcreate-react-app
Внутри сервиса webpack мы должны сначала создать новый проект.
-
npm install create-react-app -g
Установить приложение create-реагировать глобально -
create-react-app my-react-project
Создайте новый проект с помощью приложения create-реагировать
cd my-react-project
yarn start
После создания нового терминал предлагает нам напрямую войти в проект и запустить пряжу (npm), чтобы начать разработку. мы открытыpackage.json
Вы можете видеть, что команда, запускаемая запуском пряжи,"react-scripts start"
Итак, что это за команда реагирования-скриптов?
Обычно пишут вpackage.json=> scripts
команда, пойдет первымproject_path(项目目录)/node_modules/.bin
Поиск, не могу найти, а затем найти пакет, установленный глобально.
Так откуда же взялись файлы в node_modules/.bin?
Если мы добавим package.json пакетаbin
поле, npm автоматически сопоставит его с node_modules/.bin для насПортал документации npm bin
мы открываемся напрямуюnode_modules/react-scripts/package.json
можно увидеть эту строку"react-scripts": "./bin/react-scripts.js"
, направить командуnode_modules/react-scripts/.bin/react-scripts.js
, что также подтверждает нашу точку зрения.
Помните предыдущий пост, мы вcreate-react-app/packages
Найтиreact-scripts
. На самом деле это происходит из того же, поэтому следующие шаги очень понятны, просто используйте старый метод, измените конфигурацию, а затем используйте vscode для запуска отладки точки останова и чтенияproject_path/node_modules/react-scripts/.bin/react-scripts.js
Исходный код .
vscode launch.json
Здесь мы передаем start в качестве параметра для имитации работы в проекте.yarn start
Эффект.
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"program": "${workspaceFolder}/node_modules/react-scripts/bin/react-scripts.js", //调试的文件路径
"args": [
"start" // 传入 start 做为参数
]
}
]
}
пс: нижеreact-scripts
Если не указано иное, все представляютproject_path/node_modules/react-scripts
Каталоги легко читаются
react-scripts/.bin/react-scripts.js
файловый порталЗдесь мы все еще по-старому, не смотрите сначала на зависимости, сначала посмотрите на основной процесс, чтобы понять, мы видим, что этот файл также является файлом входа, очень кратким.
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) : [];
Сначала обработайте входящие параметры, используйтеscript
Переменные для получения того, какую команду мы запускаем, есть ['build', 'eject', 'start', 'test'], которые соответствуют командам сборки, конфигурации экспозиции, разработки и тестирования соответственно.
Затем передайте вместе другие параметры, напримерnpm test
Команда принимает дополнительный параметр--env=jsdom
.
switch (script) {
case 'build':
case 'eject':
case 'start':
case 'test': {
// 用 cross-spawn 去跑一个同步的命令
// 根据传入的命令来拼接对应的路径 用node去跑
const result = spawn.sync(
'node',
nodeArgs
.concat(require.resolve('../scripts/' + script))
.concat(args.slice(scriptIndex + 1)),
{ stdio: 'inherit' }
);
if (result.signal) {
if (result.signal === 'SIGKILL') {
// 输出错误提醒日志
} else if (result.signal === 'SIGTERM') {
// 输出错误提醒日志
}
process.exit(1); // 退出进程, 传1代表有错误
}
process.exit(result.status);
break;
}
default:
// 这里输出匹配不到对应的命令
break;
}
Затем по полученной команде, соответствующейreact-scripts/scripts
Следующий файл для запуска, напримерreact-scripts start
будет работатьreact-scripts/scripts/start.js
.
Вставьте сюда несколько предложений, чтобы рассказать о наиболее распространенных методах разделения библиотек классов в проекте, которые мы можем увидеть здесь.spawn
Цитируетсяreact-dev-utils/crossSpawn
. пока вreact-dev-utils/corssSpawn
В нем всего несколько простых предложений, вводящихcross-spawn
поставь это сноваcross-spawn
незащищенный.
Но запись таким образом может сыграть роль разделения библиотеки классов.Например, в будущем, если в этой библиотеке будут серьезные ошибки или прекратится обслуживание, вы можете напрямую изменить этот файл, чтобы ввести другие библиотеки классов и другой код, который ссылается на этот файл менять не надо.
// react-dev-utils/corssSpawn
'use strict';
var crossSpawn = require('cross-spawn');
module.exports = crossSpawn;
react-scripts/scripts/start.js
Люди, прочитавшие первую статью, должны быть знакомы с этой папкой.create-react-app
после установкиreact
После ожидания зависимостей он будет работать в этой папкеinit.js
чтобы скопировать файл шаблона, изменитеpackage.json
и так далее.
Теперь, когда мы знаем, что он хочет выполнить start.js, давайте изменим отладочный файл vscode на файл start.js."program": "${workspaceFolder}/node_modules/react-scripts/scripts/start.js",
Причина модификации в том, что он не ссылается на файл js для запуска, а использует для запуска терминал, поэтому он не относится к сфере отладки нашего проекта ~
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
// 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;
});
Две переменные окружения установлены в самом начале файла, потому что start используется для запуска разработки, поэтому переменные окружения здесьdevelopment
, а затем датьprocess
Привяжите функцию прослушивания ошибок, этот прослушиватель ошибок в основном используется для мониторингаНекоторые обещания, которые не являются .catch.Подробнее см. в документации по узлу., про Обещание можно прочитать в статье которую я писал ранееСтатья, знакомящая с Promise, включает в себя как принципы использования, так и принципы реализации.
Затем введите../config/env
, Посмотрите на имя файла, предположите, что он должен что-то делать с конфигурацией среды, найдите точку останова файла и зайдите
react-scripts/config/env.js
const fs = require('fs');
const path = require('path');
const paths = require('./paths');
// Make sure that including paths.js after env.js will read .env variables.
delete require.cache[require.resolve('./paths')];
env.js
файл импортируется./paths.js
После этого сразу удалить из кеша, чтобы в следующий раз при введении других модулейpaths.js
, он не будет получен из кеша, что гарантированоpaths.js
Логика выполнения внутри будет использовать самые последние переменные среды.
var dotenvFiles = [
// 举个例子:第一个元素在我的电脑路径是这样的 Users/jsonz/Documents/my-react-project/.env.development.local.js
`${paths.dotenv}.${NODE_ENV}.local`,
`${paths.dotenv}.${NODE_ENV}`,
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
paths.dotenv,
].filter(Boolean);
Затем получите другие переменные среды в соответствии с адресом, заданным путями, здесьpaths.js
Различные пути будут даны в соответствии с различными ситуациями Мы обсуждаем нормальную ситуацию создания проектов.
Несколько других ситуаций:
- Запускаем на уже созданном проекте
npm(yarn) eject
,В этот моментreact-scripts
выставит конфигурацию дляproject_path/config
Нам удобно модифицировать конфигурацию по проекту, эта операция необратима. - Обычно мы создаем проект и запускаем его напрямую, в это время конфигурация сохраняется в
project/node_modules/react-scripts
. - Разработчики используют собственную отладку, на этот раз конфигурация хранится в
create-react/packages/react-scripts/config
.
После сборки пути используйтеdotenv-expandа такжеdotenvДля загрузки переменных окружения в файл эта общая сцена не используется.
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;
},
{
NODE_ENV: process.env.NODE_ENV || 'development',
}
);
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}
затем вернутьgetClientEnvironment
функция, которая возвращает переменные окружения клиента после выполнения.
react-scripts/scripts/start.js(2)
const fs = require('fs');
const chalk = require('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 config = require('../config/webpack.config.dev');
const createDevServerConfig = require('../config/webpackDevServer.config');
const useYarn = fs.existsSync(paths.yarnLockFile);
const isInteractive = process.stdout.isTTY;
После загрузки различных переменных среды мы возвращаемся кreact-scripts/scripts/start.js
, старое правило, ряд зависимостей сначала пропускается, а потом используется.
помнишь, мы былиenv.js
Удалите node.catch внутри, здесьconts paths = require('../config/paths)
Он не будет взят из кеша, а перезагружен.
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
Сначала оцените, существуют ли наши два входных файла, ониproject_path/public/index.html
а такжеproject_path/src/index.js
, если он не существует, дайте запрос на завершение программы.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';
Затем установите порт и хост по умолчанию. Если у вас есть особые требования, вы можете изменить их в переменной среды. Если нет, будет использоваться порт по умолчанию.3000
порт.
choosePort(HOST, DEFAULT_PORT).then(...) // @return Promise
После установки порта и хоста по умолчанию начинайте судить, занят ли этот порт другими процессами, и если да, то он предоставит следующий доступный порт, следуемchoosePort
Перейдите к заголовку файла, чтобы найти зависимость, и обнаружите, что метод находится в зависимостиreact-dev-utils/WebpackDevServerUtils
.
function choosePort(host, defaultPort) {
return detect(defaultPort, host).then(
port =>
new Promise(resolve => {
if (port === defaultPort) {
return resolve(port);
}
const message =
process.platform !== 'win32' && defaultPort < 1024 && !isRoot()
? `Admin permissions are required to run a server on a port below 1024.`
: `Something is already running on port ${defaultPort}.`;
if (isInteractive) {
clearConsole();
const existingProcess = getProcessForPort(defaultPort);
const question = {
type: 'confirm',
name: 'shouldChangePort',
message:
chalk.yellow(
message +
`${existingProcess ? ` Probably:\n ${existingProcess}` : ''}`
) + '\n\nWould you like to run the app on another port instead?',
default: true,
};
inquirer.prompt(question).then(answer => {
if (answer.shouldChangePort) {
resolve(port);
} else {
resolve(null);
}
});
} else {
console.log(chalk.red(message));
resolve(null);
}
}),
err => {
// 输出错误日志
}
);
}
choosePort
используется вdetect-port-altЧтобы определить занятость порта, если он занят, он вернет ближайший доступный порт в возрастающем направлении, например, порт 3000 занят, а 3001 будет возвращен, если он не занят.
Если обнаруживается, что возвращенный доступный порт не является портом по умолчанию, дается интерактивная команда, чтобы спросить пользователя, следует ли изменить порт для доступа.Интерактивная команда используетinquirerэта сумка.
Здесь, если вы используете vsCode для отладки,process.stdout.isTTY
Возвращаемое значениеundefined
. Поэтому, если вы хотите протестировать эту интерактивную команду, вы можете вернуться только к терминалу системы для отладки~
файловый порталПосле проверки доступных портов вернитесь кstart.js
.
Внешний интерфейс обрабатывает множество переменных среды и загружает множество конфигураций, все из которых используются здесь. Здесь главное собрать переменные окружения и конфигурацию, и запустить локальную службу отладки webpack. Главное, что нужно сделать:
- Если доступный порт не найден, вернитесь напрямую и не продолжайте.
- Определите, следует ли включать или нет на основе переменных среды
https
, по умолчаниюhttp
. - Соберите серию URL-адресов в соответствии с хостом, протоколом, портом, включая
Browser
URL сTerminal
URL. - передача
createCompiler
Передайте webpack, конфигурацию webpack, appName, URL-адрес, полученный на третьем шаге, и укажите, следует ли использовать Yarn и другие параметры для создания webpackCompiler. Вещи, за которые отвечает createCompiler: 4.1 Судя по параметрам окружающей среды, есть ли необходимость в дымовом тесте, если есть, добавить одинhandleCompile
, прервите программу, как только возникнет ошибка. 4.2 Создайте webpackCompiler с входящей конфигурацией и handleCompile 4.2 Дополнениеinvalid
Хук, как только измененный файл обнаружен, и это интерактивный терминал, сначала очистите консоль, а затем выведите лог 4.3 Дополнениеdone
Крюк для организации и унификации выходного журнала веб-пакета - Создайте конфигурацию службы разработки. Конкретный код конфигурации помещается в
webpackDevServer.config.js
- кинуть 4 и 5 в
WebpackDevServer
, который создает локальную службу разработки webpack - Готово, очистите экран, откройте отладочное соединение.
Соответствующее выполнение кода написано в комментариях, говорить о каждой конфигурации метода нет возможности... Иначе место будет очень длинным, и многие моменты здесь могут быть точкой знаний.
choosePort(HOST, DEFAULT_PORT)
.then(port => {
// 没有找到可用端口,直接return
if (port == null) {
return;
}
// 根据环境变量判断是否要用https
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
// 获取当前的 host, port, protocol 生成一系列url
const urls = prepareUrls(protocol, HOST, port);
// 创建一个webpack compiler
const compiler = createCompiler(webpack, config, appName, urls, useYarn);
// 加载代理的配置,在 project_path/package.json 里面加载配置
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// 生成 webpack dev server 的配置
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// 监听 devServer
devServer.listen(port, HOST, err => {
// 一些日志输出
// 自动用默认浏览器打开调试链接
openBrowser(urls.localUrlForBrowser);
});
})
.catch(err => {
// 错误处理
});
react-dev-utils/WebpackDevServerUtils.js
function createCompiler(webpack, config, appName, urls, useYarn) {
let compiler;
try {
compiler = webpack(config, handleCompile); // handleCompile为冒烟测试的对应处理
} catch (err) {
// 错误提示
}
compiler.plugin('invalid', () => {
// invalid 钩子,如果当前处于TTY终端,那么先清除控制台再输出 Compiling...
if (isInteractive) {
clearConsole();
}
console.log('Compiling...');
});
let isFirstCompile = true;
compiler.plugin('done', stats => {
// 监听了 done 事件,对输出的日志做了格式化输出
// 正常情况下会直接输出 `Compiled successfully!`
// 如果有错误则输出错误信息,这里对错误信息做一些处理,让其输出比较友好
});
return compiler;
}
Два последних слова
Мне всегда было любопытно, как эти леса очищают экран нашего терминала. смотреть вcreate-react-app
когда я увидел такой файлreact-dev-utils/clearConsole.js. Этот файл очень короткий, а основной код состоит всего из одного предложения:
react-dev-utils/clearConsole.js
process.stdout.write(process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H');
Тогда мне стало очень любопытно, я не знал, что означают последние две строки, и я продолжал искать и не мог найти ответ, который хотел.
Я спросил своего коллегу и сказал, что оно шестнадцатеричное, но в моем узком познании я всегда думал, что шестнадцатеричное можно преобразовать только в числа.... Но присмотревшись повнимательнее, естьJ
Явно не в шестнадцатеричном формате.
Большой парень в женской одежде сказал мне, что это ASCII-код, Baidu посмотрел на ASCII-код и прочитал его.\x1B
ASCII соответствуетESC
. но последний[2J
[3J
[H
Я до сих пор не знаю, что это значит...
Большой парень позже сказал мне, что это может бытьКоды управления Linux ANSIПотребовалось много времени, чтобы найти и бросить, прежде чем тайна была раскрыта~
Эти команды примерно означают:[2J
очистить консоль[H
переместите курсор в самый верх[3J
До сих пор не нашел, это должна быть более продвинутая клиринговая консоль системного уровня.
Дайте несколько веб-сайтов с информацией об управляющем коде Linux ANSI.Если вам интересно, вы можете узнать об этом самостоятельно в качестве резерва знаний.
В конце концов, многие из фронтенд-приятелей не из таких крупных, как я, и мне действительно нужно больше работать, чтобы компенсировать некоторые базовые или системные знания компьютера ~