包看包会的《webpack-loader该怎么写》

·  阅读 1334
包看包会的《webpack-loader该怎么写》

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

webpack5.x 更新已经一周年,到现在对于 webpack 的讨论总结起来依然就四个字 yyds,如果你还不知道写么开发一个loader,这篇文章值得你看完。文章对新手比较友好哦~ 其中各种描述很保姆化请谅解(笔者有一颗保姆心),有webpack配置基础的直接从 【loader怎么实现】开始看,

奠定webpack强大的能力依靠的是各种 loader 和 plugin。

什么是 loader

我们先用一个基础示例来解释它

  1. 创建一个 write-loader 文件夹

  2. npm init -y 初始化

  3. npm install webpack webpack-cli

创建如图的目录结构

image.png

在 src > index.js 中我们写上一小段代码

function add(a, b) {
  return a + b
}
add(1, 2)
复制代码

假设这段代码就是我们全部的项目代码,接下来就是把这份js引入到html文件了,但是项目上线考虑到节约服务器资源,我们希望上线之前将项目打包压缩,那么webpack的能力就发挥了

const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js',
  }
}
复制代码

webpack的配置属性不熟悉的看官网!或者掘金上已经n多大佬的webpack配置文章了。接着我们去package.js中配置一个命令为了执行方便

...(省略部分代码)

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack --config webpack.config.js"
  },
复制代码

最后执行 npm run dev 你会看到打包成功的,根目录下的dist文件夹安静的躺着 main.js。接下来直接把mian.js引入到index.html中即可看到打印

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  // 做好引入操作
  <script src="./dist/main.js"></script>
</head>
<body>
  <div id="app">hello world</div>
</body>
</html>
复制代码

以上都是最基础的webpack的配置,本部应该出现在这个文章中(奈何笔者保姆心态改不掉)

回归正题什么是loader? 现在当我想要修改html中的 hello world 文本颜色时,在单页应用火热的如今,我们是这么写的css:

// src > style.css
#app{
  font-size: 30px;
  font-weight: bold;
  color: red;
}
复制代码

样式写好我们引入到 index.js 中:

// src > index.js

import './style.css'  // 引入样式

function add(a, b) {
  return a + b
}
add(1, 2)
复制代码

然后再去执行打包操作,你会看到什么呢?

image.png

报错!!

解释一下,我们要知道 webpack 只能识别 js 语法,它是无法识别 css 语法的,所以 主角loader该现身了,我们需要一个 css-loader 用于帮助webpack解析css语法,style-loader 用于把解析后的css代码以style标签加载到html文件中。 执行 npm i css-loader style-loader

修改webpack的配置文件:

const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,   // +++正则匹配.css后缀的文件
        use: ['style-loader', 'css-loader']  // 注意loader的执行顺序是从右往左的所以先解析再加入
      }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js',
  }
}
复制代码

再执行 npm run dev, 舒服了,你可以在打包出来的main.js中19行的位置看到我们的css

image.png 也即是说我们的css被打包到了js代码一起,那还能用嘛,去页面上看效果:

image.png 看到这了,之前不明白webpack loader是什么的同学应该已经明白了,loader就是帮助 webpack 去解析或者说执行 webpack自己做不到的文件

loader怎么实现

你已经知道loader是个什么角色了,还不够,我们还需要再深层次的了解 loader 的底层原理

1.loader的原理

webpack官方文档告诉我们以下几点:

1. loader 是一个函数

2. loader 函数必须要返回值string或者buffer

3. loader 的参数是当前匹配到的内容*

且 webpack loader的设计原则应该遵从:

1. 单一职责(表现为低耦合,loader复用性也更高)

2. 链式组合 (一个loader操作完的文件可以被另一个loader继续操作)

3. 模块化 (每一个loader都应该写成一个模块)

4. 无状态 (loader不应该具有自己的状态,想想react的无状态组件你会通透很多)

5. 结合loader-utils (后面解释)

6. loader 集成依赖

7. 代码公用

我们优先解释一下第六点 loader-utils 这对于写loader很重要,代码是最好的解释,在根目录下创建 loader-util.js 文件

function loader(content) {
  console.log('进入到了loader内部');
  return 'hello'
}

module.exports = loader
复制代码

把它看做是我们写好的一个 loader, 用上它:

// webpack.config.js 添加代码

module: {
  rules: [
   (...省略部分代码)
    {
      test: /\.js$/,
      use: [
        {
          loader: path.resolve('./loader-util.js'),
          options: {
            name: 'wn'
          }
        }
      ]
    },
  ]
}
复制代码

当webpack编译过程中匹配到js文件就会调用loader-util.js里面的函数loader,测试一下 npm run dev

image.png 果然没有问题,另外再添加一行打印:

function loader(content) {
  console.log('进入到了loader内部');
  console.log(content); // +++
  return 'hello'
}
复制代码

执行编译你会看到:

image.png

这可不就是我们在js文件中写的代码嘛!原来 3. loader 的参数是当前匹配到的内容 是这个意思!

但是我们在使用 loader 这个函数的时候配置了 options,这是webpack允许的写法,意在给 loader 函数传递参数,那么如何拿到调用loader时传递的参数,就要用到 loader-utils了,在 loader-util.js 中添加代码如下:

const loaderUtils = require('loader-utils')  // +++

function loader(content) {
  console.log('进入到了loader内部');
  console.log(content);
  console.log(loaderUtils.getOptions(this)); // +++
  return 'hello'
}

module.exports = loader
复制代码

getOptions是loader-utils提供的一个回去options参数的方法,你放佛明白了什么并迫不及待的执行了一遍打包:

image.png

好了你也知道loader-utils是什么样子的存在了, 其实它还提供了很多很实用的方法,感兴趣的小伙伴可以看这里 官方文档 loader-utils

你对loader的原理也就有一个大体的认知了,接下来就来实现一个laoder吧

2. loader的实现

那咱们拟定一个需求,比如说:我有这么一个需求,要实现 html文件打包,按照单一职责这个设计原则,这个需求应该包含两个功能

  1. html语法的解析
  2. html文件的压缩

也就是说这样一个需求应该设计成两个 loader 来完成,考虑到第一个功能解析html的语法loader实现过于复杂,我们就用一个node库已有的 html-loader 来完成,第二个功能我们来自己手动实现。

根目录下创建一个 html-minify-loader.js

// html-minify-loader.js

module.exports = function(source) {
  console.log(source);
}
复制代码

参数source将会编程匹配到的html代码,这一点大家都知道了吧。先用上这个loader吧,在src下新增 app.js, example.html两个文件

// example.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  example
</body>
</html>
复制代码
// app.js

var html = require('./example.html')
console.log(html);
复制代码

现在我们去修改配置文件

// webpack.config.js 

const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/app.js'  // 入口改成app.js
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: [
          'html-loader',  // 这个需要 npm i html-loader 安装
          {
            loader: path.resolve('./html-minify-loader.js'),  // 引入我们自己写的loader
            options: {
              comments: false  // html-loader 中的不打包注释的配置
            }
          }
        ]
      }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js',
  }
}
复制代码

万事俱备,只欠我们的 html-minify-loader, 要干成压缩html这件事,原理就是合理的把html代码中的空格和换行干掉,这一点可以靠自己写正则完成,也可以懒一点,直接用个 minimize 轮子,哈哈哈哈,被你发现了,我们真的只是聊loader的实现原理,这个需求中遇到的其他难点都被我们用轮子化解了, npm i minimize 后

// html-minify-loader.js

var Minimize = require('minimize')
var loaderUtils = require('loader-utils')

module.exports = function(source) {
  // console.log(source);
  
  // 拿到配置的options参数
  var options = loaderUtils.getOptions(this) || {}
  
  // 把参数交给 minimize
  var minimize = new Minimize(options);
  
  // 返回minimize压缩之后的html代码
  return minimize.parse(source);
}
复制代码

就是这么简单,执行打包,在dist > main.js 中能找到example的html代码,并且是压缩过的

image.png

你又懂了一个大厂面试官常考问题,下次写项目碰到,可以自己试试写个laoder了!

源码地址

参考

官网

iKamp # 手把手教你撸一个 Webpack Loader

分类:
前端
标签: