回顧webpack在vue.config.js中寫loader和plugin

水冗水孚發表於2023-03-10

前言

我們先思考一個問題:如果不使用webpack,前端能夠開發專案嗎?

先彆著急說出答案,我們繼續往下看...

工作中,我們都是使用框架的腳手架進行開發,主流的趨勢...

vue-cli & create-react-app和webpack的關係

我們知道,無論是Vue的vue-cli還是React的create-react-app這樣的腳手架,實際上都是給webpack做了一層封裝,包了一層殼子,並預設了一些預設常用的配置項(當然還有別的東西),以便提升開發效率。

所以它們的關係是:腳手架只是給webpack穿上了一個馬甲...

不過有時候腳手架提供的預設配置項不夠用了,就需要我們手動去寫一些plugin或者loader從而實現我們想要的功能。

學習本文的收穫

  • 通俗易懂地回顧webpack知識點
  • 學習在vue-cli腳手架中寫webpack的plugin的知識點
  • 學習在vue-cli腳手架中寫webpack的loader的知識點

webpack

平常我們寫程式碼都是模組化、元件化(工程化)進行開發,導致程式碼會進行拆分、細化、公共部分提取、引用等...

為何要模組化、元件化(工程化)進行開發呢?因為:這是軟體開發的主流和趨勢...

什麼是webpack & 談談你對webpack的理解

  • 平常我們寫程式碼都是模組化、元件化(工程化)進行開發,導致程式碼會進行拆分、細化、公共部分提取、引用等...
  • 比如:我們會寫很多.vue檔案(當然還有別的檔案如.less等)。但是我們寫的程式碼是最終要被瀏覽器訪問解析執行的,而瀏覽器不認識.vue檔案,也不認識.less檔案!
  • 瀏覽器不認識就不能解析,不能用。
  • 瀏覽器倒是認識js、css、html,可以解析出相應內容,並渲染展示。
  • 又因為 .vue檔案和.less檔案實際上也只是html、css、js化妝之後的樣式。
  • 那這樣,搞一個工具,能夠讓.vue檔案和.less檔案卸妝成html、css、js就行了。
  • 而webpack恰恰能夠做到這一點(編譯轉化打包)

所以,webpack就是:一個轉化編譯打包工具,將一些瀏覽器不認識的花裡胡哨的檔案轉化為瀏覽器認識的html、css、js檔案。

還記得我們最終打包好的dist資料夾目錄嗎?裡面就只有:html、css、js等一些資源...

這樣描述,不是十分嚴謹。準確來說,webpack是一個靜態模組資源打包工具,官網:https://webpack.docschina.org/concepts/

回到最開始的那個問題~

如果不使用webpack,前端能夠開發專案嗎?

  • 問:如果不使用webpack,前端能夠開發專案嗎?
  • 答:如果一個專案炒雞小,只是用來展示一點點東西,完全可以使用原生的html、css、js去寫,這樣的話,就用不到

我們要知道webpack的作用就是,去轉化編譯打包腳手架、工程化的大專案。如果是一個小專案,完全不需要去用工程化的方式開發,直接寫,寫完了丟到伺服器上,直接用

前端工程化 == 模組化 + 元件化 + 自動化 + ...

webpack的幾個重要概念

  • 打包入口(entry)
  • 打包輸出(output)
  • 載入器(loader)
  • 外掛(plugin)
  • 模式(mode)
  • nodejs環境(environment)

webpack打包入口-entry

  • 我們知道,我們開發專案有很多檔案,比如a、b、c等。a引用了b中的東西,而b又引用了c中的東西。那麼打包翻譯的話,就需要指定從哪個檔案開始打包,打包的過程中如果遇到了有別的引用,就順藤摸瓜...
  • webpack中預設打包的入口檔案是./src/index.js檔案,這個檔案中引用了好多別的檔案,所以webpack繼續順藤摸瓜尋找、編譯、轉化、打包。
  • 而vue-cli腳手架中的入口檔案是src目錄下的main.js檔案(vue-cli改寫了webpack預設的入口檔案位置罷了)
  • 這裡我們可以去vue-cli倉庫程式碼中去找到相關的程式碼,能看到指定的打包入口

vue-cli倉庫地址:https://github.com/vuejs/vue-cli

大家把程式碼下載下來,Code --> Download ZIP ,然後在vscode中開啟程式碼,在左上角第二個放大鏡中搜尋關鍵字:src/main.js

有很多的關鍵詞,其中有一個get entryFile方法,程式碼如下:

/**
* Get the entry file taking into account typescript.
*
* @readonly
*/
get entryFile () {
    if (this._entryFile) return this._entryFile
    return (this._entryFile = fs.existsSync(this.resolve('src/main.ts')) ? 'src/main.ts' : 'src/main.js')
}

對應截圖如下:

其實vue-cli中有很多的地方的程式碼,都告知了我們vue-cli是將main.js作為webpack打包的入口的,大家多看看...

好了,至此我們見證了webpack的打包入口(entry)在vue-cli腳手架中的具體應用展現形式。大家也可以在create-react-app中去看一下webpack的打包入口檔案,一個意思

vue-cliwebpack的關係,換句話說,也像蘋果手機富士康的關係...

webpack打包輸出-output

我們知道,平常專案寫完,釋出上線,打包輸出的一般都是一個dist資料夾(也可以改)

原始的webpack是這樣寫:

const path = require('path');

module.exports = {
  entry: './src/index.js', // 從當前同級目錄下的index.js作為入口檔案,順藤摸瓜開始打包
  output: {
    path: path.resolve(__dirname, 'dist'), // 這裡的path值要是一個絕對路徑,如:E:\echarts\code\dist,所以要使用path模組來操作
    filename: 'myDemo.js',
  },
};

vue-cli中叫做outputDir並指定了預設值為dist(實際上就是webpack中的output,又是套殼子),我們透過在vue.config.js檔案中更改outputDir的值,即可修改打包換名字了

vue-cli中的程式碼如下:

exports.defaults = () => ({
  // project deployment base
  publicPath: '/',

  // where to output built files
  outputDir: 'dist', // 即為webpack中的output

  // where to put static assets (js/css/img/font/...)
  assetsDir: '',

  // filename for index.html (relative to outputDir)
  indexPath: 'index.html',

  // ......

  devServer: {
    /*
    open: process.platform === 'darwin',
    host: '0.0.0.0',
    port: 8080,
    https: false,
    hotOnly: false,
    proxy: null, // string | Object
    before: app => {}
  */
  }
})

注意看,上述的引數,就是vue.config.js需要我們設定的一些引數

vue-cli中的webpack工作流程

  1. 我們在vue.config.js中寫的符合vue-cli語法的程式碼,會被傳遞到vue-cli程式碼中
  2. vue-cli接收到以後,會再轉化一下,轉化成為符合webpack語法的配置
  3. 並透過webpack-merge這個外掛,傳遞給webpack中。
  4. webpack拿到對應配置項以後,再執行相應的打包策略方式
create-react-app這個腳手架也是類似,大抵都是套殼子,定規則,拿引數(處理),丟給webpack去打包

模式(mode)

  • 開發模式(development)
  • 生產模式(production)

nodejs環境(environment)

我們知道webpack是用js語言寫的,在nodejs建立的環境中執行,我們可以透過專案中的node_modules/webpack/bin/webpack.js檔案看到 如下圖,看一下:

child_process為node的子程式

一目瞭然...


webpack工作流程

在nodejs環境下,從入口遞迴尋找各個模組(元件)依賴關係,去打包,遇到不能直接識別的比如.vue檔案、.less檔案,就使用對應的loader去解析它。另外如果還可以在webpack的生命週期鉤子的某一個時間節點,去操作打包的內容,從而控制打包的結果。

vue.config配置webpack外掛的方法,物件寫法或函式寫法

實際上,學習webpack學的就是,別的開發者或者公司去開發的loader或者plugin,學的是webpack的生態。

webpack載入器-loader

什麼是loader

loader顧名思義,就是載入的意思,載入什麼呢?載入webpack不能直接認識的檔案,載入好以後,以供webpack去打包。

webpack直接認識的只有js和json檔案內容

有哪些常見的loader

  • vue-loader去載入.vue檔案
  • react-loader用於載入react元件的
  • style-loader將css樣式掛到style標籤下
  • css-loader編譯css樣式檔案
  • less-loader去載入.less樣式檔案
  • postcss-loader給樣式加上瀏覽器字首
  • file-loader和url-loader可以壓縮圖片資源(後者可壓成base64)
  • babel-loader、ts-loader、eslint-loader等

loader執行順序

從下到上,從右到左。

簡單的loader之去除console.log

第一步,src目錄下新建資料夾myLoader,內建立js檔案removeConsole.js檔案

一個loader就是一個模組化的js檔案

第二步,暴露一個函式,loader本質上是一個函式,在函式體內部,可以去對程式碼(字串)進行加工

plugin也是類似,也可以對程式碼字串進行加工,不過功能更加強大

第三步,寫loader函式邏輯內容

const reg = /(console.log\()(.*)(\))/g;
module.exports = function (source) {
    console.log('source', source);
    source = source.replace(reg, "")
    return source;
}
loader就是一個加工函式,回想起js中的經典的一句話,萬物皆可函式

第四步,在vue.config.js中的configureWebpack中新增使用自己寫的loader

/**
* 新增自己寫的模組loader
* */
module: {
    rules: [
      /**
       * 對.vue和.js檔案生效,不包含node_modules大資料夾,載入器的位置在
       * 當前目錄下的./src/myLoader/removeConsole.js
       * */
      // {
      //   test: /\.vue$/,
      //   exclude: /node_modules/,
      //   loader: path.resolve(__dirname, "./src/myLoader/removeConsole.js")
      // },
      // {
      //   test: /\.js$/,
      //   exclude: /node_modules/,
      //   loader: path.resolve(__dirname, "./src/myLoader/removeConsole.js")
      // }
    ],
}
如果想要給loader傳參,接參,可以在loader函式內部使用this.query接收,或者npm i -D loader-utils工具包去進一步操作。

完整程式碼示例,見GitHub倉庫:https://github.com/shuirongshuifu/elementSrcCodeStudy

更多開發loader細節,見官方文件

webpack外掛-plugin

什麼是plugin

  • loader處理不了的,去使用plugin去處理
  • webpack在打包資原始碼檔案時,也會有先後執行步驟,換句話說即為webpack的生命週期
  • 所以我們可以在對應生命週期的鉤子函式中,去編寫一些程式碼從而影響最終的打包結果
  • 這些編寫的程式碼,即為webpack的外掛(程式碼)
webpack的打包也會有很多生命週期,plugin就是在合適的時機,透過webpack提供的api,去改變輸出結果。注意,loader是一個轉換器,執行在打包之前,而plugin在整個編譯週期都起作用。

plugin結構

  • plugin是一個class類(建構函式)
  • 類中的constructor函式用來接參
  • apply函式的compiler物件自帶很多的api可以供我們呼叫
  • 透過這些api的使用最終影響打包結果

如下程式碼:

class myPlugin {
    constructor(options) {
        console.log("接參-->", options);
    }
    apply(compiler) {
       console.log('上面有非常多api,可參見compiler列印結果', compiler)
    }
}

module.exports = myPlugin

列印的compiler物件

透過下方的列印結果,我們的確可以看到compiler.hooks.xxx有很多暴露出的api

實際上去編寫webpack中的plugin就是,去合理使用Compiler的相關hooks
Compiler {
  _pluginCompat: SyncBailHook {
    _args: [ 'options' ],
    taps: [ [Object], [Object], [Object] ],  
    interceptors: [],
    call: [Function: lazyCompileHook],       
    promise: [Function: lazyCompileHook],    
    callAsync: [Function: lazyCompileHook],  
    _x: undefined
  },
  // 鉤子函式,即為生命週期
  hooks: {
    shouldEmit: SyncBailHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],     
      promise: [Function: lazyCompileHook],  
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    done: AsyncSeriesHook {
      _args: [Array],
      taps: [Array],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],  
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    additionalPass: AsyncSeriesHook {        
      _args: [],
      taps: [Array],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    beforeRun: AsyncSeriesHook {
      _args: [Array],
      taps: [Array],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    run: AsyncSeriesHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    emit: AsyncSeriesHook {
      _args: [Array],
      taps: [Array],
      interceptors: [Array],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    assetEmitted: AsyncSeriesHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    afterEmit: AsyncSeriesHook {
      _args: [Array],
      taps: [Array],
      interceptors: [Array],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    thisCompilation: SyncHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    compilation: SyncHook {
      _args: [Array],
      taps: [Array],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    normalModuleFactory: SyncHook {
      _args: [Array],
      taps: [Array],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    contextModuleFactory: SyncHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    beforeCompile: AsyncSeriesHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    compile: SyncHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    make: AsyncParallelHook {
      _args: [Array],
      taps: [Array],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    afterCompile: AsyncSeriesHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    watchRun: AsyncSeriesHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: undefined,
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    failed: SyncHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    invalid: SyncHook {
      _args: [Array],
      taps: [Array],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    watchClose: SyncHook {
      _args: [],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    infrastructureLog: SyncBailHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    environment: SyncHook {
      _args: [],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    afterEnvironment: SyncHook {
      _args: [],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    afterPlugins: SyncHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    afterResolvers: SyncHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    entryOption: SyncBailHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    infrastructurelog: SyncBailHook {
      _args: [Array],
      taps: [],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    }
  },
  name: undefined,
  parentCompilation: undefined,
  outputPath: '',
  outputFileSystem: NodeOutputFileSystem {
    mkdirp: [Function: mkdirP] {
      mkdirP: [Circular],
      mkdirp: [Circular],
      sync: [Function: sync]
    },
    mkdir: [Function: bound mkdir],
    rmdir: [Function: bound rmdir],
    unlink: [Function: bound unlink],
    writeFile: [Function: bound writeFile],
    join: [Function: bound join]
  },
  inputFileSystem: CachedInputFileSystem {
    fileSystem: NodeJsInputFileSystem {},
    _statStorage: Storage {
      duration: 60000,
      running: Map {},
      data: Map {},
      levels: [Array],
      count: 0,
      interval: null,
      needTickCheck: false,
      nextTick: null,
      passive: true,
      tick: [Function: bound tick]
    },
    _readdirStorage: Storage {
      duration: 60000,
      running: Map {},
      data: Map {},
      levels: [Array],
      count: 0,
      interval: null,
      needTickCheck: false,
      nextTick: null,
      passive: true,
      tick: [Function: bound tick]
    },
    _readFileStorage: Storage {
      duration: 60000,
      running: Map {},
      data: Map {},
      levels: [Array],
      count: 0,
      interval: null,
      needTickCheck: false,
      nextTick: null,
      passive: true,
      tick: [Function: bound tick]
    },
    _readJsonStorage: Storage {
      duration: 60000,
      running: Map {},
      data: Map {},
      levels: [Array],
      count: 0,
      interval: null,
      needTickCheck: false,
      nextTick: null,
      passive: true,
      tick: [Function: bound tick]
    },
    _readlinkStorage: Storage {
      duration: 60000,
      running: Map {},
      data: Map {},
      levels: [Array],
      count: 0,
      interval: null,
      needTickCheck: false,
      nextTick: null,
      passive: true,
      tick: [Function: bound tick]
    },
    _stat: [Function: bound bound ],
    _statSync: [Function: bound bound ],
    _readdir: [Function: bound readdir],
    _readdirSync: [Function: bound readdirSync],
    _readFile: [Function: bound bound readFile],
    _readFileSync: [Function: bound bound readFileSync],
    _readJson: [Function],
    _readJsonSync: [Function],
    _readlink: [Function: bound bound readlink],
    _readlinkSync: [Function: bound bound readlinkSync]
  },
  recordsInputPath: null,
  recordsOutputPath: null,
  records: {},
  removedFiles: Set {},
  fileTimestamps: Map {},
  contextTimestamps: Map {},
  resolverFactory: ResolverFactory {
    _pluginCompat: SyncBailHook {
      _args: [Array],
      taps: [Array],
      interceptors: [],
      call: [Function: lazyCompileHook],
      promise: [Function: lazyCompileHook],
      callAsync: [Function: lazyCompileHook],
      _x: undefined
    },
    hooks: { resolveOptions: [HookMap], resolver: [HookMap] },
    cache2: Map {}
  },
  infrastructureLogger: [Function: logger],
  resolvers: {
    normal: { plugins: [Function: deprecated], apply: [Function: deprecated] },
    loader: { plugins: [Function: deprecated], apply: [Function: deprecated] },
    context: { plugins: [Function: deprecated], apply: [Function: deprecated] }
  },
  options: {
    mode: 'development',
    context: 'E:\\echarts\\code',
    devtool: 'eval-cheap-module-source-map',
    node: {
      setImmediate: false,
      process: 'mock',
      dgram: 'empty',
      fs: 'empty',
      net: 'empty',
      tls: 'empty',
      child_process: 'empty',
      console: false,
      global: true,
      Buffer: true,
      __filename: 'mock',
      __dirname: 'mock'
    },
    output: {
      path: 'E:\\echarts\\code\\dist',
      filename: 'js/[name].js',
      publicPath: '/',
      chunkFilename: 'js/[name].js',
      globalObject: "(typeof self !== 'undefined' ? self : this)",
      webassemblyModuleFilename: '[modulehash].module.wasm',
      library: '',
      hotUpdateFunction: 'webpackHotUpdate',
      jsonpFunction: 'webpackJsonp',
      chunkCallbackName: 'webpackChunk',
      devtoolNamespace: '',
      libraryTarget: 'var',
      pathinfo: true,
      sourceMapFilename: '[file].map[query]',
      hotUpdateChunkFilename: '[id].[hash].hot-update.js',
      hotUpdateMainFilename: '[hash].hot-update.json',
      crossOriginLoading: false,
      jsonpScriptType: false,
      chunkLoadTimeout: 120000,
      hashFunction: 'md4',
      hashDigest: 'hex',
      hashDigestLength: 20,
      devtoolLineToLine: false,
      strictModuleExceptionHandling: false
    },
    resolve: {
      alias: [Object],
      extensions: [Array],
      modules: [Array],
      plugins: [Array],
      unsafeCache: true,
      mainFiles: [Array],
      aliasFields: [Array],
      mainFields: [Array],
      cacheWithContext: true,
      preferAbsolute: true,
      ignoreRootsErrors: true,
      roots: [Array]
    },
    resolveLoader: {
      modules: [Array],
      plugins: [Array],
      unsafeCache: true,
      mainFields: [Array],
      extensions: [Array],
      mainFiles: [Array],
      cacheWithContext: true
    },
    module: {
      noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/,
      rules: [Array],
      unknownContextRequest: '.',
      unknownContextRegExp: false,
      unknownContextRecursive: true,
      unknownContextCritical: true,
      exprContextRequest: '.',
      exprContextRegExp: false,
      exprContextRecursive: true,
      exprContextCritical: true,
      wrappedContextRegExp: /.*/,
      wrappedContextRecursive: true,
      wrappedContextCritical: false,
      strictExportPresence: false,
      strictThisContextOnImports: false,
      unsafeCache: true,
      defaultRules: [Array]
    },
    optimization: {
      splitChunks: [Object],
      minimizer: [Array],
      removeAvailableModules: false,
      removeEmptyChunks: true,
      mergeDuplicateChunks: true,
      flagIncludedChunks: false,
      occurrenceOrder: false,
      sideEffects: false,
      providedExports: true,
      usedExports: false,
      concatenateModules: false,
      runtimeChunk: undefined,
      noEmitOnErrors: false,
      checkWasmTypes: false,
      mangleWasmImports: false,
      namedModules: true,
      hashedModuleIds: false,
      namedChunks: true,
      portableRecords: false,
      minimize: false,
      nodeEnv: 'development'
    },
    plugins: [
      VueLoaderPlugin {},
      [DefinePlugin],
      [CaseSensitivePathsPlugin],
      [FriendlyErrorsWebpackPlugin],
      [HtmlWebpackPlugin],
      [PreloadPlugin],
      [PreloadPlugin],
      [CopyPlugin],
      [HotModuleReplacementPlugin],
      [ProgressPlugin],
      HelloPlugin {}
    ],
    entry: { app: [Array] },
    cache: true,
    target: 'web',
    performance: false,
    infrastructureLogging: { level: 'info', debug: false }
  },
  context: 'E:\\echarts\\code',
  requestShortener: RequestShortener {
    currentDirectoryRegExp: /(^|!)E:\/echarts\/code/g,
    parentDirectoryRegExp: /(^|!)E:\/echarts\//g,
    buildinsAsModule: true,
    buildinsRegExp: /(^|!)E:\/echarts\/code\/node_modules\/_webpack@4\.46\.0@webpack/g,
    cache: Map {}
  },
  running: false,
  watchMode: false,
  _assetEmittingSourceCache: WeakMap { <items unknown> },
  _assetEmittingWrittenFiles: Map {},
  watchFileSystem: NodeWatchFileSystem {
    inputFileSystem: CachedInputFileSystem {
      fileSystem: NodeJsInputFileSystem {},
      _statStorage: [Storage],
      _readdirStorage: [Storage],
      _readFileStorage: [Storage],
      _readJsonStorage: [Storage],
      _readlinkStorage: [Storage],
      _stat: [Function: bound bound ],
      _statSync: [Function: bound bound ],
      _readdir: [Function: bound readdir],
      _readdirSync: [Function: bound readdirSync],
      _readFile: [Function: bound bound readFile],
      _readFileSync: [Function: bound bound readFileSync],
      _readJson: [Function],
      _readJsonSync: [Function],
      _readlink: [Function: bound bound readlink],
      _readlinkSync: [Function: bound bound readlinkSync]
    },
    watcherOptions: { aggregateTimeout: 200 },
    watcher: EventEmitter {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      options: [Object],
      watcherOptions: [Object],
      fileWatchers: [],
      dirWatchers: [],
      mtimes: [Object: null prototype] {},
      paused: false,
      aggregatedChanges: [],
      aggregatedRemovals: [],
      aggregateTimeout: 0,
      _onTimeout: [Function: bound _onTimeout]
    }
  }
}

簡單的plugin寫一個生成的靜態資原始檔

外掛程式碼

class myPlugin {
    constructor(options) { // constructor建構函式接收new myPlugin(params)時傳遞的引數params
        console.log("我是new這個類時所傳遞的引數-->", options);
    }
    apply(compiler) {
        // console.log('^_^', compiler) // 上面有非常多api,可供使用(參見compiler列印結果)
        compiler.hooks.emit.tapAsync('lss',(compliation,cb)=>{
            console.log('compliation',compliation.assets);
            const content=`
                - 生活不易
                - 打工仔加油努力
                - 奧利給
                - ??? 
            `
            compliation.assets['FBI-WARNING.md'] = {
                size() { 
                    return content.length 
                },
                source: function () {
                    return content
                }
            }
            cb()
        })
    }
}

module.exports = myPlugin

vue.config.js檔案中使用外掛

// 引入這個外掛
const myPlugin = require('./src/plugin/myPlugin')

  configureWebpack: {
    // 在plugins陣列中例項化物件,若需要傳參,變傳遞引數
    plugins: [
      new myPlugin('我是引數')
    ]
  },
未完待續。今天就先寫(水)到這裡吧