.vue文件的编译
前言本文章我们来分析一下在vue2当中是如何编译.vue文件(本文仅简单的框架的分析具体深入原理可查看参考文献)探究我们先尝试引入.vue文件,然后输出看看输出的内容代码如下:main.js文件import App from './App.vue'console.log(App)输出的结果为App.vue文件<template><div id="app1"><img
前言
本文章我们来分析一下在vue2当中是如何编译.vue文件
(本文仅简单的框架的分析具体深入原理可查看参考文献)
探究
我们先尝试引入.vue文件,然后输出看看输出的内容
代码如下:
main.js文件
import App from './App.vue'
console.log(App)
输出的结果为
App.vue文件
<template>
<div id="app1">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
},
created() {
console.log('App组件的create')
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
你会发现.vue只是暴露的name、components、created属性,但是控制台输出却多出了beforeDestroy、beforeCreate、render、staticRenderFns、__file、_compiled等属性
探其究竟,原来是vue-loader的在作怪。
Vue-loader 是一个 webpack 的 loader,他的作用就是在编译阶段对.vue文件进行解析。
一般地vue-loader都是vue-template-compiler这个loader搭配使用的,这是一个模板编译的loader。
下面我们来看看vue-loader的配置是如何的
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `<script>` 块
{
test: /\.js$/,
loader: 'babel-loader'
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
// 普通的 `.scss` 文件和 `*.vue` 文件中的
// `<style lang="scss">` 块都应用它
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
// 请确保引入这个插件来施展魔法
new VueLoaderPlugin()
]
}
当我们在运行的时候,VueLoaderPlugin插件首先会对webpack中的rules进行重新配置拓展(原本的配置不会改变只是多了另外的一些loader)
所以经过插件拓展后的rules可以如下(注意这并不是上方rules拓展后的结果我只是举个例子):
module.exports = {
module: {
rules: [
{
loader: "/node_modules/vue-loader/lib/loaders/pitcher.js",
resourceQuery: () => {},
options: {},
},
{
resource: () => {},
resourceQuery: () => {},
use: [
{
loader: "/node_modules/mini-css-extract-plugin/dist/loader.js",
},
{ loader: "css-loader" },
],
},
{
resource: () => {},
resourceQuery: () => {},
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: [["@babel/preset-env", { targets: "defaults" }]],
},
ident: "clonedRuleSet-2[0].rules[0].use",
},
],
},
{
test: /.vue$/i,
use: [
{ loader: "vue-loader", options: {}, ident: "vue-loader-options" },
],
},
{
test: /.css$/i,
use: [
{
loader: "/node_modules/mini-css-extract-plugin/dist/loader.js",
},
{ loader: "css-loader" },
],
},
{
test: /.vue$/i,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: [["@babel/preset-env", { targets: "defaults" }]],
},
ident: "clonedRuleSet-2[0].rules[0].use",
},
],
},
],
},
};
在转换拓展之后此时webpack正式开始打包,一旦遇到.vue后缀的文件就会命中webpack中的/.vue$/i规则,此时利用vue-loader对.vue文件进行解析,解析的过程就是对.vue文件的代码进行重新‘书写’,解析的过程是一个复杂的过程,我们只需知道这是一个重写的操作
例如
<template>
<div class="root">hello world</div>
</template>
<script>
export default {
data() {},
mounted() {
console.log("hello world");
},
};
</script>
<style scoped>
.root {
font-size: 12px;
}
</style>
利用vue-loader解析以后的其中一个中间结果为(因为可能还会进一步解析)
import { render, staticRenderFns } from "./index.vue?vue&type=template&id=2964abc9&scoped=true&"
import script from "./index.vue?vue&type=script&lang=js&"
export * from "./index.vue?vue&type=script&lang=js&"
import style0 from "./index.vue?vue&type=style&index=0&id=2964abc9&scoped=true&lang=css&"
/* normalize component */
import normalizer from "!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js"
var component = normalizer(
script,
render,
staticRenderFns,
false,
null,
"2964abc9",
null
)
...
export default component.exports
可以看到在重写以后最终我们在main.js引入的.vue文件暴露的并不是简单的name、components、created这三个属性。
...
<script>
...
export default {
name: 'App',
components: {
HelloWorld
},
created() {
console.log('App组件的create')
},
}
</script>
...
而对于添加的几个引入,其实是引用自己,当webpack进入该文件以后发现又有引用,那么webpack就会沿着这些引用继续打包解析(值得注意的是这些引用匹配到的webpack规则就是上面我们的VueLoaderPlugin插件拓展出来的新rule以及原本久的rules,所以VueLoaderPlugin插件拓展rules的作用就是这里)。
故这些引用的作用是进一步重写,重写以后引用也是会发生变化的
import { render, staticRenderFns } from "./index.vue?vue&type=template&id=2964abc9&scoped=true&"
import script from "./index.vue?vue&type=script&lang=js&"
export * from "./index.vue?vue&type=script&lang=js&"
import style0 from "./index.vue?vue&type=style&index=0&id=2964abc9&scoped=true&lang=css&"
在重写阶段实际上每一个对自己的引用都是一个模块的解析过程
例如
Script:"./index.vue?vue&type=script&lang=js&"
Template: "./index.vue?vue&type=template&id=2964abc9&scoped=true&"
Style: "./index.vue?vue&type=style&index=0&id=2964abc9&scoped=true&lang=css&"
每当webpack遇到特定的引用路径的时候,就会知道此时需要打包的模块是哪个,如果是<template>...</template>
模块那么就在文件中提取出来打包在利用对应的loader解析(例如<style>
模块的代码可用css-loader解析),<script>...</script>
和<style lang="scss">...</style>
同样是这个道理。
值得注意的是最终template 中的内容会通过 vue compiler 转换为 render 函数后合并到 script “模块”中。
scoped style 会经过 vue-loader/style-post-loader 的处理,成为只匹配特定元素的私有样式。
所以经过一系列的重写解析.vue文件里面的<template>...</template>
和<style lang="scss">...</style>
模块会被统一整合成render函数,最后暴露,其他属性也是差不多原理,感兴趣的可以继续深入了解其他文章。
总结
总的来说就是:
vue-loader 会把.vue文件中的内容拆分为 template,script,style 三个“虚拟模块”,然后分别匹配 webpack 配置中对应的 rules,比如 script 模块会匹配所有跟处理 JavaScript 或 TypeScript 相关的 loader。
所以这里其实就是利用了webpack中如下的特性:
- 可以在插件中动态修改webpack的配置信息
- Loader 并不一定都要实实在在的处理文件的内容,也可以是返回一些更具体,更有指向性的新路径,以复用webpack的其他模块
通过上面的特性,vue自定义的对.vue文件进行解析,达到了组件化的开发模式(css和js和html同时写在一个文件上)
参考文献
参考文献1
https://vue-loader.vuejs.org/zh/#vue-loader-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F
参考文献2
https://vue-loader-v14.vuejs.org/zh-cn/
参考文献3
https://zhuanlan.zhihu.com/p/355401219
参考文献4
https://q.shanyue.tech/fe/vue/92.html
参考文献5
https://blog.csdn.net/qq_36380426/article/details/104438390
更多推荐
所有评论(0)