当import vue文件时,发生了什么

一、背景

​ vue工程中,父组件想要使用子组件就需要通过import的方式引入子组件并在components选项中进行注册,import xxx from 'xxxx.vue'是vue工程中最常用的语法之一了,但是可能很少有人想过,在import vue文件时,背后到底是如何处理的。

​ 今天我们就来瞧一瞧,import vue文件时背后的处理历程。

二、webpack 的loader机制

​ 我们知道原生import是无法import vue文件的,那么为什么我们在代码中可以使用呢?

​ 这是因为webpack的loader机制,通过配置文件类型对应的loader,可以使用指定的loader对文件先进行一次处理,再交给原生的import语法去处理。那么我们的vue文件对应的loader就是vue-loader啦。

​ 我们可以通过webpack的配置来确认这一点:

// 项目中的webpack配置

module: {

rules: [

// 匹配到vue文件,就使用vue-loader处理

{

test: /.vue$/,

loader: 'vue-loader',

options: vueLoaderConfig

}]

}

三、vue-loader处理vue文件

​ 前面已经知道,vue文件在import时会经过vue-loader处理,那么vue-loader是如何处理的呢?

​ 这里,我们首先看一下vue-loader处理完的结果是怎么样的。

​ 为了看到vue-loader的处理结果,需要我们修改node_modules中vue-loader的代码,将处理结果打印出来:

// vue-loader-13.7.3版本

// vue-loaderlibloader.js

// line 447,添加console.log,打印输出

console.log('vue-loader output =>',output)

return output

​ 修改完成之后,需要重新启动工程,然后在vscode 的控制台就可以看到打印的结果了

// vue-loader处理import App from 'App.vue'的结果

functioninjectStyle (ssrContext) {

if (disposed) return

require("!!vue-style-loader!css-loader?{"sourceMap":true}!../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-7ba5bd90","scoped":false,"hasInlineConfig":false}!../node_modules/vue-loader/lib/selector?type=styles&index=0!./App.vue")

}

var normalizeComponent = require("!../node_modules/vue-loader/lib/component-normalizer")

/* script */

export * from"!!babel-loader!../node_modules/vue-loader/lib/selector?type=script&index=0!./App.vue"

import __vue_script__ from"!!babel-loader!../node_modules/vue-loader/lib/selector?type=script&index=0!./App.vue"

/* template */

import __vue_template__ from"!!../node_modules/vue-loader/lib/template-compiler/index?{"id":"data-v-7ba5bd90","hasScoped":false,"transformToRequire":{"video":["src","poster"],"source":"src","img":"src","image":"xlink:href"},"buble":{"transforms":{}}}!../node_modules/vue-loader/lib/selector?type=template&index=0!./App.vue"

/* template functional */

var __vue_template_functional__ = false

/* styles */

var __vue_styles__ = injectStyle

/* scopeId */

var __vue_scopeId__ = null

/* moduleIdentifier (server only) */

var __vue_module_identifier__ = null

var Component = normalizeComponent(

__vue_script__,

__vue_template__,

__vue_template_functional__,

__vue_styles__,

__vue_scopeId__,

__vue_module_identifier__

)

Component.options.__file = "src/App.vue"

exportdefault Component.exports

​ 可以看到vue-loader的输出是一段js代码,这段js代码会被原生import所执行。

​ 通过浏览这段js代码,大致可以看出:首先按照vue文件的三个部分分别进行处理,即template部分、script部分和style部分,然后使用component-normalizer.js中的normalizeComponent方法进行汇总处理。

​ 接下来我们先来看vue文件的三个部分是如何处理的。

3.1 template部分

​ 先看template部分的处理代码

// vue-loader输出结果中处理template部分的代码

import __vue_template__ from"!!../node_modules/vue-loader/lib/template-compiler/index?{"id":"data-v-7ba5bd90","hasScoped":false,"transformToRequire":{"video":["src","poster"],"source":"src","img":"src","image":"xlink:href"},"buble":{"transforms":{}}}!../node_modules/vue-loader/lib/selector?type=template&index=0!./App.vue"

​ 这行代码看起来有点长,可以拆分成两段解析:

// 第一段

!!../node_modules/vue-loader/lib/template-compiler/index?{"id":"data-v-7ba5bd90","hasScoped":false,"transformToRequire":{"video":["src","poster"],"source":"src","img":"src","image":"xlink:href"},"buble":{"transforms":{}}}

//第二段

!../node_modules/vue-loader/lib/selector?type=template&index=0!./App.vue

​ 第二段代码是使用vue-loader/lib/selector.js来处理App.vue文件,那么这个selector.js是干啥的呢,看一下vue-loader/lib/selector.js文件中的注释

// vue-loader/lib/selector.js 注释

// this is a utility loader that takes a *.vue file, parses it and returns

// the requested language block, e.g. the content inside <template>, for

// further processing.

​ selector.js中的注释说明了,这个方法是用于从vue文件中读取对应标签中的内容,因此第二段代码的处理结果就是读取App.vue中template标签中的代码。

​ 再看第一段代码,虽然看起来还是有点长,但是可以看出来是调用vue-loader/lib/template-compiler/index.js来处理,剩下的部分是处理的参数。

​ 那么vue-loader/lib/template-compiler/index.js是做什么的呢,实际上就是进行模板编译的,通过引入vue-template-compiler这个包进行编译,而vue-template-compiler就是从vue源码生成的,所以vue-loader中的模板编译和vue源码中的模板编译是一致的。经过模板编译后,输出的结果就是渲染函数。可以通过打印结果的方式来确认:

// vue-loader 处理App.vue的template部分输出结果

var render = function() {

var _vm = this

var _h = _vm.$createElement

var _c = _vm._self._c || _h

return _c("div", { attrs: { id: "app" } }, [_c("HelloWorld")], 1)

}

var staticRenderFns = []

render._withStripped = true

var esExports = { render: render, staticRenderFns: staticRenderFns }

exportdefault esExports

if (module.hot) {

module.hot.accept()

if (module.hot.data) {

require("vue-hot-reload-api") .rerender("data-v-7ba5bd90", esExports)

}

}

3.2:script部分

​ 接下来看一下script部分的处理:

// vue-loader输出结果中处理script部分的代码

import __vue_script__ from "!!babel-loader!../node_modules/vue-loader/lib/selector?type=script&index=0!./App.vue

​ 这行代码看起来比较短,但是也是有2段处理的

//第一段

!!babel-loader

//第二段

!../node_modules/vue-loader/lib/selector?type=script&index=0!./App.vue

​ 第二段与template中的处理一致,只是这里取的是script部分的代码

​ 第一段是用babel-loader对取到的script部分代码进行处理

​ script部分的处理很清晰,就是用babel处理vue文件中script标签内的代码,可以在component-normalizer.js中打断点查看结果

3.3:style部分

​ 接着看style部分的处理

// vue-loader输出结果中处理style部分的代码

functioninjectStyle (ssrContext) {

if (disposed) return

require("!!vue-style-loader!css-loader?{"sourceMap":true}!../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-7ba5bd90","scoped":false,"hasInlineConfig":false}!../node_modules/vue-loader/lib/selector?type=styles&index=0!./App.vue")

}

​ 与template和script不同的是,style部分是定义了一个处理函数injectStyle,在此函数中进行style部分处理,函数的调用时机后面再分析

​ 这里先看一下函数中是如何处理style标签内的代码的,分成三段处理

//第一段

!!vue-style-loader

//第二段

!css-loader?{"sourceMap":true}!../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-7ba5bd90","scoped":false,"hasInlineConfig":false}

//第三段

!../node_modules/vue-loader/lib/selector?type=styles&index=0!./App.vue"

​ 第三段与template中的处理一致,只是这里取的是style部分的代码

​ 第二段是用css-loader对取到的style部分代码进行处理

​ 第一段是用vue-style-loader对css-loader处理过的代码再进行处理

3.4:normalizeComponent函数处理

​ 分析了vue-loader对vue文件中三个部分的处理后,normalizeComponent相当于是对三部分的处理做综合性的处理。

// vue-loader源码 libcomponent-normalizer.js

module.exports = functionnormalizeComponent (

rawScriptExports,

compiledTemplate,

functionalTemplate,

injectStyles,

scopeId,

moduleIdentifier /* server only */

) {

var esModule

var scriptExports = rawScriptExports = rawScriptExports || {}

// ES6 modules interop

var type = typeof rawScriptExports.default

if (type === 'object' || type === 'function') {

esModule = rawScriptExports

scriptExports = rawScriptExports.default

}

// Vue.extend constructor export interop

var options = typeof scriptExports === 'function'

? scriptExports.options

: scriptExports

// render functions

if (compiledTemplate) {

options.render = compiledTemplate.render

options.staticRenderFns = compiledTemplate.staticRenderFns

options._compiled = true

}

// functional template

if (functionalTemplate) {

options.functional = true

}

// scopedId

if (scopeId) {

options._scopeId = scopeId

}

var hook

if (moduleIdentifier) { // server build

hook = function (context) {

// 2.3 injection

context =

context || // cached call

(this.$vnode && this.$vnode.ssrContext) || // stateful

(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional

// 2.2 with runInNewContext: true

if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {

context = __VUE_SSR_CONTEXT__

}

// inject component styles

if (injectStyles) {

injectStyles.call(this, context)

}

// register component module identifier for async chunk inferrence

if (context && context._registeredComponents) {

context._registeredComponents.add(moduleIdentifier)

}

}

// used by ssr in case component is cached and beforeCreate

// never gets called

options._ssrRegister = hook

} elseif (injectStyles) {

hook = injectStyles

}

if (hook) {

var functional = options.functional

var existing = functional

? options.render

: options.beforeCreate

if (!functional) {

// inject component registration as beforeCreate hook

options.beforeCreate = existing

? [].concat(existing, hook)

: [hook]

} else {

// for template-only hot-reload because in that case the render fn doesn't

// go through the normalizer

options._injectStyles = hook

// register for functioal component in vue file

options.render = functionrenderWithStyleInjection (h, context) {

hook.call(context)

return existing(h, context)

}

}

}

return {

esModule: esModule,

exports: scriptExports,

options: options

}

}

​ 看一下经过normalizeComponent的处理后的App.vue的exports

​ 这个就是import vue文件时最终导入的对象,相比于前面看到的script部分的输出:

  1. 多了render和staticRenderFns属性,这个是从template部分的编译结果中取得的;
  2. 还多了一个beforeCreate钩子属性,其值是一个数组,包含了injectStyle函数,当组件实例化时,触发beforeCreate钩子,injectStyle被执行,组件样式进而生效。

四、总结

​ 当我们import vue文件时,会触发webpack的loader机制,使用vue-loader对vue文件进行处理,vue-loader将vue按照标签分成分template、script、style三部处理:template部分引入vue-template-compiler进行模板编译,输出渲染函数;script部分通过babel-loader进行编译,生成浏览器可直接运行的代码;style部分没有直接处理,而是定义了一个处理函数,处理函数中先用css-loader处理,再用vue-style-loader进行处理;最后再使用normalizeComponent方法对三部分进行综合性处理,输出最终的结果。

以上是 当import vue文件时,发生了什么 的全部内容, 来源链接: utcz.com/a/30029.html

回到顶部