详解webpack loader和plugin编写

1 基础回顾

首先我们先回顾一下webpack常见配置,因为后面会用到,所以简单介绍一下。

1.1 webpack常见配置

// 入口文件
 entry: {
  app: './src/js/index.js',
 },
 // 输出文件
 output: {
  filename: '[name].bundle.js',
  path: path.resolve(__dirname, 'dist'),
  publicPath: '/'   //确保文件资源能够在 http://localhost:3000 下正确访问
 },
 // 开发者工具 source-map
 devtool: 'inline-source-map',
 // 创建开发者服务器
 devServer: {
  contentBase: './dist',
  hot: true        // 热更新
 },
 plugins: [
  // 删除dist目录
  new CleanWebpackPlugin(['dist']),
  // 重新穿件html文件
  new HtmlWebpackPlugin({
   title: 'Output Management'
  }),
  // 以便更容易查看要修补(patch)的依赖
  new webpack.NamedModulesPlugin(),
  // 热更新模块
  new webpack.HotModuleReplacementPlugin()
 ],
 // 环境
 mode: "development",
 // loader配置
 module: {
  rules: [
   {
    test: /\.css$/,
    use: [
     'style-loader',
     'css-loader'
    ]
   },
   {
    test: /\.(png|svg|jpg|gif)$/,
    use: [
     'file-loader'
    ]
   }
  ]
 }

这里面我们重点关注 module和plugins属性,因为今天的重点是编写loader和plugin,需要配置这两个属性。

1.2 打包原理

  • 识别入口文件
  • 通过逐层识别模块依赖。(Commonjs、amd或者es6的import,webpack都会对其进行分析。来获取代码的依赖)
  • webpack做的就是分析代码。转换代码,编译代码,输出代码
  • 最终形成打包后的代码

这些都是webpack的一些基础知识,对于理解webpack的工作机制很有帮助。

2 loader

OK今天第一个主角登场

2.1 什么是loader?

loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中

  • 处理一个文件可以使用多个loader,loader的执行顺序是和本身的顺序是相反的,即最后一个loader最先执行,第一个loader最后执行。
  • 第一个执行的loader接收源文件内容作为参数,其他loader接收前一个执行的loader的返回值作为参数。最后执行的loader会返回此模块的JavaScript源码

2.2 手写一个loader

需求:

  • 处理.txt文件
  • 对字符串做反转操作
  • 首字母大写

例如:abcdefg转换后为Gfedcba

OK,我们开始

1)首先创建两个loader(这里以本地loader为例)

为什么要创建两个laoder?理由后面会介绍

reverse-loader.js

module.exports = function (src) {
 if (src) {
  console.log('--- reverse-loader input:', src)
  src = src.split('').reverse().join('')
  console.log('--- reverse-loader output:', src)
 }
 return src;
}

uppercase-loader.js

module.exports = function (src) {
 if (src) {
  console.log('--- uppercase-loader input:', src)
  src = src.charAt(0).toUpperCase() + src.slice(1)
  console.log('--- uppercase-loader output:', src)
 }
 // 这里为什么要这么写?因为直接返回转换后的字符串会报语法错误,
 // 这么写import后转换成可以使用的字符串
 return `module.exports = '${src}'`
}

看,loader结构是不是很简单,接收一个参数,并且return一个内容就ok了。

然后创建一个txt文件

2)mytest.txt

abcdefg

3)现在开始配置webpack

module.exports = {
 entry: {
  index: './src/js/index.js'
 },
 plugins: [...],
 optimization: {...},
 output: {...},
 module: {
  rules: [
   ...,
   {
    test: /\.txt$/,
    use: [
     './loader/uppercase-loader.js',
     './loader/reverse-loader.js'
    ]
   }
  ]
 }
}

这样就配置完成了

4)我们在入口文件中导入这个脚本

为什么这里需要导入呢,我们不是配置了webapck处理所有的.txt文件么?

因为webpack会做过滤,如果不引用该文件的话,webpack是不会对该文件进行打包处理的,那么你的loader也不会执行

import _ from 'lodash';
import txt from '../txt/mytest.txt'
import '../css/style.css'
function component() {
 var element = document.createElement('div');
 var button = document.createElement('button');
 var br = document.createElement('br');

 button.innerHTML = 'Click me and look at the console!';
 element.innerHTML = _.join('【' + txt + '】');
 element.className = 'hello'
 element.appendChild(br);
 element.appendChild(button);

 // Note that because a network request is involved, some indication
 // of loading would need to be shown in a production-level site/app.
 button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
  var print = module.default;

  print();
 });

 return element;
}
document.body.appendChild(component());

package.json配置

{
 ...,
 "scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "webpack --config webpack.prod.js",
  "start": "webpack-dev-server --open --config webpack.dev.js",
  "server": "node server.js"
 },
 ...
}

然后执行命令

npm run build

这样我们的loader就写完了。

现在回答为什么要写两个loader?

看到执行的顺序没,我们的配置的是这样的

use: [
 './loader/uppercase-loader.js',
 './loader/reverse-loader.js'
]

正如前文所说, 处理一个文件可以使用多个loader,loader的执行顺序是和本身的顺序是相反的

我们也可以自己写loader解析自定义模板,像vue-loader是非常复杂的,它内部会写大量的对.vue文件的解析,然后会生成对应的html、js和css。

我们这里只是讲述了一个最基础的用法,如果有更多的需要,可以查看《loader官方文档》

3 plugin

3.1 什么是plugin?

在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

plugin和loader的区别是什么?

对于loader,它就是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A.scss或A.less转变为B.css,单纯的文件转换过程

plugin是一个扩展器,它丰富了wepack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务。

3.2 一个最简的插件

/plugins/MyPlugin.js(本地插件)

class MyPlugin {
 // 构造方法
 constructor (options) {
  console.log('MyPlugin constructor:', options)
 }
 // 应用函数
 apply (compiler) {
  // 绑定钩子事件
  compiler.plugin('compilation', compilation => {
   console.log('MyPlugin')
  ))
 }
}

module.exports = MyPlugin

webpack配置

const MyPlugin = require('./plugins/MyPlugin')
module.exports = {
 entry: {
  index: './src/js/index.js'
 },
 plugins: [
  ...,
  new MyPlugin({param: 'xxx'})
 ],
 ...
};

这就是一个最简单的插件(虽然我们什么都没干)

  • webpack 启动后,在读取配置的过程中会先执行 new MyPlugin(options) 初始化一个 MyPlugin 获得其实例。
  • 在初始化 compiler 对象后,再调用 myPlugin.apply(compiler) 给插件实例传入 compiler 对象。
  • 插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin(事件名称, 回调函数) 监听到 Webpack 广播出来的事件。
  • 并且可以通过 compiler 对象去操作 webpack。

看到这里可能会问compiler是啥,compilation又是啥?

Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;

Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。

Compiler 和 Compilation 的区别在于:

Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。

3.3 事件流

  • webpack 通过 Tapable 来组织这条复杂的生产线。
  • webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
  • webpack 的事件流机制应用了观察者模式,和 Node.js 中的 EventEmitter 非常相似。

绑定事件

compiler.plugin('event-name', params => {
 ...
});

触发事件

compiler.apply('event-name',params)

3.4 需要注意的点

  • 只要能拿到 Compiler 或 Compilation 对象,就能广播出新的事件,所以在新开发的插件中也能广播出事件,给其它插件监听使用。
  • 传给每个插件的 Compiler 和 Compilation 对象都是同一个引用。也就是说在一个插件中修改了 Compiler 或 Compilation 对象上的属性,会影响到后面的插件。
  • 有些事件是异步的,这些异步的事件会附带两个参数,第二个参数为回调函数,在插件处理完任务时需要调用回调函数通知 webpack,才会进入下一处理流程 。例如:
compiler.plugin('emit',function(compilation, callback) {
 ...

 // 处理完毕后执行 callback 以通知 Webpack
 // 如果不执行 callback,运行流程将会一直卡在这不往下执行
 callback();
});

关于complier和compilation,webpack定义了大量的钩子事件。开发者可以根据自己的需要在任何地方进行自定义处理。

《compiler钩子文档》

《compilation钩子文档》

3.5 手写一个plugin

场景:

小程序mpvue项目,通过webpack编译,生成子包(我们作为分包引入到主程序中),然后考入主包当中。生成子包后,里面的公共静态资源wxss引用地址需要加入分包的前缀:/subPages/enjoy_given。

在未编写插件前,生成的资源是这样的,这个路径如果作为分包引入主包,是没法正常访问资源的。

所以需求来了:

修改dist/static/css/pages目录下,所有页面的样式文件(wxss文件)引入公共资源的路径。

因为所有页面的样式都会引用通用样式vender.wxss

那么就需要把@import "/static/css/vendor.wxss"; 改为:@import "/subPages/enjoy_given/static/css/vendor.wxss";复制代码

OK 开始!

1)创建插件文件 CssPathTransfor.js

CssPathTransfor.js

class CssPathTransfor {
 apply (compiler) {
  compiler.plugin('emit', (compilation, callback) => {
   console.log('--CssPathTransfor emit')
   // 遍历所有资源文件
   for (var filePathName in compilation.assets) {
    // 查看对应的文件是否符合指定目录下的文件
    if (/static\/css\/pages/i.test(filePathName)) {
     // 引入路径正则
     const reg = /\/static\/css\/vendor\.wxss/i
     // 需要替换的最终字符串
     const finalStr = '/subPages/enjoy_given/static/css/vendor.wxss'
     // 获取文件内容
     let content = compilation.assets[filePathName].source() || ''

     content = content.replace(reg, finalStr)
     // 重写指定输出模块内容
     compilation.assets[filePathName] = {
      source () {
       return content;
      },
      size () {
       return content.length;
      }
     }
    }
   }
   callback()
  })
 }
}
module.exports = CssPathTransfor

看着挺多,实际就是遍历compilation.assets模块。对符合要求的文件进行正则替换。

2)修改webpack配置

var baseWebpackConfig = require('./webpack.base.conf')
var CssPathTransfor = require('../plugins/CssPathTransfor.js')

var webpackConfig = merge(baseWebpackConfig, {
 module: {...},
 devtool: config.build.productionSourceMap ? '#source-map' : false,
 output: {...},
 plugins: [
  ...,
  // 配置插件
  new CssPathTransfor(),
 ]
})

插件编写完成后,执行编译命令

搞定~

如果有更多的需求可以参考《如何写一个插件》

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Webpack常见静态资源处理-模块加载器(Loaders)+ExtractTextPlugin插件

    webpack系列目录 webpack 系列 二:webpack 介绍&安装 webpack 系列 三:webpack 如何集成第三方js库 webpack 系列 四:webpack 多页面支持 & 公共组件单独打包 webpack 系列 五:webpack Loaders 模块加载器 webpack 系列 六:前端项目模板-webpack+gulp实现自动构建部署 基于webpack搭建纯静态页面型前端工程解决方案模板, 最终形态源码见github: https://github.com

  • 详解webpack loader和plugin编写

    1 基础回顾 首先我们先回顾一下webpack常见配置,因为后面会用到,所以简单介绍一下. 1.1 webpack常见配置 // 入口文件 entry: { app: './src/js/index.js', }, // 输出文件 output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), publicPath: '/' //确保文件资源能够在 http://localhost:3000 下正确访问

  • 详解Webpack loader 之 file-loader

    简介 安装 npm install --save-dev file-loader 用法 默认情况下,生成的文件的文件名就是文件内容的 MD5 哈希值并会保留所引用资源的原始扩展名. import img from './webpack-logo.png' webpack.config.js module.exports = { module: { rules: [ { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader', option

  • 一文详解webpack中loader与plugin的区别

    目录 一.Loader 1.loader的作用: 2.loader的工作原理: 3. Loader 执行顺序 4.如何开发一个loader 二.Plugin 1.plugin解决其他的更多的自动化打包工作 2.自定义插件 常见的Loader和Plugin loader: plugin: 一.Loader 1.loader的作用: webpack 只能直接处理 javascript 格式的代码.任何非 js 文件都必须被预先处理转换为 js 代码,才可以参与打包.loader(加载器)就是这样一个

  • 详解webpack模块化管理和打包工具

    本篇文章主要介绍了详解webpack模块化管理和打包工具,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 Webpack简介 webpack是当下最热门的前端资源模块化管理和打包工具. 它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源.还可以将按需加载的模块进行代码分隔,等到实际 需要的时候再异步加载.通过 loader  的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块. AMD 模块. ES6 模块.CSS.图片. JSON.

  • 详解webpack的clean-webpack-plugin插件报错

    1.出错代码 const path = require('path') const CleanWebpackPlugin = require('clean-webpack-plugin') // const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { entry: './input.js', output: { path: path.resolve(__dirname, 'dist'),

  • 详解webpack之scss和postcss-loader的配置

    本文介绍了详解webpack之scss和postcss-loader的配置,分享给大家,具体如下: 开始 npm i sass-loader node-sass postcss-loader autoprefixer 首先配置postcss-loader 在这里postcss是为了来给浏览器内核添加私有前缀.当前postcss还有其他操作比如px2rem之类的.可以把postcss想象成babel-core只是一个控制中心,主要的还是它分散出来的插件. /**** package.json **

  • 详解webpack babel的配置

    Babel是什么 Babel是一个编译JavaScript的平台,它的强大之处表现在可以通过编译帮你达到: 使用下一代的javascript(ES6,ES7,--)代码,即使当前浏览器没有完成支持: 使用基于JavvScript进行扩展语言,比如React的JSX: npm i babel-core babel-preset-env babel-loader babel-plugin-transform-runtime babel-preset-stage-2 -D 关于babel的使用 首先

  • 详解webpack 打包文件体积过大解决方案(code splitting)

    优化对比 : 未优化前:index.html引入一个main.js文件,体积2M以上. 优化后入:index.html引入main.js.commons.js.charts.js.other.js.以达到将main.js平分目的.每个文件控制300k以内.(如果高兴100k也没问题) 用到的一堆库及工具: vue.webpack.babel.highcharts.echarts.jquery.html2canvas******此去省略若干m代码 问题: 开发环境用webpack后发现单个js文件

  • 详解webpack 入门与解析

    每次学新东西总感觉自己是不是变笨了,看了几个博客,试着试着就跑不下去,无奈只有去看官方文档. webpack是基于node的.先安装最新的node. 1.初始化 安装node后,新建一个目录,比如html5.cmd中切到当前文件夹. npm init -y 这个命令会创建一个默认的package.json.它包含了项目的一些配置参数,通过它可以进行初始安装.详细参数:https://docs.npmjs.com/files/package.json. 不要y参数的话,会在命令框中设置各项参数,但

  • 详解linux usb host驱动编写入门

    usb协议是一个复杂的协议,目前涉及到的版本就有usb1.0, usb2.0, usb3.0.大家如果打开kernel usb host目录,就会发现下面包含了ohci,uhci,ehci,xhci,whci等多种形式的控制器驱动.那么,对于我们这些不是很了解usb的开发人员,如何了解usb的代码结构呢? 1.代码分布 drivers/usb目录下面,host目录包括了host驱动代码,core目录包含了主要的api接口代码,而其他目录则主要是device驱动代码. 2.device驱动怎么看

随机推荐