Эта статья - часть серии Читая исходный код Vue.
В этой части мы:
- Раздобудем код
- Откроем редактор кода
- Найдём точку входа для библиотеки
Вы можете читать исходный код прямо на GitHub, но это медленно, к тому же неудобно смотреть на структуру папок (На самом деле нет - Octotree). Так что давайте для начала скачаем исходники.
Открываем страницу Vue на GitHub, нажимаем на кнопку "Clone or download" и выбираем "Download ZIP".
ВЫ так же можете использовать git clone [email protected]:vuejs/vue.git
если вам нравится пользоваться консолью.
Я использовал последнюю версию кода, которую смог раздобыть. Так что могут быть некоторые различия с кодом, который вы скачаете.
Не переживаете - цель этой серии заметок рассказать вам КАК понимать исходный код. Вы можете использовать тот же подход если исходники отличаются.
Так же вы можете скачать версию, которую использовал я, если хотите.
Я предпочитаю пользоваться Sublime Text, вы можете использовать свой любимый редактор.
Открывайте ваш редактор, два раза кликайте на zip-файл, который вы скачала, и перетаскивайте папку с исходниками в ваш редактор.
И первый вопрос, который перед нами встаёт: откуда начинать?
Это общий вопросы для больших проектов с открытым кодом. Vue - это npm пакет, так что мы можем для начала открыть package.json
.
{
"name": "vue",
"version": "2.3.3",
"description": "Reactive, component-oriented view layer for modern web interfaces.",
"main": "dist/vue.runtime.common.js",
"module": "dist/vue.runtime.esm.js",
"unpkg": "dist/vue.js",
"typings": "types/index.d.ts",
"files": [
"src",
"dist/*.js",
"types/*.d.ts"
],
"scripts": {
"dev": "rollup -w -c build/config.js --environment TARGET:web-full-dev",
"dev:cjs": "rollup -w -c build/config.js --environment TARGET:web-runtime-cjs",
"dev:esm": "rollup -w -c build/config.js --environment TARGET:web-runtime-esm",
"dev:test": "karma start build/karma.dev.config.js",
"dev:ssr": "rollup -w -c build/config.js --environment TARGET:web-server-renderer",
...
Первые три ключа - name
, version
и description
не нуждаются в объяснении. Это имя, версия и описание пакета.
В секциях main
, module
, unpkg
находится dist/
, что намекат на то, что секции содержат сгенерированные файлы. Мы ищем с чего начать, так что просто игнорируем их.
Дальше идёт секция typings
. Немного погуглив мы понимаем, что это файлы объявлений для Typescript. Мы можем увидеть много объявлений типов в types/index.d.ts
.
Продолжаем.
files
содержит три части. Первая - это src
, директория с исходным кодом. Это сужает круг поисков, но мы все ещё не знаем с какого файла начинать.
Следующий ключ - scripts
. О, скрипт с названием dev
. Это команда должна знать откуда начинать.
rollup -w -c build/config.js --environment TARGET:web-full-dev
И так у нас есть build/config.js
и TARGET:web-full-dev
. Открываем build/config.js
и ищем web-full-dev
:
// Runtime+compiler development build (Browser)
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
Прекрасно!
Это конфиг для дев-сборки. Его входная точка это web/entry-runtime-with-compiler.js
. Постойте, а где папка web/
?
Проверим значение entry
ещё раз, Там есть resolve
. Ищем resolve
:
const aliases = require('./alias');
const resolve = p => {
const base = p.split('/')[0];
if (aliases[base]) {
return path.resolve(aliases[base], p.slice(base.length + 1));
} else {
return path.resolve(__dirname, '../', p);
}
};
Тогда в строке resolve('web/entry-runtime-with-compiler.js')
мы имеем следующее:
p
этоweb/entry-runtime-with-compiler.js
base
этоweb
- преобразует в
alias['web']
Перейдём к alias
чтобы найти alias['web']
.
module.exports = {
vue: path.resolve(
__dirname,
'../src/platforms/web/entry-runtime-with-compiler',
),
compiler: path.resolve(__dirname, '../src/compiler'),
core: path.resolve(__dirname, '../src/core'),
shared: path.resolve(__dirname, '../src/shared'),
web: path.resolve(__dirname, '../src/platforms/web'),
weex: path.resolve(__dirname, '../src/platforms/weex'),
server: path.resolve(__dirname, '../src/server'),
entries: path.resolve(__dirname, '../src/entries'),
sfc: path.resolve(__dirname, '../src/sfc'),
};
Супер, это src/platforms/web
. Склеив с именем входного файла, мы получим src/platforms/web/entry-runtime-with-compiler.js
.
/* @flow */
import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'
import Vue from './runtime/index'
import { query } from './util/index'
import { shouldDecodeNewlines } from './util/compat'
import { compileToFunctions } from './compiler/index'
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
...
Супер! Вы нашли точку входа!
Вы заметили комментарий
/* flow */
в верху файла? Погуглив, мы узнаем, что это инструмент проверки типов. Это напомнило мне оtypings
, поискав снова выяснилось, чтоflow
может использовать объявленияtypings
для проверки вашего кода.
Возможно, в следующий раз я смогу использовать
flow
иtypings
в моем следующем проекте. Видите, мы ещё даже не начали читать исходники, а уже узнали что-то полезное.
Пройдёмся по файлу шаг за шагом:
- импортировать конфиг
- импортировать несколько вспомогательных функций
- импортировать Vue(Что? Ещё один Vue?)
- определить
idToTemplate
- определить
getOuterHTML
- определить
Vue.prototype.$mount
, который используетidTotemplate
иgetOuterHTML
- определить
Vue.compile
Два важных момента:
- Это ещё НЕ исходный код Vue, мы это должны понять из имени файла - это только точка входа.
- Этот файл извлекает функцию
$mount
и определяет новый$mount
. Прочитав новое определение, мы поймём что оно просто добавляет пару проверок перед вызовом настоящегоmount
.
Теперь вы знаеме, как найти точку входа в новом проекте. В следующей части мы продолжим искать основной код Vue.
Читать следующую часть: Глубже в ядро.
Помните те "вспомогательные функции"? Прочитайте их исходный код и скажите, что они делают. Это не сложно, но нужно быть внимательным.
Не пропустите shouldDecodeNewlines
, там можно увидеть как они борются с IE.