什么是 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. [校内训练20_01_22]ABC

    1.给出序列A,求序列B,使得bi|ai,lcm(b1,b2,...,bn)=lcm(a1,a2,...,an)且字典序最小. 可以发现,对于某个质数p,它有一个最大的次数k,将pk放在尽可能靠后且能 ...

  2. AcWing 787.归并排序

    AcWing 787.归并排序 题目描述 给定你一个长度为n的整数数列. 请你使用归并排序对这个数列按照从小到大进行排序. 并将排好序的数列按顺序输出. 输入格式 输入共两行,第一行包含整数 n. 第 ...

  3. 配置微软Azure大数据HDInsight云集群

    配置微软Azure大数据HDInsight云集群,存储账户.托管标识等问题也都参考官方文档解决了. 原文在我的开源中国博客 https://my.oschina.net/finchxu/blog/31 ...

  4. Ceph 存储集群-低级运维

    低级集群运维包括启动.停止.重启集群内的某个具体守护进程:更改某守护进程或子系统配置:增加或拆除守护进程.低级运维还经常遇到扩展.缩减 Ceph 集群,以及更换老旧.或损坏的硬件. 一.增加/删除 O ...

  5. 用goaccess实现可视化并实时监控access日志

    goaccess access.log -o ../html/report.html --real-time-html time-format='%H:%M:%S' --date-format=‘%d ...

  6. ffmpeg常用数据结构

    from :http://my.oschina.net/u/555701/blog/56748 AVCodecContext 这是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息,如 ...

  7. selenium控制浏览器操作

    selenium控制浏览器操作 控制浏览器有哪些操作? 控制页面大小 前进.后退 刷新 自动输入.提交 ........  控制页面大小,实例: # -*- coding:utf-8 -*- from ...

  8. 51Nod 1021 石子归并(区间dp经典入门)

    题意: N堆石子摆成一条线.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价.计算将N堆石子合并成一堆的最小代价. n<=100 思 ...

  9. 《Python学习手册 第五版》 -第1章 问答环节

    第一章的主要内容是解疑答惑的,这个部分也是很适合初学者的,回答了大部分初学者所关注的问题 1.为什么使用Python,或者说Python的优点, 作者是分为两个部分来谈的,人们使用中的经验总结以及Py ...

  10. python练习——第2题

    原GitHub地址:https://github.com/Yixiaohan/show-me-the-code 题目:将 0001 题生成的 200 个激活码(或者优惠券)保存到 MySQL 关系型数 ...