vue源代码自学(-):vue的调用
序言: 生前而已一位撸码仔,平常没事儿在Tint自己同时实现的方法论,那个系列产品也仅指我对该自学的几项归纳,德博瓦桑县他们来参照,假如有元老点拨呵呵那按理说
相对现代的 jQuery 两把毛蕊撸究竟的合作开发商业模式,模块化能协助他们同时实现 快照 和 方法论 的F83E43Se,因此能对每一部份展开原则上的思索。对两个小型的 Vue.js 应用领域,一般来说是由无数个模块组合成:
1、vue工程项目的出口处
git clone https://github.com/vuejs/core
拉取留下来的工程项目内部结构,先大体上看下内部结构
├── packages │ ├── compiler-core # 与平台无关的编译器同时实现的核心函数包│ ├── compiler-dom # 浏览器相关的编译器上层内容│ ├── compiler-sfc # 单文件模块的编译器│ ├── compiler-ssr # 服务端渲染相关的编译器同时实现│ ├── global.d.ts # ts 相关一些声明文件│ ├── reactivity # 响应式核心包│ ├── runtime-core # 与平台无关的渲染器相关的核心包│ ├── runtime-dom # 浏览器相关的渲染器部份│ ├── runtime-test # 渲染器测试相关代码│ ├── server-renderer # 服务端渲染相关的包│ ├── sfc-playground # 单文件模块演练场 │ ├── shared # 工具库相关│ ├── size-check # 检测代码体积相关│ ├── template-explorer # 演示模板编译成渲染函数相关的包│ └── vue # 包含编译时和运行时的发布包
他们大体上看了目录内部结构,但是他们应该从何开始看呢?
我这里以我个人的习惯喜欢看package.json那个文件去找出口处,接留下来我简单说呵呵那个文件
{ “private”: true, “version”: “3.2.45”, “packageManager”: “[email protected]”,“scripts”: { “dev”: “node scripts/dev.js”, “build”: “node scripts/build.js”,“size”: “run-s size-global size-baseline”, …. }}
我大概抄了一些内容过来,他们看到scipts中的调试命令:”dev”: “node scripts/dev.js”,这行命令就是以用node执行 scripts/dev.js这个文件,这里再大概插一嘴,vue源代码使用的rollup打包工具,所以他们简单看下那个文件
build({entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)], outfile, bundle: true, external, sourcemap: true, format: outputFormat,globalName: pkg.buildOptions?.name, platform: format === cjs ? node : browser, plugins:format === cjs || pkg.buildOptions?.enableNonBrowserBranches ? [nodePolyfills.default()] : undefined, define: {__COMMIT__: `”dev”`, __VERSION__: `”${pkg.version}”`, __DEV__: `true`, __TEST__: `false`, __BROWSER__: String(format !== cjs && !pkg.buildOptions?.enableNonBrowserBranches ), __GLOBAL__: String(format === global),__ESM_BUNDLER__: String(format.includes(esm-bundler)), __ESM_BROWSER__: String(format.includes(esm-browser)),__NODE_JS__: String(format === cjs), __SSR__: String(format === cjs || format.includes(esm-bundler)),__COMPAT__: String(target === vue-compat), __FEATURE_SUSPENSE__: `true`,__FEATURE_OPTIONS_API__: `true`, __FEATURE_PROD_DEVTOOLS__: `false` }, watch: { onRebuild(error) {if (!error) console.log(`rebuilt: ${relativeOutfile}`) } }}).then(() => { console.log(`watching: ${relativeOutfile}`)})
不管是webpack还是rollup都有个出口处文件属性叫 entry那个字段。接留下来他们看下那个属性
/**这里target默认值是vue那个文件夹**/entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
所以 大概的目录出口处找到 了packages/vue/src/index.ts,接留下来他们进去看到一句
export * from @vue/runtime-dom
2、调用两个 Vue 3 应用领域
# 安装 vue cli $ yarn global add @vue/cli# 创建 vue3 的基础脚手架 一路回车$ vue create vue3-demo
接留下来,打开工程项目,能看到Vue.js 的出口处文件 main.js 的内容如下:
<template><divclass=“helloWorld”>helloworld</div></template><script>exportdefault {setup() {// … }}</script>
现在他们只需要知道<script> 中的对象内容最终会和编译后的模板内容一起,生成两个 App 对象传入 createApp 函数中:
{render(_ctx, _cache, $props, $setup, $data, $options) { // … },setup() {// … }}
接着回到 main.js 的出口处文件,整个调用的过程只剩下如下部份了:
createApp(App).mount(#app)
接留下来他们来到 runtime-dom文件夹看下那个函数createApp
exportconstcreateApp= (…args) => {console.log(…args);// 返回的app 中有个mount函数 挂载到根目录上constapp=ensureRenderer().createApp(…args)if (__DEV__) {injectNativeTagCheck(app)injectCompilerOptionsCheck(app) }…returnapp}
能看出 上述的app会返回两个mount的挂载函数,他们在自习观察下app是如何生成的,是通过ensureRenderer().createApp(…args)
ensureRenderer().createApp(…args) 那个链式函数执行完成后肯定返回了 mount 函数,ensureRenderer 就是构造了两个带有 createApp 函数的渲染器 renderer 对象 :
// 输出renderer对象 function ensureRenderer() {// 判断假如有renderer就输出 没有就创建renderer return ( renderer ||(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)) )}// renderOptions 包含以下函数:const renderOptions = { createElement, createText, setText,setElementText, patchProp, insert, remove,}
再来看呵呵 createRenderer 返回的对象:
// packages/runtime-core/src/renderer.tsexportfunctioncreateRenderer<HostNode=RendererNode,HostElement=RendererElement>(options: RendererOptions<HostNode, HostElement>) {returnbaseCreateRenderer<HostNode, HostElement>(options)}exportfunctionbaseCreateRenderer(options) {// …// 源代码里面这里包含了很多函数 比如patch 、 render等等后面 介绍return {render,hydrate,createApp: createAppAPI(render, hydrate), }}
由此可见这里就是ensureRenderer() 返回两个renderer的对象
{render,hydrate,createApp: createAppAPI(render, hydrate), }
其中就包含了createApp的函数,接留下来看下createAppAPI
export function createAppAPI<HostElement>( render: RootRenderFunction<HostElement>, hydrate?: RootHydrateFunction): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) {if (!isFunction(rootComponent)) { rootComponent = { …rootComponent } } if (rootProps != null && !isObject(rootProps)) {__DEV__ && warn(`root props passed to app.mount() must be an object.`) rootProps = null } // 创建调用上下文const context = createAppContext() const installedPlugins = new Set() let isMounted = falseconst app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _props: rootProps,_container: null, _context: context, _instance: null, version, get config() { return context.config },set config(v) { if (__DEV__) { warn( `app.config cannot be replaced. Modify individual options instead.` ) } },use(plugin: Plugin, …options: any[]) { … }, mixin(mixin: ComponentOptions) { … },component(name: string, component?: Component): any { … }, directive(name: string, directive?: Directive) { … }, mount(rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean ): any { console.log(rootContainer);if (!isMounted) { // #5571 if (__DEV__ && (rootContainer as any).__vue_app__) { warn(`There is already an app instance mounted on the host container.\n` +` If you want to mount another app on the same host container,` + ` you need to unmount the previous app by calling \`app.unmount()\` first.` ) } // 创建虚拟domconst vnode = createVNode( rootComponent as ConcreteComponent, rootProps )// store app context on the root VNode. // this will be set on the root instance on initial mount. vnode.appContext = context// HMR root reload if (__DEV__) { context.reload = () => { render(cloneVNode(vnode), rootContainer, isSVG) } }if (isHydrate && hydrate) { hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else {render(vnode, rootContainer, isSVG) } isMounted = true app._container = rootContainer // for devtools and telemetry;(rootContainer as any).__vue_app__ = app if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {app._instance = vnode.component devtoolsInitApp(app, version) } console.log( vnode);return getExposeProxy(vnode.component!) || vnode.component!.proxy } else if (__DEV__) { warn( `App has already been mounted.\n` +`If you want to remount the same app, move your app creation logic ` +`into a factory function and create fresh app instances for each ` + `mount – e.g. \`const createMyApp = () => createApp(App)\“ ) } }, unmount() {… }, provide(key, value) { … return app } }) if (__COMPAT__) {installAppCompatProperties(app, context, render) } return app }}
那个函数是两个高阶函数,因此返回的是两个函数,那个函数的入参第两个值rootComponent就是他们上面<App /> 模块作为根模块 ,返回了两个包含 mount方法的app 对象。
mount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean): any { console.log(rootContainer); if (!isMounted) { // 创建虚拟dom const vnode = createVNode(rootComponent as ConcreteComponent, rootProps ) vnode.appContext = context if (isHydrate && hydrate) {hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else { render(vnode, rootContainer, isSVG) } isMounted = trueapp._container = rootContainer // for devtools and telemetry ;(rootContainer as any).__vue_app__ = appif (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { app._instance = vnode.componentdevtoolsInitApp(app, version) } console.log( vnode); return getExposeProxy(vnode.component!) || vnode.component!.proxy} else if (__DEV__) { warn( `App has already been mounted.\n` +`If you want to remount the same app, move your app creation logic ` + `into a factory function and create fresh app instances for each ` +`mount – e.g. \`const createMyApp = () => createApp(App)\“ ) } },
它是先判断是否展开挂载过 isMounted,然后创建虚拟dom,再然后将context挂在到虚拟dom的appContext,具体这里面有什么内容因此是怎么生成出来的,他们能打印出来调试,最后通过render函数将其展开渲染