前言

本文章我们来分析一下在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中如下的特性:

  1. 可以在插件中动态修改webpack的配置信息
  2. 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

Logo

前往低代码交流专区

更多推荐