1 Обзор
Viteлегче и быстрееwebИнструменты разработки приложений для современных браузеров. Нижняя часть основана наECMAScriptСтандартная система родного модуляES Moduleвыполнить.他的出现是为了解决webpackВремя холодного пуска слишком велико иWebpack HMRОтвет на горячее обновление медленный и другие проблемы.
по умолчаниюViteСозданный проект является обычнымVue3приложение, по сравнению сVue-cliПриложение создается с гораздо меньшим количеством файлов конфигурации и зависимостей.
ViteСозданный проект требует очень мало зависимостей разработки, толькоVite和@vue/compiler-sfc. здесьViteэто беговой инструмент,compiler-sfcсостоит в том, чтобы скомпилировать.vueОднофайловый компонент в конце. Также можно поддерживать использование других фреймворков, указав разные шаблоны при создании проекта, такого какReact. После создания проекта его можно запустить и упаковать двумя командами.
# 开启服务器
vite serve
# 打包
vite build
просто такViteначалwebСлужбу не нужно компилировать и упаковывать, поэтому скорость запуска очень высока.Большая часть кода, работающего на этапе отладки, — это код, который вы пишете в редакторе.webpackРендеринг после компиляции действительно намного быстрее. Конечно, производственную среду еще нужно упаковать, ведь мы много раз используем самые последниеESспецификация пока не поддерживается в браузерах,Viteпроцесс упаковки иwebpackТочно так же все файлы будут скомпилированы и упакованы вместе. Необходимость вырезания кодаViteОн реализован с помощью собственного динамического импорта, поэтому результат упаковки может поддерживать только современные браузеры.Если вам нужна совместимость со старыми браузерами, вы можете импортироватьPolyfill.
В дополнение к использованию Webpack для упаковки, поскольку среда браузера не поддерживает модульность и новый синтаксис, существует также тот факт, что файлы модулей будут генерировать многоhttpпросить. Если вы разрабатываете модульным способом, на странице будут десятки или даже десятки модулей, и часто их будет несколько.kbфайл, откройте страницу для загрузки десятковjsресурсы Это явно необоснованно.
ViteСозданные проекты, требующие небольшой дополнительной настройки, уже поддерживаются по умолчанию.TS,Less, Sass,Stylus,postcss, но вам нужно установить соответствующий компилятор отдельно, и он также поддерживает дефолтныйjsxа такжеWeb Assembly.
ViteПреимущество заключается в повышении опыта разработчика в процессе разработки,webСервер разработки можно запустить сразу, без ожидания, горячее обновление модулей происходит практически в режиме реального времени, а необходимые файлы компилируются по запросу, чтобы избежать компиляции неиспользуемых файлов. и из коробки избежатьloaderа такжеpluginsКонфигурация.
ViteОсновные функции включают в себя открытие статическогоwebсервер, способный компилировать однофайловые компоненты и предоставлятьHMRФункция. при запускеviteКогда текущий каталог проекта используется в качестве корневого каталога статического сервера, статический сервер будет перехватывать некоторые запросы, компилировать в реальном времени, когда запрашивается один файл, и обрабатывать модули, которые другие браузеры не могут распознать.websocketвыполнитьhmr.
2. Внедрить статический тестовый сервер
Сначала реализуйте функцию, которая включает статическиеwebИнструмент командной строки для сервера.vite1.xВнутренне используетсяKoaреализовать статический сервер. (ps: Инструмент командной строки узла может просмотреть мою предыдущую статью, которая не будет здесь представлена, просто вставьте код напрямую).
npm init
npm install koa koa-send -D
инструментbinВходной файл установлен как локальныйindex.js
#!/usr/bin/env node
const Koa = require('koa')
const send = require('koa-send')
const app = new Koa()
// 开启静态文件服务器
app.use(async (ctx, next) => {
// 加载静态文件
await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html'})
await next()
})
app.listen(5000)
console.log('服务器已经启动 http://localhost:5000')
Это напишетnodeИнструменты для статических серверов.
3. Работа со сторонними модулями
Наш подход заключается в том, что сторонние модули (node_modulesфайл), вы можете дать ему идентификатор, изменив путь стороннего модуля, а затем получить этот идентификатор в сервере для обработки этого модуля.
Во-первых, вам нужно изменить путь к стороннему модулю, для реализации которого требуется новое промежуточное ПО. Определить, является ли файл, возвращенный в данный момент в браузер,javascript, просто посмотрите на заголовок ответаcontent-type. еслиjavascriptНеобходимо найти путь к модулю, указанный в этом файле.ctx.bodyЭто файл содержимого, возвращаемый браузеру. Данные здесь представляют собойstream, который необходимо преобразовать в строку для обработки.
const stream2string = (stream) => {
return new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', chunk => {chunks.push(chunk)})
stream.on('end', () => { resolve(Buffer.concat(chunks).toString('utf-8'))})
stream.on('error', reject)
})
}
// 修改第三方模块路径
app.use(async (ctx, next) => {
if (ctx.type === 'application/javascript') {
const contents = await stream2string(ctx.body);
// 将body中导入的路径修改一下,重新赋值给body返回给浏览器
// import vue from 'vue', 匹配到from '修改为from '@modules/
ctx.body = contents.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/');
}
})
Затем начните загружать сторонний модуль.Здесь также необходимо промежуточное программное обеспечение, чтобы определить, начинается ли путь запроса с модифицированного @module.Если да, перейдите в node_modules, чтобы загрузить соответствующий модуль и вернуть его в браузер. Это промежуточное программное обеспечение должно быть размещено перед статическим сервером.
// 加载第三方模块
app.use(async (ctx, next) => {
if (ctx.path.startsWith('/@modules/')) {
// 截取模块名称
const moduleName = ctx.path.substr(10);
}
})
После получения имени модуля вам необходимо получить входной файл модуля.ES ModuleВходной файл модуля, вам нужно сначала найти входной файл модуляpackage.jsonа потом получить этоpackage.jsonсерединаmoduleЗначение поля также является файлом ввода.
// 找到模块路径
const pkgPath = path.join(process.pwd(), 'node_modules', moduleName, 'package.json');
const pkg = require(pkgPath);
// 重新给ctx.path赋值,需要重新设置一个存在的路径,因为之前的路径是不存在的
ctx.path = path.join('/node_modules', moduleName, pkg.module);
// 执行下一个中间件
awiat next();
Таким образом, хотя запрос браузера поступает по пути @modules, путь пути изменяется перед загрузкой вnode_modulesв пути, чтобы при загрузке он переходил наnode_modulesПолучите файл в формате , и ответьте на загруженный контент в браузере.
// 加载第三方模块
app.use(async (ctx, next) => {
if (ctx.path.startsWith('/@modules/')) {
// 截取模块名称
const moduleName = ctx.path.substr(10);
// 找到模块路径
const pkgPath = path.join(process.pwd(), 'node_modules', moduleName, 'package.json');
const pkg = require(pkgPath);
// 重新给ctx.path赋值,需要重新设置一个存在的路径,因为之前的路径是不存在的
ctx.path = path.join('/node_modules', moduleName, pkg.module);
// 执行下一个中间件
awiat next();
}
})
4. Однофайловая обработка компонентов
Я уже говорил, что браузер с этим не справится..vueресурс, браузер может распознать толькоjs,cssи другие общие ресурсы, поэтому другие типы ресурсов необходимо обрабатывать на стороне сервера. Когда запрашивается однофайловый компонент, однофайловый компонент необходимо скомпилировать в js-модуль на сервере и вернуть в браузер.
Итак, когда браузер впервые запрашиваетApp.vue, сервер компилирует однофайловый компонент в объект, сначала загружает компонент, а затем создает объект.
import Hello from './src/components/Hello.vue'
const __script = {
name: "App",
components: {
Hello
}
}
Затем перейдите к загрузке файла записи, на этот раз сервер скомпилирует шаблон этого однофайлового компонента и вернет функцию рендеринга. Затем смонтируйте функцию рендеринга в только что созданный объект параметров компонента и, наконец, экспортируйте объект параметров.
import { render as __render } from '/src/App.vue?type=template'
__script.render = __render
__script.__hmrId = '/src/App.vue'
export default __script
то естьviteБудут отправлены два запроса, первый запрос скомпилирует однофайловый файл, а второй запрос скомпилирует однофайловый шаблон и вернетrenderфункция.
- Параметры компиляции одного файла
Во-первых, давайте реализуем случай запроса одного файла в первый раз. Однофайловый компонент нужно скомпилировать в вариант, который здесь тоже реализован промежуточным ПО. Эта функция необходима после обработки статического сервера и перед обработкой путей сторонних модулей.
Во-первых, вам нужно скомпилировать однофайловые компоненты с помощьюcompiler-sfc.
// 处理单文件组件
app.use(async (ctx, next) => {
if (ctx.path.endsWith('.vue')) {
// 获取响应文件内容,转换成字符串
const contents = await streamToString(ctx.body);
// 编译文件内容
const { descriptor } = compilerSFC.parse(contents);
// 定义状态码
let code;
// 不存在type就是第一次请求
if (!ctx.query.type) {
code = descriptor.script.content;
// 这里的code格式是, 需要改造成我们前面贴出来的vite中的样子
// import Hello from './components/Hello.vue'
// export default {
// name: 'App',
// components: {
// Hello
// }
// }
// 改造code的格式,将export default 替换为const __script =
code = code.relace(/export\s+default\s+/g, 'const __script = ')
code += `
import { render as __render } from '${ctx.path}?type=template'
__script.rener = __render
export default __script
`
}
// 设置浏览器响应头为js
ctx.type = 'application/javascript'
// 将字符串转换成数据流传给下一个中间件。
ctx.body = stringToStream(code);
}
await next()
})
const stringToStream = text => {
const stream = new Readable();
stream.push(text);
stream.push(null);
return stream;
}
npm install @vue/compiler-sfc -D
Затем мы обработаем второй запрос однофайлового компонента, второй запросurlпринесуtype=templateпараметр, шаблон однофайлового компонента необходимо скомпилировать вrenderфункция.
Во-первых, вам нужно определить, есть ли какой-либо текущий запросtype=template.
if (!ctx.query.type) {
...
} else if (ctx.query.type === 'template') {
// 获取编译后的对象 code就是render函数
const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })
// 将render函数赋值给code返回给浏览器
code = templateRender.code
}
Здесь нам также нужно разобраться с инструментами вprocess.env, потому что эти коды вернутся в браузер для запуска, если они не обработаны, по умолчаниюnodeПривести к провалу. Изменить третье лицо может изменить путь к модулю промежуточного программного обеспечения, а затем добавить После изменения пути модификацияprocess.env.
// 修改第三方模块路径
app.use(async (ctx, next) => {
if (ctx.type === 'application/javascript') {
const contents = await stream2string(ctx.body);
// 将body中导入的路径修改一下,重新赋值给body返回给浏览器
// import vue from 'vue', 匹配到from '修改为from '@modules/
ctx.body = contents.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/').replace(/process\.env\.NODE_ENV/g, '"development"');
}
})
Пока упрощенная версияvite, конечно, здесь мы только демонстрируем.vueфайл, дляcss,lessДругие ресурсы не обрабатывались, но методы аналогичны, и заинтересованные студенты могут реализовать это самостоятельно.
#!/usr/bin/env node
const path = require('path')
const { Readable } = require('stream)
const Koa = require('koa')
const send = require('koa-send')
const compilerSFC = require('@vue/compiler-sfc')
const app = new Koa()
const stream2string = (stream) => {
return new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', chunk => {chunks.push(chunk)})
stream.on('end', () => { resolve(Buffer.concat(chunks).toString('utf-8'))})
stream.on('error', reject)
})
}
const stringToStream = text => {
const stream = new Readable();
stream.push(text);
stream.push(null);
return stream;
}
// 加载第三方模块
app.use(async (ctx, next) => {
if (ctx.path.startsWith('/@modules/')) {
// 截取模块名称
const moduleName = ctx.path.substr(10);
// 找到模块路径
const pkgPath = path.join(process.pwd(), 'node_modules', moduleName, 'package.json');
const pkg = require(pkgPath);
// 重新给ctx.path赋值,需要重新设置一个存在的路径,因为之前的路径是不存在的
ctx.path = path.join('/node_modules', moduleName, pkg.module);
// 执行下一个中间件
awiat next();
}
})
// 开启静态文件服务器
app.use(async (ctx, next) => {
// 加载静态文件
await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html'})
await next()
})
// 处理单文件组件
app.use(async (ctx, next) => {
if (ctx.path.endsWith('.vue')) {
// 获取响应文件内容,转换成字符串
const contents = await streamToString(ctx.body);
// 编译文件内容
const { descriptor } = compilerSFC.parse(contents);
// 定义状态码
let code;
// 不存在type就是第一次请求
if (!ctx.query.type) {
code = descriptor.script.content;
// 这里的code格式是, 需要改造成我们前面贴出来的vite中的样子
// import Hello from './components/Hello.vue'
// export default {
// name: 'App',
// components: {
// Hello
// }
// }
// 改造code的格式,将export default 替换为const __script =
code = code.relace(/export\s+default\s+/g, 'const __script = ')
code += `
import { render as __render } from '${ctx.path}?type=template'
__script.rener = __render
export default __script
`
} else if (ctx.query.type === 'template') {
// 获取编译后的对象 code就是render函数
const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })
// 将render函数赋值给code返回给浏览器
code = templateRender.code
}
// 设置浏览器响应头为js
ctx.type = 'application/javascript'
// 将字符串转换成数据流传给下一个中间件。
ctx.body = stringToStream(code);
}
await next()
})
// 修改第三方模块路径
app.use(async (ctx, next) => {
if (ctx.type === 'application/javascript') {
const contents = await stream2string(ctx.body);
// 将body中导入的路径修改一下,重新赋值给body返回给浏览器
// import vue from 'vue', 匹配到from '修改为from '@modules/
ctx.body = contents.replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/').replace(/process\.env\.NODE_ENV/g, '"development"');
}
})
app.listen(5000)
console.log('服务器已经启动 http://localhost:5000')