webpack工作流程

news2024/10/7 16:18:55

webpack工作流程

  1. 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置对象
  2. 用上一步得到的参数初始化 Compiler 对象
  3. 加载所有配置的插件
  4. 执行对象的 run 方法开始执行编译
  5. 根据配置中的entry找出入口文件
  6. 从入口文件出发,调用所有配置的Loader对模块进行编译
  7. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  8. 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
  9. 再把每个 Chunk 转换成一个单独的文件加入到输出列表
  10. 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果

2020webpackflow

一、初始化参数

  • 从配置文件和 Shell 语句中读取与合并参数,得出最终的参数,优先级遵循"离用户越近,优先级配置越高"

    • 例如同一个参数,如果配置环境中和shell语句都有同一个配置,从命令行终端执行的时候,以shell语句中的配置为住
  • 安装webpack-cli脚手架

    • npm install webpack webpack-cli -D
      
    • 如果直接使用webpack的核心模块,则不需要安装webpack-cli

    • 过程实际上就是用上一步得到的参数初始化Compiler对象,并加载所有配置的插件,通过执行Compiler对象的run方法开始执行编译过程

  • 运行webpack-cli

    • npx webpack-cli <command> <arguments>
      
    • 过程实际上是调用webpack-cli的主入口文件bin/webpack.js。在执行时,会将命令行参数传递给webpack()函数,这个函数是webpack的主要入口点。然后,webpack内部会解析这些参数并按照webpack的配置来执行编译过程。而entry 属性用于指定入口文件或入口文件的数组。这些入口文件是 webpack 编译的起点,它会根据配置中的entry找出所有的入口文件

shell语句

  • shell 脚本参数一般是在终端中输入或者在package.jsonscript中配置对应的命令

参数解析

  • 为了整合 webpack.config.js 中的配置和命令行参数,Webpack 使用了一个叫做 optimist 的库。optimist 是一个用于解析命令行参数的库,可以将命令行参数解析为 JavaScript 对象,这样它们就可以与 webpack.config.js 中的配置进行合并。它可以帮助 Webpack 将用户通过命令行传递的参数与配置文件中的选项整合在一起,形成一个完整的 options 对象。这个 options 对象会被传递给后续的插件或加载器,以便它们可以根据这些配置来执行相应的任务。
  • 如果想要在Webpack中获取Shell脚本参数,可以使用Node.js的命令行参数解析库,例如yargs或commander

yargs

是一个非常有用的 Node.js 库,用于解析命令行参数,可以轻松地定义和解析命令行选项、参数和子命令,并将它们转换为 JavaScript 对象,这个对象可以作为参数传递给其他函数或流程,使得命令行参数的处理更加灵活和方便。

二、开始编译

  • webpack编译会创建两个核心对象

    • compiler:包含了webpack环境的所有的配置信息,包括options,loader和plugin,和webpack整个生命周期相关的钩子

      • 在 lib/webpack.js 文件中,Compiler 对象的初始化逻辑主要包括以下几个步骤

        ① 创建 Compiler 实例:通过调用 new Compiler() 创建一个新的 Compiler 实例。
        ② 配置 Compiler:根据传入的配置对象(如 webpack.config.js 中定义的配置),对 Compiler 进行相应的配置,例如设置输出目录、加载器等。
        ③ 注册插件: 通过调用compiler.hooks上的方法,注册各种插件。这些插件可以在构建过程中执行各种自定义操作,如资源优化、代码分割等。
        ④ 初始化其他组件:根据配置 初始化其他与构建相关的组件,如Compilation、ResolverFactory等。
        ⑤ 返回 Compiler 实例:将初始化完成的 Compiler 实例返回,以便在构建过程中使用。

    • compilation:作为plugin内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的Compilation将被创建

  • 当 Webpack 初始化完成后,Compiler对象会存储所有的配置和组件,但实际的构建过程还没有开始,要开始构建过程,需要调用Compiler对象的run方法。调用Compiler对象的run方法真正启动webpack构建流程,它是启动构建过程的最后一步,也是实际生成打包文件的开始

阶段

得到compiler对象

//Compiler类继承了Tapable。Compiler对象定义在./node_modules/webpack/lib/Compiler.js
//Tapable 是一个提供钩子(hooks)功能的基类,
//这些钩子允许我们在特定的事件点上执行自定义逻辑。
class Compiler extends Tapable {
    constructor(context) {//初始化时定义了很多钩子函数
        super();
        this.hooks = {
            //生命周期钩子
            beforeCompile: new AsyncSeriesHook(["params"]),
            compile: new SyncHook(["params"]),
            afterCompile: new AsyncSeriesHook(["compilation"]),
            make: new AsyncParallelHook(["compilation"]),
            entryOption: new SyncBailHook(["context", "entry"])
            // 定义了很多不同类型的钩子
        };
        // ...
    }
}
 
//这是一个Webpack的外部API函数,它接受一个配置对象options作为参数。
//在函数内部创建了一个新的Compiler实例。这意味着使用Webpack时,实际上是在使用这个Compiler类来处理和打包资源。
function webpack(options) {
  var compiler = new Compiler();
  ...// 检查options,若watch字段为true,则开启watch线程
  return compiler;
}
...
  • compile阶段

  • 整个编译过程的开始,意味着开始将源代码转换为可执行代码或目标代码,进行初步的解析和处理

  • make阶段

    • 确定项目的依赖关系,并创建模块对象以供后续处理。会分析源代码中的import或require语句,分析项目的依赖关系,并确定哪些模块需要被编译
  • buildModule阶段

    • 对每个模块的源代码进行处理、转换、优化、合并等操作
  • afterCompile

    • 有模块都构建完成后执行一些全局的操作,可能会进行全局的代码优化、清理临时文件等
  • seal封装构建结果

    • 将构建的结果进行封装,以便于部署和分发,工具会根据其内部的依赖关系,将每个chunk(例如,由多个模块组成的代码块)输出到指定的结果文件
  • emit发射文件

    • 将各个模块或chunk输出到结果文件
  • after-emit

    • 在所有chunks都输出到结果文件后执行一些后续操作,可能会复制生成的资源到输出目录、执行一些清理任务等

加载插件,执行run开始编译

img

  • 执行了run()方法后首先会触发compile ,这一步会构建出Compilation 对象。该对象是编译阶段的主要执行者,主要会依次下述流程:执行模块创建、依赖收集、分块、打包等主要任务的对象

确定入口

  • 在创建module之前,Compiler会触发make,并调用Compilation.addEntry方法,通过options对象的entry字段找到我们的入口js文件

三、编译模块

  • 从entry入口文件开始读取,主要执行_addModuleChain()函数。_addModuleChain 是 Webpack 的内部方法,用于处理模块链,能根据给定的依赖项创建新的模块实例。这个方法主要的工作是根据给定的 module 和 context 对象,以及一些其他参数,来创建一个新的模块。具体来说,这个方法会根据模块的类型(例如,JavaScript、CSS、图片等)来获取对应的模块工厂并创建模块。

  • _addModuleChain(context, dependency, onModule, callback) {
       ...
       // 根据依赖查找对应的工厂函数
       const Dep = /** @type {DepConstructor} */ (dependency.constructor);
       const moduleFactory = this.dependencyFactories.get(Dep);
       
       // 调用工厂函数moduleFactory的create来生成一个空的NormalModule对象,NormalModul是Webpack中表示一个模块的类。 
       moduleFactory.create({
           dependencies: [dependency]
           ...
       }, (err, module) => {
           ...
           //定义一个afterBuild函数,这个函数在模块编译完成后会被调用。 
           const afterBuild = () => {
            this.processModuleDependencies(module, err => {
              if (err) return callback(err);//处理过程中发生错误,则通过回调函数返回错误信息 
               callback(null, module); //处理成功,则通过回调函数返回新创建的模块
             });
        };
           //开始模块编译。这里会调用buildModule方法来进行实际的编译操作。  
           this.buildModule(module, false, null, null, err => {
               ...
               afterBuild();//当模块编译完成后,调用之前定义的 afterBuild函数。 
           })
       })
    }// _addModuleChain 方法结束
    
  • _addModuleChain 是 Webpack 的内部方法,用于处理模块链。以下是该方法的主要过程:

    1. 接收参数:该方法接收四个参数,包括上下文对象 context、依赖项 dependency、回调函数 onModule 和回调函数 callback。
    2. 获取模块工厂:根据依赖项的构造类型,从 dependencyFactories 集合中获取对应的模块工厂。
    3. 创建新模块:使用获取到的模块工厂,通过 create方法创建一个新的模块实例。
    4. 添加模块到编译队列:将新创建的模块加入到编译对象的 modules 数组中,以便在后续的打包过程中使用该模块。
    5. 处理模块依赖:开始处理新创建模块的依赖项,递归地将它们也加入到编译队列中。
    6. 编译模块:当所有依赖项都处理完毕后,开始编译当前模块。Webpack 会调用 buildModule 方法来执行实际的编译操作,包括解析模块代码、生成依赖图等。
    7. 处理编译结果:当模块编译完成后,Webpack 会进行一些后续处理,例如将模块添加到编译对象的 modules 数组中,以便在后续的打包过程中使用该模块。
    8. 回调函数:当整个过程完成后,通过回调函数返回编译结果或错误信息。
      随后执行buildModule进入真正的构建模块module内容的过程

调用loader,找到依赖

  • webpack提供的一个很大的便利就是能将所有资源都整合成模块,不仅仅是 js文件。因此,需要一些loader ,比如url-loader、jsx-loader、css-loader等,来让我们可以直接在源文件中引用各类资源。

  • 对每一个require()用对应的loader进行加工

    • webpack调用doBuild(),对每一个require()用对应的loader进行加工,最后生成一个js module。当webpack遇到require()或import语句时,它会使用对应的加载器(loader)处理这些依赖项。加载器可以将源文件转换成Webpack能够理解和处理的模块。然后,Webpack会调用acorn来解析这些经过加载器处理的源文件,生成抽象语法树(AST)

    • // 定义在Compilation对象上的_addModuleChain方法,该方法用于处理模块链。  
      // 接收四个参数:context(上下文)、dependency(依赖项)、onModule(处理模块的回调函数)和callback(回调函数)。  
      Compilation.prototype._addModuleChain = function process(context, dependency, onModule, callback) {
        // 如果Compilation对象启用了性能分析,则记录当前时间,用于后续计算该方法的执行时间。 
        var start = this.profile && +new Date();
        ...
        // 根据模块的类型获取对应的模块工厂并创建模块
        // 根据传入的依赖项的构造函数,从dependencyFactories集合中获取对应的模块工厂。  
        // 这个集合中存储了各种类型的模块工厂,用于创建不同类型的模块。
        var moduleFactory = this.dependencyFactories.get(dependency.constructor);
        ...
        moduleFactory.create(context, dependency, function(err, module) {
          var result = this.addModule(module); // 将新创建的模块添加到Compilation对象的modules数组中。
          ...
          this.buildModule(module, function(err) { // 调用buildModule方法来构建模块。
            ...
            // 构建模块,添加依赖模块
          }.bind(this)); // 由于使用了bind(this)来绑定回调函数的执行上下文为Compilation对象,所以后续操作可以在Compilation对象的上下文中进行。
        }.bind(this));
      };
      
  • 调用 acorn 解析经 loader 处理后的源文件,生成抽象语法树 AST

    • Acorn是一个轻量级、流式的JavaScript解析器,可以生成抽象语法树(AST)。通过Acorn解析源文件后,可以获得源文件的语法结构,以便进行进一步的分析、转换或打包。虽然加载器在处理源文件时起到重要作用,但AST的生成是由Acorn解析器完成的。

    • // 定义Parser对象的parse方法,该方法用于解析源代码并返回抽象语法树(AST)。  
      // 它接收两个参数:source(待解析的源代码)和initialState(初始状态)。 
       Parser.prototype.parse = function parse(source, initialState) {
        var ast;
        if (!ast) {
          // acorn以es6的语法进行解析
          ast = acorn.parse(source, {
            ranges: true, // 解析时保留每个语法元素的原始位置范围信息。  
            locations: true, // 解析时保留每个语法元素的原始位置信息。  
            ecmaVersion: 6, // 使用ECMAScript 6(即ES6)的语法版本进行解析。  
            sourceType: "module" // 将源代码视为模块,解析为模块的抽象语法树(AST)。  
          });
        }
        ...
      };
      
  • 遍历AST,构建该模块所依赖的模块

    • 对于当前模块,或许存在着多个依赖模块。当前模块会开辟一个依赖模块的数组,在遍历AST时,将require()中的模块通过addDependency()添加到数组中。当前模块构建完成后,webpack调用processModuleDependencies开始递归处理依赖的module,接着就会重复之前的构建步骤。

    • 从配置的入口模块开始,分析其 AST,当遇到require等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系

    • // 定义Compilation对象的addModuleDependencies方法,用于向给定的模块添加依赖。  
      // 它接收五个参数:module(要添加依赖的模块)、dependencies(依赖数组)、bail(是否立即停止编译)、cacheGroup(缓存组)和recursive(是否递归添加依赖)。  
      // 最后一个参数callback是回调函数,用于在添加依赖完成时进行回调。
       Compilation.prototype.addModuleDependencies = function(module, dependencies, bail, cacheGroup, recursive, callback) {
        // 根据依赖数组(dependencies)创建依赖模块对象
        var factories = []; // 用于存储依赖模块的工厂对象
        for (var i = 0; i < dependencies.length; i++) { // 为每个依赖创建一个工厂对象并存储到factories数组中。
          var factory = _this.dependencyFactories.get(dependencies[i][0].constructor); // 获取对应的模块工厂
          factories[i] = [factory, dependencies[i]]; // 将工厂对象和依赖项一起存储到factories数组中。  
        }
        ...
        // 与当前模块构建步骤相同
      }
      

四、完成编译

  • module是webpack构建的核心实体,也是所有module的父类,它有几种不同子类:NormalModule,MultiModule,ContextModule,DelegatedModule 等。但这些核心实体都是在构建中都会去调用对应方法,也就是模块构建函数build()。
  • 对于每一个module,它都会有这样一个构建方法build()。当然,它还包括了从构建到输出的一系列的有关module生命周期的函数。

五、输出资源

seal 输出资源

  • 在所有模块及其依赖模块编译(build)完成后,webpack会触发一个名为seal的生命周期事件,这个事件标志着构建过程的结束,并且webpack将开始对构建后的结果进行封装。在seal事件触发后,webpack会逐个对每个module和chunk进行整理,这个阶段的目标是生成编译后的源码,进行合并、拆分以及生成hash
  • seal方法主要是生成chunks,对chunks进行一系列的优化操作,并生成要输出的代码。webpack中的chunk,可以理解为配置在entry中的模块,或者是动态引入的模块,根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表
// 定义Compilation对象的seal方法,该方法接受一个回调函数作为参数  
Compilation.prototype.seal = function seal(callback) {
  this.applyPlugins("seal");//调用并触发所有已注册的插件的seal事件。这允许插件执行一些自定义逻辑或操作  
  this.preparedChunks.sort(function(a, b) {//对已准备的chunks进行排序。排序的依据是chunk的名称。 
    if (a.name < b.name) {
      return -1;//如果名称a小于名称b,则返回-1;
    }
    if (a.name > b.name) {
      return 1;//如果名称a大于名称b,则返回1;
    }
    return 0;//如果两者相等,则返回0 
  });
  // 遍历已排序的chunks,并对每个chunk执行以下操作:
  this.preparedChunks.forEach(function(preparedChunk) {
    var module = preparedChunk.module;//获取与当前chunk关联的模块 
    var chunk = this.addChunk(preparedChunk.name, module);//向当前的chunks集合中添加一个新的chunk,chunk的名称为preparedChunk.name,并且与module关联  
    chunk.initial = chunk.entry = true;//设置新添加的chunk的一些属性:它是一个初始chunk,也是一个入口chunk 
    // 整理每个Module和chunk,每个chunk对应一个输出文件。将module添加到chunk中,并把chunk添加到module中。这表示module和chunk之间存在依赖关系
    chunk.addModule(module);
    module.addChunk(chunk);
  }, this);//使用bind确保回调函数中的this指向Compilation对象实例  
  //异步调用并触发所有已注册的插件的"optimize-tree"事件。这个事件允许插件对chunks和modules进行优化。优化完成后,会执行回调函数。
  this.applyPluginsAsync("optimize-tree", this.chunks, this.modules, function(err) {
    if (err) {
      return callback(err);//如果回调函数中发生错误,则将错误传递给外部的callback函数
    }
    ... // 触发插件的事件
    this.createChunkAssets(); //生成最终assets,可能包括JavaScript、CSS、图片等。生成的assets会被存储在compiler的assets属性中。
    ... // 触发插件的事件
  }.bind(this));// 使用bind确保回调函数中的this指向Compilation对象实例 
};// seal方法结束

六、写入文件

存储到compiler.assets对象

  • 在封装过程中,webpack会调用Compilation中的createChunkAssets方法进行打包后代码的生成。createChunkAssets函数通过收集和封装代码片段,将它们转化为可执行的模块,并将这些资源文件存储在compiler.assets对象中,以便后续的输出和引用。createChunkAssets流程如下

img

  • createChunkAssets的流程主要包括以下步骤:
    ① 判断是否是入口chunk:createChunkAssets函数首先判断当前处理的chunk是否是入口chunk。如果是入口chunk,则使用MainTemplate进行封装,否则使用ChunkTemplate进行封装。
    ② 处理依赖关系:在语法树解析阶段,createChunkAssets函数会收集文件的依赖关系,包括需要插入或替换的部分。
    ③ 生成代码片段:在generate阶段,createChunkAssets函数会生成各种代码片段,包括需要打包输出的资源。
    ④ 封装代码:createChunkAssets函数会对生成的代码片段进行收集和封装,将它们封装成可执行的模块。一旦代码片段被封装完毕,它们就会被存储在compiler.assets对象中。

emit输出

  • 在Webpack的打包过程中,createChunkAssets方法执行完毕后,会调用Compiler中的emitAssets()方法。所以最后一步是,webpack调用Compiler中的emitAssets(),将生成的代码输入到output的指定位置,完成最终文件的输出,从而webpack整个打包过程结束。

  • output对象在Webpack配置中定义了输出目录、文件名等选项,emitAssets()方法会将生成的代码按照output配置的规则输出到相应的目录中。输出的文件可以是JavaScript、CSS、图片等资源文件,以及其他依赖图和其他元数据。( 在确定好输出内容后,根据配置确定输出的路径和文件名)

  • 若想对结果进行处理,则需要在emitAssets()触发后对自定义插件进行扩展,可以通过编写自定义插件来实现。例如,可以编写一个自定义插件,并在emitAssets()触发后对生成的代码进行进一步处理或修改。在插件的apply方法中,可以访问compiler对象并监听emit事件。当emit事件触发时,可以执行自己的逻辑来处理生成的代码或资源

  • 具体来说,可以在插件中实现以下步骤:

    ①访问compiler对象并监听emit事件。
    ②在emit事件触发时,获取生成的代码或资源。
    ③对获取的代码或资源进行进一步处理或修改。
    ④如果你想将修改后的结果输出到文件系统,可以使用compiler.outputFileSystem来写入文件。
    ⑤完成处理后,你可以选择将修改后的结果重新赋值给compiler.assets对象,以便Webpack将其输出到指定的目录中。

调试 webpack

通过 chrome 调试

node --inspect-brk ./node_modules/webpack-cli/bin/cli.js

然后打开 Chrome 浏览器控制台就可以调试了

通过执行命令调试

  • 打开工程目录,点击调试按钮,再点击小齿轮的配置按钮系统就会生成 launch.json 配置文件
  • 修改好了以后直接点击 F5 就可以启动调试

.vscode\launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "debug webpack",
      "cwd": "${workspaceFolder}",
      "program": "${workspaceFolder}/node_modules/webpack-cli/bin/cli.js"
    }
  ]
}

debugger.js

const webpack = require("webpack");
const webpackOptions = require("./webpack.config");
const compiler = webpack(webpackOptions);
//4.执行对象的run方法开始执行编译
compiler.run((err, stats) => {
  console.log(err);
  console.log(
    stats.toJson({
      assets: true,
      chunks: true,
      modules: true,
    })
  );
});

tapable.js

  • tapable 是一个类似于 Node.js 中的 EventEmitter 的库,但更专注于自定义事件的触发和处理
  • webpack 通过 tapable 将实现与流程解耦,所有具体实现通过插件的形式存在
class SyncHook {
  constructor() {
    this.taps = [];
  }
  tap(name, fn) {
    this.taps.push(fn);
  }
  call() {
    this.taps.forEach((tap) => tap());
  }
}

let hook = new SyncHook();
hook.tap("some name", () => {
  console.log("some name");
});

class Plugin {
  apply() {
    hook.tap("Plugin", () => {
      console.log("Plugin ");
    });
  }
}
new Plugin().apply();
hook.call();

webpack 编译流程

4.1 debugger.js

debugger.js

const webpack = require("./webpack");
const options = require("./webpack.config");
const compiler = webpack(options);
compiler.run((err, stats) => {
  console.log(err);
  console.log(
    JSON.stringify(
      stats.toJson({
        assets: true, //资源
        chunks: true, //代码块
        modules: true, //模块
      }),
      null,
      2
    )
  );
});

4.2 webpack.config.js

webpack.config.js

const path = require("path");
const RunPlugin = require("./plugins/run-plugin");
const DonePlugin = require("./plugins/done-plugin");
module.exports = {
  mode: "development",
  devtool: false,
  entry: {
    entry1: "./src/entry1.js",
    entry2: "./src/entry2.js",
  },
  output: {
    path: path.resolve("dist"),
    filename: "[name].js",
  },
  resolve: {
    extensions: [".js", ".jsx", ".ts", ".tsx", ".json"],
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          path.resolve(__dirname, "loaders/logger1-loader.js"),
          path.resolve(__dirname, "loaders/logger2-loader.js"),
        ],
      },
    ],
  },
  plugins: [
    new RunPlugin(), //开始编译的时候触发run事件,RunPlugin会监听这个事件执行回调
    new DonePlugin(), //编译完成的时候会触发done事件,DonePlugin会监听这个done事件的回调
  ],
};

4.3 webpack.js

webpack.js

let Compiler = require("./Compiler");
function webpack(options) {
  //1.初始化参数:从配置文件和Shell语句中读取并合并参数,得出最终的配置对象
  console.log(process.argv); //['node.exe','debugger.js']
  let argv = process.argv.slice(2);
  let shellOptions = argv.reduce((shellOptions, option) => {
    let [key, value] = option.split("=");
    shellOptions[key.slice(2)] = value;
    return shellOptions;
  }, {});
  let finalOptions = { ...options, ...shellOptions };
  console.log("finalOptions", finalOptions);
  //2.用上一步得到的参数初始化Compiler对象
  let compiler = new Compiler(finalOptions);
  //3.加载所有配置的插件
  let { plugins } = finalOptions;
  for (let plugin of plugins) {
    plugin.apply(compiler);
  }
  return compiler;
}
module.exports = webpack;

4.4 Compiler.js

Compiler.js

let { SyncHook } = require("tapable");
let fs = require("fs");
let path = require("path");
let Complication = require("./Complication");
/**
 * Compiler就是编译大管家
 * 负责整个编译过程,里面保存整个编译所有的信息
 */
class Compiler {
  constructor(options) {
    this.options = options;
    this.hooks = {
      run: new SyncHook(), //会在开始编译的时候触发
      done: new SyncHook(), //会在结束编译的时候触发
    };
  }
  //4.执行Compiler对象的run方法开始执行编译
  run(callback) {
    this.hooks.run.call();
    //5.根据配置中的entry找出入口文件
    const onCompiled = (err, stats, fileDependencies) => {
      //10在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
      for (let filename in stats.assets) {
        let filePath = path.join(this.options.output.path, filename);
        fs.writeFileSync(filePath, stats.assets[filename], "utf8");
      }
      callback(err, {
        toJson: () => stats,
      });
      fileDependencies.forEach((fileDependency) =>
        fs.watch(fileDependency, () => this.compile(onCompiled))
      );
    };
    this.compile(onCompiled);
    this.hooks.done.call();
  }
  compile(callback) {
    //每次编译都会创建一个新的Compilcation
    let complication = new Complication(this.options);
    complication.build(callback);
  }
}
module.exports = Compiler;

4.5 Complication.js

Complication.js

let fs = require("fs");
let types = require("babel-types");
let parser = require("@babel/parser");
let traverse = require("@babel/traverse").default;
let generator = require("@babel/generator").default;
const path = require("path");
//根目录就是当前的工作目录
let baseDir = toUnixPath(process.cwd()); // \ => /
function toUnixPath(filePath) {
  return filePath.replace(/\\/g, "/");
}
class Complication {
  constructor(options) {
    this.options = options;
    this.modules = []; //存放着本次编译生产所有的模块 所有的入口产出的模块
    this.chunks = []; //代码块的数组
    this.assets = {}; //产出的资源
    this.fileDependencies = [];
  }
  //这个才是编译最核心的方法
  build(callback) {
    //5.根据配置中的entry找出入口文件
    let entry = {};
    if (typeof this.options.entry === "string") {
      entry.main = this.options.entry;
    } else {
      entry = this.options.entry;
    }
    for (let entryName in entry) {
      //找到入口文件的绝对路径
      let entryFilePath = path.posix.join(baseDir, entry[entryName]);
      this.fileDependencies.push(entryFilePath);
      //6.从入口文件出发,调用所有配置的Loader对模块进行编译
      let entryModule = this.buildModule(entryName, entryFilePath);
      //8.根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
      let chunk = {
        name: entryName, //代码块的名字就是入口的名字
        entryModule, //入口模块 entry1.js
        modules: this.modules.filter((item) => item.name.includes(entryName)),
      };
      this.chunks.push(chunk);
    }
    //9.再把每个Chunk转换成一个单独的文件加入到输出列表
    this.chunks.forEach((chunk) => {
      let filename = this.options.output.filename.replace("[name]", chunk.name);
      this.assets[filename] = getSource(chunk);
    });

    callback(
      null,
      {
        chunks: this.chunks,
        modules: this.modules,
        assets: this.assets,
      },
      this.fileDependencies
    );
  }
  //name此模块是属于哪个入口的 modulePath 模块的绝对路径
  buildModule(name, modulePath) {
    //6.从入口文件出发,调用所有配置的Loader对模块进行编译
    //1.读取模块的内容
    let sourceCode = fs.readFileSync(modulePath, "utf8");
    let { rules } = this.options.module;
    let loaders = []; //
    rules.forEach((rule) => {
      let { test } = rule;
      if (modulePath.match(test)) {
        loaders.push(...rule.use);
      }
    }); //loaders=[logger1,logger2]
    sourceCode = loaders.reduceRight((sourceCode, loader) => {
      return require(loader)(sourceCode);
    }, sourceCode);
    //当前模块的模块ID
    let moduleId = "./" + path.posix.relative(baseDir, modulePath);
    let module = { id: moduleId, dependencies: [], name: [name] };
    //7.再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
    let ast = parser.parse(sourceCode, { sourceType: "module" });
    traverse(ast, {
      CallExpression: ({ node }) => {
        if (node.callee.name === "require") {
          //获取依赖模块的相对路径 wepback打包后不管什么模块,模块ID都是相对于根目录的相对路径 ./src ./node_modules
          let depModuleName = node.arguments[0].value; // ./title
          //获取当前模块的所在的目录
          let dirname = path.posix.dirname(modulePath); //src
          //C:\aproject\hswebpack202308\4.flow\src\title.js
          let depModulePath = path.posix.join(dirname, depModuleName);
          let extensions = this.options.resolve.extensions;
          depModulePath = tryExtensions(depModulePath, extensions);
          this.fileDependencies.push(depModulePath);
          //生成此模块的模块ID
          let depModuleId = "./" + path.posix.relative(baseDir, depModulePath);
          node.arguments = [types.stringLiteral(depModuleId)]; // ./title => ./src/title.js
          //把此模块依赖的模块ID和模块路径放到此模块的依赖数组中
          module.dependencies.push({ depModuleId, depModulePath });
        }
      },
    });
    let { code } = generator(ast); //根据改造后的语法树生成源代码
    module._source = code; //module._source属必指向此模块的改造后的源码
    //7.再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
    module.dependencies.forEach(({ depModuleId, depModulePath }) => {
      let existModule = this.modules.find((item) => item.id === depModuleId);
      if (existModule) {
        existModule.name.push(name);
      } else {
        let depModule = this.buildModule(name, depModulePath);
        this.modules.push(depModule);
      }
    });
    return module;
  }
}
function tryExtensions(modulePath, extensions) {
  if (fs.existsSync(modulePath)) {
    return modulePath;
  }
  for (let i = 0; i < extensions.length; i++) {
    let filePath = modulePath + extensions[i];
    if (fs.existsSync(filePath)) {
      return filePath;
    }
  }
  throw new Error(`${modulePath}没找到`);
}

function getSource(chunk) {
  return `
   (() => {
    var modules = {
      ${chunk.modules.map(
        (module) => `
        "${module.id}": (module) => {
          ${module._source}
        },
      `
      )}  
    };
    var cache = {};
    function require(moduleId) {
      var cachedModule = cache[moduleId];
      if (cachedModule !== undefined) {
        return cachedModule.exports;
      }
      var module = (cache[moduleId] = {
        exports: {},
      });
      modules[moduleId](module, module.exports, require);
      return module.exports;
    }
    var exports ={};
    ${chunk.entryModule._source}
  })();
   `;
}
module.exports = Complication;

4.6 run-plugin.js

plugins\run-plugin.js

class RunPlugin {
  //每个插件都是一个类,而每个类都需要定义一个apply方法
  apply(compiler) {
    compiler.hooks.run.tap("RunPlugin", () => {
      console.log("run:开始编译");
    });
  }
}
module.exports = RunPlugin;

4.7 done-plugin.js

plugins\done-plugin.js

class DonePlugin {
  //每个插件都是一个类,而每个类都需要定义一个apply方法
  apply(compiler) {
    compiler.hooks.done.tap("DonePlugin", () => {
      console.log("done:结束编译");
    });
  }
}
module.exports = DonePlugin;

4.9 logger1-loader.js

loaders\logger1-loader.js

function loader(source) {
  return source + "//logger1"; //let name= 'entry1';//logger2//logger1
}
module.exports = loader;

4.10 logger2-loader.js

loaders\logger2-loader.js

function loader(source) {
  //let name= 'entry1';
  return source + "//logger2"; //let name= 'entry1';//logger2
}
module.exports = loader;

4.11 src\entry1.js

src\entry1.js

let title = require("./title");
console.log("entry12", title);

4.12 src\entry2.js

src\entry2.js

let title = require("./title.js");
console.log("entry2", title);

4.13 src\title.js

src\title.js

module.exports = "title";

Stats 对象

  • 在 Webpack 的回调函数中会得到 stats 对象
  • 这个对象实际来自于Compilation.getStats(),返回的是主要含有moduleschunksassets三个属性值的对象。
  • Stats 对象本质上来自于lib/Stats.js的类实例
字段含义
modules记录了所有解析后的模块
chunks记录了所有 chunk
assets记录了所有要生成的文件
npx webpack --profile --json > stats.json
{
  "hash": "780231fa9b9ce4460c8a", //编译使用的 hash
  "version": "5.8.0", // 用来编译的 webpack 的版本
  "time": 83, // 编译耗时 (ms)
  "builtAt": 1606538839612, //编译的时间
  "publicPath": "auto", //资源访问路径
  "outputPath": "C:\\webpack5\\dist", //输出目录
  "assetsByChunkName": {
    //代码块和文件名的映射
    "main": ["main.js"]
  },
  "assets": [
    //资源数组
    {
      "type": "asset", //资源类型
      "name": "main.js", //文件名称
      "size": 2418, //文件大小
      "chunkNames": [
        //对应的代码块名称
        "main"
      ],
      "chunkIdHints": [],
      "auxiliaryChunkNames": [],
      "auxiliaryChunkIdHints": [],
      "emitted": false,
      "comparedForEmit": true,
      "cached": false,
      "info": {
        "javascriptModule": false,
        "size": 2418
      },
      "related": {},
      "chunks": ["main"],
      "auxiliaryChunks": [],
      "isOverSizeLimit": false
    }
  ],
  "chunks": [
    //代码块数组
    {
      "rendered": true,
      "initial": true,
      "entry": true,
      "recorded": false,
      "size": 80,
      "sizes": {
        "javascript": 80
      },
      "names": ["main"],
      "idHints": [],
      "runtime": ["main"],
      "files": ["main.js"],
      "auxiliaryFiles": [],
      "hash": "d25ad7a8144077f69783",
      "childrenByOrder": {},
      "id": "main",
      "siblings": [],
      "parents": [],
      "children": [],
      "modules": [
        {
          "type": "module",
          "moduleType": "javascript/auto",
          "identifier": "C:\\webpack5\\src\\index.js",
          "name": "./src/index.js",
          "nameForCondition": "C:\\webpack5\\src\\index.js",
          "index": 0,
          "preOrderIndex": 0,
          "index2": 1,
          "postOrderIndex": 1,
          "size": 55,
          "sizes": {
            "javascript": 55
          },
          "cacheable": true,
          "built": true,
          "codeGenerated": true,
          "cached": false,
          "optional": false,
          "orphan": false,
          "dependent": false,
          "issuer": null,
          "issuerName": null,
          "issuerPath": null,
          "failed": false,
          "errors": 0,
          "warnings": 0,
          "profile": {
            "total": 38,
            "resolving": 26,
            "restoring": 0,
            "building": 12,
            "integration": 0,
            "storing": 0,
            "additionalResolving": 0,
            "additionalIntegration": 0,
            "factory": 26,
            "dependencies": 0
          },
          "id": "./src/index.js",
          "issuerId": null,
          "chunks": ["main"],
          "assets": [],
          "reasons": [
            {
              "moduleIdentifier": null,
              "module": null,
              "moduleName": null,
              "resolvedModuleIdentifier": null,
              "resolvedModule": null,
              "type": "entry",
              "active": true,
              "explanation": "",
              "userRequest": "./src/index.js",
              "loc": "main",
              "moduleId": null,
              "resolvedModuleId": null
            }
          ],
          "usedExports": null,
          "providedExports": null,
          "optimizationBailout": [],
          "depth": 0
        },
        {
          "type": "module",
          "moduleType": "javascript/auto",
          "identifier": "C:\\webpack5\\src\\title.js",
          "name": "./src/title.js",
          "nameForCondition": "C:\\webpack5\\src\\title.js",
          "index": 1,
          "preOrderIndex": 1,
          "index2": 0,
          "postOrderIndex": 0,
          "size": 25,
          "sizes": {
            "javascript": 25
          },
          "cacheable": true,
          "built": true,
          "codeGenerated": true,
          "cached": false,
          "optional": false,
          "orphan": false,
          "dependent": true,
          "issuer": "C:\\webpack5\\src\\index.js",
          "issuerName": "./src/index.js",
          "issuerPath": [
            {
              "identifier": "C:\\webpack5\\src\\index.js",
              "name": "./src/index.js",
              "profile": {
                "total": 38,
                "resolving": 26,
                "restoring": 0,
                "building": 12,
                "integration": 0,
                "storing": 0,
                "additionalResolving": 0,
                "additionalIntegration": 0,
                "factory": 26,
                "dependencies": 0
              },
              "id": "./src/index.js"
            }
          ],
          "failed": false,
          "errors": 0,
          "warnings": 0,
          "profile": {
            "total": 0,
            "resolving": 0,
            "restoring": 0,
            "building": 0,
            "integration": 0,
            "storing": 0,
            "additionalResolving": 0,
            "additionalIntegration": 0,
            "factory": 0,
            "dependencies": 0
          },
          "id": "./src/title.js",
          "issuerId": "./src/index.js",
          "chunks": ["main"],
          "assets": [],
          "reasons": [
            {
              "moduleIdentifier": "C:\\webpack5\\src\\index.js",
              "module": "./src/index.js",
              "moduleName": "./src/index.js",
              "resolvedModuleIdentifier": "C:\\webpack5\\src\\index.js",
              "resolvedModule": "./src/index.js",
              "type": "cjs require",
              "active": true,
              "explanation": "",
              "userRequest": "./title.js",
              "loc": "1:12-33",
              "moduleId": "./src/index.js",
              "resolvedModuleId": "./src/index.js"
            },
            {
              "moduleIdentifier": "C:\\webpack5\\src\\title.js",
              "module": "./src/title.js",
              "moduleName": "./src/title.js",
              "resolvedModuleIdentifier": "C:\\webpack5\\src\\title.js",
              "resolvedModule": "./src/title.js",
              "type": "cjs self exports reference",
              "active": true,
              "explanation": "",
              "userRequest": null,
              "loc": "1:0-14",
              "moduleId": "./src/title.js",
              "resolvedModuleId": "./src/title.js"
            }
          ],
          "usedExports": null,
          "providedExports": null,
          "optimizationBailout": [
            "CommonJS bailout: module.exports is used directly at 1:0-14"
          ],
          "depth": 1
        }
      ],
      "origins": [
        {
          "module": "",
          "moduleIdentifier": "",
          "moduleName": "",
          "loc": "main",
          "request": "./src/index.js"
        }
      ]
    }
  ],
  "modules": [
    //模块数组
    {
      "type": "module",
      "moduleType": "javascript/auto",
      "identifier": "C:\\webpack5\\src\\index.js",
      "name": "./src/index.js",
      "nameForCondition": "C:\\webpack5\\src\\index.js",
      "index": 0,
      "preOrderIndex": 0,
      "index2": 1,
      "postOrderIndex": 1,
      "size": 55,
      "sizes": {
        "javascript": 55
      },
      "cacheable": true,
      "built": true,
      "codeGenerated": true,
      "cached": false,
      "optional": false,
      "orphan": false,
      "issuer": null,
      "issuerName": null,
      "issuerPath": null,
      "failed": false,
      "errors": 0,
      "warnings": 0,
      "profile": {
        "total": 38,
        "resolving": 26,
        "restoring": 0,
        "building": 12,
        "integration": 0,
        "storing": 0,
        "additionalResolving": 0,
        "additionalIntegration": 0,
        "factory": 26,
        "dependencies": 0
      },
      "id": "./src/index.js",
      "issuerId": null,
      "chunks": ["main"],
      "assets": [],
      "reasons": [
        {
          "moduleIdentifier": null,
          "module": null,
          "moduleName": null,
          "resolvedModuleIdentifier": null,
          "resolvedModule": null,
          "type": "entry",
          "active": true,
          "explanation": "",
          "userRequest": "./src/index.js",
          "loc": "main",
          "moduleId": null,
          "resolvedModuleId": null
        }
      ],
      "usedExports": null,
      "providedExports": null,
      "optimizationBailout": [],
      "depth": 0
    },
    {
      "type": "module",
      "moduleType": "javascript/auto",
      "identifier": "C:\\webpack5\\src\\title.js",
      "name": "./src/title.js",
      "nameForCondition": "C:\\webpack5\\src\\title.js",
      "index": 1,
      "preOrderIndex": 1,
      "index2": 0,
      "postOrderIndex": 0,
      "size": 25,
      "sizes": {
        "javascript": 25
      },
      "cacheable": true,
      "built": true,
      "codeGenerated": true,
      "cached": false,
      "optional": false,
      "orphan": false,
      "issuer": "C:\\webpack5\\src\\index.js",
      "issuerName": "./src/index.js",
      "issuerPath": [
        {
          "identifier": "C:\\webpack5\\src\\index.js",
          "name": "./src/index.js",
          "profile": {
            "total": 38,
            "resolving": 26,
            "restoring": 0,
            "building": 12,
            "integration": 0,
            "storing": 0,
            "additionalResolving": 0,
            "additionalIntegration": 0,
            "factory": 26,
            "dependencies": 0
          },
          "id": "./src/index.js"
        }
      ],
      "failed": false,
      "errors": 0,
      "warnings": 0,
      "profile": {
        "total": 0,
        "resolving": 0,
        "restoring": 0,
        "building": 0,
        "integration": 0,
        "storing": 0,
        "additionalResolving": 0,
        "additionalIntegration": 0,
        "factory": 0,
        "dependencies": 0
      },
      "id": "./src/title.js",
      "issuerId": "./src/index.js",
      "chunks": ["main"],
      "assets": [],
      "reasons": [
        {
          "moduleIdentifier": "C:\\webpack5\\src\\index.js",
          "module": "./src/index.js",
          "moduleName": "./src/index.js",
          "resolvedModuleIdentifier": "C:\\webpack5\\src\\index.js",
          "resolvedModule": "./src/index.js",
          "type": "cjs require",
          "active": true,
          "explanation": "",
          "userRequest": "./title.js",
          "loc": "1:12-33",
          "moduleId": "./src/index.js",
          "resolvedModuleId": "./src/index.js"
        },
        {
          "moduleIdentifier": "C:\\webpack5\\src\\title.js",
          "module": "./src/title.js",
          "moduleName": "./src/title.js",
          "resolvedModuleIdentifier": "C:\\webpack5\\src\\title.js",
          "resolvedModule": "./src/title.js",
          "type": "cjs self exports reference",
          "active": true,
          "explanation": "",
          "userRequest": null,
          "loc": "1:0-14",
          "moduleId": "./src/title.js",
          "resolvedModuleId": "./src/title.js"
        }
      ],
      "usedExports": null,
      "providedExports": null,
      "optimizationBailout": [
        "CommonJS bailout: module.exports is used directly at 1:0-14"
      ],
      "depth": 1
    }
  ],
  "entrypoints": {
    //入口点
    "main": {
      "name": "main",
      "chunks": ["main"],
      "assets": [
        {
          "name": "main.js",
          "size": 2418
        }
      ],
      "filteredAssets": 0,
      "assetsSize": 2418,
      "auxiliaryAssets": [],
      "filteredAuxiliaryAssets": 0,
      "auxiliaryAssetsSize": 0,
      "children": {},
      "childAssets": {},
      "isOverSizeLimit": false
    }
  },
  "namedChunkGroups": {
    //命名代码块组
    "main": {
      "name": "main",
      "chunks": ["main"],
      "assets": [
        {
          "name": "main.js",
          "size": 2418
        }
      ],
      "filteredAssets": 0,
      "assetsSize": 2418,
      "auxiliaryAssets": [],
      "filteredAuxiliaryAssets": 0,
      "auxiliaryAssetsSize": 0,
      "children": {},
      "childAssets": {},
      "isOverSizeLimit": false
    }
  },
  "errors": [],
  "errorsCount": 0,
  "warnings": [],
  "warningsCount": 0,
  "children": []
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1834020.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何确保pcdn的稳定性?(壹)

确保PCDN的稳定性是一个重要任务&#xff0c;涉及多个方面的操作和考虑。以下是一些建议&#xff0c;帮助你确保PCDN的稳定性&#xff1a; 一&#xff0e;选择合适的服务器与硬件&#xff1a; 选择稳定可靠的服务器供应商和硬件设备&#xff0c;确保服务器具有高可用性和容错…

openGauss 6.0一主二备高可用架构部署,可靠很行

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

支撑每秒 600 万订单无压力,SpringBoot + Disruptor 太猛了!

一、背景 工作中遇到项目使用Disruptor做消息队列,对你没看错,不是Kafka,也不是rabbitmq;Disruptor有个最大的优点就是快,还有一点它是开源的哦,下面做个简单的记录. 二、Disruptor介绍 Disruptor 是英国外汇交易公司LMAX开发的一个高性能队列&#xff0c;研发的初衷是解决内存…

软银与AI初创公司Perplexity达成合作;OpenAI或将改变治理结构,考虑成立营利性公司

&#x1f989; AI新闻 &#x1f680; 软银与AI初创公司Perplexity达成合作 摘要&#xff1a;6月17日消息&#xff0c;日本软银宣布与AI初创公司Perplexity达成战略合作&#xff0c;将于6月19日起向Softbank、Y-Mobile和LINEMO用户开放Perplexity Pro一年的免费试用申请。Perp…

Einops 张量操作快速入门

张量&#xff0c;即多维数组&#xff0c;是现代机器学习框架的支柱。操纵这些张量可能会变得冗长且难以阅读&#xff0c;尤其是在处理高维数据时。Einops 使用简洁的符号简化了这些操作。 Einops &#xff08;Einstein-Inspired Notation for operations&#xff09;&#xff…

CentOS 7 下gdb任意版本的升级

文章目录 前言查看gdb版本升级步骤 小结 前言 在做项目的过程中&#xff0c;遇到了难缠的bug&#xff0c;使用gdb调试的时候&#xff0c;bt调用堆栈看的一震头疼&#xff0c;于是就想起把gdb升级一下 当前环境&#xff1a;Centos7&#xff0c;gdb&#xff1a;7.6 稍微好看了那…

SpringCloudStream原理和深入使用

简单概述 Spring Cloud Stream是一个用于构建与共享消息传递系统连接的高度可扩展的事件驱动型微服务的框架。 应用程序通过inputs或outputs来与Spring Cloud Stream中binder对象交互&#xff0c;binder对象负责与消息中间件交互。也就是说&#xff1a;Spring Cloud Stream能…

AI工具快速制作爆火的影视视频混剪

今天给大家发一个有意思的工具&#xff0c;影视混剪大家应该都刷到过&#xff0c;像下面这种视频&#xff0c;播放量都超级高。 这种视频都是怎么做的呢&#xff1f; 现在AI工具这么多样性&#xff0c;先用 AI 写一段具有网感的对话段子&#xff0c;然后找影视剧片段混剪成一…

笑脸金融测试社招面经,期望20K

面经哥只做互联网社招面试经历分享&#xff0c;关注我&#xff0c;每日推送精选面经&#xff0c;面试前&#xff0c;先找面经哥 测试总监一面 1、问一些测试理论相关的知识。 自我介绍、质量模型 2、登录如何设计测试用例。 3、给你一个东西你会从哪些方面去考虑设计测试用…

【数据结构初阶】--- 堆的应用:topk

堆的功能&#xff1a;topk 为什么使用topk 先举个例子&#xff0c;假如说全国有十万家奶茶店&#xff0c;我现在想找到评分前十的店铺&#xff0c;现在应该怎么实现&#xff1f; 第一想法当然是排序&#xff0c;由大到小排序好&#xff0c;前十就能拿到了。这是一种方法&…

2024 年最新 Python 基于 LangChain 框架基础案例详细教程(更新中)

LangChain 框架搭建 安装 langchain pip install langchain -i https://mirrors.aliyun.com/pypi/simple/安装 langchain-openai pip install langchain-openai -i https://mirrors.aliyun.com/pypi/simple/ChatOpenAI 配置环境变量 环境变量 OPENAI_API_KEYOpenAI API 密钥…

在IDEA 2024.1.3 (Community Edition)中创建Maven项目

本篇博客承继自博客Windows系统Maven下载安装-CSDN博客 Maven版本&#xff1a;maven-3.9.5 修改设置&#xff1a; 首先先对Idea的Maven依赖进行设置&#xff1b;打开Idea&#xff0c;选择“Costomize”&#xff0c;选择最下边的"All settings" 之后找到Maven选项&…

聚四氟乙烯离心管 四氟反应管 消解管 PTFE螺口带盖管 特氟龙试管

一、产品介绍 样品悬浮液盛放在管状试样容器中&#xff0c;在离心机的高速旋转下&#xff0c;由于巨大的离心力作用&#xff0c;使悬浮的微小颗粒 以一定的速度沉降&#xff0c;从而与溶液得以分离。这种带密封盖或压盖的管状试样容器&#xff0c;就是离心管。 PTFE离心管&…

编程精粹—— Microsoft 编写优质无错 C 程序秘诀 03:强化你的子系统

这是一本老书&#xff0c;作者 Steve Maguire 在微软工作期间写了这本书&#xff0c;英文版于 1993 年发布。2013 年推出了 20 周年纪念第二版。我们看到的标题是中译版名字&#xff0c;英文版的名字是《Writing Clean Code ─── Microsoft’s Techniques for Developing》&a…

【面试干货】常见的编译时异常(运行时异常)及其处理

【面试干货】常见的编译时异常&#xff08;运行时异常&#xff09;及其处理 1、SQLException2、IOException3、FileNotFoundException4、ClassNotFoundException5、EOFException6、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Jav…

电能表厂家的研发能力是实力的体现

电能表厂家的研发能力无疑是其整体实力的核心体现。一个拥有强大研发能力的电能表厂家&#xff0c;不仅能够持续推出具有竞争力的新产品&#xff0c;满足市场需求&#xff0c;还能引领行业发展&#xff0c;塑造企业品牌形象。 一、研发能力对电能表厂家的重要性 研发能力是电…

图纸管理的方法、图纸管理软件

图纸管理是一个复杂且关键的过程&#xff0c;它涉及到图纸的创建、存储、共享、修改、审核、存档和检索等多个环节。以下是根据参考文章总结的图纸管理的具体内容和方法&#xff1a; 一、图纸管理的目的 1、确保图纸的准确性&#xff1a;通过规范的管理流程和质量控制措施&…

Failed to execute goal org.apache.maven.plugins:maven-antrun-plugin:1.8:

Mvan 点击执行 mvn install https://repo1.maven.org/maven2/org/apache/maven/plugins/maven-antrun-plugin/1.8/maven-antrun-plugin-1.8.pom

小米手机怎么用代理换ip:步骤详解与实用指南

在数字化时代&#xff0c;网络安全与隐私保护日益受到重视。对于小米手机用户而言&#xff0c;使用代理换IP已成为提升网络安全性、访问特定网站或绕过地域限制的有效手段。本文将详细介绍如何在小米手机上设置代理以更换IP地址&#xff0c;帮助用户更好地保护个人信息和享受更…

【NOI-题解】1448. 随机体能测试1469. 数的统计1511. 数字之和为13的整数1846. 阿尔法乘积

文章目录 一、前言二、问题问题&#xff1a;1448. 随机体能测试问题&#xff1a;1469. 数的统计问题&#xff1a;1511. 数字之和为13的整数问题&#xff1a;1846. 阿尔法乘积 三、感谢 一、前言 本章节主要对嵌套循环的题目进行讲解&#xff0c;包括《1448. 随机体能测试》《1…