当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.jsmodule.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部分的输出:
- 多了render和staticRenderFns属性,这个是从template部分的编译结果中取得的;
- 还多了一个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