Webpack源码深入-webpack和webpack-cli

webpack源码深入-webpack和webpack-cli

webpack命令工作原理如下
在这里插入图片描述

webpack指令

// webpack/package.json
{
	...
	"mian":"lib/index.js",
	"bin": {
		"webpack": "bin/webpack.js"
	},
	...
}

webpack指令的入口是webpack.js。

  1. 首先脚本内部创建cli对象
const cli = {
	name: "webpack-cli",
	package: "webpack-cli",
	binName: "webpack-cli",
	installed: isInstalled("webpack-cli"),
	url: "https://github.com/webpack/webpack-cli"
};
  1. 检查isInstalled方法检查安装情况,原理是:fs.statSync获取stat对象,在通过stat.isDierectory()判断webpack-cli目录是否存在
const isInstalled = packageName => {
	if (process.versions.pnp) {
		return true;
	}
	const path = require("path");
	const fs = require("graceful-fs");
	let dir = __dirname;
	do {
		try {
			if (
				fs.statSync(path.join(dir, "node_modules", packageName)).isDirectory()
			) {
				return true;
			}
		} catch (_error) {
			// Nothing
		}
	} while (dir !== (dir = path.dirname(dir)));
	for (const internalPath of require("module").globalPaths) {
		try {
			if (fs.statSync(path.join(internalPath, packageName)).isDirectory()) {
				return true;
			}
		} catch (_error) {
			// Nothing
		}
	}
	return false;
};

while循环从node_modules/webpack/bin下面这个目录向上查找,一直找到根目录下面的node_modules的过程,直到找到根目录,如果没有找到,则认定为没有。这个对应的node.js查找依赖包的规则。
3. 如果没有cli.installed,可以得出webpack-cli的安装情况,如果安装则调用cli,未安装引导安装

if(!cli.installed) {
    // 引导安装
} else {
    // 调用
    runCli(cli)
}
  1. 已经安装
runCli(cli)
const runCli = cli => {
	const path = require("path");
	const pkgPath = require.resolve(`${cli.package}/package.json`);
	const pkg = require(pkgPath);

	if (pkg.type === "module" || /\.mjs/i.test(pkg.bin[cli.binName])) {
		import(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName])).catch(
			error => {
				console.error(error);
				process.exitCode = 1;
			}
		);
	} else {
		require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
	}
};

进入require(path.resolve(path.dirname(pkgPath),pkg.bin[cli.binName]))这段函数会进入cli.js文件,然后进入lib下面的bootstrap.js

 "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// eslint-disable-next-line @typescript-eslint/no-var-requires
const WebpackCLI = require("./webpack-cli");
const runCLI = async (args) => {
    // Create a new instance of the CLI object
    const cli = new WebpackCLI();
    try {
        await cli.run(args);
    }
    catch (error) {
        cli.logger.error(error);
        process.exit(2);
    }
};
module.exports = runCLI;

实例化了个WebpackCLI(),这个实例的对象就是webpack-cli.js文件中的。这个webpack-cli是处理命令行参数的,然后调用webpack进行打包,不论是什么类型的cli,最后都是调用webpack,执行webpack(config)
5. 引导调用
包管理检查: 根据yarn.lockjk判定yarn,根据pnpm-lock.yaml判定pnpm,否则使用npm

let packageManager;  
if (fs.existsSync(path.resolve(process.cwd(), "yarn.lock"))) {  
packageManager = "yarn";  
} else if (fs.existsSync(path.resolve(process.cwd(), "pnpm-lock.yaml"))) {  
packageManager = "pnpm";  
} else {  
packageManager = "npm";  
}  

接下来就是通过交互式命令行界面,完成webpack-cli的剩余安装引导。

webpack-cli指令

在这里插入图片描述

webpack-cli/bin/cli.js => 导入bootstrap模块,执行该模块,然后传入process.argv进程参数。
webpack-cli/lib/bootstrap.js 导出一个runCLI,在这个函数内部中,创建了一个WebpackCLI实例cli,然后调用cli.run()方法,run方法是WebpackCLI类型的入口方法。

webpack-cli/lib/webpack-cli.js

clsaa WebpackCLI {
    constructor() {
    
    },
    async run(args, parseOptions) {
    
    }
}
module.exports = WebpackCLI

run中有build, watch, version, help

  • build: 运行webpack
  • watch: 运行webpack并且监听文件变化
  • version: 显示已经安装的package以及已经安装的子package的版本
  • help: 列出命令行可以使用的基础命令喝flag
    externalbBuiltInCommandsInfo中有外置内建命令,包括
  • serve: 运行webpack开发服务器
  • info: 输入系统信息
  • init: 用于初始化一个新的webpack项目
  • loader: 初始化一个loader
  • plugin: 初始化一个插件
  • migrate: 这个命令文档未列出[npm]
  • configtest: 校验webpack配置。
contrutor

构造函数内部通过commander创建了program对象并挂载在webpackcli实例上。

constructor() {
    this.colors = this.createColors();
    this.logger = this.getLogger();
    // Initialize program
    this.program = program;
    this.program.name("webpack");
    this.program.configureOutput({
        writeErr: this.logger.error,
        outputError: (str, write) => write(`Error: ${this.capitalizeFirstLetter(str.replace(/^error:/, "").trim())}`),
    });
}
run方法

run方法是webpackcli的主入口

exitOverride改写退出
this.program.exitOverride(async (error) => {
    var _a;
    if (error.exitCode === 0) {
        process.exit(0);
    }
    if (error.code === "executeSubCommandAsync") {
        process.exit(2);
    }
    if (error.code === "commander.help") {
        process.exit(0);
    }
    if (error.code === "commander.unknownOption") {
        let name = error.message.match(/'(.+)'/);
        if (name) {
            name = name[1].slice(2);
            if (name.includes("=")) {
                name = name.split("=")[0];
            }
            const { operands } = this.program.parseOptions(this.program.args);
            const operand = typeof operands[0] !== "undefined"
                ? operands[0]
                : getCommandName(buildCommandOptions.name);
            if (operand) {
                const command = findCommandByName(operand);
                if (!command) {
                    this.logger.error(`Can't find and load command '${operand}'`);
                    this.logger.error("Run 'webpack --help' to see available commands and options");
                    process.exit(2);
                }
                const levenshtein = require("fastest-levenshtein");
                for (const option of command.options) {
                    if (!option.hidden && levenshtein.distance(name, (_a = option.long) === null || _a === void 0 ? void 0 : _a.slice(2)) < 3) {
                        this.logger.error(`Did you mean '--${option.name()}'?`);
                    }
                }
            }
        }
    }
    this.logger.error("Run 'webpack --help' to see available commands and options");
    process.exit(2);
});

这是由于comander在声明式的命令行中有一些默认的退出规则。这里做了一些拦截动作,然后自定义退出过程

注册color/no-color options
this.program.option("--color", "Enable colors on console.");
this.program.on("option:color", function () {
    // @ts-expect-error shadowing 'this' is intended
    const { color } = this.opts();
    cli.isColorSupportChanged = color;
    cli.colors = cli.createColors(color);
});
this.program.option("--no-color", "Disable colors on console.");
this.program.on("option:no-color", function () {
    // @ts-expect-error shadowing 'this' is intended
    const { color } = this.opts();
    cli.isColorSupportChanged = color;
    cli.colors = cli.createColors(color);
});

颜色设置

注册version option
this.program.option("-v, --version", "Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.");
处理help option
this.program.helpOption(false);
this.program.addHelpCommand(false);
this.program.option("-h, --help [verbose]", "Display help for commands and options.");

生成式命令中,webpack-cli自己处理help的命令具体动作

action handler
this.program.action(async (options, program) => {
    if (!isInternalActionCalled) {
        isInternalActionCalled = true;
    }
    else {
        this.logger.error("No commands found to run");
        process.exit(2);
    }
    // Command and options
    const { operands, unknown } = this.program.parseOptions(program.args);
    const defaultCommandToRun = getCommandName(buildCommandOptions.name);
    const hasOperand = typeof operands[0] !== "undefined";
    const operand = hasOperand ? operands[0] : defaultCommandToRun;
    const isHelpOption = typeof options.help !== "undefined";
    const isHelpCommandSyntax = isCommand(operand, helpCommandOptions);
    if (isHelpOption || isHelpCommandSyntax) {
        let isVerbose = false;
        if (isHelpOption) {
            if (typeof options.help === "string") {
                if (options.help !== "verbose") {
                    this.logger.error("Unknown value for '--help' option, please use '--help=verbose'");
                    process.exit(2);
                }
                isVerbose = true;
            }
        }
        this.program.forHelp = true;
        const optionsForHelp = []
            .concat(isHelpOption && hasOperand ? [operand] : [])
            // Syntax `webpack help [command]`
            .concat(operands.slice(1))
            // Syntax `webpack help [option]`
            .concat(unknown)
            .concat(isHelpCommandSyntax && typeof options.color !== "undefined"
            ? [options.color ? "--color" : "--no-color"]
            : [])
            .concat(isHelpCommandSyntax && typeof options.version !== "undefined" ? ["--version"] : []);
        await outputHelp(optionsForHelp, isVerbose, isHelpCommandSyntax, program);
    }
    const isVersionOption = typeof options.version !== "undefined";
    if (isVersionOption) {
        const info = await this.getInfoOutput({ output: "", additionalPackage: [] });
        this.logger.raw(info);
        process.exit(0);
    }
    let commandToRun = operand;
    let commandOperands = operands.slice(1);
    if (isKnownCommand(commandToRun)) {
        await loadCommandByName(commandToRun, true);
    }
    else {
        const isEntrySyntax = fs.existsSync(operand);
        if (isEntrySyntax) {
            commandToRun = defaultCommandToRun;
            commandOperands = operands;
            await loadCommandByName(commandToRun);
        }
        else {
            this.logger.error(`Unknown command or entry '${operand}'`);
            const levenshtein = require("fastest-levenshtein");
            const found = knownCommands.find((commandOptions) => levenshtein.distance(operand, getCommandName(commandOptions.name)) < 3);
            if (found) {
                this.logger.error(`Did you mean '${getCommandName(found.name)}' (alias '${Array.isArray(found.alias) ? found.alias.join(", ") : found.alias}')?`);
            }
            this.logger.error("Run 'webpack --help' to see available commands and options");
            process.exit(2);
        }
    }
    await this.program.parseAsync([commandToRun, ...commandOperands, ...unknown], {
        from: "user",
    });
});

主要功能就是:

  • 解析进程参数获取operands, options
  • 判断是否为help
  • 判断是否为version
  • 处理非help或version的语法
  • operand在前面判断过,如果没有传递则默认使用build命令
判断commandToRun是否为已知命令

如果是,则直接进行加载并执行的动作。

if (isKnownCommand(commandToRun)) {
    await loadCommandByName(commandToRun, true);
}
const loadCommandByName = async (commandName, allowToInstall = false) => {
const isBuildCommandUsed = isCommand(commandName, buildCommandOptions);
const isWatchCommandUsed = isCommand(commandName, watchCommandOptions);
if (isBuildCommandUsed || isWatchCommandUsed) {
    await this.makeCommand(isBuildCommandUsed ? buildCommandOptions : watchCommandOptions, async () => {
        this.webpack = await this.loadWebpack();
        return this.getBuiltInOptions();
    }, async (entries, options) => {
        if (entries.length > 0) {
            options.entry = [...entries, ...(options.entry || [])];
        }
        await this.runWebpack(options, isWatchCommandUsed);
    });
}
else if (isCommand(commandName, helpCommandOptions)) {
    // Stub for the `help` command
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    this.makeCommand(helpCommandOptions, [], () => { });
}
else if (isCommand(commandName, versionCommandOptions)) {
    // Stub for the `version` command
    this.makeCommand(versionCommandOptions, this.getInfoOptions(), async (options) => {
        const info = await cli.getInfoOutput(options);
        cli.logger.raw(info);
    });
}
else {
    const builtInExternalCommandInfo = externalBuiltInCommandsInfo.find((externalBuiltInCommandInfo) => getCommandName(externalBuiltInCommandInfo.name) === commandName ||
        (Array.isArray(externalBuiltInCommandInfo.alias)
            ? externalBuiltInCommandInfo.alias.includes(commandName)
            : externalBuiltInCommandInfo.alias === commandName));
    let pkg;
    if (builtInExternalCommandInfo) {
        ({ pkg } = builtInExternalCommandInfo);
    }
    else {
        pkg = commandName;
    }
    if (pkg !== "webpack-cli" && !this.checkPackageExists(pkg)) {
        if (!allowToInstall) {
            return;
        }
        pkg = await this.doInstall(pkg, {
            preMessage: () => {
                this.logger.error(`For using this command you need to install: '${this.colors.green(pkg)}' package.`);
            },
        });
    }
    let loadedCommand;
    try {
        loadedCommand = await this.tryRequireThenImport(pkg, false);
    }
    catch (error) {
        // Ignore, command is not installed
        return;
    }
    let command;
    try {
        command = new loadedCommand();
        await command.apply(this);
    }
    catch (error) {
        this.logger.error(`Unable to load '${pkg}' command`);
        this.logger.error(error);
        process.exit(2);
    }
}
};

commandToRun => build / watch
commandToRun => help
commandToRun => version
commandToRun => externalBuiltIn命令

未知命令
entry命令

webpack-CLI中支持entry语法

$ npx webpack <entry> --output-path <output-path>  
错误命令

如果为止命令不是入口语法的情况下,webpackcli认为我们的输入有无,cli会查找和输入单词命令最接近的命令并提示到命令行。

this.logger.error(`Unknown command or entry '${operand}'`);  
const levenshtein = require("fastest-levenshtein"); // 这个库用于计算两个词之间的差别  
const found = knownCommands.find((commandOptions) => levenshtein.distance(operand, getCommandName(commandOptions.name)) < 3);  
if (found) {  
this.logger.error(`Did you mean '${getCommandName(found.name)}' (alias '${Array.isArray(found.alias) ? found.alias.join(", ") : found.alias}')?`);  
}  
this.logger.error("Run 'webpack --help' to see available commands and options");  
process.exit(2);  
调用program.parseAsyanc执行新创建的命令
makeCommand
签名
  1. commandOptions: 创建命令所需要的option
  2. options: 命令执行所需要的options
  3. action: 处理命令的action handler
函数工作流
  1. 判断是否已经加载过的命令,如果是加载过,则不在使用make
  2. 判断program.comman()注册新的子命令
  3. 注册command.description()描述星系
  4. 注册command.usage()用法信息
  5. 注册command.alias()别名信息
  6. 检查命令的依赖包的安装信息
  7. 为新增的command注册传入的options
  8. 最后为新的command注册action handler
async makeCommand(commandOptions, options, action) {
    const alreadyLoaded = this.program.commands.find((command) => command.name() === commandOptions.name.split(" ")[0] ||
        command.aliases().includes(commandOptions.alias));
    if (alreadyLoaded) {
        return;
    }
    const command = this.program.command(commandOptions.name, {
        hidden: commandOptions.hidden,
        isDefault: commandOptions.isDefault,
    });
    if (commandOptions.description) {
        command.description(commandOptions.description, commandOptions.argsDescription);
    }
    if (commandOptions.usage) {
        command.usage(commandOptions.usage);
    }
    if (Array.isArray(commandOptions.alias)) {
        command.aliases(commandOptions.alias);
    }
    else {
        command.alias(commandOptions.alias);
    }
    if (commandOptions.pkg) {
        command.pkg = commandOptions.pkg;
    }
    else {
        command.pkg = "webpack-cli";
    }
    const { forHelp } = this.program;
    let allDependenciesInstalled = true;
    if (commandOptions.dependencies && commandOptions.dependencies.length > 0) {
        for (const dependency of commandOptions.dependencies) {
            const isPkgExist = this.checkPackageExists(dependency);
            if (isPkgExist) {
                continue;
            }
            else if (!isPkgExist && forHelp) {
                allDependenciesInstalled = false;
                continue;
            }
            let skipInstallation = false;
            // Allow to use `./path/to/webpack.js` outside `node_modules`
            if (dependency === WEBPACK_PACKAGE && WEBPACK_PACKAGE_IS_CUSTOM) {
                skipInstallation = true;
            }
            // Allow to use `./path/to/webpack-dev-server.js` outside `node_modules`
            if (dependency === WEBPACK_DEV_SERVER_PACKAGE && WEBPACK_DEV_SERVER_PACKAGE_IS_CUSTOM) {
                skipInstallation = true;
            }
            if (skipInstallation) {
                continue;
            }
            await this.doInstall(dependency, {
                preMessage: () => {
                    this.logger.error(`For using '${this.colors.green(commandOptions.name.split(" ")[0])}' command you need to install: '${this.colors.green(dependency)}' package.`);
                },
            });
        }
    }
    if (options) {
        if (typeof options === "function") {
            if (forHelp && !allDependenciesInstalled && commandOptions.dependencies) {
                command.description(`${commandOptions.description} To see all available options you need to install ${commandOptions.dependencies
                    .map((dependency) => `'${dependency}'`)
                    .join(", ")}.`);
                options = [];
            }
            else {
                options = await options();
            }
        }
        for (const option of options) {
            this.makeOption(command, option);
        }
    }
    command.action(action);
    return command;
}
doInstall
  1. 获取包管理工具
  2. 创建REPL引导用户输入
  3. 创建子进程执行安装命令
async  
doInstall(packageName, options = {})  
{  
// 获取包管理器i  
const packageManager = this.getDefaultPackageManager();  
if (!packageManager) {  
this.logger.error("Can't find package manager");  
process.exit(2);  
}  
if (options.preMessage) {  
options.preMessage();  
}  
// 创建 REPL  
const prompt = ({ message, defaultResponse, stream }) => {  
const readline = require("readline");  
const rl = readline.createInterface({  
input: process.stdin,  
output: stream,  
});  
return new Promise((resolve) => {  
rl.question(`${message} `, (answer) => {  
// Close the stream  
rl.close();  
const response = (answer || defaultResponse).toLowerCase();  
// Resolve with the input response  
if (response === "y" || response === "yes") {  
resolve(true);  
} else {  
resolve(false);  
}  
});  
});  
};  
// yarn uses 'add' command, rest npm and pnpm both use 'install'  
const commandArguments = [packageManager === "yarn" ? "add" : "install", "-D", packageName];  
const commandToBeRun = `${packageManager} ${commandArguments.join(" ")}`;  
let needInstall;  
try {  
needInstall = await prompt({  
message: `[webpack-cli] Would you like to install '${this.colors.green(packageName)}' package? (That will run '${this.colors.green(commandToBeRun)}') (${this.colors.yellow("Y/n")})`,  
defaultResponse: "Y",  
stream: process.stderr,  
});  
} catch (error) {  
this.logger.error(error);  
process.exit(error);  
}  
if (needInstall) {  
// 子进程执行安装命令  
const { sync } = require("cross-spawn");  
try {  
sync(packageManager, commandArguments, { stdio: "inherit" });  
} catch (error) {  
this.logger.error(error);  
process.exit(2);  
}  
return packageName;  
}  
process.exit(2);  
}  

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据质量管理-规范性管理

数据质量管理简介 数据质量管理是一个持续性的管理动作&#xff0c;有些人在做数据质量管理的时候会陷入一步到位的误区&#xff0c;想要通过一个工具、平台&#xff0c;或者一套质检规则就完成整体的数据质量管理&#xff0c;而实际数据质量管理从数据接入的那一刻就需要介入…

rockchip linux sdk指定编译配置文件

SDK&#xff1a;rk3568_linux4.19_V1.4.0 硬件平台&#xff1a;RK3566 一、指定板级配置文件 板级配置文件在<SDK>/device/rockchip/rk3566_rk3568目录下。 1、方法1 ./build.sh后⾯加上板级配置⽂件&#xff0c;例如&#xff1a; ./build.sh /device/rockchip/rk3…

《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》封面五年构想

禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 2028年再版时&#xff0c;我要组个九宫图&#xff0c;构思中。。。

AI教育的“智”变大考,小度率先提交答卷!!

目录 01 LLM重定义“AI教师” 02 全新小度&#xff0c;换上“最强大脑” 03 智能原生&#xff1a;AI硬件破局的关键 作为继OpenAI、微软和谷歌之后&#xff0c;万众期待的最后一个尖子生&#xff0c;苹果在上周的WWDC24全球开发者大会上&#xff0c;终于交出了自己的“AI答卷…

【java分布式计算】分布式计算程序设计基础

期末复习 自留 重点只抓考点 目录 基本技术 SOCKETS网络套接字 多线程 数据序列化 Java I/O流 集合容器 范型 内部类、匿名类、Lambda&#xff08;代码&#xff09; 项目构建管理工具 高级技术 注解&#xff08;代码&#xff09; 反射&#xff08;代码&#xff09;…

信息学奥赛初赛天天练-29-CSP-J2022阅读程序-掌握递归、递推、动态规划、二分与极值函数应用

PDF文档公众号回复关键字:20240619 2022 CSP-J 阅读程序2 阅读程序(判断题1.5分 选择题3分 共计40分 ) 01 #include <algorithm> 02 #include <iostream> 03 #include <limits> 04 05 using namespace std; 06 07 const int MAXN 105; 08 const int MAX…

使用docker离线制作es镜像,方便内网环境部署

1、自己在本地安装docker以及docker-compose 2、拉取elasticsearch镜像 docker pull elasticsearch:7.14.0 docker pull kibana:7.14.0 3、将拉取到的镜像打包到本地目录 docker save elasticsearch:7.14.0 -o /Users/yanjun.hou/es/elasticsearch-7.14.0.tar docker save kib…

application/x-www-form-urlencoded和json的区别

application/x-www-form-urlencoded 和 application/json 是两种不同的数据格式&#xff0c;常用于HTTP请求中传递数据。 它们各自的特点和使用场景如下&#xff1a; 1. application/x-www-form-urlencoded •特点&#xff1a;这是一种传统的表单提交时采用的编码类型&#x…

# 消息中间件 RocketMQ 高级功能和源码分析(五)

消息中间件 RocketMQ 高级功能和源码分析&#xff08;五&#xff09; 一、 消息中间件 RocketMQ 源码分析&#xff1a;NameServer 路由元数据 1、消息中间件 RocketMQ 中&#xff0c;NameServer 路由管理 NameServer 的主要作用是为消息的生产者和消息消费者提供关于主题 To…

相交链表(Leetcode)

题目分析&#xff1a; . - 力扣&#xff08;LeetCode&#xff09; 相交链表&#xff1a;首先我想到的第一个思路是&#xff1a;如图可知&#xff0c;A和B链表存在长度差&#xff0c;从左边一起遍历链表不好找交点&#xff0c;那我们就从后面开始找&#xff0c;但是这是单链表&…

惊天大瓜陈晓与陈妍希婚姻生变

惊天大瓜&#xff01;陈晓与陈妍希婚姻生变&#xff0c;疑似去年底已走向终点娱乐圈再次掀起波澜&#xff01;今日&#xff0c;知名狗仔曝光了一段视频&#xff0c;内容直指陈晓与陈妍希这对曾经的金童玉女疑似婚姻破裂。据悉&#xff0c;陈晓在去年底单方面向陈妍希提出了离婚…

AI 已经在污染互联网了。。赛博喂屎成为现实

大家好&#xff0c;我是程序员鱼皮。这两年 AI 发展势头迅猛&#xff0c;更好的性能、更低的成本、更优的效果&#xff0c;让 AI 这一曾经高高在上的技术也走入大众的视野&#xff0c;能够被我们大多数普通人轻松使用&#xff0c;无需理解复杂的技术和原理。 其中&#xff0c;…

Gin 详解

Gin 介绍 gin框架是一个基于go语言的轻量级web框架&#xff0c;它具有高效性、灵活性、易扩展性路由 gin框架使用的是定制版的httprouter 其路由原理是大量使用公共前缀的树结构&#xff0c;注册路由的过程就是构造前缀树的过程。 具有公共前缀的节点也共享一个公共父节点。…

【第19章】Vue实战篇之主页面

文章目录 前言一、代码1. 主界面代码2. App.vue 二、展示总结 前言 登录完成之后&#xff0c;应该自动跳转到主页面&#xff0c;接下来我们搭建主界面。 一、代码 1. 主界面代码 <script setup> import {Management,Promotion,UserFilled,User,Crop,EditPen,SwitchBut…

Linux搭建Minio单机环境

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; Linux搭建Minio单机环境 ⏱️ 创作时间&#xff1a; 2024年06月19日 目…

群辉DSM7下ZeroTier的安装

目录 一、起因 二、具体操作 1、添加组件源: 2、安装套件 3、开启ssh 4、连接ssh执行修补 5、手工启动ZeroTier 6、使用终端命令加入网络 7、审核通过该节点的加入 三、测试链接 1、PC端测试 2、手机APP测试 ZeroTier 是一款异地组网工具,它可以将不同网络环境的设…

鸿蒙开发通信与连接:【@ohos.bluetooth (蓝牙)】

蓝牙 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 蓝牙模块提供了基础的传统蓝牙能力以及BLE的扫描、广播等功能。 导入模块 import bluetooth from ohos.bluetooth;bluetooth.enableBluet…

PCB设计隐藏的陷进

1、BGA芯片的开窗和过油设计。 加工工艺中&#xff0c;范式过孔都需要盖油设计&#xff0c;实心焊盘需要开窗设计&#xff0c;坚决不能盖油。 2、通孔设计的互联连通性 比如H3芯片的wifi设计&#xff0c;实际上是没有联通的&#xff0c;虽然四层板的中间层有焊盘&#xff0c;但…

复旦大学:将推出至少100门AI领域课程

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 最近AI圈又发生了啥&#xff1f; 复旦大学&#xff1a;将在下一个学年推出至少100门AI领域课程 复旦大学召开2024年招生培养政策发布会&#xff0c;公布今年本科招生培养政策亮点。从今年秋季学期开…

【黑马TS】学习资料Day4

五、在 React 中使用 TypeScript 现在&#xff0c;我们已经掌握了 TS 中基础类型、高级类型的使用了。但是&#xff0c;如果要在前端项目开发中使用 TS&#xff0c;还需要掌握 React、Vue、Angular 等这些库或框架中提供的 API 的类型&#xff0c;以及在 TS 中是如何使用的。 …