谈: webpack的loader与plugin

886 阅读4分钟

webpack

一句话总结,webpack是用来编译打包代码的.

在当下ES6语法在项目中屡见不鲜的情况下,一些浏览器其实对ES6还不是很友好,这时候就需要有个中间介质来做翻译.那webpack就是市面上众多'翻译'中的一员.

在日常开发中,我们除了一些样式/图片元素还会使用很多框架,类库...来提升我们的输出效率.让我们可以更有效率的完成开发工作.比如: vue.js、Eslint、Less/Sass、Es6、image...但是就会出现一个问题,这些东西,或者说模块浏览器不认识,这就需要使用webpack来让浏览器认识这些代码.

loader

loader用于对模块源代码进行转换.loader可以将不同语言转换为JavaScript,可以将内联图像转换为data URL,他可以让开发者直接在JavaScript模块中使用import();

loader就是一个导出的function.当要对资源进行转换时,会调用这个函数.

loader是怎么在项目中使用的

可以直接使用path到一个loader.js

const path = require('path');

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: path.resolve('path/to/loader.js'),
            options: {
              /* ... */
            },
          },
        ],
      },
    ],
  },
};

要测试多个loader,使用resolveLoader.modules来配置webpack在什么地方去查找:

const path = require('path');

module.exports = {
  //...
  resolveLoader: {
    modules: ['node_modules', path.resolve(__dirname, 'loaders')],
  },
};

如果是通过npm之类的包管理工具安装的loader:

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js/,
        use: ['bar-loader', 'foo-loader'],
      },
    ],
  },
};

如何编写一个loader

一个loader首先需要导出一个函数, 这个函数会拿到需要编译处理的内容,然后对这个内容进行一些列的处理,最后返回处理后的内容.

module.exports = function(source) {
    // ...
    return source;
}

下面我们来实现一个简单的编译markdown的loader来落地上面的想法:

  • 新建文件夹loader,npm init 初始化package.json然后安装webpack-cli webpack-dev-server webpack

npm init

npm i -D webpack webpack-cli webpack-dev-server

  • 创建webpack.config.js
var path = require('path')
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.md$/,
        // html是用来处理md转换之后生成的html的
        use: ['html-loader', './src/loader/md-loader'], 
      },
    ],
  },
  devServer: {
    contentBase: './dist',
    overlay: {
      warnings: true,
      errors: true,
    },
    open: true,
  },
}
  • 创建index.js 使用 index.md模块
import md from './index.md'

var content = document.createElement('div')
content.innerHTML = md
document.body.appendChild(content)
  • 新建loader文件 添加md-loader.js
var marked = require('marked')

module.exports = function (source) {
  var html = marked(source)
  return html
}

然后run 就可以看到页面上展示出我们markdown中的内容了. 源码入口

上面我们实现的是一个简单的loader,实质上loader有很多options可供配置选择.loader-utils提供了很多有用的工具.更多loader配置可以移步.

常用loader示例

  • babel-loader
  • vue-loader
  • html-loader
  • style-loader
  • css-loader
  • sass-loader
  • file-loader
  • url-loader ...

plugin

plugin比loader更强大,当loader满足不了你的需求时就需要plugin了.plugin向开发者提供了webpack引擎中完整能力.用官方的话说就是创建plugin比创建loader更高级!

plugin是怎么在项目中使用的

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
      },
    ],
  },
  plugins: [
    new webpack.ProgressPlugin(), // 用于自定义编译过程中的进度报告
    // 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 my-first-webpack.bundle.js 的 JS 文件, 这样我们就不用像上文那样在index.html中引入打包之后的bundle.js文件了.
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
};

如何编写一个plugin

一个plugin的构成部分分为以下几点:

  • 一个具名的JavaScript函数
  • 函数的prototype上要有一个apply方法
  • 指定一个触及到webpack本身的钩子(hooks) complier hooks
    • complier 代表了整个webpack从启动到关闭的生命周期
  • 操作webpack内部的实例特定数据 compilation hooks
    • compilation 只代表一次单独的编译
  • 实现功能后调用callback
// 一个 JavaScript class
class MyExampleWebpackPlugin {
  // 将 `apply` 定义为其原型方法,此方法以 compiler 作为参数
  apply(compiler) {
    // 指定要附加到的事件钩子函数
    compiler.hooks.emit.tapAsync(
      'MyExampleWebpackPlugin',
      (compilation, callback) => {
        // 钩子第二个参数以compilation 为参数

        // 使用 webpack 提供的 plugin API 操作构建结果
        compilation.addModule(/* ... */)

        callback()
      }
    )
  }
}
module.exports =  MyExampleWebpackPlugin

下面我们实现一个简单的代码来实现html-webpack-plugin(它的源码还是比较复杂的,这边只是实现简单功能,不做过多参数配置).

  • 首先,npm init -y 并安装webpack以及 webpack-cli.我这边使用了webpack-dev-server(手动执行webpack太麻烦,起服务方便)
{
  ...
  "dependencies": {
    "webpack": "^4.46.0",
    "webpack-dev-server": "^3.11.2"
  },
  "devDependencies": {
    "webpack-cli": "^3.3.12"
  }
}
  • 创建webpack.config.js
var path = require('path')
var MyExampleWebpackPlugin = require('./src/plugins/index')
module.exports = {
  mode: 'development',
  devServer: {
    contentBase: './dist',
    overlay: {
      warnings: true,
      errors: true,
    },
    open: true,
  },
  entry: '/src/app.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js',
  },
  plugins: [new MyExampleWebpackPlugin()],
}
  • 编写plugin
class MyExampleWebpackPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      'MyExampleWebpackPlugin',
      (compilation, callback) => {
        let html = `
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
        </head>
        <body>
            <div id="app"></div>
        </body>
        <script src="./bundle.js"></script>
        </html>
      `
        compilation.assets['index.html'] = {
          source() {
            return html
          },
          size() {
            return html.length
          },
        }
        callback()
      }
    )
  }
}
  • 执行npm run dev就ok了

WechatIMG222352.png

对了,这里可能会碰到一个问题就是执行webpack-dev-server会起一个服务,然后执行配置中的内容, 但是它不会生成打包后的文件.根本原因是webpack与webpack-dev-server是两个工具,后者不做打包的事情,就是起个服务.

打包的话执行./node_modules/.bin/webpack 源码入口.

常用的plugin示例

  • HtmlWebpackPlugin
  • CleanWebpackPlugin
  • CopyWebpackPlugin
  • BannerPlugin
  • TerserWebpackPlugin
  • UglifyjsWebpackPlugin
  • DLLPlugin与DllReferencePlugin
  • HotModuleReplacementPlugin

总结

webpacktapable有这密不可分的关系,上文中的compilercompilation都是tapable实例化出来的.上文中也提到了它们各自有很多hooks,可以分别用在编译或者打包不同进行时间做处理.

在现在的开发工作中, webpack与我们息息相关.对于它海量的文档API,其实不用望而却步,我们需要理解它的目的,以及它做的事情,这样用起来肯定游刃有余.至于API,我认为常用的比如loader/plugin稍加了解即可,在实际业务场景中使用到的话,去做深入学习即可.