vue-loader原理基本实现

788 阅读4分钟

根据vue-loader原理实现了基础版本

vue3版本能解析.vue文件 my-vue-loader源码

vue2版本 my-vue-loader源码,在template解析时有不一样,手动添加export

学习视频 vue-loader

vue-loader原理过程

1、整体过程是,第一次进入vue-loader中,第二次进入VueLoaderPlugin中,第三次再进入vue-loader中,最终得到编译完成代码

2、第一次进入vue-loader是因为,VueLoaderPlugin 初始化 apply方法中,在所有loader前添加一个 带 pitch 方法的loader叫pitcher

3、pitcher的pitch方法因为是第一个loader,pitch方法最先执行,resourceQuery方法判断,当前引入文件是否带有vue参数,发现没有vue参数,那么就跳过pitch,继续执行,就进入了vue-loader,这就是第一次执行

import App from './App.vue'

我们代码中这样引入Vue文件,就会第一次进入 vue-loader方法中

4、上面这句代码,经过vue-loader后返回如下代码

import scrpt from './App.vue?vue&type=script&id=data-v-yug6h445&lang=js'
import { render } from './App.vue?vue&type=template&id=data-v-yug6h445&lang=js'
import './App.vue?vue&type=style&id=data-v-yug6h445&index=0&lang=style'
import './App.vue?vue&type=style&id=data-v-yug6h445&index=1&lang=style'

script.render = render
script.__scopeId = 'data-v-yug6h445'
export default script

5、上述代码就是第一次进入vue-loader后返回的代码,webpack识别到代码里新增了三个import,又执行代码解析,此时就会进入VueLoaderPlugin的pitch方法中,因为在pitch方法中有判断路径参数中是否有App.vue?vue,因为第一次没有vue参数,所以就直接跳过了pitch方法,第二次就进入了pitch方法

6、pitch方法的目的是用内联方式把template,script,style需要用到的解析loader都写在import语句里面,所以pitch方法后,返回的代码变为了

import scrpt from '!!babel-loader!vue-loader!./App.vue?vue&type=script&id=data-v-yug6h445&lang=js'
import { render } from '!!vue-loader!./App.vue?vue&type=template&id=data-v-yug6h445&lang=js'
import '!!style-loader!css-loader!./App.vue?vue&type=style&id=data-v-yug6h445&index=0&lang=style'
import '!!style-loader!css-loader!./App.vue?vue&type=style&id=data-v-yug6h445&index=1&lang=style'

script.render = render
script.__scopeId = 'data-v-yug6h445'
export default script

7、但是怎么知道每种类型文件由哪些loader执行呢?答案就是VueLoaderPlugin中,里面会改写在webpack.config.js中配置的rules对象,一是在rules第一个增加pitcher的loader,二是克隆原有rules并添加进入,但是cloneRules会删除test判断,增加resourceresourceQuery,其中把每个文件的后缀比如 import scrpt from './App.vue?vue&type=script&id=data-v-yug6h445&lang=js'改为./App.vue.js,取得文件路径和lang字段拼接,这样第二步中pitch就就可以得到匹配到的loader集合了

8、第二步pitch中还需要增加./stylePostLoader.js,这个loader会放到 style-loader, css-loader , ./stylePostLoader.js,这样来处理css文件,因为stylePostLoader中调用 @vue/compiler-sfc编译style样式,增加scoped属性选择器

9、现在我们得到了如下的代码import

import scrpt from '!!babel-loader!vue-loader!./App.vue?vue&type=script&id=data-v-yug6h445&lang=js'

现在到了第三步,再次进入vue-loader,这里是根据是否有参数判断是第一次还是第二次进入,第二次进入需要解析出每个template, script, style 块的具体代码返回了,但是也有不同。

需要不同调用compileTemplate,compileScript,style则是根据index返回不同style块

const VueCompiler = require('@vue/compiler-sfc')

    const params = getUrlParams(loaderContext.resourceQuery)
    const id = params.get('id')

    switch (params.get('type')) {
        case 'template':
            const { code } = VueCompiler.compileTemplate({
                source: descriptor.template.content,
                id: id
            })
            return loaderContext.callback(null, code)
        case 'script':
            const script = VueCompiler.compileScript(descriptor, { id: id })
            return loaderContext.callback(null, script.content)
        case 'style':
            const style = descriptor.styles[params.get('index')].content
            return loaderContext.callback(null, style)
        default:
            return loaderContext.callback(null, sourceCode)
    }

上述就是vue-loader的流程。

项目中从0配置webpack,babel,vue-loader,css解析包

"devDependencies": {
    "@babel/core": "^7.21.3",
    "@babel/preset-env": "^7.20.2",
    "@vue/babel-plugin-jsx": "^1.1.1",
    "@vue/babel-preset-jsx": "^1.4.0",
    "babel-loader": "^9.1.2",
    "css-loader": "^6.7.3",
    "hash-sum": "^2.0.0",
    "html-webpack-plugin": "^5.5.0",
    "style-loader": "^3.3.2",
    "vue-loader": "^17.0.1",
    "webpack": "^5.76.3",
    "webpack-cli": "^5.0.1"
},
"dependencies": {
    "vue": "^3.2.47"
}

关于 vue-loader ,需要了解到它的作用,我们普遍都是在SFC文件中写 template, script, style ,通过看源码,还有custom标签,基本使用方式如下:

1、webpack中配置 loader -> vue-loader

module: {
    rules: [
        {
            test: /.vue$/,
            // use: ['vue-loader']
            loader: path.resolve(__dirname, './vue-loader/index.js')
        }
    ]
},

2、配置插件 VueLoaderPlugin

const { VueLoaderPlugin } = require('vue-loader')

plugins: [
    new VueLoaderPlugin()
]

为什么会这么麻烦,要配置loader还要配置plugin呢?后面我们说清楚之后就很明白了。

前置知识: loader 的 pitch 方法

官网引述:

loader 总是 从右到左被调用。有些情况下,loader 只关心 request 后面的 元数据(metadata) ,并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的 pitch 方法。

举个例子:

我们配置的css处理器,都是如下

module:{
    rules:[
        {
            test: /\.[s]css/,
            use: ['style-loader','css-loader','postcss-loader','sass-loader']
        }
    ]
}

我们知道,执行顺序是从右往左执行,sass-loader -> postcss-loader -> css-loader -> style-loader,这种就叫normal loader执行顺序。

而,我们知道loader的写法如下,就是导出一个默认函数,参数是源文件代码,返回值是esm或者commonjs的导出字符串,比如:

function MyLoader(source){
    return `export default 'hello world!'`
}

![图片转存失败,建议将图片保存下来直接上传

那么,MyLoader这个loader就会导出hello world 这句话

但是normal loader执行顺序是从 右->左,还有一种顺序,如果loader导出上有 pitch 属性方法,那么pitch属性方法会先执行,按照左 -> 右的顺序,如果pitch有返回值,那么就中断执行后面的

image.png