爲了更好的說明,咱們模仿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等相關項),如此,便實現了。