本文在github做了收录 github.com/,demo源码地址 github.com/
在 webpack 中,专注于处理 webpack 在编译过程中的某个特定的任务的功能模块,可以称为插件。它和 loader 有以下区别:
loader 是一个转换器,将 A 文件进行编译成 B 文件,比如:将 A.less 转换为 A.css,单纯的文件转换过程。webpack 自身只支持 js 和 json 这两种格式的文件,对于其他文件需要通过 loader 将其转换为 commonJS 规范的文件后,webpack 才能解析到。
plugin 是一个扩展器,它丰富了 webpack 本身,针对是 loader 结束后,webpack 打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行广泛的任务。
webpack 插件有以下特征
class HelloPlugin {
// 在构造函数中获取用户给该插件传入的配置
constructor(options) {}
// Webpack 会调用 HelloPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler) {
// 在emit阶段插入钩子函数,用于特定时机处理额外的逻辑;
compiler.hooks.emit.tap('HelloPlugin', (compilation) => {
// 在功能流程完成后可以调用 webpack 提供的回调函数;
})
// 如果事件是异步的,会带两个参数,第二个参数为回调函数,
compiler.plugin('emit', function (compilation, callback) {
// 处理完毕后执行 callback 以通知 Webpack
// 如果不执行 callback,运行流程将会一直卡在这不往下执行
callback()
})
}
}
module.exports = HelloPlugin
webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 Tapable。
Webpack 的 Tapable 事件流机制保证了插件的有序性,将各个插件串联起来, Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条 webapck 机制中,去改变 webapck 的运作,使得整个系统扩展性良好。
Tapable 也是一个小型的 library,是 Webpack 的一个核心工具。类似于 node 中的 events 库,核心原理就是一个订阅发布模式。作用是提供类似的插件接口。方法如下:
// 广播事件
compiler.apply('event-name', params)
compilation.apply('event-name', params)
// 监听事件
compiler.plugin('event-name', function (params) {})
compilation.plugin('event-name', function (params) {})
我们来看下 Tapable
function Tapable() {
this._plugins = {}
}
//发布name消息
Tapable.prototype.applyPlugins = function applyPlugins(name) {
if (!this._plugins[name]) return
var args = Array.prototype.slice.call(arguments, 1)
var plugins = this._plugins[name]
for (var i = 0; i < plugins.length; i++) {
plugins[i].apply(this, args)
}
}
// fn订阅name消息
Tapable.prototype.plugin = function plugin(name, fn) {
if (!this._plugins[name]) {
this._plugins[name] = [fn]
} else {
this._plugins[name].push(fn)
}
}
//给定一个插件数组,对其中的每一个插件调用插件自身的apply方法注册插件
Tapable.prototype.apply = function apply() {
for (var i = 0; i < arguments.length; i++) {
arguments[i].apply(this)
}
}
Tapable 为 webpack 提供了统一的插件接口(钩子)类型定义,它是 webpack 的核心功能库。webpack 中目前有十种 hooks,在 Tapable 源码中可以看到,他们是:
exports.SyncHook = require('./SyncHook')
exports.SyncBailHook = require('./SyncBailHook')
exports.SyncWaterfallHook = require('./SyncWaterfallHook')
exports.SyncLoopHook = require('./SyncLoopHook')
exports.AsyncParallelHook = require('./AsyncParallelHook')
exports.AsyncParallelBailHook = require('./AsyncParallelBailHook')
exports.AsyncSeriesHook = require('./AsyncSeriesHook')
exports.AsyncSeriesBailHook = require('./AsyncSeriesBailHook')
exports.AsyncSeriesLoopHook = require('./AsyncSeriesLoopHook')
exports.AsyncSeriesWaterfallHook = require('./AsyncSeriesWaterfallHook')
Tapable 还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:
webpack 里的几个非常重要的对象,Compiler, Compilation 和 JavascriptParser 都继承了 Tapable 类,它们身上挂着丰富的钩子。
一个 webpack 插件由以下组成:
下面实现一个最简单的插件
class WebpackPlugin1 {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.done.tap('MYWebpackPlugin', () => {
console.log(this.options)
})
}
}
module.exports = WebpackPlugin1
然后在 webpack 的配置中注册使用就行,只需要在 webpack.config.js 里引入并实例化就可以了:
const WebpackPlugin1 = require('./src/plugin/plugin1')
module.exports = {
entry: {
index: path.join(__dirname, '/src/main.js'),
},
output: {
path: path.join(__dirname, '/dist'),
filename: 'index.js',
},
plugins: [new WebpackPlugin1({ msg: 'hello world' })],
}
此时我们执行一下 npm run build 就能看到效果了
Compiler 对象包含了当前运行 Webpack 的配置,包括 entry、output、loaders 等配置,这个对象在启动 Webpack 时被实例化,而且是全局唯一的。Plugin 可以通过该对象获取到 Webpack 的配置信息进行处理。
compiler 上暴露的一些常用的钩子:
下面来举个例子
class WebpackPlugin2 {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.run.tap('run', () => {
console.log('开始编译...')
})
compiler.hooks.compile.tap('compile', () => {
console.log('compile')
})
compiler.hooks.done.tap('compilation', () => {
console.log('compilation')
})
}
}
module.exports = WebpackPlugin2
此时我们执行一下 npm run build 就能看到效果了
有一些编译插件中的步骤是异步的,这样就需要额外传入一个 callback 回调函数,并且在插件运行结束时执行这个回调函数
class WebpackPlugin2 {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.beforeCompile.tapAsync('compilation', (compilation, cb) => {
setTimeout(() => {
console.log('编译中...')
cb()
}, 1000)
})
}
}
module.exports = WebpackPlugin2
Compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 Compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息,简单来讲就是把本次打包编译的内容存到内存里。Compilation 对象也提供了插件需要自定义功能的回调,以供插件做自定义处理时选择使用拓展。
简单来说,Compilation 的职责就是构建模块和 Chunk,并利用插件优化构建过程。
Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译,只要文件有改动,compilation 就会被重新创建。
Compilation 上暴露的一些常用的钩子:
Compiler 和 Compilation 的区别
在每次 webpack 打包之后,自动产生一个一个 markdown 文件清单,记录打包之后的文件夹 dist 里所有的文件的一些信息。
思路:
class FileListPlugin {
constructor(options) {
// 获取插件配置项
this.filename = options && options.filename ? options.filename : 'FILELIST.md'
}
apply(compiler) {
// 注册 compiler 上的 emit 钩子
compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
// 通过 compilation.assets 获取文件数量
let len = Object.keys(compilation.assets).length
// 添加统计信息
let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpacknn`
// 通过 compilation.assets 获取文件名列表
for (let filename in compilation.assets) {
content += `- ${filename}n`
}
// 往 compilation.assets 中添加清单文件
compilation.assets[this.filename] = {
// 写入新文件的内容
source: function () {
return content
},
// 新文件大小(给 webapck 输出展示用)
size: function () {
return content.length
},
}
// 执行回调,让 webpack 继续执行
cb()
})
}
}
module.exports = FileListPlugin
开发一个插件能够去除打包后代码的注释,这样我们的 bundle.js 将更容易阅读
思路:
class RemoveCommentPlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
// 去除注释正则
const reg = /("([^"]*(.)?)*")|('([^']*(.)?)*')|(/{2,}.*?(r|n))|(/*(n|.)*?*/)|(/******/)/g
compiler.hooks.emit.tap('RemoveComment', (compilation) => {
// 遍历构建产物,.assets中包含构建产物的文件名
Object.keys(compilation.assets).forEach((item) => {
// .source()是获取构建产物的文本
let content = compilation.assets[item].source()
content = content.replace(reg, function (word) {
// 去除注释后的文本
return /^/{2,}/.test(word) || /^/*!/.test(word) || /^/*{3,}//.test(word) ? '' : word
})
// 更新构建产物对象
compilation.assets[item] = {
source: () => content,
size: () => content.length,
}
})
})
}
}
module.exports = RemoveCommentPlugin
webpack 在前端工程中随处可见,当前流行的 vue, react, weex 等解决方案都推崇 webpack 作为打包工具。前端工具云集的时代,这是你值得选择的之一。
webpack是前端工程构建的一套工具,为什么一个程序称之为一套呢,是因为webpack其实是npm的一个模块,使用起来的话,这期间还需要很多其它模块来进行支持,所以我称之为一套工具。
本文从一个小Demo开始,通过不断增加功能来说明webpack的基本配置,只针对新手。webpack基本的配置就可以熟悉了,会引入loader,配置loader选项,会设置alias,会用plugins差不多。
Plugins是webpack的基础,我们都知道webpage的plugin是基于事件机制工作的,这样最大的好处是易于扩展。讲解如果扩展内置插件和其他插件,以及我们常用的Plugins插件
webpack技巧的总结:进度汇报、压缩、复数文件打包、分离app文件与第三方库文件、资源映射、输出css文件、开发模式、分析包的大小、更小的react项目、更小的Lodash、引入文件夹中所有文件、清除extract-text-webpack-plugin日志。
Webpack 作为目前最流行的前端构建工具之一,在 vue/react 等 Framework 的生态圈中都占据重要地位。在开发现代 Web 应用的过程中,Webpack 和我们的开发过程和发布过程都息息相关,如何改善 Webpack 构建打包的性能也关系到我们开发和发布部署的效率。
新版 Webpack 中我们所做的每一个更新目的都在于此,为了当大家在使用 Webpack 的时候敏捷连续毫无顿挫感。 webpack 4 进行构建性能测试,得出的结果非常有趣。结果很惊人,构建时间降低了 60%-98%!
Webpack 是一个现代 JavaScript 应用程序的模块打包器 (module bundler) 。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块
Tobias Koppers是一位自由软件开发者,家住德国纽伦堡。他因写出webpack这个已有数百万开发者使用的开源软件而名噪一时。他目前专注于JavaScript和开源项目。以下是我对他个人的专访,希望对大家有所启发。
本文讲述css-loader开启css模块功能之后,如何与引用的npm包中样式文件不产生冲突。比如antd-mobilenpm包的引入。在不做特殊处理的前提下,样式文件将会被转译成css module。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!