首先咱们先回顾一下webpack常见配置,由于后面会用到,因此简单介绍一下。css
// 入口文件
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,须要配置这两个属性。html
这些都是webpack的一些基础知识,对于理解webpack的工做机制颇有帮助。vue
OK今天第一个主角登场node
loader是文件加载器,可以加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一块儿打包到指定的文件中webpack
需求:es6
例如:abcdefg转换后为Gfedcbaweb
OK,咱们开始npm
1)首先建立两个loader(这里以本地loader为例)json
为何要建立两个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官方文档》
在 Webpack 运行的生命周期中会广播出许多事件,Plugin 能够监听这些事件,在合适的时机经过 Webpack 提供的 API 改变输出结果。
plugin和loader的区别是什么?
对于loader,它就是一个转换器,将A文件进行编译造成B文件,这里操做的是文件,好比将A.scss或A.less转变为B.css,单纯的文件转换过程
plugin是一个扩展器,它丰富了wepack自己,针对是loader结束后,webpack打包的整个过程,它并不直接操做文件,而是基于事件机制工做,会监听webpack打包过程当中的某些节点,执行普遍的任务。
/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'})
],
...
};
复制代码
这就是一个最简单的插件(虽然咱们什么都没干)
看到这里可能会问compiler是啥,compilation又是啥?
Compiler 对象包含了 Webpack 环境全部的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局惟一的,能够简单地把它理解为 Webpack 实例;
Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被建立。Compilation 对象也提供了不少事件回调供插件作扩展。经过 Compilation 也能读取到 Compiler 对象。
Compiler 表明了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是表明了一次新的编译。
绑定事件
compiler.plugin('event-name', params => {
...
});
复制代码
触发事件
compiler.apply('event-name',params)
复制代码
compiler.plugin('emit',function(compilation, callback) {
...
// 处理完毕后执行 callback 以通知 Webpack
// 若是不执行 callback,运行流程将会一直卡在这不往下执行
callback();
});
复制代码
关于complier和compilation,webpack定义了大量的钩子事件。开发者能够根据本身的须要在任何地方进行自定义处理。
场景:
小程序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(),
]
})
复制代码
插件编写完成后,执行编译命令
搞定~
若是有更多的需求能够参考《如何写一个插件》