前端工程化4-webpack之loader和plugin


寫在前面

弄清楚了tapable,我們基本上對webpack的plugin沒有什么問題了,下面我們將webpack的plugin和loader組合起來看一下。我們主要從以下幾個方面來深入研究。照着網上的資料寫幾個例子(模仿),然后大致了解loader是什么(作用),它是怎么執行的(原理)以及找一個比較有代表性的loader來看看它的代碼(源碼),最后我們可以自己寫一個自定義的loader(學以致用)。plugin也可以套用這樣的模式來進行學習。參考了官網給出的一些例子之后pluginloader之后
,我開了兩個工程webpack-demo-loaderwebpack-demo-plugin來寫一些示例來深入研究下webpack的loader和plugin

loader

loader即加載器,由於webpack只能打包js模塊,如果要打包其他文件,如jsx、vue、css等文件,必須要將其轉化成js模塊形式。而這個轉化的中間工具就是loader,我們可以理解為loader即文件的預處理。例如我們要打包css文件,我們就要使用css-loader和style-loader,要打包圖片文件我們要使用file-loader或者url-loader。我們常用的loader有如下幾個:
style-loader、css-loader、vue-loader、babel-loader、ts-loader、file-loader、url-loader等

loader使用

loader的使用方法大概就是在webpack.config.js配置文件中的module的rules中添加項。具體的使用方法在官網的文檔loader以及Loader Interface寫的非常清楚,照着寫幾遍大概就能熟練使用了。我們主要看下Loader Interface這節,這里解釋了loader的原理,對我們理解loader以及后面寫自定義loader有很重大的意義。loader 本質上是導出為函數的 JavaScript 模塊。loader runner 會調用此函數,然后將上一個 loader 產生的結果或者資源文件傳入進去。loader也給出了很多鈎子,都存在上下文對象this上的,這個上下文對外開放了很多方法,我們能很輕松的操作源碼字符串。

loader執行原理

為了研究loader的執行原理,我們建了一個webpack應用實例來專門探索loader。webpack-demo-loader,通過上述的例子我們可以了解到如何在項目中配置使用loader。通過使用上述幾個常用的loader我們可以大致窺探出webpack執行loader的工作的過程,大概的過程就是先注冊,可以理解成數據結構--棧。

vue-loader研究

vue-loader是一個比較有代表性的loader,我們找到其源碼來研究一下vue的模版語法。基本上就是基於webpack從0開始創建一個vue腳手架項目webpack-demo-vueloader
創建該工程也比較簡單,按照如下步驟即可

  • 創建目錄並初始化
mkdir webpack-demo-vueloader && cd webpack-demo-vueloader

npm init -y 
  • 安裝vue vue-loader vue-template-compiler
  • 安裝 webpack webpack-cli webpack-dev-server(這三個包版本會相互影響,所以固定版本了)
npm install --save vue vue-loader vue-template-compiler

npm install --save webpack webpack-cli webpack-dev-server@3.11.0

根目錄新建webpack.config.js

const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  entry: './src/index.js',
  mode: 'development',
  output: {
    filename: '[name].bundle.js',
    path: __dirname + '/dist'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  devServer: {
    contentBase: __dirname + '/dist',
    host: '127.0.0.1',
    port: 8000
  },
  plugins: [
    new VueLoaderPlugin()
  ],
}
  • 新建src目錄,創建index.js和app.vue
import Vue from 'vue';
import App from './app.vue';

new Vue({
  el: '#root',
  render: h => h(App)
});

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: "Hello world!",
    };
  },
  created: function () {
    console.log("create");
  },
  mounted: function () {
    console.log("mounted");
  },
};
</script>
  • 最后在package.json的script下添加執行命令就完成了一個最基本的vue腳手架
  "start": "webpack serve",
  "build": "webpack",

測試項目可以完全跑起來了,我們下一步來研究一下vue-loader是如何工作的,從上面創建的工程中package.json中依賴的包我們可以看到,要讓vue項目完整的在webpack中跑起來,最少要依賴三個包,分別是vue、vue-loader和vue-template-compiler。我們知道vue-loadervue-template-compiler是webpack和vue的中間連接接工具,查看源碼我們得知,vue-template-compiler主要的作用是將vue的模版代碼的字符串編譯成ast和渲染函數的形式。vue-loader的作用在官網解釋的很清楚,總結起來就是一句話,vue-loader的作用就是能將.vue文件轉化成渲染函數的形式,可以讓webpack完成打包。webpack只能打包js模塊,在單頁應用中,一個js文件可以理解為一個js模塊,vue-loader的作用就是能將.vue文件編譯成webpack能夠識別的模塊形式.

寫一個自定義的loader

我們知道,loader能對js模塊字符串的形式進行操作,那我們就寫一個能夠幫助我們清除console.log的loader。源碼地址在webpack-demo-vueloader里的loaders/removeLog.js

plugin

上一節我們知道了webpack的plugin的核心是tapable,本質上是一個訂閱-發布模式,先把插件寫好注冊到webpack.config.js文件中,在編譯源代碼的時候再調用它。我們呢常用的plugin有如下幾個:
webpack-bundle-analyzer、CommonsChunkPlugin、DllPlugin、ExtractTextWebpackPlugin、HtmlWebpackPlugin、HotModuleReplacementPlugin等

plugin使用

我們說loader就是一個js函數,可以操作js字符串形式的函數,那么plugin就是一個類。plugin使用非常簡單,如果是引用第三方插件,也就是別人寫好的npm包,只需在webpack.config.js配置一下即可,首先require進來,然后在plugins中new一個實例,如果有參數,直接傳進去即可。示例(html-webpack-plugin)如下

const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin()
  ]
  ...
}

自定義的plugin,首先需要你先開發好plugin(參考tapable democompilercompilation),然后用同樣的方式引入即可。
plugin會深入到代碼編譯和構建的各個階段,學習plugin可以幫助我們窺探代碼編譯的全過程。plugin對外提供了兩個主要的鈎子,一個是compiler,另一個是compilation

  • Compiler 對象包含了 Webpack 環境所有的的配置信息,包含 options,loaders,plugins 這些信息,這個對象在 Webpack 啟動時候被實例化,它是全局唯一的,可以簡單地把它理解為 Webpack 實例;
  • Compilation 對象包含了當前的模塊資源、編譯生成資源、變化的文件等。當 Webpack 以開發模式運行時,每當檢測到一個文件變化,一次新的 Compilation 將被創建。Compilation 對象也提供了很多事件回調供插件做擴展。通過 Compilation 也能讀取到 Compiler 對象。

VueLoaderPlugin研究

VueLoaderPlugin是有兩個plugin分別兼容webpack的<=4、5兩個版本。為什么會有兼容問題,查看文檔發現webpack<=4 normalModuleLoader已經廢棄,也就是說webpack<=4可以使用normalModuleLoader鈎子來訪問loader,webpack5就要使用NormalModule.getCompilationHooks(compilation).loader來訪問loader。
plugin-webpack4.js
plugin-webpack5.js

兩個plugin的代碼量都不多,我們就來讀一下plugin-webpack5.js的代碼,看下這個plugin究竟做了什么事。官網上也說了很清楚,vueLoaderPlugin的職責是將你定義過的其它規則復制並應用到.vue 文件里相應語言的塊。例如,如果你有一條匹配 /.js$/ 的規則,那么它會應用到.vue 文件里的<script>塊。一個vue文件包含template模版,script和style等3個部分,編譯的時候需要將他們區分開,並且把loader應用到各個部分,因此需要這個plugin來做這件事。

寫一個自定義plugin

在寫自定義plugin之前,我們要了解webpack打包過程,大概有如下幾個步驟

  • 讀取配置
  • 生成compiler(編譯器)對象
  • 初始化
  • run/watch
  • 生成compilation(構建包)
  • emit:文件內容准備完成,准備生成文件
  • afterEmit:文件已經寫入磁盤完成
  • done: 完成編譯
    在compilation生成之后,emit階段之前我們都可以操作源碼模塊,compilation也提供了很多對外的鈎子,以便開發者能更好的操作模塊。
    弄清楚大概的打包過程之后,我們就知道怎么使用webpack plugin compiler鈎子和compilation鈎子的使用時機了。

webpack-demo-plugin

上面的demo我先把構建之后的包輸出到一個json文件中,以便我們查看構建之后的模塊是什么結構還有里面有什么內容。通過構建完成之后的包可以發現我們的js代碼已經完全轉化成ast的形式,我們可以在astexplorer網站上測試我們的js代碼。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM