Автор: Глубокий горный муравей
Исходный код Vue3 был выпущен так внезапно во время праздника Национального дня, праздник не закончился, и разработчики кода, которые усердно работали, проводили различные анализы.
В настоящее время это все еще предварительная альфа-версия, и с оптимизмом можно предположить, что существуют альфа-, бета-версии и, наконец, официальная версия.
Не говори много, смотриPre-Alpha. вуаляcompiler-core
Популярная реактивность снова и снова изучалась большими парнями, поэтому я буду интерпретировать «непопулярный» компилятор со всеми! 😄😄😄😄
Если вы не знакомы с AST или как реализовать простой парсер AST, можете ткнуть:Научить вас писать парсер AST
Разбор шаблона в vue3.0 сильно отличается от парсинга в vue2.0, но как бы он ни менялся, основной принцип тот же.Различные html-коды, которые мы пишем, на самом деле представляют собой строку, когда используется js.Данные, преобразованные в структурированный AST, мы используем мощные регулярные выражения и indexOf для оценки.
Одной из основных функций ядра компилятора является преобразование строк в синтаксические деревья абстрактных объектов AST.
Let's do IT !
Структура каталогов
- _tests_прецедент
- Определения типов важных элементов синтаксиса src/ast ts, таких как тип, перечисление, интерфейс и т. д.
- src/codegen преобразует сгенерированный ast в строку рендеринга
- src/errors определяет типы ошибок компилятора
- Файл записи src/index в основном имеет baseCompile, который используется для компиляции файлов шаблонов.
- src/parse преобразует строки шаблона в AST
- src/runtimeHelper определяет соответствующую связь констант при генерации кода
- src/transform обрабатывает специфичный для vue синтаксис в AST, например v-if , v-on parsing
При входе в каталог ядра компилятора структура ясна с первого взгляда. Скажи это здесь _tests_Каталог — это тестовый пример для vue.
Прежде чем читать исходный код, посмотрите на вариант использования, который очень полезен для чтения исходного кода.
Как следует, протестируйте простой текст, после выполнения метода синтаксического анализа получите ast, ожидая, что первый узел ast будет соответствовать определенному объекту.
Как и в случае с другими тестовыми примерами модуля, вы можете взглянуть перед чтением исходного кода, чтобы узнать, как используется этот модуль и каковы входные и выходные данные.
test('simple text', () => {
const ast = parse('some text')
const text = ast.children[0] as TextNode
expect(text).toStrictEqual({
type: NodeTypes.TEXT,
content: 'some text',
isEmpty: false,
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
source: 'some text'
}
})
})
Сначала посмотрите на картинку, основное внимание уделяется четырем частям:
- начальный тег
- конечный тег
- динамический контент
- нормальный контент
Начальный тег будет использовать рекурсию для обработки дочерних узлов.
Далее, давайте начнем читать вместе с исходным кодом~~~~~~
parse: преобразовать строковый шаблон в абстрактное синтаксическое дерево AST
Это основной метод внешнего воздействия. Давайте сначала проверим результаты:
const source = `
<div id="test" :class="cls">
<span>{{ name }}</span>
<MyCom></MyCom>
</div>
`.trim()
import { parse } from './compiler-core.cjs'
const result = parse(source)
output:
Представлен простой результат преобразования.С точки зрения сгенерированной структуры есть несколько важных изменений по сравнению с vue2.x:
- Добавлено свойство местоположения
Каждый узел записывает начало и конец узла в исходном коде и определяет подробное расположение кода, столбца, строки, смещения.
vu3.0 также основана на подробном выводе журнала для проблем, возникших в процессе разработки, и поддерживает исходную карту. - Добавлено свойство tagType
Атрибут tagType определяет тип узла. Мы знаем, что vue2.x оценивает тип узла только во время выполнения, а vu3.0 продвигает оценку на этапе компиляции, что повышает производительность.
В настоящее время существует три типа tagType: 0 элемент, 1 компонент, 2 слота, 3 шаблона. - Добавлено Isstatic Property
Заранее скомпилируйте шаблон и определите, является ли он динамическим, например динамические инструкции. - ...
Новая версия AST явно сложнее, чем vue2.x.Видно, что vue3.0 определит много вещей, которые можно определить на этапе компиляции, и идентифицирует результаты компиляции.Не нужно ждать, пока время выполнения, экономя память и производительность. Это также ключевой момент, о котором вы сказали, оптимизация компиляции и повышение производительности.
Далее давайте рассмотрим код преобразования, в основном, следующими способами:
- основная запись parse & parseChildren
- parseTag обрабатывает теги
- parseAttribute обрабатывает атрибуты тегов
- parseElement обрабатывает начальные теги
- parseInterpolation обрабатывает динамический текстовый контент
- parseText обрабатывает статический текстовый контент
основная запись parse & parseChildren
Здесь создается основная запись parse, parseContext, что удобно для получения содержимого, параметров и т. д. непосредственно из контекста в будущем.
getCursor Получает текущую обрабатываемую позицию указателя, пользователь генерирует loc, и начальное значение равно 1.
export function parse(content: string, options: ParserOptions = {}): RootNode {
const context = createParserContext(content, options)
const start = getCursor(context)
return {
type: NodeTypes.ROOT,
children: parseChildren(context, TextModes.DATA, []),
helpers: [],
components: [],
directives: [],
hoists: [],
codegenNode: undefined,
loc: getSelection(context, start)
}
}
Сосредоточьтесь на parseChildren, который является основным методом входа для преобразования.
function parseChildren(
context: ParserContext,
mode: TextModes,
ancestors: ElementNode[]
): TemplateChildNode[] {
const parent = last(ancestors)
const ns = parent ? parent.ns : Namespaces.HTML
const nodes: TemplateChildNode[] = []
while (!isEnd(context, mode, ancestors)) {
const s = context.source
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
if (startsWith(s, context.options.delimiters[0])) {
// '{{'
node = parseInterpolation(context, mode)
} else if (mode === TextModes.DATA && s[0] === '<') {
// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
if (s.length === 1) {
emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
} else if (s[1] === '!') {
// <!DOCTYPE <![CDATA[ 等非节点元素 暂不讨论
} else if (s[1] === '/') {
if (s.length === 2) {
} else if (s[2] === '>') {
advanceBy(context, 3)
continue
} else if (/[a-z]/i.test(s[2])) {
parseTag(context, TagType.End, parent)
continue
} else {
}
} else if (/[a-z]/i.test(s[1])) {
node = parseElement(context, ancestors)
} else if (s[1] === '?') {
} else {
}
}
if (!node) {
node = parseText(context, mode)
}
if (Array.isArray(node)) {
for (let i = 0; i < node.length; i++) {
pushNode(context, nodes, node[i])
}
} else {
pushNode(context, nodes, node)
}
}
return nodes
}
предки используются для хранения несопоставленных начальных узлов и представляют собой стек LIFO.
Исходник обрабатывается в цикле.Условием отсечки цикла является то, что метод isEnd возвращает true, то есть обработка завершена.Условий окончания два:
- context.source пустой, то есть обрабатывается весь шаблон
- Обнаружен тег конечного узла (), и соответствующий тег можно найти среди несовпадающих начальных тегов (предков). Обрабатывается дочерний узел, соответствующий parseChildren.
Если матч еще не закончился, введите повторный матч. Есть три случая:
- if (startsWith(s, context.options.delimiters[0]))
разделители — это разделенные совпадения, vue — это {{ и }}. Начните сопоставлять содержимое текстового вывода vue {{ , это означает, что вам нужно обработать вставку текстового содержимого, - else if (mode === TextModes.DATA && s[0] === '<')
Если содержимое уже имеет - Ни одно из вышеперечисленных условий, или матч был неудачным
Тогда это динамический текстовый контент.
Если это третья динамическая вставка текста, выполните parseInterpolation для сборки текстового узла, где флаг isStatic=false является относительно простой переменной, и метод не будет опубликован.
return {
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
content,
loc: getSelection(context, innerStart, innerEnd)
},
loc: getSelection(context, start)
}
Давайте посмотрим на эти два метода обработки исходного контента в обратном порядке:
advanceBy(context,number) : переместите исходный код шаблона для обработки числовыми символами и перезапишите местоположение.
advanceSpaces() : продвиньте существующие последовательные пробелы
Возвращаясь к приведенному выше условию соответствия, если оно начинается с
- Второй символ "/"
Соответствующее
Если это > , то он считается недопустимым тегом, просто переместите его на 3 символа назад.
Если это , то он считается обрезающим тегом и выполняется метод parseTag. - второй символ это буква
Соответствующий начальный текст тега, например
parseTag обрабатывает теги
Если это обрезной тег: parseTag, обработка завершается напрямую.
Если это начальный тег: выполняется parseElement, вызывается parseTag для обработки тега, а затем рекурсивно обрабатывает дочерние узлы и т. д.
Обычный: /^?([a-z][^\t\r\n\f />]*)/i Об этом особо нечего сказать, он соответствует тегам типа