1. React脚手架解析
https://www.bilibili.com/video/BV1cv411C74F?p=2
2. loader
loader本质是一个函数,其接收三个参数,第一参数content是入口文件的代码内容
2.1 配置loader解析规则
旧:
const { resolve } = require('path')module.exports = {module: {rules: [{test: /\.js$/,// 在这里写很长一串loader: resolve(__dirname, 'loaders', 'loader1')}]},mode:"development"}
新:
const { resolve } = require('path')module.exports = {module: {rules: [{test: /\.js$/,// 在这里写loader文件名,后续配置loader解析规则loader: 'loader1'}]},// ********配置loader解析规则********resolveLoader: {// 先去node_modules文件中找loader,找不到再去自定义文件夹中modules: ['node_modules',resolve(__dirname, 'loaders')]},// *********************************mode: "development"}
2.2 loader执行顺序
自定义loader
//loader3.jsmodule.exports = function (content,map,meta){console.log(333);return content}module.exports.pitch = () => {console.log("pitch 333");}//loader2.jsmodule.exports = function (content,map,meta){console.log(222);return content}module.exports.pitch = () => {console.log("pitch 222");}//loader1.jsmodule.exports = function (content,map,meta){console.log(111);return content}module.exports.pitch = () => {console.log("pitch 111");}
webpack.config.js
const { resolve } = require('path')module.exports = {module: {rules: [{test: /\.js$/,// 将多个loader放入use数组里面use:['loader3','loader2','loader1']}]},resolveLoader: {modules: ['node_modules',resolve(__dirname, 'loaders')]},mode: "development"}
loader执行结果:
会先按照use数组从前到后解析loader并依次调用了loader的pitch方法
然后真正从后到前执行loader
pitch 333pitch 222pitch 111111222333
2.3 同步和异步loader
同步loader:
// 同步写法1:module.exports = function (content,map,meta){console.log(111);return content}// 同步写法2:module.exports = function (content,map,meta){console.log(111);this.callback(null,content,map,meta)}module.exports.pitch = () => {console.log("pitch 111");}
异步loader:
module.exports = function (content,map,meta){console.log(222);const callback = this.async()setTimeout(() => {callback(null,content)}, 1000);}module.exports.pitch = () => {console.log("pitch 222");}
2.4 获取和校验loader的options
webpack.config.js
//.....use:[{loader:'loader3',options:{name:"hxy"}}]//.....
loader3.js
// 引入工具,来获取loader的optionsconst { getOptions } = require('loader-utils')// 引入工具,来校验options是否合法const { validate } = require('schema-utils')// 引入校验规则const schema = require('./schema.json')module.exports = function (content, map, meta) {// 获取loader的optionsconst options = getOptions(this)console.log(options);// 校验options是否合法,第一个参数是校验规则,第二个是被校验loader的options// 校验失败会报错validate(schema, options, {name: 'loader3'})return content}module.exports.pitch = () => {console.log("pitch 333");}
schema.json 配置校验规则
{//options的类型"type": "object",// 配置options下的属性"properties": {// 定义一个options中的name属性"name": {// 属性name的类型"type": "string","description": "名称~"}},// 在options中追加额外的属性"additionalProperties": true}
2.5 自定义babel-loader
webpack.config.js
//...module: {rules: [{test: /\.js$/,loader:'babelLoader',options:{presets:['@babel/preset-env']}}]}//...
babelLoader.js
const { getOptions } = require('loader-utils')const { validate } = require('schema-utils')// 引入babel核心库const babel = require('@babel/core')// 使用工具utilconst util = require('util')// 引入babelLoader的options的校验规则const babelSchema = require("./babelSchema.json")// babel.transform是用来编译代码的方法,它是一个普通异步方法// util.promisify将普通异步方法转化成基于promise的异步方法const transform = util.promisify(babel.transform)module.exports = function (content, map, meta) {// 获取babelLoader的options配置const options = getOptions(this)// 校验babelLoader的options的配置validate(babelSchema, options, {name: "babelLoader"})// 创建异步const callback = this.async()// 使用loadertransform(content, options).then(({ code, map }) => {callback(null, code, map, meta)}).catch((e) => callback(e))}
babelSchema.json
{"type": "object","properties": {"presets": {"type": "array"}},"additionalProperties": true}
3. plugin
3.1 tapable的使用
tapable 这个小型 library 是 webpack 的一个核心工具,但也可用于其他地方,以提供类似的插件接口。webpack 中许多对象扩展自 Tapable 类。这个类暴露 tap, tapAsync 和 tapPromise 方法,可以使用这些方法,注入自定义的构建步骤,这些步骤将在整个编译过程中不同时机触发。
const { SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require('tapable')
3.1.1 同步hooks
同步hook依次执行
- SyncHook ```javascript const { SyncHook } = require(‘tapable’)
class Lesson { constructor() { // 初始化hooks容器 this.hooks = { go: new SyncHook([‘address’]) } }
tap() { // 往hooks容器中注册事件/添加回调函数 // 被触发时会依次调用下面被注册的回调函数 this.hooks.go.tap(‘class0318’, (address) => { console.log(‘class0318’, address); }) this.hooks.go.tap(‘class0410’, (address) => { console.log(‘class0410’, address); }) }
start() { // 触发hooks this.hooks.go.call(‘c318’) } }
const l = new Lesson(); l.tap() l.start() / 输出: class0318 c318 class0410 c318 /
2. SyncBailHook```javascriptconst { SyncBailHook } = require('tapable')class Lesson {constructor() {// 初始化hooks容器this.hooks = {go: new SyncBailHook(['address'])}}tap() {// 往hooks容器中注册事件/添加回调函数this.hooks.go.tap('class0318', (address) => {console.log('class0318', address);// 当使用SyncBailHook时,如果回调函数中有返回值,就不会再执行后面的回调函数return 111})this.hooks.go.tap('class0410', (address) => {console.log('class0410', address);})}start() {// 触发hooksthis.hooks.go.call('c318')}}const l = new Lesson();l.tap()l.start()/*输出:class0318 c318*/
3.1.2 异步hooks
const { AsyncParallelHook } = require('tapable')class Lesson {constructor() {// 初始化hooks容器this.hooks = {// 异步hooks// AsyncParallelHook:异步并行leave: new AsyncParallelHook(['name', 'age']),}}tap() {// 往hooks容器中注册事件/添加回调函数this.hooks.leave.tapAsync('class0510', (name, age, cb) => {setTimeout(() => {console.log('class0510', name, age);cb()}, 2000);})this.hooks.leave.tapPromise('class0510', (name, age, cb) => {// 返回一个promise对象return new Promise((resolve) => {setTimeout(() => {console.log('class0610', name, age);resolve()}, 1000);})})}start() {// 触发hooksthis.hooks.leave.callAsync('hxy', 19, function () {// 代表所有leave容器中的函数触发完了,才触发console.log("end~~~")})}}const l = new Lesson();l.tap()l.start()/*输出:第一秒输出:class0610 hxy 19第二秒输出:class0510 hxy 19end~~~*/
const { AsyncSeriesHook } = require('tapable')class Lesson {constructor() {// 初始化hooks容器this.hooks = {// 异步hooks// AsyncSeriesHook:异步串行leave: new AsyncSeriesHook(['name', 'age'])}}tap() {// 往hooks容器中注册事件/添加回调函数this.hooks.leave.tapAsync('class0510', (name, age, cb) => {setTimeout(() => {console.log('class0510', name, age);cb()}, 2000);})this.hooks.leave.tapPromise('class0510', (name, age, cb) => {// 返回一个promise对象return new Promise((resolve) => {setTimeout(() => {console.log('class0610', name, age);resolve()}, 1000);})})}start() {// 触发hooksthis.hooks.leave.callAsync('hxy', 19, function () {// 代表所有leave容器中的函数触发完了,才触发console.log("end~~~")})}}const l = new Lesson();l.tap()l.start()/*输出:第二秒输出:class0510 hxy 19end~~~第三秒输出:class0610 hxy 19*/
3.2 compiler的hooks的使用
compiler钩子
plugins/Plugin1.js
class Plugin1 {apply(compiler) {compiler.hooks.emit.tap('Plugin1', (compilation) => {console.log('emit.tap 触发了')})compiler.hooks.emit.tapAsync('Plugin1', (compilation, cb) => {setTimeout(() => {console.log('emit.tapAsync 触发了')cb()}, 1000);})compiler.hooks.emit.tapPromise('Plugin1', (compilation) => {return new Promise((resolve) => {setTimeout(() => {console.log('emit.tapPromise 触发了')resolve()}, 1000);})})compiler.hooks.afterEmit.tap('Plugin1', (compilation) => {console.log('afterEmit.tap 触发了')})compiler.hooks.done.tap('Plugin1', (stats) => {console.log('done.tap 触发了')})}}module.exports = Plugin1
webpack.config.js
const Plugin1 = require('./plugins/Plugin1')module.exports = {plugins:[new Plugin1()]}
3.3 compilation的使用
const fs = require('fs')
const { resolve } = require('path')
const webpack = require('webpack')
// 引入webpack里面的资源格式化工具,来格式化资源以便于添加到dist目录中
const { RawSource } = webpack.sources
class Plugin2 {
apply(compiler) {
// 初始化compilation钩子,thisCompilation周期是最早拿到compilation的地方
compiler.hooks.thisCompilation.tap('Plugin2', (compilation) => {
// debugger
// console.log(compilation);
// 添加资源
compilation.hooks.additionalAssets.tapAsync('Plugin2', (cb) => {
// 构造资源添加到dist目录
const content = 'hello plugin2'
// 往要输出的资源中添加一个a.txt文件
compilation.assets['a.txt'] = new RawSource(content)
// 也可以将已有的文件资源添加到dist目录中
const data = fs.readFileSync(resolve(__dirname, 'b.txt'))
compilation.assets['b.txt'] = new RawSource(data)
/*
或者这种写法:
compilation.emitAsset("b.txt",new RawSource(data))
*/
cb()
})
})
}
}
module.exports = Plugin2
3.4 实战—自定义CopyWebpackPlugin
3.4.1 plugin代码编写
CopyWebpackPlugin.js 将from中的资源复制到to中,输出出去
const path = require('path')
// 获取校验options的方法
const { validate } = require("schema-utils")
// 获取校验options的标准
const schema = require("./schema.json")
// 用于匹配某一个文件夹下的文件,根据规则忽略一些文件
const globby = require('globby')
// 获取资源格式化的方法
const webpack = require("webpack")
const { RawSource } = webpack.sources
const { readFileSync } = require('fs')
class CopyWebpackPlugin {
constructor(options = {}) {
// 验证options是否符合规范
validate(schema, options, {
name: "CopyWebpackPlugin"
})
this.options = options
}
apply(compiler) {
// 初始化compilation
compiler.hooks.thisCompilation.tap('CopyWebpackPlugin', async (compilation) => {
// 添加资源的hooks
compilation.hooks.additionalAssets.tapAsync('CopyWebpackPlugin', async (cb) => {
// 我们下面代码要做的是将from中的资源复制到to中,输出出去
// 获取options里面的配置
const { from, to = '.', ignore } = this.options
// 0. 得到from的绝对路径,因为globby最好用绝对路径处理
// context就是webpack配置
// 运行node指令的目录
const context = compiler.options.context // process.cwd():运行时的路径
// 将输入路径变成绝对路径
const absoluteFrom = path.isAbsolute(from) ? from : path.resolve(context, from)
// 1. 从from下面获取所有文件的绝对路径并且过滤掉ignore的文件
// globby(要处理的文件夹路径,options),返回值为promise对象
let paths = await globby(absoluteFrom, { ignore })
console.log(paths); // 所有要加载的文件路径数组['D:/code/hxy/webpack5/高阶进阶/plugin/public/reset.css']
// 2. 读取paths中所有资源
const files = paths.map((absolutepath) => {
// 读取文件
const data = readFileSync(absolutepath)
// 生成该文件的文件名(basename方法得到路径的最后一个文件名称)
const relativePath = path.basename(absolutepath)
/*
和to属性结合
没有to --> reset.css
有to --> css/reset.css
*/
const filename = path.join(to,relativePath)
return {
// 文件数据
data,
// 文件名称
filename
}
})
// 3. 生成webpack格式的资源
const assets = files.map((file) => {
const source = new RawSource(file.data)
const filename = file.filename
return {
source,
filename
}
})
// 4. 添加compilation中,输出出去
assets.forEach((asset) => {
compilation.emitAsset(asset.filename, asset.source)
})
// 最后调用callback函数结束
cb()
})
})
}
}
module.exports = CopyWebpackPlugin
webpack.config.js 应用插件
// 引入自定义plugin
const CopyWebpackPlugin = require('./plugins/CopyWebpackPlugin')
module.exports = {
plugins:[
new CopyWebpackPlugin({
// 从哪里复制
from:'public',
// 输出到哪里去,不填默认为'.'
to:'css',
// 忽略文件
ignore:['**/index.html'],
}),
]
}
schema.json options的校验规则
{
"type":"object",
"properties":{
"from":{
"type":"string"
},
"to":{
"type":"string"
},
"ignore":{
"type":"array"
}
},
"additionalProperties":false
}
3.4.2 plugin使用结果
设置了to属性
未设置to属性
5. references
webpack-API: https://www.webpackjs.com/api/
