前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2. 「uniapp 源码分析」vue-loader@15.8.3 的整体流程

2. 「uniapp 源码分析」vue-loader@15.8.3 的整体流程

作者头像
tinyant
发布2023-02-24 10:29:03
2.4K0
发布2023-02-24 10:29:03
举报

通常我们会使用vue-cli来创建一个vue项目,由于vue-cli对常见的开发需求进行了预先配置,做到了开箱即用。但是阻碍碍我们窥探其真面目脚步。当然官方也提供了手动配置的方案。参考

安装依赖,下面库的作用后面都会分析到。

代码语言:javascript
复制
npm install -D vue-loader vue-template-compiler

webpack 配置,有loader有plugin `js // webpack.config.js const { VueLoaderPlugin } = require('vue-loader')

module.exports = { module: { rules: // ... 其它规则 { test: /.vue$/, loader: 'vue-loader' } }, plugins: // 请确保引入这个插件! new VueLoaderPlugin() }

代码语言:javascript
复制
手动配置方式的一个简易[demo](https://github.com/yusongjohn/anaylyze-vue-loader),来调试看看vue-loader做了哪些事情。
# 构建前后对比
这里相关的库的版本和我们当前分析的uniapp中用到的版本保持一致
```json

// devDependecnies

"vue-loader": "15.8.3",

"vue-template-compiler": "2.6.11",

// dependecnies

"vue": "2.6.11"

代码语言:txt
复制
## demo

- App.vue:就是要分析vue文件是如何被构建的,当然需要一个vue文件啦 ```html

  export default {   name: "App",   data() {     return {       msg: "hello vue + webpack",     };   } };  #id {   background: red; } 

```javascript
  • main.js:应用入口的js文件,为了App.vue构建后是独立的文件(因为构建后小程序也是独立的文件 js,wxml,wxss等),通过异步引用进行代码分割。import Vue from 'vue' import ('./App.vue'/* webpackChunkName: 'App' */).then((App) => { new Vue({ render: h => h(App.default), }).$mount('#app') })

html,应用入口

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="./runtime~main.js"></script>
</head>
<body>
  <div id="app"></div>
  <script src="./main.js"></script>
</body>
</html>

webpack.config.js ` const path = require('path'); const CopyPlugin = require("copy-webpack-plugin"); const {VueLoaderPlugin} = require('vue-loader'); const MiniCssExtractPlugin = require('mini-css-extract-plugin') const {CleanWebpackPlugin} = require('clean-webpack-plugin');

module.exports = { mode: 'development', entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /.vue/, // 注意 loader: 'vue-loader' }, { test: /.css

代码语言:javascript
复制
1. optimizatin.runtimeChunk 是为了将运行时拆分(不要被你当前不需要关注的内容干扰啊)
2. MiniCssExtractPlugin 是为了拆分css内容为单独文件
3. 另外就是vue-loader的配置

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da29b388669143de859369ec5b898179~tplv-k3u1fbpfcp-watermark.image?)

## 产物
### App.css
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa9d0c5144cc45ab843d0f7ab9f66418~tplv-k3u1fbpfcp-watermark.image?)

### App.js
![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6c8f63c81f9b42a18aefda3e507694bb~tplv-k3u1fbpfcp-watermark.image?)

## 小结
`App.vue`本身是三段式内容,分别是`<template></template>`、`<script>`、`<style>`。

而当前的构建结果只有两个部分:`App.vue` -> `App.js` + `App.css`。

实际上`App.vue`中`template`部分也被构建到了`App.js`中了,这是因为`vue`是基于`render`函数来构造虚拟DOM,而后将虚拟DOM渲染到界面中的,`template`部分实际是被转为了`render`函数了(可以参考[vue@2.6.11 源码分析](https://juejin.cn/column/7192880378015645752))。

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/13e8f94b647b4733a472ef1300263faf~tplv-k3u1fbpfcp-watermark.image?)

# vue-loader的整体流程
需要从webpack的构建流程讲起,可以参考[webpack@4.46.0 源码分析](https://juejin.cn/column/7161609931563466766)。

进入webpack流程后,首先是注册插件,即调用插件的`apply`方法,通过插件`apply`方法中会拿到`compiler`实例,然后通过`compilation.hook.xxx`去监听自己关心的事件,从而参与构建流程,但是VueLoaderPlugin这里不是这么做的,而是重写了`module.rules`。

下面看下VueLoaderPlugin的逻辑

## VueLoaderPlugin
```js

const id = 'vue-loader-plugin'

const NS = 'vue-loader'

class VueLoaderPlugin {

apply (compiler) {

代码语言:txt
复制
// add NS marker so that the loader can detect and report missing plugin    
代码语言:txt
复制
// 第一步:找到 vue-loader,设置ident和options
代码语言:txt
复制
// use webpack's RuleSet utility to normalize user rules
代码语言:txt
复制
const rawRules = compiler.options.module.rules
代码语言:txt
复制
const { rules } = new RuleSet(rawRules) // 会将用户提供的规则标准化
代码语言:txt
复制
// find the rule that applies to vue files
代码语言:txt
复制
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
代码语言:txt
复制
const vueRule = rules[vueRuleIndex]
代码语言:txt
复制
const vueUse = vueRule.use
代码语言:txt
复制
const vueLoaderUseIndex = vueUse.findIndex(u => {
代码语言:txt
复制
  return /^vue-loader|(/|\|@)vue-loader/.test(u.loader)
代码语言:txt
复制
})
代码语言:txt
复制
const vueLoaderUse = vueUse[vueLoaderUseIndex]
代码语言:txt
复制
vueLoaderUse.ident = 'vue-loader-options'
代码语言:txt
复制
vueLoaderUse.options = vueLoaderUse.options || {}
代码语言:txt
复制
// 第二步:克隆除了vue-loader以外的其他规则
代码语言:txt
复制
// for each user rule (expect the vue rule), create a cloned rule
代码语言:txt
复制
// that targets the corresponding language blocks in *.vue files.
代码语言:txt
复制
const clonedRules = rules.filter(r => r !== vueRule).map(cloneRule)
代码语言:txt
复制
// global pitcher (responsible for injecting template compiler loader &amp; CSS post loader)
代码语言:txt
复制
const pitcher = {
代码语言:txt
复制
  loader: require.resolve('./loaders/pitcher'),
代码语言:txt
复制
  resourceQuery: query => {
代码语言:txt
复制
    const parsed = qs.parse(query.slice(1))
代码语言:txt
复制
    return parsed.vue != null
代码语言:txt
复制
  },
代码语言:txt
复制
  options: {
代码语言:txt
复制
    cacheDirectory: vueLoaderUse.options.cacheDirectory,
代码语言:txt
复制
    cacheIdentifier: vueLoaderUse.options.cacheIdentifier
代码语言:txt
复制
  }
代码语言:txt
复制
}
代码语言:txt
复制
// replace original rules
代码语言:txt
复制
compiler.options.module.rules = [
代码语言:txt
复制
  pitcher,
代码语言:txt
复制
  ...clonedRules,
代码语言:txt
复制
  ...rules
代码语言:txt
复制
]

}

}

VueLoaderPlugin.NS = NS

module.exports = VueLoaderPlugin

代码语言:txt
复制
核心步骤如下:

找出应用在`.vue`文件上的`rules`,然后从找出`vue-loader`

给`vue-loader`设置`ident`和`options`,目的是通过该标识来查找到对应的`loader`,并获取该`loader`上挂载的`options`,从而在不同的`loader`间进行信息传递,会在后面分析到。

克隆除了`vue-loader`以外的其他规则。为什么要克隆其他的所有规则呢?
 因为`vue`文件的可能会包含多个`块`,比如`<template>`、`<style>`、`<script>`,甚至是自定义块。而对于这些块比如`style`中的内容实际上应该要被用户提供的如`css-loader`等应用的,但是由于这些内容被嵌套在`.vue`文件中,并不能被用户提供的`test`规则匹配上。`.vue`文件经过`vue-loader`处理会产中间内容,比如会把`style`文件块转为如下形式,被克隆后的规则实际被添加了一个`resourceQuery`方法,该方法就是用来验证该块该不该应用用户提供的规则的,比如下面`lang=css`,实际就会应用用户提供的`css`相关的`loader`。所有[cloneRule](https://github.com/vuejs/vue-loader/blob/v15.8.3/lib/plugin-webpack4.js)的作用就是将用户提供的`rules`尝试应用到`.vue`文件中的`块`中。

```javascript

import style0 from "./App.vue?vue&type=style&index=0&id=4aa9bdb2&prod&lang=css&"

代码语言:txt
复制
创建一个[pitching-loader](https://webpack.js.org/api/loaders/#pitching-loader),webpack中loader的类型和执行顺序,可以[参考](https://juejin.cn/post/7161663443026575368)。这里需要知道`pitching-loader`会优先于`normal-loader`先执行,并且有熔断机制,一旦`pitching-loader`有内容返回,则后面的`pitching-loader`不会执行;转而执行上一个`normal-loader`。 

![请在此添加图片描述](https://developer.qcloudimg.com/http-save/yehe-10164320/72a59e6839de886967096fdf8b751d7a.png?qc_blockWidth=1347&qc_blockHeight=534)

 注意:新创建的`pitcher`的`resourceQuery`,如下,只会匹配 `?vue....`这样的路径

```javascript

resourceQuery: query => {

const parsed = qs.parse(query.slice(1))

return parsed.vue != null

},

代码语言:txt
复制
改写 `module.rules`,注意创建的`pitching-loader`位于第一个位置。注意,克隆的规则和原先的规则都是需要的,克隆的规则并不能代替原先的规则。因为克隆的规则仅仅针对`.vue`文件中的块的,但是项目中其他的文件如`.css`文件,依然是需要被处理的,此时还是用户自己提供的规则去处理。

### 小结

这部分的成果如下: 

![请在此添加图片描述](https://developer.qcloudimg.com/http-save/yehe-10164320/55eb21739ad1460ad987128abb0138c7.png?qc_blockWidth=1347&qc_blockHeight=427)

1. 第一个pitcher匹配`?vue...`路径
2. 第二个规则,是我们上面webpack.config.js中针对css的那个规则的克隆,引用在`.vue`文件中相应`块`中,这里是`<style />`
3. 后面的两个还是用户提供的规则(被规范化过了,开始`new RuleSet()`会做这个事情)

那下一步是什么,webpack会从entry配置项开始,我们这里是src/main.js,然后会找到依赖链上的所有模块并进行构建。这里忽略`mian.js`文件的构建,如果知道我们代码中的异步引用会自动将`App.vue`作为一个新的chunk就更好了,可以[参考](https://juejin.cn/post/7161667732079902727)。

下面看下`App.vue`文件的构建过程吧,首先会经过pitching阶段,由于这里没有匹配的pitching-loader,会从本地路径中读取该文件的内容(传递给第一个`normal-loader`哦,也就是我们下面`vue-loader`的入参`source`)进入normal阶段的执行,这里只有`vue-loader`,下面分析下`vue-loader`

## vue-loader

### 处理 App.vue

```javascript

module.exports = function (source) {

代码语言:txt
复制
const loaderContext = this;
代码语言:txt
复制
const {
代码语言:txt
复制
    resourceQuery
代码语言:txt
复制
} = loaderContext
代码语言:txt
复制
const rawQuery = resourceQuery.slice(1)
代码语言:txt
复制
const inheritQuery = `&amp;${rawQuery}`
代码语言:txt
复制
//...
代码语言:txt
复制
const descriptor = parse({
代码语言:txt
复制
    source,
代码语言:txt
复制
    compiler: options.compiler || loadTemplateCompiler(loaderContext),
代码语言:txt
复制
    filename,
代码语言:txt
复制
    sourceRoot,
代码语言:txt
复制
    needMap: sourceMap
代码语言:txt
复制
})
代码语言:txt
复制
// if the query has a type field, this is a language block request
代码语言:txt
复制
// e.g. foo.vue?type=template&amp;id=xxxxx
代码语言:txt
复制
// and we will return early
代码语言:txt
复制
if (incomingQuery.type) {
代码语言:txt
复制
    // return selectBlock(...)
代码语言:txt
复制
}
代码语言:txt
复制
// template
代码语言:txt
复制
let templateImport = `var render, staticRenderFns`
代码语言:txt
复制
let templateRequest
代码语言:txt
复制
if (descriptor.template) {
代码语言:txt
复制
    //...
代码语言:txt
复制
}
代码语言:txt
复制
// script
代码语言:txt
复制
let scriptImport = `var script = {}`
代码语言:txt
复制
if (descriptor.script) {
代码语言:txt
复制
    //...
代码语言:txt
复制
}
代码语言:txt
复制
// styles
代码语言:txt
复制
let stylesCode = ``
代码语言:txt
复制
if (descriptor.styles.length) {
代码语言:txt
复制
    //...
代码语言:txt
复制
}
代码语言:txt
复制
if (descriptor.customBlocks &amp;&amp; descriptor.customBlocks.length) {
代码语言:txt
复制
    //...
代码语言:txt
复制
}
代码语言:txt
复制
//...
代码语言:txt
复制
return code

}

代码语言:txt
复制
这里主要分为两种场景:`query`中是否有`type`

- App.vue?type=template&id=xxxxx,`query`中有`type`,有`type`是则会走`if(incomingQuery.type)`,并返回`selectBlock(...)`;
 
- App.vue,没有query(或者`query`中没有`type`),走后面的逻辑,清晰的看到后面的逻辑主要对`descriptor`进行处理,那`descriptor`是什么了,实际上就是`.vue`文件中的内容按照`块`划分,每个块都有自己的内容,以`App.vue`为例,如下: 

![请在此添加图片描述](https://developer.qcloudimg.com/http-save/yehe-10164320/cf08d3ce849887c24600e5ec32b04d40.png?qc_blockWidth=1347&qc_blockHeight=397)

 看到仅仅是获取每个块的内容,并没有进入每个块并进行处理,看到这里主要有四个关键的属性:`scritp`、`style`、`template`、`customBlock`,最常用的就是前三个。
 

分别将三个标签的内容转化为如下:

```javascript

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=css&"

代码语言:txt
复制
这里还会注入一些运行时相关的逻辑,最终经过这部分处理,返回了如下内容

```javascript

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=css&"

/ normalize component /

import normalizer from "!../node_modules/vue-loader/lib/runtime/componentNormalizer.js"

var component = normalizer(

script,

render,

staticRenderFns,

false,

null,

null,

null

)

component.options.__file = "src/App.vue"

export default component.exports

代码语言:txt
复制
`App.vue`经过`loader`处理完后,会经过`parser.parse`来解析返回后的内容从而收集依赖。简单穿插下`webpack`这部分的大致逻辑,可以参考[这里](https://juejin.cn/post/7161659745789542414)。`parser.parse`的分析可以参考[这里](https://juejin.cn/post/7161666763640782884)

```javascript

// node_modules/webpack/lib/NormalModule.js

build(options, compilation, resolver, fs, callback) {

代码语言:txt
复制
//...
代码语言:txt
复制
this._source = null;
代码语言:txt
复制
this._ast = null;
代码语言:txt
复制
return this.doBuild(options, compilation, resolver, fs, err => {
代码语言:txt
复制
    //...
代码语言:txt
复制
    const result = this.parser.parse(this._ast || this._source.source(), { /*...*/ }, (err, result) => {/*...*/ });
代码语言:txt
复制
});

}

代码语言:txt
复制
经过`parser.parse`处理完后,我们看下这部分有哪些依赖,这里会产生很多依赖,但大多数会被过滤掉(见`webpack/lib/compilation.js`中的`processModuleDependencies`方法),实际作为模块一步解析的是如下依赖(Dependency.request)

```javascript

0: "./App.vue?vue&type=template&id=7ba5bd90&"

1: "./App.vue?vue&type=script&lang=js&"

2: "./App.vue?vue&type=style&index=0&lang=css&"

3: "!../node_modules/vue-loader/lib/runtime/componentNormalizer.js"

代码语言:txt
复制
前面三个都会命中VueLoaderPlugin中创建的`pitching-loader`,和用户自己提供的`vue-loader`,先执行`pitching-loader`再执行`vue-loader`。

### pitching-loader: pitcher

```javascript

// node_modules/vue-loader/lib/loaders/pitcher.js

module.exports.pitch = function (remainingRequest) {

代码语言:txt
复制
// 1. 获取当前模块的所有loaders,保存在loaderContext中,
代码语言:txt
复制
// 并过滤掉自己(pitcher)和eslint-loader
代码语言:txt
复制
// (因为整个vue文件应该是被eslint-loader处理了?),
代码语言:txt
复制
// 那在当前案例中就只剩下vue-loader了
代码语言:txt
复制
// 2. 将上一步过滤后的所有loaders,构造成内联loader形式,这里会区分type如script、template、style

}

代码语言:txt
复制
内联loader,参考官方介绍[inline loader](https://webpack.js.org/concepts/loaders/#inline),这里处理后的内联loader被添加了前缀`-!`

>  Prefixing with `-!` will disable all configured preLoaders and loaders but not postLoaders
>  

----

以`./App.vue?vue&amp;type=template&amp;...`为例,经过`pitcher`处理后的内容如下

```javascript

export * from "-!../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options

!../node_modules/vue-loader/lib/index.js??vue-loader-options

!./App.vue?vue&type=template&id=7ba5bd90&"

代码语言:txt
复制
注意,`pitcher`返回了内容,由于`pitcher`是第一个`loader`会结束当前模块的整个`loader`的执行,而后当前进入模块的依赖收集即进入`webpack`中的`parser.parse()`。然后会把上面`pitcher`返回的`request`作为新的模块进行解析。看到这里有两个loader

而是按照内联形式的loader的顺序和规则执行。

而后进入`vue-loader`的执行,由于`./App.vue?vue&amp;type=template&amp;id=7ba5bd90&amp;`中有`type`,因此会走下面:

```javascript

// vue-loader/lib/index.js

if (incomingQuery.type) {

代码语言:txt
复制
return selectBlock(
代码语言:txt
复制
    descriptor,
代码语言:txt
复制
    loaderContext,
代码语言:txt
复制
    incomingQuery,
代码语言:txt
复制
    !!options.appendExtension
代码语言:txt
复制
)

}

代码语言:txt
复制
`selectBlock`逻辑很简单,就是根据`type`将`descriptor`中的内容返回,比如`template`部分

```javascript

<div id="app"> {{ msg }} </div>

代码语言:txt
复制
然后返回的这部分内容会交给`templateLoader.js`处理,处理后的结果如下: 

![请在此添加图片描述](https://developer.qcloudimg.com/http-save/yehe-10164320/7ef74ae46675f438ae7e7244e6f2831a.png?qc_blockWidth=1347&qc_blockHeight=578)

`templateLoader` 会在后面小节中单独分析

----

再看下:`./App.vue?vue&amp;type=script&amp;...`经过pitcher处理后的内容

```javascript

import mod from "-!../node_modules/vue-loader/lib/index.js??vue-loader-options

!./App.vue?vue&type=script&lang=js&";

export default mod;

export * from "-!../node_modules/vue-loader/lib/index.js??vue-loader-options

!./App.vue?vue&type=script&lang=js&"

代码语言:txt
复制
由于我们当前demo中没有提供其他的处理`js`的`loader`,因此下次处理该模块时,会将`.vue`文件中`script`直接作为最终结果输出(注意: 模块化的处理是`webpack`内置能力,我们不需要关心),如下: 

![请在此添加图片描述](https://developer.qcloudimg.com/http-save/yehe-10164320/6a0bede6d2050b47dd3e55f13f7ee488.png?qc_blockWidth=1347&qc_blockHeight=690)

----

再看下:`./App.vue?vue&amp;type=style&amp;...`经过pitcher处理后的内容

```javascript

import mod from "-!../node_modules/mini-css-extract-plugin/dist/loader.js

!../node_modules/css-loader/dist/cjs.js

!../node_modules/vue-loader/lib/loaders/stylePostLoader.js

!../node_modules/vue-loader/lib/index.js??vue-loader-options

!./App.vue?vue&type=style&index=0&lang=css&";

export default mod; export * from "-!../node_modules/mini-css-extract-plugin/dist/loader.js

!../node_modules/css-loader/dist/cjs.js

!../node_modules/vue-loader/lib/loaders/stylePostLoader.js

!../node_modules/vue-loader/lib/index.js??vue-loader-options

!./App.vue?vue&type=style&index=0&lang=css&"

代码语言:txt
复制
`.vue`文件中`style`块,在当前案例中,会先后经过下面几个loader的处理 


1. vue-loader(->selectBlock直接返回原生内容)
2. stylePostLoader.js
3. css-loader
4. mini-css-extract-plugin.loader

经过处理后的结果如下(被插件分离成单独的文件啦) 

![请在此添加图片描述](https://developer.qcloudimg.com/http-save/yehe-10164320/c3939481821dbd932ad27dbd2d0c331f.png?qc_blockWidth=1347&qc_blockHeight=252)

`stylePostLoader`,`css-loader`,`mini-css-extract-plugin` 会在后面小节中单独分析。

# 构建后的产物的运行过程是怎样的

实际上构建过程中的中间内容,最终也都会被输出,我们当前案例中添加了soucemap配置,可以更清晰的验证这一点。 

![请在此添加图片描述](https://developer.qcloudimg.com/http-save/yehe-10164320/d2d491e5a72de8fb07746e18426f8367.png?qc_blockWidth=1347&qc_blockHeight=644)

当然这些中间代码会被webpack再次处理(主要是模块化相关),因此看到`App.js`中定义了很多个模块,如下: 

![请在此添加图片描述](https://developer.qcloudimg.com/http-save/yehe-10164320/884f123a677a37f20bb95a5b50b30eb5.png?qc_blockWidth=1347&qc_blockHeight=785)

现在我们再来看看最终的产物是如何运行的吧,主要是引用关系。

----

index.html -> main.js -> App.js

main.js

```javascript

// main.js

webpack_require.e(/! import() | App / "App").then(webpack_require.bind(null, /! ./App.vue / "./src/App.vue")).then((App) => {

new vueWEBPACK_IMPORTED_MODULE_0"default"({

代码语言:txt
复制
  render: h => h(App.default),

}).$mount('#app')

})

代码语言:txt
复制
App.js "./src/App.vue"在App.js中定义的,如下: ``` /_**/ "./src/App.vue": /**_**!**\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*!
_!_\*\* ./src/App.vue \*\*\*! \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/ /_! exports provided: default / /_\*/ (function(module, **webpack\_exports**, **webpack\_require**) {

"use strict"; **webpack\_require**.r(**webpack\_exports**); /\* harmony import _/ var App\_vue\_vue\_type\_template\_id\_7ba5bd90\_\_\_WEBPACK\_IMPORTED\_MODULE\_0\_ =_ _**webpack\_require**__(/_! ./App.vue?vue&type=template&id=7ba5bd90& _/ "./src/App.vue?vue&type=template&id=7ba5bd90&"); /_ harmony import _/ var App\_vue\_vue\_type\_script\_lang\_js\_\_\_WEBPACK\_IMPORTED\_MODULE\_1\_ =_ _**webpack\_require**__(/_! ./App.vue?vue&type=script&lang=js& _/ "./src/App.vue?vue&type=script&lang=js&"); /_ empty/unused harmony star reexport _//_ harmony import _/ var App\_vue\_vue\_type\_style\_index\_0\_lang\_css\_\_\_WEBPACK\_IMPORTED\_MODULE\_2\_ =_ _**webpack\_require**__(/_! ./App.vue?vue&type=style&index=0&lang=css& _/ "./src/App.vue?vue&type=style&index=0&lang=css&"); /_ harmony import _/ var node\_modules\_vue\_loader\_lib\_runtime\_componentNormalizer\_js\_\_WEBPACK\_IMPORTED\_MODULE\_3\_ =_ _**webpack\_require**__(/_! ../node\_modules/vue-loader/lib/runtime/componentNormalizer.js \*/ "./node\_modules/vue-loader/lib/runtime/componentNormalizer.js");

/\* normalize component \*/

var component = Object(_node\_modules\_vue\_loader\_lib\_runtime\_componentNormalizer\_js\_\_WEBPACK\_IMPORTED\_MODULE\_3["default"])(   App\_vue\_vue\_type\_script\_lang\_js\_\_\_WEBPACK\_IMPORTED\_MODULE\_1_["default"],   _App\_vue\_vue\_type\_template\_id\_7ba5bd90\_\_\_WEBPACK\_IMPORTED\_MODULE\_0["render"],   App\_vue\_vue\_type\_template\_id\_7ba5bd90\_\_\_WEBPACK\_IMPORTED\_MODULE\_0_["staticRenderFns"],   false,   null,   null,   null

)

/\* hot reload _/ if (false) { var api; } component.options.\_\_file = "src/App.vue" /_ harmony default export \*/ **webpack\_exports**["default"] = (component.exports);

/\*\*\*/ }),

```javascript

然后又会去引入其他模块(这些都是vue-loader生成的中间模块),就不往下继续了。如果你了解webpack自己的模块化机制,看到上述__webpack_require__等方法时就不会一脸懵逼,可以参考这里,粗略的介绍了webpack自己的模块化机制。

总结

补充下 !../node_modules/vue-loader/lib/runtime/componentNormalizer.js ,同样没有额外的处理js的loader,因此这个文件也是原始内容直接输出。 该文件的作用是标准化组件选项的,主要是挂载renderstaticRenderFns,这两个方法来自template部分处理结果,vue运行时在创建虚拟DOM时依赖render方法(就是通过render方法来创建虚拟DOM的)

vue-loader作用大致过程:

  1. VueLoaderPlugin,修改module.rules,注入pitcher克隆的rulesimport { render, staticRenderFns } from "./App.vue?vue&amp;type=template&amp;id=7ba5bd90&amp;" import script from "./App.vue?vue&amp;type=script&amp;lang=js&amp;" export * from "./App.vue?vue&amp;type=script&amp;lang=js&amp;" import style0 from "./App.vue?vue&amp;type=style&amp;index=0&amp;lang=css&amp;" /* normalize component */ import normalizer from "!../node_modules/vue-loader/lib/runtime/componentNormalizer.js" var component = normalizer( script, render, staticRenderFns, false, null, null, null ) component.options.__file = "src/App.vue" export default component.exports
  2. App.vue首先会被vue-loader解析成如下内容
  3. webpack会从上述内容中解析出依赖,并将这些依赖构造成模块,并进行解析
  4. ./App.vue?vue&type=script&lang=js&
  5. ./App.vue?vue&type=script&lang=js&
  6. ./App.vue?vue&type=style&index=0&lang=css&
  7. !../node_modules/vue-loader/lib/runtime/componentNormalizer.js

./App.vue?vue&amp;type=...会匹配到pitcher和vue-loader,但是由于pitcher有返回内容,此次的vue-loader并不会执行。

pitcher针对type=template/script/style返回的内容如下

type=template

代码语言:javascript
复制
export * from "-!../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options
!../node_modules/vue-loader/lib/index.js??vue-loader-options
!./App.vue?vue&amp;type=template&amp;id=7ba5bd90&amp;"

type=script `text import mod from "-!../node_modules/vue-loader/lib/index.js??vue-loader-options !./App.vue?vue&type=script&lang=js&";

export default mod; export * from "-!../node_modules/vue-loader/lib/index.js??vue-loader-options !./App.vue?vue&type=script&lang=js&"

代码语言:javascript
复制
- type=style
```text

import mod from "-!../node_modules/mini-css-extract-plugin/dist/loader.js

!../node_modules/css-loader/dist/cjs.js

!../node_modules/vue-loader/lib/loaders/stylePostLoader.js

!../node_modules/vue-loader/lib/index.js??vue-loader-options

!./App.vue?vue&type=style&index=0&lang=css&";

export default mod; export * from "-!../node_modules/mini-css-extract-plugin/dist/loader.js

!../node_modules/css-loader/dist/cjs.js

!../node_modules/vue-loader/lib/loaders/stylePostLoader.js

!../node_modules/vue-loader/lib/index.js??vue-loader-options

!./App.vue?vue&type=style&index=0&lang=css&"

代码语言:txt
复制
  1. 而后会对上面的内容进行依赖解析收集依赖,并创建对应的模块,对新的模块进行解析,此时解析模块的loader主要来自内联路径中。经过这些内联loader的解析生成各个块的内容。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-02-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档