一、Loader的执行顺序
在正式开始前,我们先简单看一下在Webpack中,多个loader的执行顺序。比如我们配置了如下loader:
loader:["loader3", "loader2", "loader1"]平时我们感觉的执行顺序是:
loader1 -> loader2 -> loader3这是正确的,只不过,一个loader可以设置pitch方法,比如我们对loader1设置:
const loader1 = function(source) {// loader};loader1.pitch = function (remainingRequest) {// pitch};module.exports = loader1;
实际上,如果我们对每个loader都配置了pitch后,执行顺序就应该是:
picth3 -> pitch2 -> pitch1-> loader1 -> loader2 -> loader3
所以,pitch先于loader执行,如果一个loader没有配置pitch就跳过。
还有一点需要明确: `如果pitch有return,会提前返回执行` ;所以,上述的执行可能会被某个pitch中断。
二、Vue文件如何加载?
比如我们有如下一个文件(App.vue):
<template><!-- 页面的元素在这里 --></template><script>// 逻辑控制代码在这里export default {};</script><style>/* 在这里编辑样式代码 */</style>
你想,我们使用webpack打包项目的时候,他是不可能认识.vue文件的,当然就无法知道如何解析上面这份文件了,而vue-loader就是告诉webpack如何解析上面的文件,而vue-loader如何做到这一点的,就是这里要说明的了。
看一下下面的代码:
import Vue from 'vue';import App from './App.vue';new Vue({el: '#app',render: createElement => createElement(App)});
因为render里面只记录了页面内容,可是.vue文件里面可是记录了页面内容+逻辑控制+页面样式的。其余的内容怎么办?
2.1 vue-loader
事实上,vue-loader会把App.vue解析一下,然后返回类似下面的语句:
// 导入js [逻辑控制]import script from './${filename}?Vue&type=script&lang=js&hash=${id}&';// 导入css [页面样式]import './${filename}?Vue&type=style&lang=css&hash=${id}&';script.render=${templateToRender(template)};// [页面内容]export default script;
可以看出来,页面内容直接默认导出后给render配置项即可,别的内容因为新增了导入语句,会触发对应的loader进行解析,也就是说,这里其实可以分为两步:
第一步:对于未考虑到的内容执行新的导入语句,触发对应的loader解析
第二步:导出render需要的内容
很明显,导出的`export default script;`到处的`script`包含页面`template`和`script`,其实就是`import App from './App.vue';`中的App,很好理解。
2.2 style-loader
vue中配置的是vue-style-loader,其实就是在style-loader基础上进行了一些改造。
现在看看页面样式部分的导入语句:
import './${filename}?Vue&type=style&lang=css&hash=${id}&';我们是如何让webpack知道这是一个样式文件,并且是使用css还是scss或别的loader来解析的,这属于插件需要说明的部分,我们稍后说明。在此之前,我们还需要先说明一下样式loader的工作原理。
2.2.1 为什么样式loader比较特殊?
根据返回值类型,可以把loader分成两种:一种是返回js代码(也就是一个模块的代码,有类似module.export语句)的loader,一个是不能作为最左边loader的其他loader(比如返回一个CSS字符串)。
我们来看看我们webpack里面是如何配置css的loader的:
{test: /\.css$/,loader: ['vue-style-loader', 'css-loader', 'postcss-loader']}
这里的重点是css-loader,他属于第一种,返回js代码的loader,对于我们自定义的`vue-style-loader`而言,如果让loader按照从右往左的顺序执行,很难拿到真正的css代码。
2.2.2 执行顺序(loader和picth)
有了开头`Loader的执行顺序`的说明,我们现在很容易知道,对于上面配置的三个loader而言,执行顺序分为Pitch阶段和Normal阶段(可以理解为loader本身的行为):
Pitch阶段:'vue-style-loader'->'css-loader'->'postcss-loader'
Normal阶段:'postcss-loader'->'css-loader'->'vue-style-loader'
再次记住这样一个特点:在Pitch阶段,如果某个loader有返回值,就会停止后续执行。
停止执行的意思是,在其右边的loader,包括自己都执行完毕了(Pitch阶段和Normal阶段都结束了),返回的值会返回给前一个loader(Normal阶段)!
2.2.3 那style-loader是如何利用这个特点做文章的?
如果是你来开发vue-style-loader,你肯定可以想到,我们可以借助给vue-style-loader设置一个有返回值的Pitch来实现:
const loaderApi = () => { };loaderApi.pitch = function (remainingRequest) {// request = ""!!../../node_modules/css-loader/dist/cjs.js!../../node_modules/postcss-loader/src/in...let request = loaderUtils.stringifyRequest(this, '!!' + remainingRequest)return `// 获取真正的css内容var content = require(' + request + ');// 然后调用方法添加到页面中生效require('./addStylesClient.js')(content);`;};module.exports = loaderApi;
我们在vue-style-loader中定义了Picth方法,在此方法里面,返回了一个js字符串,项目运行的时候会运行这段字符串,这段字符串的意义就是调用样式loader获取真正的css后,运行addStylesClient.js方法使得在页面生效。
关于addStylesClient.js等方法或更多细节,你可以直接查看vue-style-loader源码,很容易读懂。
三、插件VueLoaderPlugin是如何工作的?
我们这里来解释一下,一个vue文件拆分以后,如何让对应的loader来进行解析。
3.1 插件的执行时机
首先需要理解,什么是插件?
你可以这样理解:如果说loader帮助webpack获得解析更多类型文件,那插件就是一个打杂工,前者有专门的分工,后者是在特殊情况下帮助,而不是针对某个文件。
比如你可以在每次打包前调用一个查看删除上次打包的结果,或者在打包失败的时候重置一些参数,或者是别的一些操作等。
3.2 如何实现?
那么,我们这里需要插件干什么?还是拿css举例子,比如下面这句:
import './${filename}?Vue&type=style&lang=css&hash=${id}&';这样的导入语句,我们工具lang=css发现是一个样式文件,应该交给专门解析css的loader处理,当然,我们可以主动修改webpack的配置:
{test: /type=style&lang=css/,loader: ['vue-style-loader', 'css-loader', 'postcss-loader']}
可是,为了更简单,我们可以通过插件,在每次打包前对loader配置进行修改(当然,也包括js等相关项),如此,便实现了。
夜雨聆风