vite анализ + построение vite-pro крупная компания MIS проектная практика, действительно ароматная

Vite

Всем привет, меня зовут Делайк, и сегодня я принесу вам вайт-анализ.

Наконец, предоставляется фоновый проект с использованием vite+react+concent.

слова, написанные впереди

vite, известный как следующее поколение инструментов разработки и построения интерфейса. Появление vite извлекает выгоду из поддержки браузером модулей, используя новые функции браузера для достижения чрезвычайно быстрой разработки; он может обеспечить чрезвычайно быструю горячую перезагрузку (hmr).

В режиме разработки поддержка модулей браузера используется для достижения максимальной эффективности разработки;

Компиляция и упаковка формальной среды построены с использованием свертки, которая впервые предложила встряхивание дерева;

Vite предоставляет множество параметров конфигурации, включая настройку самого vite, настройку esbuild, настройку накопительного пакета и т. д. Сегодня я познакомлю вас с vite с точки зрения исходного кода.

Фактически Vite можно разделить на три части: одна часть — это клиентская часть в процессе разработки, одна часть — это серверная часть в процессе разработки, а другая часть — это часть упаковки и компиляции, связанная с производством, поскольку упаковка и компиляция vite на самом деле используется для свертки, мы не делаем анализ, просто посмотрим на первые две части.

vite-client

Клиент Vite фактически обрабатывается как отдельный модуль, а его исходный код помещается вpackages/vite/src/client; в нем четыре файла:

  • client.ts: основная запись файла, выделенная ниже;
  • env.ts: конфигурация, связанная с окружением, здесь мы обработаем конфигурацию определения в vite.config.js (файл конфигурации vite);
  • overlay.ts: это отображение маски ошибок, которая будет отображать нашу информацию об ошибках;
  • tsconfig.json: это файл конфигурации ts.

Раздел инструментов

Клиент предоставляет ряд инструментальных функций, в основном для HMR;

image.png

часть веб-сокета

  • Установить соединение через веб-сокет
  • Вызов вышеуказанного наложения для отображения ошибок
  • Обмен сообщениями

Часть обмена сообщениями имеет несколько типов событий, как показано на следующем рисунке:

image.png

Например

Создал простую демонстрацию с помощью vite-app:

yarn create @vitejs/app my-react-ts-app --template react-ts

Используя приведенную выше команду, вы можете просто создать приложение react-ts vite.

npm install
npm run dev

Выполните приведенную выше команду, чтобы установить зависимости, затем запустите службу и откройте браузер:http://localhost:3000/, сетевой интерфейс, вы можете увидеть следующие запросы:

image.png

Я разделил эти типы данных на разные подразделения в соответствии с разными типами:

image.png

Давайте проанализируем содержимое html дальше:

<!DOCTYPE html>
<html lang="en">
  <head>
<script type="module" src="/@vite/client"></script>
  <script type="module">
import RefreshRuntime from "/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Как видите, задействованы три части js:

  • клиент, путь запроса /@vite/client, обратите внимание на этот путь, который является зависимым путем самого vite;
  • Код модуля react-refresh, это код, инжектируемый плагином react-refresh, @react-refresh запрашивается внутри кода, который является запросом sdk плагина react-refresh;
  • main, путь запроса — /src/main/tsc, что связано с реальным кодом в нашем проекте;

В дополнение к вышеперечисленным трем есть еще env, путь запроса — /@vite/env.js, это запрос зависимостей env, выдаваемый @vite/client:import '/node_modules/vite/dist/client/env.js';;

Конечно, есть также запрос sdk @react-refresh;

В дополнение к js, упомянутому выше, другие запросы на самом деле являются запросами в коде нашего проекта;

Первое, что должен сделать клиент, — это установить канал связи с веб-сокетом.Вы можете увидеть запрос localhost типа веб-сокета выше.Это конвейер для связи клиента с сервером и выполнения горячих обновлений.

vite- server

Поговорив о клиенте, возвращаемся к серверной части, файл входаpackages/vite/src/node/serve.ts, основная логика на самом деле вpackages/vite/src/node/server/index.ts;Серверную сторону мы временно называем нодовой, нодовая сторона в основном включает в себя обработку нескольких типов файлов, ведь это всего лишь прокси-сервер;

image.png

Давайте рассмотрим эти виды обработки в нескольких частях.

node watcher

Основная функция наблюдателя — отслеживать изменения файлов, а затем общаться с клиентом:image.png

Каталог прослушивания — это корневой каталог всего проекта, а watchOptions — этоvite.config.jsВнутри конфигурации server.watch код инициализации выглядит следующим образом:

// 使用chokidar进行对文件目录的监听,
  const watcher = chokidar.watch(path.resolve(root), {
    ignored: ['**/node_modules/**', '**/.git/**', ...ignored],
    ignoreInitial: true,
    ignorePermissionErrors: true,
    ...watchOptions
  }) as FSWatcher

Начните слушать файл:

// 如果发生改变,调用handleHMRUpdate,
  watcher.on('change', async (file) => {
    file = normalizePath(file)
    // invalidate module graph cache on file change
    moduleGraph.onFileChange(file)
    if (serverConfig.hmr !== false) {
      try {
        await handleHMRUpdate(file, server)
      } catch (err) {
        ws.send({
          type: 'error',
          err: prepareError(err)
        })
      }
    }
  })

  // 增加文件连接
  watcher.on('add', (file) => {
    handleFileAddUnlink(normalizePath(file), server)
  })

  // 减少文件连接
  watcher.on('unlink', (file) => {
    handleFileAddUnlink(normalizePath(file), server, true)
  })

Обработчик, соответствующий событию прослушивания, находится вpackages/vite/src/node/server/hmr.tsвнутри файла. Детали обработки описывать не будем, на самом деле логика внутри почти такая же, в конце вызывается вебсокет и отправляется на сторону клиента.

тип зависимости узла

Тип зависимости на самом деле является пакетом зависимостей в node_modules, например:

image.pngЭти пакеты в основном неизменны.Подход Vite заключается в том, чтобы поместить эти зависимости в запуск службы..viteНиже каталога полученный запрос направляется непосредственно в .vite для его получения, а затем возвращается.

статические ресурсы узла

Статические ресурсы на самом деле представляют собой контент в разделе public/ или в разделе static/, который нам известен и с которым мы знакомы.Эти ресурсы относятся к статическим файлам, например:

image.pngДля таких данных vite не выполняет никакой обработки и возвращает напрямую.

node html

для входного файлаindex.html, здесь мы будем говорить только о файлах с одной записью, также поддерживаются файлы с несколькими записями, смотрите подробностимногостраничное приложение;

// 删减后得代码如下
// @file packages/vite/src/node/server/middlewares/indexHtml.ts
export function indexHtmlMiddleware(server){
  return async (req, res, next) => {
    const url = req.url && cleanUrl(req.url)
    const filename = getHtmlFilename(url, server)
    try {
      // 从本地读取index.html的内容
      let html = fs.readFileSync(filename, 'utf-8')
      // dev模式下调用createDevHtmlTransformFn转换html的内容,插入两个script
      html = await server.transformIndexHtml(url, html)
      // 把html的内容返回。
      return send(req, res, html, 'html')
    } catch (e) {
      return next(e)
    }
  }
}

Для входного файла index.html vite сначала прочитает содержимое файла с жесткого диска. После серии операций он вернет содержимое операций. Давайте посмотрим на эту серию операций:

  • Вызовите createDevHtmlTransformFn, чтобы получить обработчик:
// @file packages/vite/src/node/plugins/html.ts
export function resolveHtmlTransforms(plugins: readonly Plugin[]) {
  const preHooks: IndexHtmlTransformHook[] = []
  const postHooks: IndexHtmlTransformHook[] = []

  for (const plugin of plugins) {
    const hook = plugin.transformIndexHtml
    if (hook) {
      if (typeof hook === 'function') {
        postHooks.push(hook)
      } else if (hook.enforce === 'pre') {
        preHooks.push(hook.transform)
      } else {
        postHooks.push(hook.transform)
      }
    }
  }

  return [preHooks, postHooks]
}

// @file packages/vite/src/node/server/middlewares/indexHtml.ts
export function createDevHtmlTransformFn(server: ViteDevServer) {
  const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins)
  return (url: string, html: string): Promise<string> => {
    return applyHtmlTransforms(
      html,
      url,
      getHtmlFilename(url, server),
      [...preHooks, devHtmlHook, ...postHooks],
      server
    )
  }
}

Здесь мы по-прежнему берем проект реакции в качестве примера,Плагин react-refresh вставляется в postHooks; В итоге он фактически возвращает функцию безымянного типа промиса, вот и замыкание. Безымянная функция называетсяapplyHtmlTransforms, посмотрим на параметры:

  • html — это содержимое index.html в корневом каталоге.
  • URL-адрес /index.html,
  • Результатом выполнения третьего параметра является /index.html.
  • Четвертый параметр — большой массив, prehooks пустой, второй — функция возврата собственной ссылки vite /@vite/client, а третий — плагин react-refresh в нем
  • Пятый параметр - текущий сервер

Далее идет время вызова applyHtmlTransforms, где html-контент будет перезаписан, а затем возвращен.

image.png

Окончательный обработанный html-контент — это html-контент, который мы видели выше.

узлы других типов

Временно считать другие типы как другие типы, включая /@vite/client и бизнес-запросы, начинающиеся с @vite; все эти запросы будут направляться в один и тот жеtransformMiddlewareпромежуточное ПО. Это промежуточное ПО делает следующее:

// @file packages/vite/src/node/server/middlewares/transform.ts

image.png

На самом деле, если вышеприведенная логика работает нормально, она достигнет кэш-попадания и кэш-промаха. Выберите один из двух, и попадание вернется напрямую. Если нет попадания, оно перейдет к преобразованию. Далее, давайте посмотрим на вызов.transformпроцесс:

// @file packages/vite/src/node/server/transformRequest.ts
// 调用插件获取当前请求的id,如/@react-refresh,当然也有获取不到的情况;
const id = (await pluginContainer.resolveId(url))?.id || url
// 调用插件获取插件返回的内容,如/@react-refresh,肯定有不是插件返回的情况,
const loadResult = await pluginContainer.load(id, ssr)
// 接下来是重点
// 如果没有获取到结果,也就是不是插件类型的请求,如我们的入口文件/src/main.tsx
if (loadResult == null) {
    // 从硬盘读取非插件提供的返回结果
    code = await fs.readFile(file, 'utf-8')
  } else {
    if (typeof loadResult === 'object') {
      code = loadResult.code
      map = loadResult.map
    } else {
      code = loadResult
    }
  }
}
// 启动文件监听,调用watcher,和上面讲到的watcher遥相呼应
ensureWatchedFile(watcher, mod.file, root)
// 代码运行到这里,是获取到内容了不假,不过code还是源文件,也就是编写的文件内容
// 下面的transform是开始进行替换
const transformResult = await pluginContainer.transform(code, id, map, ssr)
code = transformResult.code!
map = transformResult.map
return (mod.transformResult = {
      code,
      map,
      etag: getEtag(code, { weak: true })
    } as TransformResult)

Общий процесс выглядит следующим образом:

image.png

async transform(code, id, inMap, ssr) {
  const ctx = new TransformContext(id, code, inMap as SourceMap)
  ctx.ssr = !!ssr
  for (const plugin of plugins) {
    if (!plugin.transform) continue
    ctx._activePlugin = plugin
    ctx._activeId = id
    ctx._activeCode = code
    let result
    try {
      result = await plugin.transform.call(ctx as any, code, id, ssr)
    } catch (e) {
      ctx.error(e)
    }
    if (!result) continue
    if (typeof result === 'object') {
      code = result.code || ''
      if (result.map) ctx.sourcemapChain.push(result.map)
    } else {
      code = result
    }
  }
  return {
    code,
    map: ctx._getCombinedSourcemap()
  }
},

image.png

Фактически, на данный момент мы в основном поняли функции, реализуемые сервером vite, прокси-сервером, а затем модифицируем ссылку на свои собственные правила, а также анализируем и обрабатываем свои собственные правила. Что еще более важно, это vite:import-analysisэтот плагин.

vite + react

Прикрепите адрес, прежде чем мы начнем:github: vite-реагировать-concent-про; этот предмет был создан пользователемgithub: веб-пакет-реакция-концентрация-проПосле изменения проекта модуль кода бизнес-логики не изменился, изменилась только часть компиляции и упаковки.

Здесь я расскажу о процессе перехода с webpack на vite и некоторых возникших проблемах.

Изменения в проекте на самом деле не большие.По сути, после клонирования проекта удалите зависимости, связанные с веб-пакетом, и замените их на vite.Не забудьте добавить плагин для реакции vite:@vitejs/plugin-react-refresh; После замены, поскольку ссылочный путь в нашем проекте находится в папке src, нам необходимо указать следующий псевдоним для vite:

resolve: {
    alias: { // 别名
      "configs": path.resolve(__dirname, 'src/configs'),
      "components": path.resolve(__dirname, 'src/components'),
      "services": path.resolve(__dirname, 'src/services'),
      "pages": path.resolve(__dirname, 'src/pages'),
      "types": path.resolve(__dirname, 'src/types'),
      "utils": path.resolve(__dirname, 'src/utils'),
    },
},

Таким образом, мы можем сообщить vite, где найти какой файл, не меняя ссылки в нем. цитируется вprocess.env.***Подобные ссылки, которые используются для оценки некоторой логики, связанной с окружением, недоступны в vite.Переменные окружения vite передаются черезimport.meta.env.***;

После изменения этих исполненийnpm run start, может работать нормально.

яма 1

в исполненииnpm run buildПосле этого при предварительном просмотре выполняемnpm run preview, появится следующий экран:

image.pngВозникает такая невидимая ошибка, и каково наше решение?

Прежде всего, убейте сжатие, не сжимайте его, сжатый код весь abcd, и вы ничего не видите; способ его убить — изменить конфигурацию vite:

build: {
    minify: false, // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用terser
    manifest: false, // 是否产出maifest.json
    sourcemap: false, // 是否产出soucemap.json
    outDir: 'build', // 产出目录
  },

Мы изменили minify на false, а затем повторно выполнили команды сборки и предварительного просмотра.Мы можем видеть точную строку и место, где было сообщено об ошибке.

Как в итоге разрешилось? TMD оказывается библиотекой для проверки объектов, которая ссылается на пакет util, а затем в наших node_modules нет пакета util.

Больше об этих причинах говорить не буду.После двух-трёх часов метаний решение - это команда:npm i -S util.

После повторного выполнения сборки и предварительного просмотра это нормально.

яма 2

Локальная разработка запущена, сборка + предварительный просмотр в порядке. Далее мы должны попробовать модульный тест. воплощать в жизньnpm run test.果不其然,报错了,原因是没有babel-preset-react-app的babel配置。

Тогда мы можем увеличить конфигурацию, разве это не хорошо?

Мы добавили конфигурацию babel в package.json:

"bable": {
    "presets": [
        "react-app"
    ],
}

Затем мы бежимnpm run test; Ну ладно, прогон прошел успешно.

Давайте снова протестируем и выполнимnpm run start, TMD бросай трубку, беги! ! !

** Using babel-preset-react-app requires that you specify NODE_ENV or BABEL_ENV environment variables. Valid values are "development", "test", and "production". Instead, received: undefined **

Что означает приведенная выше фраза? нашbabel-preset-react-appЭтот пакет требуетprocess.env.NODE_ENVилиprocess.env.BABEL_ENVПеременные. Основываясь на том принципе, что vite ничего не делает в процессе, эту проблему нельзя решить, то есть конфигурацию babel нельзя реализовать конфигурацией, так как же это исправить? ?

проверилbabel-preset-react-appИсходный код этого пакета передается в виде параметров, поэтому мы должны начать с того, что мы делали во время теста.Во время теста мы запускаем jest, и у jest есть свой конфигурационный файл, называемыйjest.config.js; В конфигурационном файле jest есть объект преобразования, которыйbabel-jestЭта библиотека, это Babel.

Здесь нам нужно что-то сделать, и, наконец, после долгой отладки конфигурация выглядит так:

// vite react项目里面单测需要在这里把babel-react-app传递进去,不可在项目中或者package.json里面配置babel
transform: {
    // vite react项目里面单测需要在这里把babel-react-app传递进去,不可在项目中或者package.json里面配置babel
    "^.+\\.(js|jsx|ts|tsx)$": ["<rootDir>/node_modules/babel-jest", {"presets": ['babel-preset-react-app'] }],
    "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
    "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js",
  },

Это самая большая яма, и мне потребовалось 2 часа, чтобы бросить ее.

напиши в конце

Вышла 2 версия vite, и ее можно использовать во внутренних проектах компании.Поскольку онлайн и офлайн запуск это не набор кодов, Boss You так же специально предусмотрел функцию предпросмотра, советую попробовать.

Кроме того, упомянутый выше проект:github: vite-реагировать-concent-про, функции, включенные в настоящее время, также относительно полны:

  • start: начать разработку и отладку локально
  • build: скомпилировать и упаковать
  • Предварительный просмотр: Предварительный просмотр упакованного кода:
  • тест: одиночный тест
  • snap: создать снимок

В проект интегрированы react, concent (особенно полезная библиотека управления состоянием), antd, react-router-dom, axios и т. д., и его можно разрабатывать бесплатно.

Конечно, если ваш существующий проект хочет перейти на vite, это тоже очень просто:

  • Клонируйте проект и удалите содержимое ниже src;
  • Переместите файл под src вашего старого проекта в файл src этого проекта, а затем измените псевдоним и process.env;
  • Не забудьте изменить index.html на ваш файл ввода

Подождем, чтобы увидеть чудо

После использования вите,npm run start может быть улучшен примерно на 80%;Сборка npm run может улучшиться примерно на 50%

! Ммм, как вкусно пахнет~

Наконец, если вы считаете, что текст неплох, не забудьте поставить лайк, добро пожаловать, чтобы следовать за мной.github:draven