Вите утверждает, чтоНовое поколение интерфейсных инструментов разработки и сборки, постепенно стал популярным в сообществе фронтенда. Он принимает новую идею разделения, чтобы улучшить общий опыт разработки интерфейса. По сравнению с традиционной конструкцией веб-пакета происходит качественное улучшение производительности и скорости. Поэтому в следующей статье мы в основном познакомимся с его использованием и принципом работы.
что
Название Vite происходит от французского, что означает быстрый, быстрый. Это просто отражает его основной коммерческий аргумент -"быстро". Общая функция аналогична предварительно настроенному серверу webpack plus dev, который используется для повышения общей скорости построения внешнего проекта. Согласно тесту, скорость запуска сервера и HMR могут достигать миллисекундного уровня.
инструкции
Использование vite очень простое, на данный момент для быстрого старта нового проекта предусмотрен официальный скаффолдинг:
npm init @vitejs/app
// yarn
yarn create @vitejs/app
Затем он перейдет в интерактивный режим, позволяющий выбрать соответствующий шаблон, ввести название проекта и другие операции. Если вам нужно вручную указать шаблон и имя проекта, вы можете использовать следующую команду:
npm init @vitejs/app my-vite-demo --template react
Все соответствующие шаблоны проектов, указанные здесь, можно найти вGitHub.com/Vite просто/ужас…нашел на складе. После запуска проекта вы можете напрямую использовать следующие команды для запуска и предварительного просмотра.
# 安装依赖
yarn install
# 开发环境下使用
yarn dev
# 打包
yarn run build
# 用来预览打包后的效果
yarn run serve
Механизм плагина
Vite в основном использует плагины для расширения функций.Вы можете видеть, что после запуска простейшего проекта инициализации выше в его файле конфигурацииvite.config.ts
Под файлом есть следующий код:
import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'
// [https:](https://vitejs.dev/config/)[//vitejs.dev/config/](https://vitejs.dev/config/)
export default defineConfig({
plugins: [reactRefresh()]
})
Вы можете видеть, что есть ссылка наreactRefresh
Плагин, этот плагин может модифицировать реагирующий компонент, не теряя его состояния.
Точно так же, если есть необходимость реализовать другие дополнительные функции, их можно расширить с помощью механизма плагинов vite. Доступ к этим сторонним подключаемым модулям можно получить черезGitHub.com/Vite просто/ужас…нашел в этом репозитории.
В то же время, поскольку подключаемый модуль vite расширяет интерфейс объединения, реализация собственного подключаемого модуля vite аналогична написанию подключаемого модуля объединения. Здесь вы можете обратиться кAPI плагинов | Официальный китайский документ Vite.
Принцип работы
Так много было сказано выше, так как же Vite обеспечивает сверхбыструю разработку?GitHub.com/Вите есть/Вите…Все мы знаем, что традиционные инструменты упаковки и сборки должны полностью анализировать и собирать все приложение из входного файла перед запуском сервера. Поэтому много времени уходит на генерацию зависимостей, сборку и компиляцию.
И vite в основном следует спецификации ESM (модули Es) для выполнения кода.Поскольку современные браузеры в основном поддерживают спецификацию ESM, нет необходимости упаковывать и компилировать код в модули es5 на этапе разработки. Нам нужно только начать с входного файла, когда мы столкнемся с соответствующимimport
При выполнении оператора соответствующий модуль загружается в браузер. Следовательно, эта характеристика, которую не нужно упаковывать, также является причиной скорости Vite.
В то же время перевод ts/jsx и других файлов также будет использовать esbuild для повышения скорости. Внутри Vite запустит сервер разработки, примет HTTP-запросы от независимых модулей и позволит браузеру самому анализировать и обрабатывать загрузку модулей. В качестве примера возьмем официальную демку, вы можете видеть, что после запуска при доступе к соответствующей странице вместо загрузки всего файла bundle.js он загружается по модулям.
Из реализации кода, позволяяyarn dev
После команды Vite запустит сервер разработки, затем загрузит различное промежуточное ПО, а затем отслеживает соответствующие запросы на доступ к внешнему интерфейсу.GitHub.com/Вите есть/Вите…
const { createServer } = await import('./server')
try {
const server = await createServer({
root,
base: options.base,
mode: options.mode,
configFile: options.config,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
server: cleanOptions(options) as ServerOptions
})
await server.listen()
} catch (e) {
createLogger(options.logLevel).error(
chalk.red(`error when starting dev server:\n${e.stack}`)
)
process.exit(1)
}
В то же время собственный клиентский код Vite будет внедрен в среду разработки для мониторинга HMR и другой обработки.GitHub.com/Вите есть/Вите…
Голый модуль переписан
Поскольку в настоящее время ESM не поддерживает что-то вродеimport vue from "vue"
Такая голая загрузка модуля (предложение по импорту картGitHub.com/WICG/импорт…Эту проблему можно решить, но она еще не реализована), поэтому адрес загрузки модуля необходимо переписать. преобразовать его во что-то вродеimport vue from "/
@modules/vue"
эта форма.
В принципе, это в основном достигается за счетes-module-lexer
а такжеmagic-string
Замена двух пакетов дает больше преимуществ в производительности, чем семантический анализ и преобразование AST.
Вот два пакета:
Es-module-lexer
GitHub.com/гай Бедфорд/...Хотя при лексическом анализе кода js обычно используются такие инструменты, как babel, acorn и т. д., для ESM-файлов использование библиотеки es-module-lexer позволяет значительно повысить производительность.Сжатый объем составляет всего 4кб, а по официальным данным В приведенном примере библиотека Angular1 размером 720 КБ требует более 100 мс для анализа через acorn, в то время как для использования библиотеки es-module-lexer требуется всего 5 мс, что повышает производительность почти в 20 раз.
Magic-string
GitHub.com/Daily-Харрис…Vite использует большую часть этой библиотеки для выполнения некоторой работы по замене строк, чтобы избежать манипулирования AST. Конкретный код может относиться кGitHub.com/Вите есть/Вите…Общая идея примерно похожа на следующий код:
import { init, parse as parseImports, ImportSpecifier } from 'es-module-lexer'
// 借助es-module-lexer来分析import语句
imports = parseImports(source)[0]
// 接着在依赖分析及路径重写过程中利用magic-string来替换源码。
let s: MagicString | undefined
const str = () => s || (s = new MagicString(source))
// 省略部分代码
for (let index = 0; index < imports.length; index++) {
const {
s: start,
e: end,
ss: expStart,
se: expEnd,
d: dynamicIndex,
n: specifier
} = imports[index]
// 省略部分代码
// 解析代码
const { imports, importsString, exp, endIndex, base, pattern } =
await transformImportGlob(
source,
start,
importer,
index,
root,
normalizeUrl
)
str().prepend(importsString)
str().overwrite(expStart, endIndex, exp)
imports.forEach((url) => importedUrls.add(url.replace(base, '/')))
if (!(importerModule.file! in server._globImporters)) {
server._globImporters[importerModule.file!] = {
module: importerModule,
importGlobs: []
}
}
server._globImporters[importerModule.file!].importGlobs.push({
base,
pattern
})
}
// 最终返回处理过的代码
if (s) {
return s.toString()
} else {
return source
}
индивидуальная обработка блоков
Эта функция создается путем связывания после модуля?type=
параметры для различения разных блоков. Затем каждый блок обрабатывается отдельно.
В зависимости от типа блока для компиляции при преобразовании используются разные плагины.
Ниже в качестве примера используется файл json, при обработкеxxx.json
Когда файл заканчивается, сначала подключаемый модуль json будет соответствовать тому, является ли имя идентификатора модуля json. Затем займитесь переводом.
// Custom json filter for vite
const jsonExtRE = /\.json($|\?)(?!commonjs-proxy)/
export function jsonPlugin(
options: JsonOptions = {},
isBuild: boolean
): Plugin {
return {
name: 'vite:json',
transform(json, id) {
if (!jsonExtRE.test(id)) return null
if (SPECIAL_QUERY_RE.test(id)) return null
try {
if (options.stringify) {
if (isBuild) {
return {
code: `export default JSON.parse(${JSON.stringify(
JSON.stringify(JSON.parse(json))
)})`,
map: { mappings: '' }
}
} else {
return `export default JSON.parse(${JSON.stringify(json)})`
}
}
const parsed = JSON.parse(json)
return {
code: dataToEsm(parsed, {
preferConst: true,
namedExports: options.namedExports
}),
map: { mappings: '' }
}
} catch (e) {
const errorMessageList = /[\d]+/.exec(e.message)
const position = errorMessageList && parseInt(errorMessageList[0], 10)
const msg = position
? `, invalid JSON syntax found at line ${position}`
: `.`
this.error(`Failed to parse JSON file` + msg, e.idx)
}
}
}
}
HMR
Горячее обновление — очень важная часть интерфейса разработки, поэтому Vite в основном полагается на следующие шаги для реализации функции HMR:
- При перезаписи адреса модуля записывайте цепочку зависимостей модуля
importMaps
. Таким образом, во время последующих обновлений вы можете знать, какие файлы нуждаются в горячем обновлении.
- можно использовать в коде
import.meta.hot
интерфейс для метки «Граница HMR».
- Затем, когда файл будет обновлен, он будет записан по
imoprtMaps
Структура цепочки находит соответствующую «Границу HMR», а затем перезагружает отсюда соответствующий обновленный модуль.
- Если соответствующая граница не встречается, обновляется все приложение.
Способ применения следующий:
import foo from './foo.js'
foo()
if (import.meta.hot) {
import.meta.hot.accept('./foo.js', (newFoo) => {
newFoo.foo()
})
}
Далее будет представлен его принцип с конкретным кодом. Логика клиента:GitHub.com/Вите есть/Вите…
// record for HMR import chain analysis
// make sure to normalize away base
importedUrls.add(url.replace(base, '/'))
if (hasHMR && !ssr) {
debugHmr(
`${
isSelfAccepting
? `[self-accepts]`
: acceptedUrls.size
? `[accepts-deps]`
: `[detected api usage]`
} ${prettyImporter}`
)
// 在用户业务代码中注入Vite客户端代码
str().prepend(
`import { createHotContext as __vite__createHotContext } from "${clientPublicPath}";` +
`import.meta.hot = __vite__createHotContext(${JSON.stringify(
importerModule.url
)});`
)
}
case 'update':
notifyListeners('vite:beforeUpdate', payload)
// 发生错误的时候,重新加载整个页面
if (isFirstUpdate && hasErrorOverlay()) {
window.location.reload()
return
} else {
clearErrorOverlay()
isFirstUpdate = false
}
payload.updates.forEach((update) => {
if (update.type === 'js-update') {
// js更新逻辑, 会进入一个缓存队列,批量更新,从而保证更新顺序
queueUpdate(fetchUpdate(update))
} else {
// css更新逻辑, 检测到更新的时候,直接替换对应模块的链接,重新发起请求
let { path, timestamp } = update
path = path.replace(/\?.*/, '')
const el = (
[].slice.call(
document.querySelectorAll(`link`)
) as HTMLLinkElement[]
).find((e) => e.href.includes(path))
if (el) {
const newPath = `${path}${
path.includes('?') ? '&' : '?'
}t=${timestamp}`
el.href = new URL(newPath, el.href).href
}
console.log(`[vite] css hot updated: ${path}`)
}
})
break
break
Сервер обрабатывает логику обновления модуля HMR:GitHub.com/Вите есть/Вите…
export async function handleHMRUpdate(
file: string,
server: ViteDevServer
): Promise<any> {
const { ws, config, moduleGraph } = server
const shortFile = getShortName(file, config.root)
const isConfig = file === config.configFile
const isConfigDependency = config.configFileDependencies.some(
(name) => file === path.resolve(name)
)
const isEnv = config.inlineConfig.envFile !== false && file.endsWith('.env')
if (isConfig || isConfigDependency || isEnv) {
// 重启server
await restartServer(server)
return
}
// (dev only) the client itself cannot be hot updated.
if (file.startsWith(normalizedClientDir)) {
ws.send({
type: 'full-reload',
path: '*'
})
return
}
const mods = moduleGraph.getModulesByFile(file)
// check if any plugin wants to perform custom HMR handling
const timestamp = Date.now()
const hmrContext: HmrContext = {
file,
timestamp,
modules: mods ? [...mods] : [],
read: () => readModifiedFile(file),
server
}
for (const plugin of config.plugins) {
if (plugin.handleHotUpdate) {
const filteredModules = await plugin.handleHotUpdate(hmrContext)
if (filteredModules) {
hmrContext.modules = filteredModules
}
}
}
if (!hmrContext.modules.length) {
// html file cannot be hot updated
if (file.endsWith('.html')) {
[config.logger.info](http://config.logger.info/)(chalk.green(`page reload `) + chalk.dim(shortFile), {
clear: true,
timestamp: true
})
ws.send({
type: 'full-reload',
path: config.server.middlewareMode
? '*'
: '/' + normalizePath(path.relative(config.root, file))
})
} else {
// loaded but not in the module graph, probably not js
debugHmr(`[no modules matched] ${chalk.dim(shortFile)}`)
}
return
}
updateModules(shortFile, hmrContext.modules, timestamp, server)
}
function updateModules(
file: string,
modules: ModuleNode[],
timestamp: number,
{ config, ws }: ViteDevServer
) {
const updates: Update[] = []
const invalidatedModules = new Set<ModuleNode>()
let needFullReload = false
for (const mod of modules) {
invalidate(mod, timestamp, invalidatedModules)
if (needFullReload) {
continue
}
const boundaries = new Set<{
boundary: ModuleNode
acceptedVia: ModuleNode
}>()
// 向上传递更新,直到遇到边界
const hasDeadEnd = propagateUpdate(mod, timestamp, boundaries)
if (hasDeadEnd) {
needFullReload = true
continue
}
updates.push(
...[...boundaries].map(({ boundary, acceptedVia }) => ({
type: `${boundary.type}-update` as Update['type'],
timestamp,
path: boundary.url,
acceptedPath: acceptedVia.url
}))
)
}
if (needFullReload) {
// 重刷页面
} else {
// 相ws客户端发送更新事件, Websocket 监听模块更新, 并且做对应的处理。
ws.send({
type: 'update',
updates
})
}
}
Стратегия оптимизации
Поскольку упаковка vite позволяет браузеру загружать модули один за другим, легко возникает проблема каскадного потока http-запросов (браузер может одновременно выполнять до 6 запросов за раз). На этот раз, чтобы решить эту проблему, vite в основном принял три решения.
-
Предварительно упакован, чтобы гарантировать, что каждая зависимость соответствует только одному запросу/файлу. Как лодаш. Здесь вы можете обратиться кGitHub.com/Вите есть/Вите…
-
кодовый сплит кодовый сплит. Вы можете использовать встроенный накопитель
manualChunks
реализовать. -
Код состояния Etag 304 позволяет браузеру напрямую использовать кеш браузера при перезагрузке.
// check if we can return 304 early
const ifNoneMatch = req.headers['if-none-match']
if (
ifNoneMatch &&
(await moduleGraph.getModuleByUrl(url))?.transformResult?.etag ===
ifNoneMatch
) {
isDebug && debugCache(`[304] ${prettifyUrl(url, root)}`)
res.statusCode = 304
return res.end()
}
Использование esbuild
GitHub.com/Вите есть/Вите…Используйте esbuild для преобразования файлов ts/jsx для более быстрой компиляции.
export async function transformWithEsbuild(
code: string,
filename: string,
options?: TransformOptions,
inMap?: object
): Promise<ESBuildTransformResult> {
// if the id ends with a valid ext, use it (e.g. vue blocks)
// otherwise, cleanup the query before checking the ext
const ext = path.extname(
/\.\w+$/.test(filename) ? filename : cleanUrl(filename)
)
let loader = ext.slice(1)
if (loader === 'cjs' || loader === 'mjs') {
loader = 'js'
}
const resolvedOptions = {
loader: loader as Loader,
sourcemap: true,
// ensure source file name contains full query
sourcefile: filename,
...options
} as ESBuildOptions
delete resolvedOptions.include
delete resolvedOptions.exclude
delete resolvedOptions.jsxInject
try {
const result = await transform(code, resolvedOptions)
if (inMap) {
const nextMap = JSON.parse(result.map)
nextMap.sourcesContent = []
return {
...result,
map: combineSourcemaps(filename, [
nextMap as RawSourceMap,
inMap as RawSourceMap
]) as SourceMap
}
} else {
return {
...result,
map: JSON.parse(result.map)
}
}
} catch (e) {
debug(`esbuild error with options used: `, resolvedOptions)
// patch error information
if (e.errors) {
e.frame = ''
e.errors.forEach((m: Message) => {
e.frame += `\n` + prettifyMessage(m, code)
})
e.loc = e.errors[0].location
}
throw e
}
}
Суммировать
В целом, Vite открыл совершенно иной путь, чем веб-пакет, в области инструментов разработки интерфейса и решил проблему медленного построения на этапе разработки интерфейса. Ожидается, что он выведет интерфейс разработки на новый уровень. В то же время исходный код vite.js также находится в процессе постоянной итерации.Если вы хотите узнать больше о конкретных деталях его реализации, вы все еще надеетесь прочитать его исходный код лично. Эта статья в основном надеется сыграть роль в привлечении нефрита.
Справочная документация
Талант .Vite — это .Dev/дорого/#over…