|
1 | | -# vue-loader [](https://circleci.com/gh/vuejs/vue-loader/tree/master) [](https://ci.appveyor.com/project/yyx990803/vue-loader/branch/master) |
| 1 | +# vue-loader-matchresource |
| 2 | +# 功能 |
| 3 | +修改了vue-loader的rules匹配策略,用webpack4 matchResource功能替代克隆Rules实现. |
2 | 4 |
|
3 | | -> webpack loader for Vue Single-File Components |
| 5 | +带来的是更直观的rules匹配策略,减少了webpack编译.vue文件生成的module数量. |
4 | 6 |
|
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) |
6 | 8 |
|
7 | | -- [Documentation](https://vue-loader.vuejs.org) |
8 | | -- [Migrating from v14](https://vue-loader.vuejs.org/migrating.html) |
| 9 | +来两张流程图来看一下出入点: |
9 | 10 |
|
10 | | -## What is Vue Loader? |
| 11 | +## before |
| 12 | + |
11 | 13 |
|
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 | + |
13 | 16 |
|
14 | | -``` vue |
15 | | -<template> |
16 | | - <div class="example">{{ msg }}</div> |
17 | | -</template> |
18 | 17 |
|
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 |
74 | 18 |
|
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 |
78 | 23 |
|
79 | | - Will expand to: |
| 24 | +## vue-loader |
| 25 | +如`./a.vue?query...`, |
80 | 26 |
|
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 |
84 | 30 |
|
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 | +``` |
0 commit comments