Skip to content

Commit ee709dd

Browse files
committed
feat: add matchResource
1 parent 55de28c commit ee709dd

6 files changed

Lines changed: 120 additions & 173 deletions

File tree

README.md

Lines changed: 99 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,110 @@
1-
# vue-loader [![Build Status](https://circleci.com/gh/vuejs/vue-loader/tree/master.svg?style=shield)](https://circleci.com/gh/vuejs/vue-loader/tree/master) [![Windows Build status](https://ci.appveyor.com/api/projects/status/8cdonrkbg6m4k1tm/branch/master?svg=true)](https://ci.appveyor.com/project/yyx990803/vue-loader/branch/master)
1+
# vue-loader-matchresource
2+
# 功能
3+
修改了vue-loader的rules匹配策略,用webpack4 matchResource功能替代克隆Rules实现.
24

3-
> webpack loader for Vue Single-File Components
5+
带来的是更直观的rules匹配策略,减少了webpack编译.vue文件生成的module数量.
46

5-
**NOTE:** The master branch now hosts the code for v15! Legacy code is now in the [v14 branch](https://github.com/vuejs/vue-loader/tree/v14).
7+
出自于 [webpack PR 7462](https://github.com/webpack/webpack/pull/7462)
68

7-
- [Documentation](https://vue-loader.vuejs.org)
8-
- [Migrating from v14](https://vue-loader.vuejs.org/migrating.html)
9+
来两张流程图来看一下出入点:
910

10-
## What is Vue Loader?
11+
## before
12+
![image](./public/img.png)
1113

12-
`vue-loader` is a loader for [webpack](https://webpack.js.org/) that allows you to author Vue components in a format called [Single-File Components (SFCs)](./docs/spec.md):
14+
## after
15+
![image](./public/img_1.png)
1316

14-
``` vue
15-
<template>
16-
<div class="example">{{ msg }}</div>
17-
</template>
1817

19-
<script>
20-
export default {
21-
data () {
22-
return {
23-
msg: 'Hello world!'
24-
}
25-
}
26-
}
27-
</script>
28-
29-
<style>
30-
.example {
31-
color: red;
32-
}
33-
</style>
34-
```
35-
36-
There are many cool features provided by `vue-loader`:
37-
38-
- Allows using other webpack loaders for each part of a Vue component, for example Sass for `<style>` and Pug for `<template>`;
39-
- Allows custom blocks in a `.vue` file that can have custom loader chains applied to them;
40-
- Treat static assets referenced in `<style>` and `<template>` as module dependencies and handle them with webpack loaders;
41-
- Simulate scoped CSS for each component;
42-
- State-preserving hot-reloading during development.
43-
44-
In a nutshell, the combination of webpack and `vue-loader` gives you a modern, flexible and extremely powerful front-end workflow for authoring Vue.js applications.
45-
46-
## How It Works
47-
48-
> The following section is for maintainers and contributors who are interested in the internal implementation details of `vue-loader`, and is **not** required knowledge for end users.
49-
50-
`vue-loader` is not a simple source transform loader. It handles each language blocks inside an SFC with its own dedicated loader chain (you can think of each block as a "virtual module"), and finally assembles the blocks together into the final module. Here's a brief overview of how the whole thing works:
51-
52-
1. `vue-loader` parses the SFC source code into an *SFC Descriptor* using `@vue/component-compiler-utils`. It then generates an import for each language block so the actual returned module code looks like this:
53-
54-
``` js
55-
// code returned from the main loader for 'source.vue'
56-
57-
// import the <template> block
58-
import render from 'source.vue?vue&type=template'
59-
// import the <script> block
60-
import script from 'source.vue?vue&type=script'
61-
export * from 'source.vue?vue&type=script'
62-
// import <style> blocks
63-
import 'source.vue?vue&type=style&index=1'
64-
65-
script.render = render
66-
export default script
67-
```
68-
69-
Notice how the code is importing `source.vue` itself, but with different request queries for each block.
70-
71-
2. We want the content in `script` block to be treated like `.js` files (and if it's `<script lang="ts">`, we want to to be treated like `.ts` files). Same for other language blocks. So we want webpack to apply any configured module rules that matches `.js` also to requests that look like `source.vue?vue&type=script`. This is what `VueLoaderPlugin` (`src/plugins.ts`) does: for each module rule in the webpack config, it creates a modified clone that targets corresponding Vue language block requests.
72-
73-
Suppose we have configured `babel-loader` for all `*.js` files. That rule will be cloned and applied to Vue SFC `<script>` blocks as well. Internally to webpack, a request like
7418

75-
``` js
76-
import script from 'source.vue?vue&type=script'
77-
```
19+
# 具体实现
20+
## 思路
21+
1. 修改vue-loader处理后request为`!=!`语法,以便交给webpack去匹配对应rules
22+
2. 修改vueLoaderPlugin中rules调度,以便matchResource匹配对应rules
7823

79-
Will expand to:
24+
## vue-loader
25+
`./a.vue?query...`,
8026

81-
``` js
82-
import script from 'babel-loader!vue-loader!source.vue?vue&type=script'
83-
```
27+
* template处理为`a.lang!=!vue-loader!request...`lang为对应模板引擎后缀,如无就是空
28+
* script同上,不过默认是.js
29+
* style同上,默认.css
8430

85-
Notice the `vue-loader` is also matched because `vue-loader` are applied to `.vue` files.
86-
87-
Similarly, if you have configured `style-loader` + `css-loader` + `sass-loader` for `*.scss` files:
88-
89-
``` html
90-
<style scoped lang="scss">
91-
```
92-
93-
Will be returned by `vue-loader` as:
94-
95-
``` js
96-
import 'source.vue?vue&type=style&index=1&scoped&lang=scss'
97-
```
98-
99-
And webpack will expand it to:
100-
101-
``` js
102-
import 'style-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
103-
```
104-
105-
3. When processing the expanded requests, the main `vue-loader` will get invoked again. This time though, the loader notices that the request has queries and is targeting a specific block only. So it selects (`src/select.ts`) the inner content of the target block and passes it on to the loaders matched after it.
106-
107-
4. For the `<script>` block, this is pretty much it. For `<template>` and `<style>` blocks though, a few extra tasks need to be performed:
108-
109-
- We need to compile the template using the Vue template compiler;
110-
- We need to post-process the CSS in `<style scoped>` blocks, **before** `css-loader`.
111-
112-
Technically, these are additional loaders (`src/templateLoader.ts` and `src/stylePostLoader.ts`) that need to be injected into the expanded loader chain. It would be very complicated if the end users have to configure this themselves, so `VueLoaderPlugin` also injects a global [Pitching Loader](https://webpack.js.org/api/loaders/#pitching-loader) (`src/pitcher.ts`) that intercepts Vue `<template>` and `<style>` requests and injects the necessary loaders. The final requests look like the following:
113-
114-
``` js
115-
// <template lang="pug">
116-
import 'vue-loader/template-loader!pug-loader!vue-loader!source.vue?vue&type=template'
117-
118-
// <style scoped lang="scss">
119-
import 'style-loader!css-loader!vue-loader/style-post-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
120-
```
31+
```javascript
32+
//template
33+
const fileName = src.match (/(\w+)\.vue/)[1]
34+
const matchResource = `${fileName}${descriptor.template.attrs.lang ? '.' + descriptor.template.attrs.lang : ''}?type=template!=!vue-loader!`
35+
const request = templateRequest = stringifyRequest (matchResource + src + query)
36+
//script
37+
const matchResource = `${fileName}.${descriptor.script.attrs.lang || 'js'}!=!vue-loader!`
38+
//style
39+
const matchResource = `${fileName}.${style.attrs.lang || 'css'}?type=style!=!vue-loader!`
40+
```
41+
## VueLoaderPlugin
42+
1. 在template block中, 处理顺序为vue-loader->(模板引擎)-loader->templateLoader,因为最后要由templateLoader生成render函数.
43+
2. 在style block中,处理顺序为vue-loader->预处理器-loader->stylePostLoader->css-loader->style-loader,故stylePostLoader应该一直确保在css-loader之前.
44+
3. 在script block中,直接交给webpack处理js规则就可.
45+
46+
```javascript
47+
const templateLoaderRule = {
48+
loader: templateLoaderPath,
49+
resourceQuery: query => {
50+
const parsed = qs.parse (query.slice (1))
51+
return parsed.type === "template"
52+
}
53+
}
54+
const stylePostLoaderRule = {
55+
loader: stylePostLoaderPath,
56+
resourceQuery: query => {
57+
const parsed = qs.parse (query.slice (1))
58+
return parsed.type === "style"
59+
}
60+
}
61+
for (let rule of rules) {
62+
let loaders = rule.use
63+
for (let i in loaders) {
64+
if (loaders[i].loader === 'css-loader') {
65+
loaders.splice (++i, 0, stylePostLoaderRule)
66+
}
67+
}
68+
}
69+
// replace original rules
70+
compiler.options.module.rules = [
71+
templateLoaderRule,
72+
...rules
73+
]
74+
```
75+
可以看到就是把pitcher中干的事在这里做了,确保templateLoader执行顺序在webapck rules之后,确保stylePostLoader执行在webapck rules中的css-loader之前.
76+
77+
# 优化
78+
* 减少了module生成,webpack的整体流程是建立在对module对象的操作上,故module减少会优化webpack依赖构建和chunk graph生成
79+
* 减少了rules,webpack的resolve流程确定loader是和rules中条目一一对比的,显然会减少对比次数.
80+
81+
在ruoyi前端框架下测试数据:
82+
83+
```json
84+
//origin vue-loader
85+
{
86+
"time": 38325,
87+
"moduleNums": 1734,
88+
"chunkNums": 13,
89+
"vueFileNums": 84,
90+
"vueStyleRequestNums": 66,
91+
"vueStylePitcherReqNums": 33,
92+
"vueTemplateRequestNums": 81,
93+
"vueTemplatePitcherReqNums": 81,
94+
"vueScriptRequestNums": 83,
95+
"vueScriptPitcherReqNums": 83
96+
}
97+
// matchResource vue-loader
98+
{
99+
"time": 36903,
100+
"moduleNums": 1537,
101+
"chunkNums": 13,
102+
"vueFileNums": 84,
103+
"vueStyleRequestNums": 66,//实际style block数量33 因为css-loader会额外生成一次request
104+
"vueStylePitcherReqNums": 0,
105+
"vueTemplateRequestNums": 81,
106+
"vueTemplatePitcherReqNums": 0,
107+
"vueScriptRequestNums": 83,
108+
"vueScriptPitcherReqNums": 0
109+
}
110+
```

lib/codegen/styleInjection.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ module.exports = function genStyleInjectionCode(
2222
const src = style.src || resourcePath
2323
const attrsQuery = attrsToQuery(style.attrs, 'css')
2424
const inheritQuery = `&${loaderContext.resourceQuery.slice(1)}`
25+
const fileName = src.match(/(\w+)\.vue/)[1]
26+
const matchResource = `${fileName}.${style.attrs.lang || 'css'}?type=style&vue!=!vue-loader!`
2527
// make sure to only pass id when necessary so that we don't inject
2628
// duplicate tags when multiple components import the same css file
2729
const idQuery = style.scoped ? `&id=${id}` : ``
2830
const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${inheritQuery}`
29-
return stringifyRequest(src + query)
31+
return stringifyRequest(matchResource + src + query)
3032
}
3133

3234
function genCSSModulesCode(style, request, i) {

lib/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ module.exports = function (source) {
112112
const src = (script && !scriptSetup && script.src) || resourcePath
113113
const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js')
114114
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
115-
const request = stringifyRequest(src + query)
115+
const fileName = src.match(/(\w+)\.vue/)[1]
116+
const matchResource = `${fileName}.${descriptor.script.attrs.lang || 'js'}!=!vue-loader!`
117+
const request = stringifyRequest(matchResource + src + query)
116118
scriptImport =
117119
`import script from ${request}\n` + `export * from ${request}` // support named exports
118120
}
@@ -128,7 +130,9 @@ module.exports = function (source) {
128130
// const tsQuery =
129131
// options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``
130132
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
131-
const request = (templateRequest = stringifyRequest(src + query))
133+
const fileName = src.match(/(\w+)\.vue/)[1]
134+
const matchResource = `${fileName}${descriptor.template.attrs.lang ? '.' + descriptor.template.attrs.lang : ''}?type=template&vue!=!vue-loader!`
135+
const request = templateRequest = stringifyRequest(matchResource + src + query)
132136
templateImport = `import { render, staticRenderFns } from ${request}`
133137
}
134138

lib/plugin-webpack4.js

Lines changed: 12 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ class VueLoaderPlugin {
7070
vueLoaderUse.ident = 'vue-loader-options'
7171
vueLoaderUse.options = vueLoaderUse.options || {}
7272

73-
// for each user rule (except the vue rule), create a cloned rule
74-
// that targets the corresponding language blocks in *.vue files.
75-
const clonedRules = rules.filter((r) => r !== vueRule).map(cloneRule)
76-
7773
// rule for template compiler
7874
const templateCompilerRule = {
7975
loader: require.resolve('./loaders/templateLoader'),
@@ -98,26 +94,26 @@ class VueLoaderPlugin {
9894
.map(cloneRuleForRenderFn)
9995
}
10096

101-
// global pitcher (responsible for injecting template compiler loader & CSS
102-
// post loader)
103-
const pitcher = {
104-
loader: require.resolve('./loaders/pitcher'),
105-
resourceQuery: (query) => {
97+
const stylePostLoaderRule = {
98+
loader: require.resolve('./loaders/stylePostLoader'),
99+
resourceQuery: query => {
106100
const parsed = qs.parse(query.slice(1))
107-
return parsed.vue != null
108-
},
109-
options: {
110-
cacheDirectory: vueLoaderUse.options.cacheDirectory,
111-
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
101+
return parsed.vue != null && parsed.type === 'style'
112102
}
113103
}
114104

105+
for (const rule of rules) {
106+
const loaders = rule.use
107+
for (let i in loaders) {
108+
if (loaders[i].loader === 'css-loader') {
109+
loaders.splice(++i, 0, stylePostLoaderRule)
110+
}
111+
}
112+
}
115113
// replace original rules
116114
compiler.options.module.rules = [
117-
pitcher,
118115
...jsRulesForRenderFn,
119116
...(is27 ? [templateCompilerRule] : []),
120-
...clonedRules,
121117
...rules
122118
]
123119
}
@@ -133,51 +129,6 @@ function createMatcher(fakeFile) {
133129
}
134130
}
135131

136-
function cloneRule(rule) {
137-
const { resource, resourceQuery } = rule
138-
// Assuming `test` and `resourceQuery` tests are executed in series and
139-
// synchronously (which is true based on RuleSet's implementation), we can
140-
// save the current resource being matched from `test` so that we can access
141-
// it in `resourceQuery`. This ensures when we use the normalized rule's
142-
// resource check, include/exclude are matched correctly.
143-
let currentResource
144-
const res = Object.assign({}, rule, {
145-
resource: {
146-
test: (resource) => {
147-
currentResource = resource
148-
return true
149-
}
150-
},
151-
resourceQuery: (query) => {
152-
const parsed = qs.parse(query.slice(1))
153-
if (parsed.vue == null) {
154-
return false
155-
}
156-
if (resource && parsed.lang == null) {
157-
return false
158-
}
159-
const fakeResourcePath = `${currentResource}.${parsed.lang}`
160-
if (resource && !resource(fakeResourcePath)) {
161-
return false
162-
}
163-
if (resourceQuery && !resourceQuery(query)) {
164-
return false
165-
}
166-
return true
167-
}
168-
})
169-
170-
if (rule.rules) {
171-
res.rules = rule.rules.map(cloneRule)
172-
}
173-
174-
if (rule.oneOf) {
175-
res.oneOf = rule.oneOf.map(cloneRule)
176-
}
177-
178-
return res
179-
}
180-
181132
function cloneRuleForRenderFn(rule) {
182133
const resource = rule.resource
183134
const resourceQuery = rule.resourceQuery

public/img.png

30.8 KB
Loading

public/img_1.png

19.8 KB
Loading

0 commit comments

Comments
 (0)