graph TD
内容总览 --> 1.React/Vue最新版本脚手架的详细配置
内容总览 --> 2.基于webpack5打包原理分析
内容总览 --> 3.基于webpack5自定义loader,自定义plugin
React
1.React脚手架详细配置
1.1 通过脚手架下载react项目
create-react-app 01.react_cli
1.2 暴露出react的详细配置
运行npm run eject 注意是不可逆的
//在package.json中
{
//eslint的配置 可以单独提出放在 .eslintrc.js中 react喜欢放在packge.json中方便查阅
"eslintConfig": {
"extends": [
"react-app"
]
},
//browserslist浏览器在开发和生产环境的兼容性
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
}
1.3 webpack.config.js解析
//在package.json中:加上cross-env GENERATE_SOURCEMAP=false
"scripts":{
"build":"cross-env GENERATE_SOURCEMAP=false node scripts/build.js"
}
Vue
1.vue配置详解
1.1 vue.config.js与webpack.config.js区别
1.vue.config.js 是vue里面的配置 执行vue-cli-service serve 会加载这个配置
2.webpack.config.js 是webapck的默认执行配置 webpack-dev-server 会执行这个配置
3.如果在vue项目中 执行webpack-dev-server 则等于不用vue的配置,需要从写配置
1.2 vue.config.js配置解释
//chainWebpack和configureWebpack都可以用对象或函数的方式写
const vueConfig = {
//用来修改vue中webpack的配置
chainWebpack:config=>{
},
//在configureWebpack里面配置会和vue中的webpack配置做合并
configureWebpack:{
}
}
module.exports = vueConfig
2.Vue脚手架详细配置
2.1 vue create 01.vue_cli 创建vue项目
2.2 暴露出vue的详细配置
//在产生的 js 文件开头,添加:module.exports =,然后格式化即可查看
//将vue的开发环境的配置输出到 webpack.dev.js中
开发环境:npx vue-cli-service inspect --mode development >> webpack.dev.js
生产环境:npx vue-cli-service inspect --mode production >> webpack.prod.js
webpack
1.loader的基本使用
1.1laoder本质是一个函数
//laoder本质是一个函数
/*
暴露3个参数:
content:文件内容
map:文件sourcemap的映射信息
meta:文件的源信息
*/
//1.同步laoder 第一种写法
module.exports = function (content, map, meta) {
console.log(content);
return content;
};
//2.同步laoder第二种写法
module.exports = function (content, map, meta) {
console.log(111);
//第一参数表示有没有错误 没有就传null
this.callback(null,content, map, meta)
};
//3.异步laoder写法 推荐使用
module.exports = function (content, map, meta) {
console.log(222);
const callback = this.async();
setTimeout(() => {
callback(null, content);
}, 1000);
return content;
};
1.2 resolveLoader的使用
const path = require("path");
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.js$/,
loader: "loader1",//下面配置了 这里直接就可以使用自定义loader的文件名
},
],
},
//laoder的解析规则
//loader先去node_modules里面找 没找到再去laoders里面找
resolveLoader: {
modules: ["node_modules", path.resolve(__dirname, "loaders")],
},
};
1.3 loader的执行顺序
1.4
loader中options的 获取和校验
- 自定义的laoder文件
//loader-utils 来自于webpack
//getOptions用于获取laoder的options配置选项 webpack4版本以前
//webpack5版本直接 this.query获取laoder的options配置选项
const { getOptions } = require("loader-utils");
const { validate } = require("schema-utils"); //校验options的规则的函数
const schema = require("./schema.json"); //校验options的规则
module.exports = function (content, map, meta) {
//webpack4 版本获取options
//const options = getOptions(this);
//console.log(333, options);
//webpack5版本直接 this.query 获取options
console.log(this.query, "---");
//校验options是否合法
validate(schema, this.query, {
name: "loader3",
});
return content;
};
- schema.json文件 校验规则
{
"type":"object",
"properties":{
"name":{
"type":"string",
"description":"名称~"
}
},
"additionalProperties":true//为true表示可以追加属性不报错,为fasle追加属性会报错
}
1.5 自定义babel-loader
//`要安装@babel/core 和@babel/preset-env`
//1.在babelLoaderNew.js里面
const { validate } = require("schema-utils");
const babelSchema = require("./babelSchema");
//安装 yarn add @babel/core -D
//编译 1.里面有一个babel.transform编译代码的异步方法 是一个普通的异步方法
const babel = require("@babel/core");
const util = require("util"); //工具函数
//2.util.promisify() 将普通的异步方法转化成基于promise的异步方法
const transform = util.promisify(babel.transform);
module.exports = function (content, map, meta) {
//1.获取laoder中的options配置
const options = this.query;
//2.校验babel的options
validate(babelSchema, options, {
name: "Babel Loader",
});
//3.异步创建
const callback = this.async();
//使用babel编译代码
transform(content, options)
.then(({ code, map }) => callback(null, code, map, meta))
.catch((e) => callback(e));
};
//2.新建 babelSchema.json文件
{
"type":"object",
"properties":{
"presets":{
"type":"array"
}
},
"additionalProperties":true
}
//3.在webpack.config.js中
module: {
rules: [
{
test: /\.js$/,
//因为有配置路径所以可以这样写
loader: "babelLoaderNew",
options: {
presets: ["@babel/preset-env"], //使用 要安装 yarn add @babel/preset-env -D
},
},
],
},
2.plugin讲解
2.1 钩子的生命周期介绍 Tapable
//1.Sync开头表示同步钩子
//2.Async代表异步钩子
//3.不论同步还是异步,事情做完才会往下走
const {
SyncHook, //基本同步钩子
SyncBailHook, //表示同步钩子有返回值就会退出,没有返回值就会继续执行
SyncWaterfallHook,//瀑布流在这个钩子中可以注册n个钩子,上一个钩子返回值传递给下一个钩子
SyncLoopHook,
AsyncParallelHook,//并行执行,异步代码可同时执行
AsyncParallelBailHook,//并行执行有返回值就终止
AsyncSeriesHook,//同步执行,一个一个的执行
AsyncSeriesBailHook,//同步执行 有返回值就退出
AsyncSeriesWaterfallHook//同步执行的瀑布流
} = require("tapable");
钩子案例代码
const {
SyncHook,
SyncBailHook,
AsyncParallelHook,
AsyncSeriesHook,
} = require("tapable");
class Lesson {
constructor() {
//定义一个钩子容器
this.hooks = {
//go: new SyncHook(["address"]), //同步钩子 go相当于容器
/*
SyncHook 执行结果:
class0410 c318
class0410 c318
*/
go: new SyncBailHook(["address"]), //同步钩子
/*
SyncBailHook 执行结果:
有第一个有return后面的就不会执行
*/
leave: new AsyncParallelHook(["name", "age"]), //异步并行执行
//leave:new AsyncSeriesHook(["name", "age"]),//异步串行 一个个执行
};
}
//定义方法使用容器
tap() {
//往hooks容器中注册事件/添加回调函数 通过tap来绑定事件
//往go容器中添加'class0318'这样一个东西,值为回调函数
this.hooks.go.tap("class0318", (address) => {
console.log("class0318", address);
return 111;
});
this.hooks.go.tap("class0410", (address) => {
console.log("class0410", address);
});
//tapAsync绑定异步钩子 cb为callback函数
this.hooks.leave.tapAsync("class0510", (name, age, cb) => {
setTimeout(() => {
console.log("class0510", name, age);
cb();
}, 2000);
});
//tapPromise也是定义异步钩子 没有callback函数
this.hooks.leave.tapPromise("class0610", (name, age) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("class0610", name, age);
resolve();
}, 1000);
});
});
}
//在start里面触发hooks
start() {
//通过call来触发钩子 会把go容器都触发
//this.hooks.go.call("c318");
//callAsync触发异步钩子
this.hooks.leave.callAsync("jack", 18, function () {
//代表所有leave容器的函数触发完,才触发
console.log("end");
});
}
}
const l = new Lesson();
l.tap();
l.start();
2.2 compiler 执行plugin时会执行此钩子上的各个生命周期
class Plugin1 {
apply(compiler) {
compiler.hooks.emit.tap("Plugin1", (compilation) => {
console.log("emit.tap 111");
});
compiler.hooks.emit.tapPromise("Plugin1", (compilation) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("tapPromise.tap 111");
}, 1000);
resolve();
});
});
compiler.hooks.afterEmit.tapAsync("Plugin1", (compilation, cb) => {
setTimeout(() => {
console.log("afterEmit.tap 111");
cb();
}, 1000);
});
compiler.hooks.done.tap("Plugin1", (compilation) => {
console.log("done.tap 111");
});
}
}
module.exports = Plugin1;
2.3 断点调试webpack
(1)配置及打断点
//1.现在 package.json中
"scripts": {
"build": "webpack",
//表示私用node运行 启动调试模式,打上断点 调试webpack.js
"start": "node --inspect-brk ./node_modules/webpack/bin/webpack.js"
},
//2.在自定义Plugin.js文件中 打上断点
class Plugin2 {
apply(compiler) {
//compilation在thisCompilation这个勾子时初始被注册
compiler.hooks.thisCompilation.tap("Plugin2", (compilation) => {
debugger;
console.log(compilation);
});
}
}
module.exports = Plugin2;
(2)打开webpack网站(最好英文)
2.3 compilation的勾子生命周期
也是个对象 里面也有很多hooks勾子
- 添加资源
- 往打包的dist目录下添加一个a.txt文件(
方式一)
//第一种:操作起来不方便 需要自己计算大小和内容
class Plugin2 {
apply(compiler) {
//compilation在thisCompilation这个勾子时初始被注册
//1.初始化compilation勾子
compiler.hooks.thisCompilation.tap("Plugin2", (compilation) => {
//2.通过additionalAssets添加资源
compilation.hooks.additionalAssets.tapAsync("Plugin2", (cb) => {
const content = "hello zhangpan";
compilation.assets["a.txt"] = {
//文件大小
size() {
return content.length;
},
//文件内容
source() {
return content;
},
};
cb();
});
});
}
}
module.exports = Plugin2;
- 往打包的dist目录下添加一个b.txt文件(
方式二)
//在自定义plugins目录下建一个b.txt文件
const fs = require("fs");
const util = require("util");
const path = require("path");
const { webpack } = require("webpack");
//将fs.readFile方法变成一个promise风格的异步方法
const readFile = util.promisify(fs.readFile);
// RawSource 将data数据变成下面风格数据
/*
{
//文件大小
size() {
return content.length;
},
//文件内容
source() {
return content;
},
};
*/
const { RawSource } = require("webpack-sources");
class Plugin2 {
apply(compiler) {
//compilation在thisCompilation这个勾子时初始被注册
//1.初始化compilation勾子
compiler.hooks.thisCompilation.tap("Plugin2", (compilation) => {
//2.通过additionalAssets添加资源
compilation.hooks.additionalAssets.tapAsync("Plugin2", async (cb) => {
const data = await readFile(path.resolve(__dirname, "b.txt"));
compilation.assets["b.txt"] = new RawSource(data);
cb();
});
});
}
}
module.exports = Plugin2;
- 模拟copywbpackplugin
const { validate } = require("schema-utils");
const schema = require("./schema.json");
const path = require("path");
const fs = require("fs");
const { promisify } = require("util");
//专门用于匹配文件列表,根据你给的规则忽略掉一些文件 返回promise对象
const globby = require("globby");
const readFile = promisify(fs.readFile);
const {RawSource} = require('webpack-sources')
class CopyWebpackPlugin {
constructor(options = {}) {
//验证options是否符合规范
validate(schema, options, {
name: "CopyWebpackPlugin", //plugin的名
});
this.options = options;
}
apply(compiler) {
//初始化compilation
compiler.hooks.thisCompilation.tap("CopyWebpackPlugin", (compilation) => {
//添加资源的hooks
compilation.hooks.additionalAssets.tapAsync(
"CopyWebpackPlugin",
async (cb) => {
//将from中的资源复制到to中,输出出去
//1.读取from资源 -------
const { from, ignore } = this.options;
//用法 globby('要处理的文件夹',options)
//context就是webpack配置
//获取运行指令的目录
const context = compiler.options.context; //等价于 const context = process.cwd()
//from要为绝对路径
//判断from是否为绝对路径,不是变成绝对路径
const absoluteFrom = path.isAbsolute(from)
? from
: path.resolve(context, from);
//2.过滤掉ignore的资源 ----------
const paths = await globby(absoluteFrom, { ignore }); //paths所有要加载的文件路径
//paths 值格式 [ './src/components/index/index.js','./src/components/news/n.js']
//读取paths所有资源
const files = await Promise.all(
paths.map((absolutePath)=>{
//读取文件
const data = readFile(absolutePath)
//获取文件名称
const filename = path.basename(absolutePath)
return {
//文件数据
data,
//文件名称
filename
}
})
)
//3.生成webpack格式的资源 -------
const assets = files.map((file)=>{
const source = new RawSource(file.data)
return {
source,
filename:file.filename
}
})
//4.添加到compilation中 输出出去 -------
assets.forEatch((asset)=>{
compilation.emitAsset(asset.filename,asset.source)
})
cb()
}
);
});
}
}
module.exports = CopyWebpackPlugin;