为了更好的说明,咱们模仿Vue.js开发了一个相似的简化版本的前端框架Quick Paper(文档) 来帮助你理解一些细节。所以在开始以前,让咱们先大体了解一下此项目的结构,方便后续描述。css
舒适提示:咱们推荐你在开始以前去Github上把此项目clone下来后,对照着源码进行学习!html
其实你只须要关注下面四个文件夹:前端
接着,咱们对源码src部分的目录结构再稍微展开一下(由于咱们这里的重点不是源码部分,而是那些loader或plug是如何配置完成一系列解析工做的,所以源码部分就在下面简单的说明就点到为止)。vue
core:框架对象的基础代码node
instance:框架对象webpack
因此从上面的代码就能够看出来,文件src/core/instance/index.js是对象自己,从这个文件开始开便可!git
若是有什么不清楚的,能够去issue给咱们留言。github
对于咱们用于学习的项目Quick Paper而言,咱们是把代码整合到文件.paper中去,文件结构大体以下:web
<template> <!-- 页面的元素在这里 --> </template> <script> // 逻辑控制代码在这里 export default { }; </script> <style> /* 在这里编辑样式代码 */ </style>
你想,咱们使用webpack打包项目的时候,他是不可能认识.paper文件的,固然就没法知道如何解析上面这份文件了,而开发一个loader用以解析上面的文件,就是这里要说明的。api
在说明loader以前,咱们先要看看咱们编辑的.paper是如何被咱们使用的。由于如何使用就决定了咱们须要如何解析。
和vue相似,先假设咱们有一个App.paper文件:
import App from './App.paper'; new QuickPaper({ render:createElement => createElement(App), // ... });
由于render里面只记录了页面内容,但是.paper文件里面但是记录了页面内容+逻辑控制+页面样式的。其他的内容怎么办?
// 导入js [逻辑控制] import script from './${filename}?QuickPaper&type=script&lang=js&hash=${id}&'; // 导入css [页面样式] import './${filename}?QuickPaper&type=style&lang=css&hash=${id}&'; script.render=${code}; // [页面内容] export default script;
能够看出来,页面内容直接默认导出后给render配置项便可,别的内容由于新增了导入语句,会触发对应的loader进行解析,也就是说,这里其实能够分为两步:
好比页面样式部分的导入语句:
import './${filename}?QuickPaper&type=style&lang=css&hash=${id}&';
咱们是如何让webpack知道这是一个样式文件,而且是使用css仍是scss或别的loader来解析的,这属于插件须要说明的部分。在此以前,咱们还须要先说明一下样式loader的工做原理。
根据返回值类型,能够把loader分红两种:一种是返回js代码(也就是一个模块的代码,有相似module.export语句)的loader,一个是不能做为最左边loader的其余loader(好比返回一个CSS字符串)。
咱们来看看咱们webpack里面是如何配置css的loader的:
{ test: /\.css$/, loader: ['quick-paper/style-loader/index.js', 'css-loader', 'postcss-loader'] }
这里的重点是css-loader,他属于第一种,返回js代码的loader,对于咱们自定义的'quick-paper/style-loader/index.js'而言,若是让loader按照从右往左的顺序执行,很难拿到真正的css代码。
在说明如何解决上个问题前,咱们须要先说明一下loader的picth和执行顺序。
好比上面配置的三个loader而言,执行顺序分为Pitch阶段和Normal阶段(能够理解为loader自己的行为):
有一个特色是,在Pitch阶段,若是某个loader有返回值,就会中止后续执行。
舒适提示:中止执行的意思是,在其右边的loader,包括本身都执行完毕了(Pitch阶段和Normal阶段都结束了),返回的值会返回给前一个loader(Normal阶段)!
这里,咱们就能够借助给'quick-paper/style-loader/index.js'设置一个有返回值的Pitch来实现。
看看代码结构:
// quick-paper/style-loader/index.js 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;
咱们在'quick-paper/style-loader/index.js'中定义了Picth方法,在此方法里面,返回了一个js字符串,项目运行的时候会运行这段字符串,这段字符串的意义就是调用样式loader获取真正的css后,运行addStylesClient.js方法使得在页面生效。
舒适提示:关于addStylesClient.js方法请直接查看项目源码,很容易读懂,给样式添加hash值让scope生效,就是这个方法里。
咱们这里来解释一下,一个.paper文件拆分之后,如何让对应的loader来进行解析。
首先须要理解,什么是插件?
你能够这样理解:若是说loader帮助webpack得到解析更多类型文件,那插件就是一个打杂工,前者有专门的分工,后者是在特殊状况下帮助,而不是针对某个文件。
好比你能够在每次打包前调用一个查看删除上次打包的结果,或者在打包失败的时候重置一些参数,或者是别的一些操做等。
那么,咱们这里须要插件干什么?
别忘了咱们的需求是(拿css举例子),若是遇到:
import './${filename}?QuickPaper&type=style&lang=css&hash=${id}&';
这样的导入语句,咱们工具lang=css发现是一个样式文件,应该交给专门解析css的loader处理,固然,咱们能够主动修改webpack的配置:
{ test: /type=style&lang=css/, loader: ['quick-paper/style-loader/index.js', 'css-loader', 'postcss-loader'] }
但是,为了更简单,咱们能够经过插件,在每次打包前对loader配置进行修改(固然,也包括js等相关项),如此,便实现了。