准备工作:
npm init -y
初始化一个项目
npm i webpack webpack-cli -D
创建一个webpack.config.js文件
const path = require('path')
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
}
loader实现
通过实现一个简单的字符串替换loader来了解下loader的工作原理
// index.js
console.log('yuxiaoyu')
// 创建一个loader文件夹 先创建个replaceLoader.js
// loader就是一个函数,需要用声明式函数,因为要取到上下文中的this,这个函数接受一个参数,是我们要打包的源码
module.exports = function(source) {
console.log(source, this.query)
return source.replace('yuxiaoyu', '于晓俞')
}
// 然后修改webpack.config.js
const path = require('path')
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
module: {
rules: [
{
test: /\.js$/,
use: {
loader: path.resolve(__dirname, 'loader/replaceLoader.js'),
options: {
name: '实现loader'
}
}
}
]
}
}
npx webpack
之后,我们可以在main.js中看到index.js中的字符串已经被替换掉了
使用this.callback来返回多个结果信息,而不仅仅是源码
/*
this.callback()接受四个参数
err,content,sourceMap,meta
*/
// 修改replaceLoader.js
module.exports = function(source) {
console.log(source, this.query)
// return source.replace('yuxiaoyu', '于晓俞')
this.callback(null, source.replace('yuxiaoyu', '于晓俞'))
}
结果同样可以正常编译
处理异步
官方提供this.async处理loader内的异步
// 修改loader
module.exports = function(source) {
console.log(source, this.query)
// 我们使用this.async来处理,他会返回this.callback
// 定义一个异步处理,告诉webpack,这个loader里有异步事件,在里面调用下这个异步
// callback 就是 this.callback 注意参数的使用
const callback = this.async()
setTimeout(() => {
callback(null, source.replace('yuxiaoyu', '于晓俞'))
}, 3000)
}
多个loader的使用
// 再创建一个同步的replaceLoaderSync.js
module.exports = function(source) {
return source.replace('于晓俞', '程序猿')
}
//修改webpack.config.js文件
module: {
rules: [
{
test: /\.js$/,
use: [
path.resolve(__dirname, 'loader/replaceLoaderSync.js'),
{
loader: path.resolve(__dirname, 'loader/replaceLoader.js'),
options: {
name: '实现loader'
}
}
]
}
]
}
处理loader的路径问题
我们写的配置文件地址写法过于繁琐,怎么能像我们npm i 下载的loader那样,直接写个loader名称就行了呢?这就需要我们再配置下webpack.config.js
// 配置resolveLoader, 告诉webpack去哪里查找loader,先去node_modules里查找,找不到再去loader文件夹汇总
resolveLoader: {
modules: ['node_modules', './loader']
},
module: {
rules: [
{
test: /\.js$/,
use: [
// 改成文件名
'replaceLoaderSync',
{
// 改成文件名
loader: 'replaceLoader',
options: {
name: '实现loader'
}
}
]
}
]
}
同样可以编译成功!
plugins实现
plugin: 开始打包,在某个时刻,帮助我们处理一些什么事情的机制
一个插件由以下构成:
一个具名 JavaScript 函数。
在它的原型上定义 apply 方法。
指定一个触及到 webpack 本身的 事件钩子。
操作 webpack 内部的实例特定数据。
在实现功能后调用 webpack 提供的 callback。
我们实现一个功能,在编译完成后,在输出文件时,同时输出一个txt文本
// 创建一个plugin文件夹 创建文件TxtWebpackPlugin.js
/*
插件是由一个构造函数(此构造函数上的 prototype 对象具有 apply 方法)的所实例化出来的。这个 apply 方法在安装插件时,会被 webpack compiler 调用一次。apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象。
*/
class TxtWebpackPlugin {
constructor() {}
apply(compiler) {
// compiler.hooks中存放这各种钩子,emit在编译成功时输出文件前执行的事件
// 有些插件 hooks 是异步的。想要 tap(触及) 某些 hooks,我们可以使用同步方式运行的 tap 方法,或者使用异步方式运行的 tapAsync 方法或 tapPromise 方法。
compiler.hooks.emit.tapAsync('TxtWebpackPlugin', (compilation, callback) => {
// 处理异步的事情
let content = '生成的文件列表:\n'
for (var filename in compilation.assets) {
content += filename + '\n'
}
compilation.assets['filelist.txt'] = {
source: function () {
return content
},
size: function () {
return content.length
}
}
callback()
});
}
}
module.exports = TxtWebpackPlugin