博客 / 詳情

返回

從vue-loader開始理解webpack的一些設計思想

寫在前面

為了更好的説明,我們模仿Vue.js開發了一個類似的簡化版本的前端框架Quick Paper(文檔) 來幫助你理解一些細節。因此在開始之前,讓我們先大致瞭解一下此項目的結構,方便後續描述。

温馨提示:我們推薦你在開始之前去Github上把此項目clone下來後,對照着源碼進行學習!

目錄結構

其實你只需要關注下面四個文件夾:

  1. src:框架源碼;
  2. loader:類似vue-loader,用來解析.paper文件的loader;
  3. style-loader:和上面的一樣,只不過這個是用來解析樣式文件的(包括.css文件和.paper文件中的style標籤部分);
  4. loader-plug:一些輔助功能,比如校對webpack的一些配置。

框架源碼

接着,我們對源碼src部分的目錄結構再稍微展開一下(因為我們這裏的重點不是源碼部分,而是那些loader或plug是如何配置完成一系列解析工作的,因此源碼部分就在下面簡單的説明就點到為止)。

  • core:框架對象的基礎代碼

    • global-api:給框架對象掛載的全局方法
    • instance:框架對象

      • index.js:框架對象運行入口
      • init.js:負責對象的初始化相關工作
      • lifecycle.js:負責對象的生命週期管理
      • render.js:對象的渲染啓動等方面的任務
    • observe:監聽數據改變方法(被框架對象使用)
    • vnode:虛擬DOM相關代碼(被框架對象使用)
  • module:為框架對象擴展內置指令,組件等的地方
  • tools:一些工具方法,因為複用性和方便管理,集中寫在一起
  • index.js:打包入口文件,也就是這份文件把所有的資源整合成一個完整的框架

所以從上面的代碼就可以看出來,文件src/core/instance/index.js是對象本身,從這個文件開始開即可!

如果有什麼不清楚的,可以去issue給我們留言。

Loader和執行順序

對於我們用於學習的項目Quick Paper而言,我們是把代碼整合到文件.paper中去,文件結構大致如下:

<template>
    <!-- 頁面的元素在這裏 -->
</template>
<script>
    // 邏輯控制代碼在這裏
    export default {

    };
</script>
<style>
    /* 在這裏編輯樣式代碼 */
</style>

你想,我們使用webpack打包項目的時候,他是不可能認識.paper文件的,當然就無法知道如何解析上面這份文件了,而開發一個loader用以解析上面的文件,就是這裏要説明的。

loader

在説明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進行解析,也就是説,這裏其實可以分為兩步:

  • 第一步:對於未考慮到的內容執行新的導入語句,觸發對應的loader解析
  • 第二步:導出render需要的內容

style-loader

比如頁面樣式部分的導入語句:

import './${filename}?QuickPaper&type=style&lang=css&hash=${id}&';

我們是如何讓webpack知道這是一個樣式文件,並且是使用css還是scss或別的loader來解析的,這屬於插件需要説明的部分。在此之前,我們還需要先説明一下樣式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的picth和執行順序。

比如上面配置的三個loader而言,執行順序分為Pitch階段和Normal階段(可以理解為loader本身的行為):

  • Pitch階段:'quick-paper/style-loader/index.js'->'css-loader'->'postcss-loader'
  • Normal階段:'postcss-loader'->'css-loader'->'quick-paper/style-loader/index.js'

有一個特點是,在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等相關項),如此,便實現了。

user avatar yaofly 頭像 flymon 頭像 sunhengzhe 頭像 gaoming13 頭像 tingzhong666 頭像 cipchk 頭像 mouyi_63f6f68ba66d9 頭像 nealyang231 頭像 jello_5c89ccf558dcc 頭像 xiaodaigua_ray 頭像 zhengcaiyunqianduantuandui 頭像 wuyuedexingkong 頭像
17 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.