19

关于Webpack中Loader与Plugin的实践

 4 years ago
source link: http://blog.alanwu.site/2020/04/12/loader-plugin/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近看一下webpack相关的内容,谈一下如何编写loader和plugin

Fziy22U.gif

01 前言

相信大家对webpack也有一定的了解,其实深入浅出webpack这本书也看了很多遍,每一次看都会有一些细节之前没有注意到,我觉得其实可以把它当成是一本工具书来看,之前我也是只看配置都有点让你看不过来,更别说其他的了。

所以今天我就说一下如何编写一个loader与plugin,以及它们之间有什么却别等。

02 Loader

Loader其实就是一个转换器,把你输入的内容翻译一遍,本质上是没有什么变化的,就像中文翻译成英文一样。我们其实在不知不觉当中也使用了很多的Loader,但是我们没有过多关注而已。常用的Loader有以下几类:

常见Loader

语言转换类

babel-loader
ts-loader
sass-loader
less-loader
css-loader

文件加载类

raw-loader
file-loader
source-map-loader
node-loader
json-loader

其他loader

vue-loader
ui-component-loader
i18n-loader
ignore-loader

Loader配置

module.exports = {
  module:{
    rules:[
      {
        test:/\.scss/,
        use:[
        'style-loader',
        {
          'css-loader',
          options:{
            minimize:true
          }
        },
        'sass-loader'
        ],
      }
    ]
  }
}

以上的代码意思就是对.scss文件的转换过程,test的配置就是对某一类文件进行转换,use是使用的loader(转换器),它是一个数组,遵循从右往左的使用。先sass-loader再css-loader再style-loader。

加载本地Loaderre

我们默认的loader都是从npm上面下载的,但是假如我们要使用自己本地写的loader怎么办呢?我们在webpack里面有这样一个配置 resolveLoader ,它的意思就是说我们使用哪里的loader来加载文件,可以配置多个地方:

module.exports = {
  resolveLoader:{
    modules:['node_modules','yourPath']
  }
}

意思就是说我们可以自定义loader的路径,默认就是从node_modules里面找,但是假如你的自定义loader放在本地,可以把你的路径写在yourPath里面(相对路径)。匹配规则就是从左向右查找。

Loader编写

说了这么多,我们自己来写一个简单的loader吧,虽然是一个没有意义的loader,但是也让大家有一个简单的印象,原来写一个loader是很简单的。

  • 新建一个目录,如myLoader
  • 进入目录初始化package.json文件,安装webpack npm init -y , npm i -D webpack webpack-cli
  • 根目录下新建文件夹src,然后创建入口文件index.js —— src/index.js
  • 根目录下新建文件夹loaders,然后新建自定义myLoader.js—— loaders/myloader.js
  • 根目录下新建webpack配置文件 webpack.config.js
  • 在package.json配置webpack打包build命令

22eiYru.png!web

package.json

{
  "name": "myLoader",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.42.1",
    "webpack-cli": "^3.3.11"
  }
}

index.js

console.log('世界上最好的语言是PHP!')

myLoader.js

const loaderUtils = require('loader-utils');

module.exports = function (source) {
  const options = loaderUtils.getOptions(this);
  const result = source.replace('PHP', options.name);
  return result;
}

webpack.config.js

const path = require("path");

module.exports = {
  mode: "development", 
  entry: {
    main: "./src/index.js",
  },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: path.resolve(__dirname, "./loaders/myLoader.js"),
          options: {
            name: "JavaScript",//这里就是你要替换的值
          },
        },
      },
    ],
  },
};

好了,到此为止我们已经成功地写了一个loader,接下来我们就测试一下是否真的work!运行 npm run build 之后会输出一个dist文件夹,里面有一个main.js文件,盘它。

3ieURbr.png!web

运行/dist/main.js文件

03 Plugin

相对于Loader来说,其实plugin的机制更加灵活,它可以在webpack的运行过程中改变输出结果。简单来说就是为输出添砖加瓦。

常见Plugin

extract-text-webpack-plugin
webpack-parallel-uglify-plugin
html-webpack-plugin
dll-plugin
ignore-plugin

Plugin的工作原理

我们先来看一下简单的插件是怎么编写出来的:

class myPlugin {
  constructor(options){
    
  }
  apply(complier){
    complier.plugin('compilation',function(compilation){
      
    })
  }
}

//导出
module.exports = myPlugin;

工作流程:

  • webpack启动,执行new myPlugin(options),初始化插件并获取实例
  • 初始化complier对象,调用myPlugin.apply(complier)给插件传入complier对象
  • 插件实例获取complier,通过complier.plugin监听webpack广播的事件,通过complier对象操作webpack

Plugin编写

俗话说, talk is cheap,show me the code

还是沿用上面的代码结构,在根目录下面建一个myPlugins文件夹,里面建一个myPlugin.js文件,我们就自定义一个plugin:

class myPlugin {

    constructor(doneCallback, failCallback) {
        this.doneCallback = doneCallback;
        this.failCallback = failCallback;
    }

    apply(compiler) {
        compiler.hooks.done.tap('myPlugin', (stats) => {
            this.doneCallback(stats);
        });
        compiler.hooks.failed.tap('myPlugin', (err) => {
            this.failCallback(err);
        });
    }
}

module.exports = myPlugin;

然后我们在 webpack.config.js 文件里面先 引入插件 ,然后 配置插件 即可。

还是先打包一下,你会发现在打包过程中,webpack会广播默认的事件,这里我就监听了webpack的 done事件 (webpack构建成功,即将退出)和 fail事件 (webpack构建失败,即将退出)。

const myPlugin = require("./plugins/myPlugin");
module.exports = {
  plugins: [
    new myPlugin(
      () => {
        //throw new Error('Error!')
        console.log("成功监听到结束事件,可以执行你想要的函数!");
      },
      (error) => {
        console.log(error);
      }
    ),
  ],
}

我们运行之后发现是可以监听的,这里我把两种情况都试一下。首先正常构建,然后手动抛出错误,结果如下:

3yIJrmj.png!web

成功构建

3AVFniV.png!web

失败构建

04 小结

今天没事就折腾一下看似简单的东西,比如想看一下一些经典loader和plugin的源代码,自信的我还想看一下webpack的源代码,发现看不懂,就此打住了。

不过其实plugin和loader的区别也是面试常问的,也会问你知道怎么写loader和plugin的问题,虽然自己实现的很简单甚至有点幼稚,自己感觉还好。

相信大家读了之后应该有自己的理解,以后遇到这个问题也会有自己的想法,大家实践一下就知道其中的奥秘了。

参考文章:深入浅出Webpack

EZjUzqA.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK