Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

    • JS

    • NodeJS

    • Vue

      • 个人理解Vue和React区别
      • Vue高级用法
      • Vue2.x源码分析 - 框架结构
      • Vue2.x源码分析 - 模版编译以及挂载
      • 虚拟dom算法库 - snabbdom
      • Vue2.x源码分析 - Virtual DOM实现
      • Vue2.x源码分析 - 事件系统
      • Vue2.x源码分析 - 组件系统
      • Vue2.x源码分析 - Vue.nextTick
      • Vue2.x源码分析 - inject provide
      • Vue2.x源码分析 - 解析Template模板
      • Vue2.x源码分析 - 响应式原理
      • Vue2.x源码分析 - v-model
      • Vue CLI3 插件系统原理
      • Vue Loader v15 源码解析
      • Vue3 设计思想
      • Vue3 RFCS导读
      • Vue3 响应式原理 - Ref Reactive Effect源码分析
      • Vue3 API 源码解析
      • 为何Vue3 Proxy 更快
      • Vue核心原理 - 笔记
    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • Vue
wangmings
2022-07-19

Vue Loader v15 源码解析

# # Vue Loader v15 源码解析

Vue Loader v15相对于以前的版本,改动比较大。不仅拆分出VueLoaderPlugin,而且其Loader 推导策略完全改变了。更多详细改动可以看下官方迁移文档 (opens new window) (opens new window)。Loader15之前的源码解析可以看这篇文章 (opens new window) (opens new window),作者分析了Loader15前的源码,对比现在这篇文章会有更多体会。

Vue Loader简单讲,就是将 *.vue 文件变成可以在浏览器中运行的js模块文件。以下我们具体分析下Vue Loader v15源码。

    const selectBlock = require('./select')
    const { parse } = require('@vue/component-compiler-utils')
    
    module.exports = function (source) {
        // this是webpack注入的内容
        const {
            target,
            request,
            minimize,
            sourceMap,
            rootContext,
            resourcePath,
            resourceQuery
        } = this
    
        const rawQuery = resourceQuery.slice(1)
        const incomingQuery = qs.parse(rawQuery)
        // 通过parse解析.vue文件,获取不同的类型,如template、style、script
        const descriptor = parse({
            source,
            compiler: options.compiler || require('vue-template-compiler'),
            filename,
            sourceRoot,
            needMap: sourceMap
        })
    
        // 带有type字段,使用语言推导(关键代码)
        // 如:foo.vue?type=template&id=xxxxx会进入下面selectBlock
        // 这里第一次是直接./foo.vue,不带type;而第一次生成的code又需要进入到Vue Loader中
        if (incomingQuery.type) {
            return selectBlock(
                descriptor,
                loaderContext,
                incomingQuery,
                !!options.appendExtension
                )
        }
    
        // 如果没有type字段,则导出code源码。
        // 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?
            )
        }
        let code = `
            ${templateImport}
            ${scriptImport}
            ${stylesCode}
            ...`
        return code
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
  1. 先通过html-parse (opens new window) (opens new window)解析整个.vue文件。使用正则逐步拿到信息,最终拿到descriptor信息(JavaScript对象)。类似如下内容:

image

  1. 当编译vue单组件时,是不带type如./source.vue,所以会作为同步Loader返回编译后的code。类似如下内容:

image

  1. 上图红框处,可以看到生成的代码里,又进行了vue模块的导入,所以又会找到Vue Loader源码里,再走一遍逻辑。但是可以看到,其路径后都带有type字段,所以这次会走入selectBlock这个模块进行处理。源码在select.js中:
    module.exports = function selectBlock (
      descriptor,
      loaderContext,
      query,
      appendExtension
    ) {
      // loaderContext === this
      // template
      if (query.type === `template`) {
        if (appendExtension) {
          loaderContext.resourcePath += '.' + (descriptor.template.lang || 'html')
        }
        // 异步loader
        loaderContext.callback(
          null,
          descriptor.template.content,
          descriptor.template.map
        )
        return
      }
    
      // script
      if (query.type === `script`) {
        if (appendExtension) {
          loaderContext.resourcePath += '.' + (descriptor.script.lang || 'js')
        }
        loaderContext.callback(
          null,
          descriptor.script.content,
          descriptor.script.map
        )
        return
      }
    
      // styles
      if (query.type === `style` && query.index != null) {
        const style = descriptor.styles[query.index]
        if (appendExtension) {
          loaderContext.resourcePath += '.' + (style.lang || 'css')
        }
        loaderContext.callback(
          null,
          style.content,
          style.map
        )
        return
      }
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

可以看到其分别对template/script/style内容进行处理。处理也很简单,就是使用webpack配置的loader对相应的.html后缀/.js后缀/.css后缀进行规则匹配。这也是官方文档中说明的Loader推导变更 (opens new window) (opens new window),v15以前是使用loaders选项进行loader定制,但从v15之后就不需要这样做了。

编辑 (opens new window)
Vue CLI3 插件系统原理
Vue3 设计思想

← Vue CLI3 插件系统原理 Vue3 设计思想→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式