如何编写一个自己的web前端脚手架

news2025/6/20 13:03:40

脚手架简介

脚手架是创建前端项目的命令行工具,集成了常用的功能和配置,方便我们快速搭建项目,目前网络上也有很多可供选择的脚手架。

一个"简单脚手架"的构成其实非常少,即 代码模板 + 命令行工具。其中代码模板是脚手架需要生成给用户的工程代码,命令行工具的作用是提供命令行界面,根据用户输入的信息和代码模板生成工程。

代码模板根据脚手架功能的不同而不同,但是不同脚手架命令行工具相似性较大。今天就来聊一下如何编写一个脚手架。

脚手架使用方式

先看一下我们编写的脚手架的使用方式。

启动脚手架

npm create xxx
# 或者
npx create-xxx

其中npx方式可以接收更多参数:

# 查看脚手架版本
npx create-xxx -v
# 指定生成的工程名称
npx create-xxx -n appname

命令行执行命令后,会自动下载我们的create-xxx包,并执行。

命令行界面和生成代码

脚手架启动后,会出现一个交互式命令行界面,有若干个输入项或选择项等,这个根据不同脚手架的功能而不同。例如create-xxx@0.0.1的选项。

在这里插入图片描述

在用户输入和选择全部完成后,脚手架会把代码生成到本地,同时提示用户启动方式。

第一版工程结构

首先我们创建脚手架工程的第一版。

|-- create-xxx@0.0.1
    |-- package.json
    |-- src
        |-- appName.js
        |-- create.js
        |-- index.js
        |-- prompt.js
        |-- setFileConfig.js
    |-- templates
        |-- auto-element
            |-- ...
        |-- lite-element
            |-- ...
        |-- trad-element
            |-- ...

其中src是脚手架工具命令行工具部分,templates是代码模板部分。

第一版代码和说明

启动方式

首先来看一下package.json

{
  "name": "create-xxx",
  "version": "0.0.1",
  "description": "",
  "main": "src/index.js",
  "bin": {
    "create-xxx": "src/index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "jiazhen",
  "dependencies": {
    "chalk": "^4",
    "commander": "^10.0.0",
    "inquirer": "^8.0.0",
    "ora": "^5"
  }
}

注意,这里所有的依赖都是dependencies,即生产环境依赖。

这里设置了bin字段,值为工具的入口文件,可以使用该命令启动。同时设置为create-xxx,可以使用npm create xxx的方式启动。不过,使用npx会在全局自动缓存该npm包,因此启动时后面带个@latest表示最新版本更好。否则你上传了新版本,用户依然还会使用本地缓存的旧版本。

# 建议
npx create-xxx@latest

我们在第一版开发时,还不是一个完整的包,因此本地调试可以直接使用node执行:

node src/index.js

后续代码中所有的模块引入都使用的require,也就是CommonJS规范。这种规范可以让代码在Node.js中直接执行。如果使用ES Modules规范,即import,使用Node.js中直接执行会报错。

主要流程

入口文件

先看代码,首先是入口文件src/index.js

#! /usr/bin/env node
const { program } = require("commander");
const { createDir, getCmdName } = require("./appName");
const package = require("../package.json");
const { getPromptValue } = require("./prompt");
const create = require("./create");
const ora = require("ora");
const chalk = require("chalk");

// 创建成功后的提示
function succConsole(configs) {
  console.log('');
  console.log(chalk.cyan(`  ${chalk.gray("$")} cd ${configs.name}`));
  console.log(chalk.cyan(`  ${chalk.gray("$")} npm install`));
  console.log(chalk.cyan(`  ${chalk.gray("$")} npm run dev`));
}

// 主函数
async function main() {
  program.option("-n, --name <value>", "app name");
  program.version(package.version, "-v");
  program.parse();
  const options = program.opts();
  // 从命令行中获取AppName
  let name = getCmdName(options, program);
  // 检测名称并创建文件夹
  name = await createDir(name);
  // 获取脚手架选项
  const promptValue = await getPromptValue();
  const configs = {
    ...promptValue,
    name,
  };

  const spinner = ora("工程正在创建中");
  // 创建工程
  await create(configs);
  console.log('');
  spinner.succeed("工程创建完成!");
  succConsole(configs);
}

main();

流程说明

  1. 使用commander库获取命令行参数,主要有name和version。
  2. 检测名称并创建工程文件夹。
  3. 用户交互式的输入脚手架选项。
  4. 复制代码,并根据脚手架选项调整代码。
  5. 生成结束,输出成功提示。

注意示项

  • 脚本说明
    首先是代码的第一句#! /usr/bin/env node,这表示将用node脚本执行该命令。如果没有这一句,后面作为一个npm包被执行的时候会报错。
  • 获取版本
    版本信息我们直接使用package.json中的版本号即可。把它作为一个模块引入,直接取值。

获取工程名称

文件src/appName.js

const inquirer = require("inquirer");
const fs = require("fs");
const namePrompt = [
  {
    type: "input",
    name: "name",
    message: "请输入工程名称",
    default: "xxx-app",
  },
];

// 检测名称并创建文件夹
async function createDir(appName) {
  while (1) {
    if (!appName) {
      const res = await inquirer.prompt(namePrompt);
      appName = res.name;
    }
    try {
      // 创建文件夹
      const res = fs.mkdirSync(appName);
      break;
    } catch (e) {
      console.log("error: 工程名称与现有文件夹重名,请重新输入!");
      appName = null;
    }
  }
  return appName;
}

// 从命令行中获取AppName
function getCmdName(options, program) {
  // 输入了name参数优先取name
  if (options.name) return options.name;
  // 没有name参数则使用第一个输入项
  if (program.args && program.args.length > 0) return program.args[0];
  return null;
}

module.exports = {
  createDir,
  getCmdName,
};

我们把工程名称作为后面放置工程代码所创建的文件夹名称,因此这个名称特别重要。

  1. 首先我们尝试从命令行参数中获取工程名称。如果没有获取到则提示用户输入工程名称。
  2. 这里还对工程名称做了校验,校验内容是————是否与当前已有的文件重名,如果重名则提示用户重新输入。
  3. 文件名校验成功则创建文件夹。

获取脚手架选项

文件src/prompt.js

const inquirer = require("inquirer");
const promptList = [
  {
    type: "list",
    message: "请选择模板类型",
    name: "tamplate",
    default: "auto",
    choices: [
      {
        name: "自动版 (推荐首选,集成vue3生态新功能)",
        value: "auto",
      },
      {
        name: "精简版 (无多语言/多皮肤等功能)",
        value: "lite",
      },
      {
        name: "传统版 (不使用各类按需引入插件)",
        value: "trad",
      },
    ],
  },
  {
    type: "input",
    name: "namespace",
    message: "请输入组件上下文,即公共基础路径",
    default: "/",
  },
];

async function getPromptValue() {
  const res = await inquirer.prompt(promptList);
  return res;
}

module.exports = {
  getPromptValue,
};

这部分非常简单,按照inquirer库的格式做好需要用户输入的内容,取得用户输入的值即可。

获取模板代码

代码模板和脚手架一起存放

文件src/create.js

const path = require("path");
const fs = require("fs");
const { setTargetConfig  } = require('./setFileConfig')

// 获取模板代码
async function getTempLateCodes(configs) {
  // 包中的代码位置
  let srcPath = path.join(
    __dirname,
    "../templates",
    `${configs.tamplate}-element`
  );
  // 代码要放置的目标工程位置
  const targetPath = path.join(process.cwd(), configs.name);
  // 复制代码到工程中
  // node.js 16.7 Aug 18,2021发布
  fs.cpSync(srcPath, targetPath, { recursive: true });
  // 根据配置修改模板文件
  await setTargetConfig(targetPath, configs)
}

// 创建工程的主函数
async function create(configs) {
  getTempLateCodes(configs);
}

module.exports = create;

有了这些配置之后,我们就可以获取模板代码了。我这里需要根据不同的配置而使用不同的代码模板。通过查看上面的工程结构,我们看到代码模板是和脚手架代码放置在同一个npm包中的,因此直接使用fs复制文件即可。相比于分开存放,放置在通过一个npm包中复制文件速度更快。而分开存放一半需要通过网络下载代码模板,速度慢一些。

使用这种方式,需要注意两个路径:

  • npm包路径
    脚手架代码所在的位置的路径,这个路径可以用__dirname获取。
    从这个路径内获取模板代码。
  • 工作目录
    当前执行脚手架命令所在的目录。这个路径可以用process.cwd()获取。
    这个路径+工程名称就是工程的存放位置。模板代码要放置到这里。

代码模板和脚手架分开存放

代码模板是和脚手架代码放置在同一个npm包中,脚手架版本会和模板版本强绑定。如果不希望绑定,可以脚手架一个npm包,代码模板使用另外一个npm包。这样假设代码模板有多个版本,可以使用同一个脚手架安装不同版本的模板。甚至可以模板放置在git上,脚手架启动时直接去git上下载代码。

根据脚手架选项调整代码

文件src/setFileConfig.js

const path = require("path");
const fs = require("fs");
const { promisify } = require("util");

// 回调转promise版本
const fsWriteFile = promisify(fs.writeFile);
const fsReadFile = promisify(fs.readFile);

// 根据配置修改模板文件
async function setTargetConfig(targetPath, configs) {
  await Promise.all([
    setPackageJson(targetPath, configs),
    setReadme(targetPath, configs),
    setEnv(targetPath, configs),
  ]);
}

// 修改package.json
async function setPackageJson(targetPath, configs) {
  const objPath = path.join(targetPath, "package.json");
  const package = require(objPath);
  package.name = configs.name;
  const jsonData = JSON.stringify(package, null, 2);
  await fsWriteFile(objPath, jsonData);
}

// 修改README.md
async function setReadme(targetPath, configs) {
  const objPath = path.join(targetPath, "README.md");
  let data = await fsReadFile(objPath, "utf8");
  data = data.replace("XXX-APP-NAME", configs.name);
  await fsWriteFile(objPath, data);
}

// 修改.env
async function setEnv(targetPath, configs) {
  let namespace = configs.namespace;
  // 完善namespace数据
  if(!namespace.length) {
    namespace = '/'
  }
  if(namespace[0] !== '/') {
    namespace = '/' + namespace
  }
  if(namespace[namespace.length - 1] !== '/') {
    namespace = namespace +  '/'
  }
  // 写入文件
  const objPath = path.join(targetPath, ".env");
  let data = await fsReadFile(objPath, "utf8");
  // 正则中.不包含换行符,这里正好截取一行
  data = data.replace(
    /VITE_NAMESPACE.*/g,
    `VITE_NAMESPACE = ${namespace}`
  );
  await fsWriteFile(objPath, data);
}

module.exports = {
  setTargetConfig,
};
  • 这里也非常简单,我们把模板代码当作普通文件去读,根据配置修改文件内容,再写入即可。
  • 我们可以把模板的可变部分使用特殊的字符串标记,方便我们使用正则查找并替换。如果修改的内容较复杂,甚至可以使用一些模板引擎。
  • 读文件是耗时操作,可以使用Promise.all一起执行多个。如果操作更复杂耗时,甚至可以考虑引入多线程技术。

打包上传

写好之后并测试完成后,我们就可以把我们的脚手架作为一个npm包发布,这样用户才能下载使用。

# 发布包
npm publish
# 删除包
npm unpublish create-xxx@0.0.1

在公共网络发布npm包需要先注册。

第二版工程结构

第一版的工程,已经可以作为一个脚手架来使用了。但是还有几个小问题可以改进:

  1. 所有依赖都是生产依赖,这意味着用户在启动脚手架时,还需要额外下载很多npm包,需要等待一段时间。
  2. 脚手架代码可以进行压缩。

针对这两个问题,我对第一版脚手架工程进行了改进,改进后的工程结构如下:

|-- create-xxx@0.0.2
    |-- package.json
    |-- bin
        |-- index.js
    |-- dist
        |-- index.cjs
    |-- src
        |-- appName.js
        |-- create.js
        |-- index.js
        |-- prompt.js
        |-- setFileConfig.js
    |-- templates
        |-- auto-element
            |-- ...
        |-- lite-element
            |-- ...
        |-- trad-element
            |-- ...
    |-- .eslintignore
    |-- .eslintrc.js
    |-- .gitignore
    |-- .npmrc
    |-- .prettierignore
    |-- .prettierrc.js
    |-- build.config.js

第二版的改动

使用unbuild打包

第一版代码没有经过打包直接发布,第二版如果希望去除生产环境的依赖,那么就必须进行打包。我参考create-vite,使用了unbuild作为打包工具。这是一个轻量级的,基于rollup的工具。
看一下打包配置build.config.js:

import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  // 入口文件
  entries: ['src/index'],
  clean: true,
  // 生成ts声明文件
  declaration: false,
  // 警告是否会引发报错
  failOnWarn: false,
  // rollup配置
  rollup: {
    // 生成cjs
    emitCJS: true,
    inlineDependencies: true,
    esbuild: {
      // 压缩代码
      minify: true,
    },
    resolve: {
      exportConditions: ['node'],
    },
  },
})

打包和压缩代码都是unbuild完成的。打包之后会生成一个dist文件夹,里面有index.cjsindex.mjs。我们使用的是cjs,即CommonJS规范的文件。

更新依赖

来看一下package.json

{
  "name": "create-xxx",
  "version": "0.0.2",
  "description": "xxx脚手架",
  "main": "bin/index.js",
  "bin": {
    "create-xxx": "bin/index.js"
  },
  "scripts": {
    "build": "unbuild",
    "lint": "eslint src --fix",
    "pretty": "prettier --write ."
  },
  "author": "jiazhen",
  "devDependencies": {
    "commander": "^10.0.0",
    "unbuild": "^1.1.2",
    "prompts": "^2.4.2",
    "eslint": "^8.30.0",
    "prettier": "^2.8.1",
    "eslint-config-prettier": "^8.5.0",
    "eslint-define-config": "^1.12.0",
    "eslint-plugin-prettier": "^4.2.1"
  },
  "files": [
    "templates",
    "dist/index.cjs",
    "bin/index.js"
  ]
}

可以看到,没有了生产依赖,全部换成开发依赖了。而且功能代码直接使用的依赖只剩下两个必须的:commanderpromptspackage.json还有其它改动,后面再说。

为啥要换依赖?因为我们要打包后要成为一个CommonJS规范,无任何依赖的Node.js可执行文件,但是很多包并不支持这种打包方式。原因可能有很多,可能是使用了某些浏览器才有的属性,可能引入了不能打包的依赖等等。就算同一个npm包,也可能部分版本支持,部分版本不支持。

比如我们使用的prompts,就在文档里说明了依赖非常少:

Simple: prompts has no big dependencies nor is it broken into a dozen tiny modules that only work well together.

关于依赖的选用,可以参考对应npm包的文档说明、issues等,也可以参考其它实现该方式的脚手架。

换了有些依赖,部分功能需要稍微修改一下,比如从inquirer换为prompts,就需要改部分配置。

注意:对于不支持的依赖,我出现过打包成功,但是dist中出现reuqire('string_decoder/')这种不存在的依赖的报错。

更换库引入方式

我试过rollup系列的打包工具,包括vite,rollup和unbuild,对于require,即CommonJS规范引入的依赖不处理,即依然作为一个外部依赖,而不是直接打包进成果物。可能是这些工具不支持,也可能是由于我没配置对的原因,后续我再研究一下。目前先全部转为ES Modules规范。需要改动的地方很少,大致只有引入和导出的方式。

// CommonJS规范 示例
const { program } = require("commander");
const { createDir, getCmdName } = require("./appName");
const package = require("../package.json");
const { getPromptValue } = require("./prompt");
const create = require("./create");
const ora = require("ora");
const chalk = require("chalk");

module.exports = {
  createDir,
  getCmdName,
};

// ES Modules规范 示例
import { program } from 'commander'
import { createDir, getCmdName } from './appName'
import packageData from '../package.json'
import { getPromptValue } from './prompt'
import create from './create'

export { createDir, getCmdName }

Node.js本身自带的库可以保持原样,依然使用CommonJS规范。因为只要安装了Node.js(不安装啥也用不了),就可以直接使用这些工具,不需要被打包。而且这些工具部分是使用C++写的,也不能打包到前端成果中。

// 部分Node.js本身自带的库
const path = require('path')
const fs = require('fs')
const { promisify } = require('util')

固定入口文件

通过上面的改动,我们已经可以生成一个包含依赖的文件dist/index.cjs了。但是我们注意到,即使我们在入口文件src/index.js中写了#! /usr/bin/env node,生成的文件中也不包含这一句。因为这一句被当作注释删掉了。

因此我们再新建一个文件bin/index.js来引入:

#!/usr/bin/env node
require('../dist/index.cjs')

注意这个文件不需要被打包,而是固定的,直接引入打包后的文件。这里执行的环境是Node.js,因此需要使用CommonJS规范。

更新package.json,这个文件直接作为bin中的启动入口文件。

其它改动

  • npm publish时仅仅上传必须的文件,比如bin/index.jsdist/index.cjstemplates模板文件夹。在package.json中使用files指定。上传的内容越少,执行时下载的速度越快。
  • 加入Eslint和Prettier,方便进行语法检查和代码格式化。可以看到对应的依赖和命令。
  • 使用npm配置文件.npmrc,上传时不用切换仓库。

参考

这个脚手架的实现有参考create-vite和其它脚手架等,在这里表示感谢~

  • create-vite vite项目脚手架
    https://github.com/vitejs/vite/tree/main/packages/create-vite
  • Prompts
    https://github.com/terkelg/prompts
  • unbuild
    https://github.com/unjs/unbuild

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

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

相关文章

【李宏毅】-生成对抗式网络(GAN)

生成对抗式网络GAN 1. Network as Generator 输入不再是只是x&#xff0c;还有一个simple distribution&#xff08;样本分布&#xff09;&#xff0c;输出也是一个分布 Why distribution 不同的分布即意味着&#xff1a;相同的输入会有不同的输出。 尤其在任务需要创造力的时…

Win+VisualStudio+vcpkg+Zeromq安装方法

1. 缘由 因为工作上要用到Windows上的zeromq来收发消息&#xff0c;所以我在网上搜集了一些资料最终成功地在Visual Studio2022中用c的libzmq库实现了zmq的收发。 2. 基本资料 2.1 ZeroMQ基本介绍 ZeroMQ官网介绍&#xff0c;因为我也不是专门搞网络和通信的&#xff0c;就…

RocketMQ 事务消息 详解

&#x1f34a; Java学习&#xff1a;Java从入门到精通总结 &#x1f34a; 深入浅出RocketMQ设计思想&#xff1a;深入浅出RocketMQ设计思想 &#x1f34a; 绝对不一样的职场干货&#xff1a;大厂最佳实践经验指南 &#x1f4c6; 最近更新&#xff1a;2023年4月9日 &#x1…

VMware ESXi 8.0c - 领先的裸机 Hypervisor (sysin Custom Image)

本站发布 Dell 和 HPE 定制版 ESXi 8.0c 镜像 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-8/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 产品简介 VMware ESXi&#xff1a;专门构建的裸机 Hyperviso…

【MATLAB数学建模编程实战】Kmeans算法编程及算法的简单原理

欢迎关注&#xff0c;本专栏主要更新MATLAB仿真、界面、基础编程、画图、算法、矩阵处理等操作&#xff0c;拥有丰富的实例练习代码&#xff0c;欢迎订阅该专栏&#xff01;&#xff08;等该专栏建设成熟后将开始收费&#xff0c;快快上车吧~~&#xff09; 【MATLAB数学建模编…

【MySQL数据库原理】MySQL Community安装与配置

目录 安装成功之后查看版本验证1、介绍、安装与配置数据库2、操作MySQL数据库3、MySQL数据库原理安装成功之后查看版本验证 SELECT VERSION();查看mysql版本号 1、介绍、安装与配置数据库 下载安装包:https://download.csdn.net/download/weixin_41194129/87672588 MySQL…

NumPy 秘籍中文第二版:二、高级索引和数组概念

原文&#xff1a;NumPy Cookbook - Second Edition 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 在本章中&#xff0c;我们将介绍以下秘籍&#xff1a; 安装 SciPy安装 PIL调整图像大小比较视图和副本翻转 Lena花式索引位置列表索引布尔值索引数独的步幅技巧广播数…

比尔·盖茨最新分享:ChatGPT的发展,不止于此

来源: 笔记侠 最近&#xff0c;ChatGPT、GPT 4、文心一言、Copilot&#xff0c;人工智能产品层出不容&#xff0c;一路轰炸&#xff1b;王慧文、王兴、李开复等各位高调以及低调的商业领袖和技术专家&#xff0c;纷纷入局AI赛道。人声鼎沸&#xff0c;十分热闹。 昨天&#xff…

自动写作ai-自动写作神器

自动生成文章 自动生成文章是指使用自然语言处理和人工智能技术&#xff0c;通过算法来自动生成文章的过程。一些自动生成文章的工具可以使用大量数据&#xff0c;学习数据背后的语言规范和知识结构&#xff0c;从而生成高质量和有用的文章。这种技术能够减少写作时间和人力成…

Nature子刊 定制饮食去除半胱氨酸和蛋氨酸可诱导细胞自毁进而治疗脑瘤?

恶性胶质瘤是成人最常见的脑部肿瘤。恶性胶质瘤的致死率为100%&#xff0c;无法治愈&#xff0c;是一种极度的恶性肿瘤。如此糟糕的预后促使研究者及神经外科医生不断学习研究肿瘤生物学&#xff0c;期望创造更好的疗法。神经外科助理教授Dominique Higgins博士从事肿瘤生物学的…

必胜方法,矩阵堆量

0x1 问题 今天不讲量化&#xff0c;来聊聊其他的 铁废柴一年到头也想不明白&#xff0c;那些几万人用的程序&#xff0c;几百万人关注的自媒体账号 到底是怎么做出来的啊 为什么我发一百个视频才赚100块钱 我要怎么才可以过上小康生活&#xff1f; 0x2 答案 矩阵&#xff…

WPF mvvm框架Stylet使用教程-特殊用法

事件绑定 除了绑定Command属性&#xff0c;在WPF中经常需要绑定一些事件的操作&#xff0c;在别的框架中需要引入其他包支持&#xff0c;在Stylet框架中&#xff0c;可以 同样使用s:Action进行绑定&#xff0c;对应绑定ViewModel中的方法。 示例&#xff1a; XAML:<Button…

中科网联CCData借助亚马逊云科技实现高效融媒体测量

近年来&#xff0c;随着媒体与广告传媒行业数字化转型向纵深发展&#xff0c;如何利用数据洞察用户生态、实现精准触达以及业务持续创新已成为媒体产业深入发展的“必答题”。与此同时&#xff0c;随着数据应用的不断深入&#xff0c;借助人工智能和机器学习技术&#xff0c;找…

TCP为什么要三次握手,而不是两次或四次?

文章目录TCP为什么要三次握手&#xff0c;而不是两次或四次&#xff1f;三次握手才可以阻止重复历史连接的初始化&#xff08;主要原因&#xff09;同步双方初始序列号避免资源浪费小结TCP为什么要三次握手&#xff0c;而不是两次或四次&#xff1f; TCP连接时用于保证可靠性和…

eclipse下载与安装(汉化教程)超详细

一、下载eclipse安装包 首先进入 eclipse官网 如下&#xff1a; 这里面有很多版本&#xff1b;我们小白一般选择第二个&#xff0c;向下滑动&#xff1b; 点击符合自己系统的版本。 这里我们切换镜像下载&#xff0c;一般选择离你最近的地址下载。 我建议选择大连东软信息学…

【MATLAB图像处理实用案例详解(9)】——基于最大类间方差遗传算法的道路分割

目录一、最大类间方差遗传算法二、代码示例一、最大类间方差遗传算法 最大类间方差的求解过程&#xff0c;就是在解空间中查找到一个最优的解&#xff0c;使得其方差最大&#xff0c;而遗传算法能非线性快速查找最优解k*及最大的方差&#xff0c;其步骤如下&#xff1a; ①为了…

【算法】01-算法解剖学-二分查找

汝之观览&#xff0c;吾之幸也&#xff01;本系列主要讲解的是算法知识&#xff0c;从算法基本概念&#xff0c;利用图解的方式更好的认识算法&#xff0c;再通过letcode算法题 进行进一步的巩固。刷题三步走&#xff08;1、掌握一门基本的编程语言&#xff1b;2、深入理解基础…

Redis7持久化

一、redis持久化 1、RDB RDB持久性以指定的时间间隔执行数据集的时间点快照 也就是说在一定的时间间隔内&#xff0c;将某一时刻的数据和状态以文件的形式写到磁盘上&#xff0c;这个快照文件交dump.rdb Redis6更新策略 Redis7更新策略 RDB手动触发 5秒2次修改 RDB手动触…

【安卓源码】SystemServer系统进程启动原理

一. SystemServer进程启动概括 Android系统中&#xff0c;第一个启动的是init进程&#xff0c;通过解析init.rc文件启动对应的service。Zygote就是由init启动起来的。Zygote作为应用的孵化器&#xff0c;所有的应用程序都是由他创建而来的。 Zygote是C/S架构的&#xff0c;当…

GANSeg:通过无监督分层图像生成学习分割

文章目录GANSeg: Learning to Segment by Unsupervised Hierarchical Image Generation摘要引言方法Level 1: Point Generation and Part ScaleLevel 2: From Points to MasksLevel 3: Mask-conditioned Image Generation损失函数实验结果GANSeg: Learning to Segment by Unsup…