回顾webpack在vue.config.js中写loader和plugin

lxf2023-04-12 21:36:01

前言

我们先思考一个问题:如果不使用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是一个静态模块资源打包工具,官网: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仓库地址:github.com/vuejs/vue-c…

大家把代码下载下来,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')
}

对应截图如下:

回顾webpack在vue.config.js中写loader和plugin

其实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文件看到 如下图,看一下:

回顾webpack在vue.config.js中写loader和plugin

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仓库:github.com/shuirongshu…

更多开发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=`
                - 生活不易
                - 打工仔加油努力
                - 奥利给
                -