webpack进阶版

173 阅读6分钟
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 注意是不可逆的 1658991627922.png

//在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"
}

1658994840607.png

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的执行顺序 1659079042714.png 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网站(最好英文

1659249694083.png 1659249863879.png

2.3 compilation的勾子生命周期
也是个对象 里面也有很多hooks勾子

  • 添加资源 1659250565274.png
  • 往打包的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;

3.webpack 执行流程

1659343612729.png