什么是 vue-loader?

vue-loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件的格式撰写 Vue 组件。

如何使用?

1. 安装

npm install vue-loader vue-template-compiler --save-dev

2. 配置 webapck

// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin') 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'
        ]
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件来施展魔法
    new VueLoaderPlugin()
  ]
}

3. 创建一个 .vue 组件

一个标准的 .vue 组件可以分为三部分:

  • template: 模板

  • script: 脚本

  • stype: 样式

<template>
  <div id="app">
    <div class="title">{{msg}}</div>
  </div>
</template> <script>
export default {
  name: 'app',
  data() {
    return {
      msg: 'Hello world',
    };
  },
}
</script> <style lang="scss">
#app {
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.title {
  color: red;
}
</style>

4. 见证奇迹的时刻

打包完之后,这个 Vue 组件就会被解析到页面上:

<head>
  <style type="text/css">
    #app {
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    .title {
      color: red;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="title">Hello world</div>
  </div>
  <script type="text/javascript" src="/app.js"></script>
</body>

上面 Vue 组件里的 <template> 部分解析到 <body> 下,css 部分解析成<style> 标签,<script> 部分则解析到 js 文件里。

简单来说 vue-loader 的工作就是处理 Vue 组件,正确地解析各个部分。

vue-loader 的源码较长,我们分几个部分来解析。

源码解析之主要流程

我们先从入口看起,从上往下看:

module.exports = function (source) {}

vue-loader 接收一个 source 字符串,值是 vue 文件的内容。

const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)

loaderUtils.stringifyRequest 作用是将绝对路径转换成相对路径。

接下来有一大串的声明语句,我们暂且先不看,我们先看最简单的情况。

const { parse } = require('@vue/component-compiler-utils')

const descriptor = parse({
  source,
  compiler: options.compiler || loadTemplateCompiler(loaderContext),
  filename,
  sourceRoot,
  needMap: sourceMap
})

parse 方法是来自于 component-compiler-utils,代码简略一下是这样:

// component-compiler-utils parse
function parse(options) {
  const { source, filename = '', compiler, compilerParseOptions = { pad: 'line' }, sourceRoot = '', needMap = true } = options;
  // ...
  output = compiler.parseComponent(source, compilerParseOptions);
  // ...
  return output;
}

可以看到,这里还不是真正 parse 的地方,实际上是调用了compiler.parseComponent 方法,默认情况下 compiler 指的是 vue-template-compiler

// vue-template-compiler parseComponent
function parseComponent (
  content,
  options
) {
  var sfc = {
    template: null,
    script: null,
    styles: [],
    customBlocks: [],
    errors: []
  };
  // ...
  function start() {}
  function end() {}
  parseHTML(content, {
    warn: warn,
    start: start,
    end: end,
    outputSourceRange: options.outputSourceRange
  });
  return sfc;
}

这里可以看到,parseComponent 应该是调用了 parseHTML 方法,并且传入了两个方法:start 和 end,最终返回 sfc

这一块的源码我们不多说,我们可以猜测 start 和 end 这两个方法应该是会根据不同的规则去修改 sfc,我们看一下 sfc 即 vue-loader 中 descriptor 是怎么样的:

// vue-loader descriptor
{
  customBlocks: [],
  errors: [],
  template: {
    attrs: {},
    content: "\n<div id="app">\n  <div class="title">{{msg}}</div>\n</div>\n",
    type: "template"
  },
  script: {
    attrs: {},
    content: "... export default {} ...",
    type: "script"
  },
  style: [{
    attrs: {
      lang: "scss"
    },
    content: "... #app {} ...",
    type: "style",
    lang: "scss"
  }],
}

vue 文件里的内容已经分别解析到对应的 type 去了,接下来是不是只要分别处理各个部分即可。

parseHTML 这个命名是不是有点问题。。。

vue-loader 如何处理不同 type

你们可以先思考五分钟,这里的分别处理是如何处理的?比如,样式内容需要通过style-loader 才能将其放到 DOM 里。

好了,就当作聪明的你已经有思路了。我们继续往下看。

// template
let templateImport = `var render, staticRenderFns`
let templateRequest
if (descriptor.template) {
  const src = descriptor.template.src || resourcePath
  const idQuery = `&id=${id}`
  const scopedQuery = hasScoped ? `&scoped=true` : ``
  const attrsQuery = attrsToQuery(descriptor.template.attrs)
  const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
  const request = templateRequest = stringifyRequest(src + query)
  templateImport = `import { render, staticRenderFns } from ${request}`
} // script
let scriptImport = `var script = {}`
if (descriptor.script) {
  const src = descriptor.script.src || resourcePath
  const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
  const query = `?vue&type=script${attrsQuery}${inheritQuery}`
  const request = stringifyRequest(src + query)
  scriptImport = (
    `import script from ${request}\n` +
    `export * from ${request}` // support named exports
  )
} // styles
let stylesCode = ``
if (descriptor.styles.length) {
  stylesCode = genStylesCode(
    loaderContext,
    descriptor.styles,
    id,
    resourcePath,
    stringifyRequest,
    needsHotReload,
    isServer || isShadow // needs explicit injection?
  )
}

这三段代码的结构很像,最终作用是针对不同的 type 分别构造一个 import 字符串:

templateImport = "import { render, staticRenderFns } from './App.vue?vue&type=template&id=7ba5bd90&'";

scriptImport = "import script from './App.vue?vue&type=script&lang=js&' \n export * from './App.vue?vue&type=script&lang=js&'";

stylesCode = "import style0 from './App.vue?vue&type=style&index=0&lang=scss&'";

这三个 import 语句有啥子用呢, vue-loader 是这样做的:

let code = `
${templateImport}
${scriptImport}
${stylesCode}`.trim() + `\n`
code += `\nexport default component.exports`
return code

此时, code 是这样的:

code = "
import { render, staticRenderFns } from './App.vue?vue&type=template&id=7ba5bd90&'
import script from './App.vue?vue&type=script&lang=js&'
export * from './App.vue?vue&type=script&lang=js&'
import style0 from './App.vue?vue&type=style&index=0&lang=scss&' // 省略 ...
export default component.exports"

我们知道 loader 会导出一个可执行的 node 模块,也就是说上面提到的 code 是会被 webpack 识别到然后执行的。

我们看到 code 里有三次的 importimport 的文件都是 App.vue,相当于又加载了一次触发这次 vue-loader 的那个 vue 文件。不同的是,这次加载是带参的,分别对应着 template / script / style 三种 type 的处理。

你们可以先思考五分钟,这里的分别处理是如何处理的?

这个问题的答案就是,webpack 在加载 vue 文件时,会调用 vue-loader 来处理vue 文件,之后 return 一段可执行的 js 代码,其中会根据不同 type 分别import 一次当前 vue 文件,并且将参数传递进去,这里的多次 import 也会被vue-loader 拦截,然后在 vue-loader 内部根据不同参数进行处理(比如调用style-loader)。

浅谈 vue-loader---合格前端的更多相关文章

  1. 浅谈Vue.js

    作为一名Vue.js的忠实用户,我想有必要写点文章来歌颂这一门美好的语言了,我给它的总体评价是“简单却不失优雅,小巧而不乏大匠”,下面将围绕这句话给大家介绍Vue.js,希望能够激发你对Vue.js的 ...

  2. 浅谈Vue不同场景下组件间的数据交流

    浅谈Vue不同场景下组件间的数据“交流”   Vue的官方文档可以说是很详细了.在我看来,它和react等其他框架文档一样,讲述的方式的更多的是“方法论”,而不是“场景论”,这也就导致了:我们在阅读完 ...

  3. 【Vue】浅谈Vue不同场景下组件间的数据交流

    浅谈Vue不同场景下组件间的数据“交流”   Vue的官方文档可以说是很详细了.在我看来,它和react等其他框架文档一样,讲述的方式的更多的是“方法论”,而不是“场景论”,这也就导致了:我们在阅读完 ...

  4. 浅谈Vue下的components模板

    浅谈Vue下的components模板在我们越来越深入Vue时,我们会发现我们对HTML代码的工程量会越来越少,今天我们来谈谈Vue下的 components模板的 初步使用方法与 应用 我们先来简单 ...

  5. 浅谈Vue响应式(数组变异方法)

    很多初使用Vue的同学会发现,在改变数组的值的时候,值确实是改变了,但是视图却无动于衷,果然是因为数组太高冷了吗? 查看官方文档才发现,不是女神太高冷,而是你没用对方法. 看来想让女神自己动,关键得用 ...

  6. 浅谈Vue中计算属性(computed)和方法(methods)的差别

    浅谈Vue中计算属性(computed)和方法(methods)的差别 源码地址 methods方法和computed计算属性,两种方式的最终结果确实是完全相同 计算属性是基于它们的响应式依赖进行缓存 ...

  7. 【Vue】浅谈Vue(一):从模板语法数据绑定、指令到计算属性

    写在前面 今年前端届比较有意思,从大漠穷秋发表文章比较angular和vue,继而致歉vue作者.社区,从谷歌辞去Angular Developer PM in China一职并且呼吁大家停止各种无谓 ...

  8. 浅谈Vue.js2.0核心思想

    Vue.js是一个提供MVVM数据双向绑定的库,专注于UI层面,核心思想是:数据驱动.组件系统. 数据驱动: Vue.js数据观测原理在技术实现上,利用的是ES5Object.defineProper ...

  9. 浅谈WEB安全性(前端向)

    相信进来的时候你已经看到alert弹窗,显示的是你cookie信息(为配合博客园要求已删除).单纯地在你的客户端弹出信息只是类似于迫使你在自己的房间脱衣服——没人看得到,自然也不算啥恶意行为.那么如果 ...

  10. 浅谈vue性能优化

    基础优化 所谓的基础优化是任何 web 项目都要做的,并且是问题的根源.HTML,CSS,JS 是第一步要优化的点 分别对应到 .vue 文件内的,<template>,<style ...

随机推荐

  1. 迭代器中set的使用

    今天对迭代器中的set方法进行了一下简单的使用,由于之前使用过list方法,所以将他与list进行了一下对比. list中加入对象时不会进行查重,也就是只要是一个符合的对象就可以加到list中,而对于 ...

  2. TypeScript躬行记(6)——高级类型

    本节将对TypeScript中类型的高级特性做详细讲解,包括交叉类型.类型别名.类型保护等. 一.交叉类型 交叉类型(Intersection Type)是将多个类型通过“&”符号合并成一个新 ...

  3. 如何优雅的将Mybatis日志中的Preparing与Parameters转换为可执行SQL

    原文链接 疫情期间大家宅在家里是不是已经快憋出“病”了~~ ​ 公司给开了VPN,手机电脑都能连,手机装上APP测试包,就能干活了,所以walking从2020.02.01入京以来,已经窝在家里11天 ...

  4. sougn开源,自己写的一个项目

    源代码:http://yun.baidu.com/share/link?shareid=1504480803&uk=2739888323 联系方式:1034465036@qq.com

  5. JAVA中如何获取变量的类型

    JAVA中如何获取变量的类型? package xiya; public class Demo { public static void main(String[] args) { String ty ...

  6. 学习Qt的资源-网站、论坛、博客等

    来自<零基础学Qt 4编程>一书的附录 附录C Qt资源 C.1 Qt 官方资源 全球各大公司以及独立开发人员每天都在加入 Qt 的开发社区.他们已经认识到了Qt 的架构本身便可加快应用程 ...

  7. 目标检测之RCNN,fast RCNN,faster RCNN

    RCNN: 候选区生成(Selective Search). 分割成2000左右的候选小区域 合并规则:颜色.纹理相近,尺度均匀,合并后形状规则 特征提取. 归一候选区尺寸为227×227,归一方法. ...

  8. WeChall_Enlightment (Encoding)

    解题: 刚开始一看这题就蒙逼了,完全没思路,过了几天后再仔细去想想,应该是二进制的ascii码,但是原来的三张图虽然都是8的倍数,但完全转换不成有用的东西,题目的意思能否找到光,百度了一下关于三原色的 ...

  9. Codeforces 977D Divide by three, multiply by two(拓扑排序)

      Polycarp likes to play with numbers. He takes some integer number xx, writes it down on the board, ...

  10. EMC NW NMM to restore MS AG database

    Following last article, how to restore MS AG database , that is in the following: You see ?  Cheer u ...