• 关于webpack(v5.74.0)的dllPlugin插件的原理


    众所周知webpack的dll的作用是提前编译好模块,并生成一个json文件。然后用的时候就从该json文件中查找,如果有就从打包好的模块直接引入。下面我们看看具体实现。

    构建

    // webpack.dll.js
    
    const DllPlugin = require('./plugins/testDllPlguin')
    const path = require('path')
    module.exports = {
      mode: 'development',
      entry: ['lodash', 'jquery'],
      output: {
        filename: '[name].dll.js',
        path: path.join(__dirname,'dll'),
        library: '_dll_[name]',
      },
      plugins: [
        new DllPlugin({
          name: '_dll_[name]',
          path: path.join(__dirname, 'dll/mainfest.json'),
        }),
      ],
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    lodash和jquery打包的逻辑发生在DllPlugin插件中:

    compiler.hooks.entryOption.tap("DllPlugin", (context, entry) => {
    	if (typeof entry !== "function") {
    		for (const name of Object.keys(entry)) {
    			const options = {
    				name,
    				filename: entry.filename
    			};
    			new DllEntryPlugin(context, entry[name].import, options).apply(
    				compiler
    			);
    		}
    	} else {
    		throw new Error(
    			"DllPlugin doesn't support dynamic entry (function) yet"
    		);
    	}
    	return true;
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    首先遍历entry调用DllEntryPlugin插件:

    /*
    	MIT License http://www.opensource.org/licenses/mit-license.php
    	Author Tobias Koppers @sokra
    */
    
    "use strict";
    
    const DllModuleFactory = require("webpack/lib/DllModuleFactory");
    const DllEntryDependency = require("webpack/lib/dependencies/DllEntryDependency");
    const EntryDependency = require("webpack/lib/dependencies/EntryDependency");
    
    class DllEntryPlugin {
    	constructor(context, entries, options) {
    		this.context = context;
    		this.entries = entries;
    		this.options = options;
    	}
    
    	apply(compiler) {
    		// compiltion创建之后执行去拿到compiltion,设置当前依赖的模块创建工厂
    		compiler.hooks.compilation.tap(
    			"DllEntryPlugin",
    			(compilation, { normalModuleFactory }) => {
    				const dllModuleFactory = new DllModuleFactory();
            		// 主模块的内容
    				compilation.dependencyFactories.set(
    					DllEntryDependency,
    					dllModuleFactory
    				);
           			 // 依赖的内容是通过普通模块生成
    				compilation.dependencyFactories.set(
             			 // ntryDependency创建依赖,jquery变依赖对象,方便后面筛选
    					EntryDependency,
             			 // 根据依赖对象生成模块内容
    					normalModuleFactory
    				);
    			}
    		);
       		 // compiltion结束前执行
    		compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => {
    			compilation.addEntry(
    				this.context,
            		// module.exports = __webpack_require__的依赖
    				new DllEntryDependency(
    					this.entries.map((e, idx) => {
                			// 添加依赖,jquery,lodash
    						const dep = new EntryDependency(e);
    						dep.loc = {
    							name: this.options.name,
    							index: idx
    						};
    						return dep;
    					}),
    					this.options.name
    				),
    				this.options,
    				callback
    			);
    		});
    	}
    }
    
    module.exports = DllEntryPlugin;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    1. 给主模块设置DllEntryDependency依赖和创建依赖模块的类dllModuleFactory:
    /*
    	MIT License http://www.opensource.org/licenses/mit-license.php
    	Author Tobias Koppers @sokra
    */
    
    "use strict";
    
    const DllModule = require("./DllModule");
    const ModuleFactory = require("./ModuleFactory");
    
    /** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
    /** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
    /** @typedef {import("./dependencies/DllEntryDependency")} DllEntryDependency */
    
    class DllModuleFactory extends ModuleFactory {
    	constructor() {
    		super();
    		this.hooks = Object.freeze({});
    	}
    	/**
    	 * @param {ModuleFactoryCreateData} data data object
    	 * @param {function(Error=, ModuleFactoryResult=): void} callback callback
    	 * @returns {void}
    	 */
    	create(data, callback) {
    		const dependency = /** @type {DllEntryDependency} */ (data.dependencies[0]);
    		callback(null, {
    			module: new DllModule(
    				data.context,
    				dependency.dependencies,
    				dependency.name
    			)
    		});
    	}
    }
    
    module.exports = DllModuleFactory;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    主模块内容的生成是有DllModule负责的:

    ...
    codeGeneration(context) {
    	const sources = new Map();
    	sources.set(
    		"javascript",
    		new RawSource("module.exports = __webpack_require__;")
    	);
    	return {
    		sources,
    		runtimeRequirements: RUNTIME_REQUIREMENTS
    	};
    }
    	...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以看到主模块内容是module.exports = __webpack_require__;。确定了主模块内容接下来就是确定该模块的依赖,我们往回看DllEntryPlugin:

    compilation.dependencyFactories.set(
        // ntryDependency创建依赖,jquery变依赖对象,方便后面筛选
    	EntryDependency,
        // 根据依赖对象生成模块内容
    	normalModuleFactory
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    先确定依赖的模块创建函数normalModuleFactory,然后添加入口依赖DllEntryDependency:

    compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => {
    	compilation.addEntry(
    		this.context,
    	    // module.exports = __webpack_require__的依赖
    		new DllEntryDependency(
    			this.entries.map((e, idx) => {
    	         	// 添加依赖,jquery,lodash
    				const dep = new EntryDependency(e);
    				dep.loc = {
    					name: this.options.name,
    					index: idx
    				};
    				return dep;
    			}),
    			this.options.name
    		),
    		this.options,
    		callback
    	);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    DllEntryDependency是我们的主模块,遍历入口传入的lodash和jquery去添加依赖模块。此时打包我们得到三个模块:
    在这里插入图片描述
    在这里插入图片描述
    第二个主要内容是new LibManifestPlugin(this.options).apply(compiler)生成manifest.json文件:

    /*
    	MIT License http://www.opensource.org/licenses/mit-license.php
    	Author Tobias Koppers @sokra
    */
    
    "use strict";
    
    const asyncLib = require("neo-async");
    const EntryDependency = require("./dependencies/EntryDependency");
    const { someInIterable } = require("./util/IterableHelpers");
    const { compareModulesById } = require("./util/comparators");
    const { dirname, mkdirp } = require("./util/fs");
    
    /** @typedef {import("./Compiler")} Compiler */
    
    /**
     * @typedef {Object} ManifestModuleData
     * @property {string | number} id
     * @property {Object} buildMeta
     * @property {boolean | string[]} exports
     */
    
    class LibManifestPlugin {
    	constructor(options) {
    		this.options = options;
    	}
    
    	/**
    	 * Apply the plugin
    	 * @param {Compiler} compiler the compiler instance
    	 * @returns {void}
    	 */
    	apply(compiler) {
    		// 输出资源前
    		compiler.hooks.emit.tapAsync(
    			"LibManifestPlugin",
    			(compilation, callback) => {
    				const moduleGraph = compilation.moduleGraph;
    				asyncLib.forEach(
    					Array.from(compilation.chunks),
    					(chunk, callback) => {
    						if (!chunk.canBeInitial()) {
    							callback();
    							return;
    						}
    						const chunkGraph = compilation.chunkGraph;
    						const targetPath = compilation.getPath(this.options.path, {
    							chunk
    						});
    						const name =
    							this.options.name &&
    							compilation.getPath(this.options.name, {
    								chunk
    							});
    						const content = Object.create(null);
    						for (const module of chunkGraph.getOrderedChunkModulesIterable(
    							chunk,
    							compareModulesById(chunkGraph)
    						)) {
    							if (
    								this.options.entryOnly &&
    								!someInIterable(
    									moduleGraph.getIncomingConnections(module),
    									c => c.dependency instanceof EntryDependency
    								)
    							) {
    								continue;
    							}
    							const ident = module.libIdent({
    								context: this.options.context || compiler.options.context,
    								associatedObjectForCache: compiler.root
    							});
    							if (ident) {
    								const exportsInfo = moduleGraph.getExportsInfo(module);
    								const providedExports = exportsInfo.getProvidedExports();
    								/** @type {ManifestModuleData} */
    								const data = {
    									id: chunkGraph.getModuleId(module),
    									buildMeta: module.buildMeta,
    									exports: Array.isArray(providedExports)
    										? providedExports
    										: undefined
    								};
    								content[ident] = data;
    							}
    						}
    						const manifest = {
    							name,
    							type: this.options.type,
    							content
    						};
    						// Apply formatting to content if format flag is true;
    						const manifestContent = this.options.format
    							? JSON.stringify(manifest, null, 2)
    							: JSON.stringify(manifest);
    						const buffer = Buffer.from(manifestContent, "utf8");
    						mkdirp(
    							compiler.intermediateFileSystem,
    							dirname(compiler.intermediateFileSystem, targetPath),
    							err => {
    								if (err) return callback(err);
    								compiler.intermediateFileSystem.writeFile(
    									targetPath,
    									buffer,
    									callback
    								);
    							}
    						);
    					},
    					callback
    				);
    			}
    		);
    	}
    }
    module.exports = LibManifestPlugin;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    该段的主要逻辑是拿到当前编译的所有模块并判断是不是EntryDependency依赖创建的,我们知道lodash和jquery的依赖是EntryDependency对应的normalModuleFactory生成的。所以会过滤出lodash和jquery并组成content,然后通过mkdirp生成mainfest.json文件。

    使用dll

    此时已生成这两个文件:在这里插入图片描述
    接下来我们要配置使用该文件:

    
    ...
      plugins: [
        ...
        new DllReferencePlugin({
          context: __dirname,
          manifest: require('./dll/mainfest.json'),
        })
      ]
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们先来看看DllReferencePlugin:

    ...
    
    class DllReferencePlugin {
    	/**
    	 * @param {DllReferencePluginOptions} options options object
    	 */
    	constructor(options) {
    		validate(options);
    		this.options = options;
    		/** @type {WeakMap} */
    		this._compilationData = new WeakMap();
    	}
    
    	apply(compiler) {
    		compiler.hooks.compilation.tap(
    			"DllReferencePlugin",
    			(compilation, { normalModuleFactory }) => {
    				// 将dll-reference _dll_main这个依赖设置为normalModuleFactory,然后监听normalModuleFactory的hooks
    				// 当解析模块的时候就可以返回我们设置的模块内容
    				compilation.dependencyFactories.set(
    					// 代理资源的依赖
    					DelegatedSourceDependency,
    					normalModuleFactory
    				);
    			}
    		);
    
    		compiler.hooks.compile.tap("DllReferencePlugin", params => {
    			let name = this.options.name;
    			let sourceType = this.options.sourceType;
    			let content =
    				"content" in this.options ? this.options.content : undefined;
    			if ("manifest" in this.options) {
    				let manifestParameter = this.options.manifest;
    				let manifest;
    				if (typeof manifestParameter === "string") {
    					const data = this._compilationData.get(params);
    					// If there was an error parsing the manifest
    					// file, exit now because the error will be added
    					// as a compilation error in the "compilation" hook.
    					if (data.error) {
    						return;
    					}
    					manifest = data.data;
    				} else {
    					manifest = manifestParameter;
    				}
    				if (manifest) {
    					if (!name) name = manifest.name;
    					if (!sourceType) sourceType = manifest.type;
    					if (!content) content = manifest.content;
    				}
    			}
    			/** @type {Externals} */
    			const externals = {};
    			const source = "dll-reference " + name;
    			externals[source] = name;
    			const normalModuleFactory = params.normalModuleFactory;
    			// 拦截依赖提供dll-reference _dll_main模块
    			// 入口:dll-reference _dll_main 内容:module.exports = _dll_main;var表示是一个变量
    			new ExternalModuleFactoryPlugin(sourceType || "var", externals).apply(
    				normalModuleFactory
    			);
    			// 从mainfest.json文件中返回新模块
    			new DelegatedModuleFactoryPlugin({
    				source: source,
    				type: this.options.type,
    				scope: this.options.scope,
    				context: this.options.context || compiler.options.context,
    				content,
    				extensions: this.options.extensions,
    				associatedObjectForCache: compiler.root
    			}).apply(normalModuleFactory);
    		});
    ...
    		// );
    	}
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    首先我们设置了一个依赖DelegatedSourceDependency,然后调用了ExternalModuleFactoryPlugin插件:

    ...
    callback(
    	null,
    	new ExternalModule(
    		externalConfig,// _dll_main
    		type || globalType, // var
    		dependency.request // dll-reference _dll_main
    	)
    );
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    该插件主要是监听了normalModuleFactory.hooks.factorize方法在解析依赖前判断是不是externals对象里面的模块。如果是则会调用ExternalModule方法返回模块内容。直接看该模块的codeGeneration函数:
    在这里插入图片描述
    此时也只是完成了查找依赖的过程,还会通过DelegatedModuleFactoryPlugin从mainfest.json文件中查找对应模块,如果有就会从dll-reference _dll_main中导入:

    // 创建 NormalModule 实例后调用
    normalModuleFactory.hooks.module.tap(
    	"DelegatedModuleFactoryPlugin",
    	module => {
    		const request = module.libIdent(this.options);
    		if (request) {// './node_modules/lodash/lodash.js'是否在mainfest.json文件中
    			if (request in this.options.content) {
    				const resolved = this.options.content[request];
    				// 返回新模块
    				return new DelegatedModule(
    					this.options.source,// 'dll-reference _dll_main'
    					resolved,// './node_modules/lodash/lodash.js'
    					this.options.type,
    					request,// './node_modules/lodash/lodash.js'
    					module // './node_modules/lodash/lodash.js'模块
    				);
    			}
    		}
    		return module;
    	}
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在DelegatedModule模块的build中我们会添加一个依赖:

    /** 每个模块生成的时候添加依赖dll-reference _dll_main
     * @param {WebpackOptions} options webpack options
     * @param {Compilation} compilation the compilation
     * @param {ResolverWithOptions} resolver the resolver
     * @param {InputFileSystem} fs the file system
     * @param {function(WebpackError=): void} callback callback function
     * @returns {void}
     */
    build(options, compilation, resolver, fs, callback) {
    	this.buildMeta = { ...this.delegateData.buildMeta };
    	this.buildInfo = {};
    	this.dependencies.length = 0;
    	this.delegatedSourceDependency = new DelegatedSourceDependency(
    		this.sourceRequest
    	);
    	// 给当前模块(lodash jquery wsy)添加依赖dll-reference _dll_main,当解析的时候就会根据该内容查找到依赖,再根据依赖生成模块内容
    	this.addDependency(this.delegatedSourceDependency);
    	this.addDependency(
    		new StaticExportsDependency(this.delegateData.exports || true, false)
    	);
    	callback();
    }
    
    /**
     * @param {CodeGenerationContext} context context for code generation
     * @returns {CodeGenerationResult} result
     */ // 
    codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) {
    	// 当前模块依赖于dll-reference _dll_main
    	const dep = /** @type {DelegatedSourceDependency} */ (this.dependencies[0]);
    	const sourceModule = moduleGraph.getModule(dep);// 当前依赖生成的模块
    	let str;
    
    	if (!sourceModule) {
    		str = runtimeTemplate.throwMissingModuleErrorBlock({
    			request: this.sourceRequest
    		});
    	} else {
    		str = `module.exports = (${runtimeTemplate.moduleExports({
    			module: sourceModule,// dll-reference _dll_main模块
    			chunkGraph,
    			request: dep.request, // dll-reference _dll_main
    			runtimeRequirements: new Set()
    		})})`;
    
    		switch (this.delegationType) {
    			case "require":
    				str += `(${JSON.stringify(this.request)})`;
    				break;
    			case "object":
    				str += `[${JSON.stringify(this.request)}]`;
    				break;
    		}
    
    		str += ";";
    	}
    
    	const sources = new Map();
    	if (this.useSourceMap || this.useSimpleSourceMap) {
    		sources.set("javascript", new OriginalSource(str, this.identifier()));
    	} else {
    		sources.set("javascript", new RawSource(str));
    	}
    
    	return {
    		sources,
    		runtimeRequirements: RUNTIME_REQUIREMENTS
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    在生成代码时会取出当前依赖对应的模块生成对应的code:
    在这里插入图片描述
    在这里插入图片描述
    当解析到'module.exports = (__webpack_require__(/*! dll-reference _dll_main */ "dll-reference _dll_main"))("./node_modules/lodash/lodash.js")'会引入查找dll-reference _dll_main模块,此时会触发normalModuleFactory.hooks.factorize方法:

    callback(
    	null,
    	new ExternalModule(
    		externalConfig,// _dll_main
    		type || globalType, // var
    		dependency.request // dll-reference _dll_main
    	)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    返回ExternalModule模块的内容。

    总结

    我们来理清楚一下逻辑,上面的代码可能有点乱。

    1. DllPlugin主要通过DllEntryPlugin监听compiler.hooks.compilation去添加当前依赖DllEntryDependency对应的模块工厂dllModuleFactory,随后通过监听compiler.hooks.make(compilation 结束之前执行)添加入口依赖DllEntryDependency。并遍历entries给当前入口添加依赖EntryDependency(jquery,lodash)。此时DllEntryDependency对应的DllModuleFactory会返回DllModule模块生成的内容(暴露lodash,jquery)。随后执行LibManifestPlugin插件监听compiler.hooks.emit(输出资源前)获取当前chunk的所有的模块,并通过获取incomingConnections(含有父级模块引入的依赖类型)判断该模块是不是来自EntryDependency(除去运行时的模块),去生成mainfest.json。
    2. DllReferencePlugin通过ExternalModuleFactoryPlugin插件监听normalModuleFactory.hooks.factorize(在初始化解析之前判断是不是externals对象里面的模块),当解析该模块的导入入径时候会触发并返回自定义的dll-reference _dll_main模块内容。同时还会通过DelegatedModuleFactoryPlugin插件监听normalModuleFactory.hooks.module(创建 NormalModule 实例后调用)判断当前请求的模块是不是在mainfest.json文件中。如果在则调用DelegatedModule方法。DelegatedModule类在build过程中由于要从我们打包好的文件中返回模块内容,所以依赖于dll-reference _dll_main。会添加DelegatedSourceDependency。该依赖在解析时会触发我们在开头定义的normalModuleFactory.hooks.factorize函数返回模块内容。
  • 相关阅读:
    11 Fork/Join
    研发中台拆分过程的一些心得总结
    STM32H750之FreeRTOS学习--------(一)初识RTOS
    C++中指针与引用的区别
    吃瓜教程第一二章学习记录
    【ROS】RViz2源码分析(二):main函数及编译配置详解
    维护领域数字化转型的原因、方式和目标
    Java内部类(自用)
    03. C语言编写LED
    创建并配置一个新的python环境
  • 原文地址:https://blog.csdn.net/qq_35094120/article/details/127752199