【前端工程化】脚手架篇 - 模板引擎 & 动态依赖管理脚手架

作者:ObjectX不知名程序员日期:2025/10/7

🧑‍💻 写在开头

点赞 + 收藏 === 学会🤣🤣🤣

在日常工作中,我们经常为会遇到需要创建新项目的需求,为了统计代码风格,项目配置,提升效率,我们可以创建一个cli工具,帮助我们实现这样的功能。你也可以搭建一个自己用,毕竟省下来的时间都是自己的

🥑 你能学到什么?

希望在你阅读本篇文章之后,不会觉得浪费了时间。如果你跟着读下来,你将会学到:

  • cli工具的基本搭建流程
  • 如何通过模板引擎实现可选依赖
  • 模板系统如何设计
  • 如何根据模板引擎生成所需项目
  • 熟悉一个组件库的基本结构
  • 熟悉一个类型库的基本结构
  • 熟悉一个cli项目的基本结构

后续你可以在此项目ObjectX-CLI的基础上,扩展项目支持的技术栈,和项目类型,以此了解各种项目的搭建流程

实现效果

2025-10-06 21.05.19.gif

🍎 系列文章

本系列是一个从0到1的实现过程,如果您有耐心跟着实现,您可以实现一个完整的react18 + ts5 + webpack5 + 代码质量&代码风格检测&自动修复 + storybook8 + rollup + git action实现的一个完整的组件库模板项目。如果您不打算自己配置,也可以直接clone组件库仓库切换到rollup_comp分支即是完整的项目,当前实现已经足够个人使用,后续我们会新增webpack5优化、按需加载组件、实现一些常见的组件封装:包括但不限于拖拽排序、瀑布流、穿梭框、弹窗等

项目概述

ObjectX-CLI 是一个现代化的前端项目脚手架工具,支持快速创建以下三种类型的项目:

  • 组件库项目 (component-lib):基于 React + TypeScript + Vite
  • 工具包项目 (tool-lib):通用 JavaScript/TypeScript 工具库
  • 类型包项目 (types-lib):纯 TypeScript 类型定义包

核心特性

  • 🚀 零配置启动:开箱即用,一键创建项目
  • 📦 模板化设计:基于 EJS 模板引擎,支持灵活配置
  • 🎨 多样式方案:支持 Less、Tailwind CSS、CSS Modules
  • 📚 文档集成:可选 Storybook 组件文档
  • 🧩 Monorepo 支持:使用 pnpm workspace
  • 现代化打包:Rollup + TypeScript

核心架构设计

整体架构图

1objectx-cli/
2├── bin/                      # CLI 入口
3│   └── index.js             # 可执行文件入口
4├── src/                     # 源代码
5│   ├── index.ts             # 主程序入口
6│   ├── commands/            # 命令实现
7│   │   └── create.ts        # create 命令
8│   └── utils/               # 工具函数
9│       ├── generate.ts      # 项目生成逻辑
10│       └── validate.ts      # 验证逻辑
11├── templates/               # 项目模板
12│   ├── component-lib/       # 组件库模板
13│   ├── tool-lib/            # 工具库模板
14│   └── types-lib/           # 类型库模板
15├── lib/                     # 编译输出(发布到 npm)
16├── package.json             # 包配置
17├── rollup.config.js         # 打包配置
18└── tsconfig.json            # TypeScript 配置
19

核心流程图

1用户执行命令
23bin/index.js(可执行入口)
45src/index.ts(初始化 Commander)
67src/commands/create.ts(处理 create 命令)
89├─ validate.ts(验证项目名)
10├─ inquirer 交互式问答
11└─ generate.ts(生成项目文件)
1213├─ 读取模板文件
14├─ EJS 模板渲染
15├─ 条件文件过滤
16└─ 生成 package.json
1718Git 初始化
1920完成提示
21

技术栈分析

1. CLI 框架层

Commander.js - 命令行框架
1// src/index.ts
2import { Command } from 'commander';
3
4const cli = new Command();
5
6cli
7  .name('objectx-cli')
8  .description('前端项目脚手架工具')
9  .version(pkg.version);
10
11cli
12  .command('create <project-name>')
13  .description('创建一个新的项目')
14  .option('-t, --template <template>', '指定项目模板')
15  .action(create);
16

作用

  • 定义 CLI 命令和参数
  • 自动生成 --help--version
  • 参数解析和验证

2. 交互层

@inquirer/prompts - 交互式问答
1// src/commands/create.ts
2import * as inquirer from '@inquirer/prompts';
3
4// 确认覆盖
5const overwriteResult = await inquirer.confirm({
6  message: `目标目录 ${chalk.cyan(projectName)} 已存在。是否覆盖?`,
7  default: false
8});
9
10// 单选
11const projectType = await inquirer.select({
12  message: '请选择项目类型:',
13  choices: [
14    { value: 'component-lib', name: '组件库项目' },
15    { value: 'tool-lib', name: '工具包项目' },
16    { value: 'types-lib', name: '类型包项目' }
17  ]
18});
19
20// 多选
21const styles = await inquirer.checkbox({
22  message: '选择样式解决方案 (可多选):',
23  choices: [
24    { value: 'less', name: 'Less', checked: true },
25    { value: 'tailwind', name: 'Tailwind CSS' },
26    { value: 'css-modules', name: 'CSS Modules', checked: true }
27  ]
28});
29

3. 用户体验层

Chalk - 终端颜色
1import chalk from 'chalk';
2
3console.log(`${chalk.bgBlue('OBJECTX CLI ')} 🚀 创建新项目...`);
4console.log(chalk.green('✔') + ' 项目创建成功!');
5console.log(chalk.red('✖') + ' 项目名称不能为空');
6console.log([`  cd ${chalk.cyan(projectName)}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.cd.md));
7
Ora - 加载动画
1import ora from 'ora';
2
3const spinner = ora('正在生成项目文件...').start();
4await generateProject(targetDir, projectOptions);
5spinner.succeed('项目文件生成完成');
6
7// 失败情况
8spinner.fail('项目文件生成失败');
9

4. 文件处理层

fs-extra - 增强的文件系统
1import fs from 'fs-extra';
2
3// 确保目录存在
4await fs.ensureDir(targetDir);
5
6// 删除目录
7await fs.remove(targetDir);
8
9// 输出文件(自动创建父目录)
10await fs.outputFile(targetPath, content);
11
12// 检查文件是否存在
13if (fs.existsSync(targetDir)) { ... }
14
fast-glob - 文件搜索
1import glob from 'fast-glob';
2
3// 读取所有模板文件(包括隐藏文件)
4const templateFiles = await glob('**/*', {
5  cwd: templateDir,
6  dot: true,  // 包括 .gitignore 等
7  ignore: ['**/node_modules/**', '**/.git/**']
8});
9

5. 模板引擎层【核心】

EJS - 模板渲染
1import ejs from 'ejs';
2
3// 模板数据
4const templateData = {
5  projectName: 'my-component',
6  hasLess: true,
7  hasTailwind: false,
8  year: 2024
9};
10
11// 渲染模板
12const content = await fs.readFile(sourcePath, 'utf8');
13const renderedContent = ejs.render(content, templateData);
14

EJS 模板示例

1// Button.tsx.ejs
2<% if (hasCssModules) { %>
3import styles from './Button.module.css';
4<% } else if (hasLess) { %>
5import './Button.less';
6<% } else if (hasTailwind) { %>
7// 使用Tailwind类名
8<% } %>
9
10export const Button: React.FC<ButtonProps> = ({ children }) => {
11  <% if (hasCssModules) { %>
12  return <button className={styles.button}>{children}</button>;
13  <% } else if (hasTailwind) { %>
14  return <button className="bg-blue-500 text-white">{children}</button>;
15  <% } %>
16};
17

6. 子进程管理

execa - 执行外部命令
1import { execa } from 'execa';
2
3// 初始化 Git
4await execa('git', ['init'], { cwd: targetDir });
5

实现原理深度解析【核心】

1. 可执行入口实现

package.json 配置
1{
2  "name": "objectx-cli",
3  "type": "module",
4  "bin": {
5    "objectx-cli": "./bin/index.js"
6  },
7  "files": ["bin", "lib", "templates"]
8}
9
  • bin 字段:指定命令名称和执行文件
  • type: "module":使用 ES Module
  • files 字段:指定发布到 npm 的文件
bin/index.js
1#!/usr/bin/env node
2
3import { createRequire } from 'module';
4const require = createRequire(import.meta.url);
5const pkg = require('../package.json');
6
7import '../lib/index.js';
8

关键点

  1. #!/usr/bin/env node:Shebang,告诉系统用 Node.js 执行
  2. createRequire:在 ESM 中使用 require 加载 JSON
  3. 引入编译后的 lib/index.js(不是源码 src/index.ts

2. 项目名称验证

1// src/utils/validate.ts
2
3export function validateProjectName(projectName: string): void {
4  // 1. 空值检查
5  if (!projectName) {
6    console.log(chalk.red('✖') + ' 项目名称不能为空');
7    process.exit(1);
8  }
9
10  // 2. 空格检查
11  if (projectName.trim() !== projectName) {
12    console.log(chalk.red('✖') + ' 项目名称不能以空格开头或结尾');
13    process.exit(1);
14  }
15
16  // 3. npm 包名验证
17  const npmNameValidation = validateNpmPackageName(projectName);
18  if (!npmNameValidation.validForNewPackages) {
19    npmNameValidation.errors.forEach(err => {
20      console.log('  ' + chalk.red('✖') + ' ' + err);
21    });
22    process.exit(1);
23  }
24
25  // 4. 保留关键字检查
26  const RESERVED_KEYWORDS = ['node_modules', 'favicon.ico', '.git'];
27  if (RESERVED_KEYWORDS.includes(projectName.toLowerCase())) {
28    console.log(chalk.red('✖') + ` 项目名称不能使用保留关键字`);
29    process.exit(1);
30  }
31}
32
33function validateNpmPackageName(name: string) {
34  const errors: string[] = [];
35
36  if (name.length > 214) {
37    errors.push('名称不能超过214个字符');
38  }
39
40  if (name.match(/^[._]/)) {
41    errors.push('名称不能以 . 或 _ 开头');
42  }
43
44  if (name.match(/[/\\]/)) {
45    errors.push('名称不能包含斜杠');
46  }
47
48  return {
49    validForNewPackages: errors.length === 0,
50    errors
51  };
52}
53

3. 目录覆盖处理

1// src/commands/create.ts
2
3const targetDir = path.join(process.cwd(), projectName);
4
5// 检查目录是否已存在
6if (fs.existsSync(targetDir)) {
7  const overwriteResult = await inquirer.confirm({
8    message: `目标目录 ${chalk.cyan(projectName)} 已存在。是否覆盖?`,
9    default: false
10  });
11  
12  if (!overwriteResult) {
13    console.log(chalk.red('✖') + ' 操作取消');
14    return;
15  }
16  
17  const spinner = ora(`正在删除 ${chalk.cyan(targetDir)}...`).start();
18  await fs.remove(targetDir);
19  spinner.succeed(`已删除 ${chalk.cyan(targetDir)}`);
20}
21
22await fs.ensureDir(targetDir);
23

安全性考虑

  • 先询问用户确认
  • 显示加载动画,提升体验
  • 使用 fs.remove 完全删除旧目录

模板系统设计【核心】

1. 模板目录结构

1templates/
2├── component-lib/              # 组件库模板
3│   ├── _gitignore.ejs         # .gitignore(以 _ 开头)
4│   ├── demo/                   # 示例应用(无 Storybook 时使用)
5│   │   ├── App.tsx.ejs
6│   │   ├── index.html.ejs
7│   │   └── main.tsx.ejs
8│   ├── pnpm-workspace.yaml.ejs
9│   ├── README.md.ejs
10│   ├── src/
11│   │   ├── components/
12│   │   │   ├── Button/
13│   │   │   │   ├── Button.tsx.ejs
14│   │   │   │   ├── Button.module.css.ejs
15│   │   │   │   ├── Button.module.less.ejs
16│   │   │   │   └── Button.stories.tsx.ejs
17│   │   │   └── Card/
18│   │   │       ├── Card.tsx.ejs
19│   │   │       └── Card.stories.tsx.ejs
20│   │   ├── index.ts
21│   │   ├── styles/
22│   │   │   └── tailwind.css.ejs
23│   │   └── types/
24│   │       └── css.d.ts
25│   ├── tailwind.config.js.ejs
26│   ├── tsconfig.json.ejs
27│   └── vite.config.ts.ejs
2829├── tool-lib/                   # 工具库模板
30│   ├── README.md.ejs
31│   ├── src/
32│   │   ├── index.ts
33│   │   └── utils/
34│   │       └── string.ts
35│   ├── tsconfig.json.ejs
36│   └── vite.config.ts.ejs
3738└── types-lib/                  # 类型库模板
39    ├── README.md.ejs
40    ├── src/
41    │   ├── index.ts
42    │   └── types/
43    │       ├── api.ts
44    │       └── common.ts
45    ├── tests/
46    │   └── api.test-d.ts
47    └── tsconfig.json.ejs
48

2. 特殊文件命名约定

隐藏文件处理
1// src/utils/generate.ts
2
3function getTargetPath(file: string, targetDir: string, options: ProjectOptions): string {
4  let filename = file;
5  
6  // 处理点文件(如 .gitignore)
7  // 模板中命名为 _gitignore.ejs,生成时转为 .gitignore
8  if (filename.startsWith('_')) {
9    filename = [`.${filename.slice(1)}`](https://xplanc.org/primers/document/zh/02.Python/EX.%E5%86%85%E5%BB%BA%E5%87%BD%E6%95%B0/EX.slice.md);
10  }
11  
12  return path.join(targetDir, filename);
13}
14

为什么这样做?

  • npm 发布时会忽略 .gitignore 文件
  • 使用 _gitignore 绕过这个限制
  • 生成项目时再重命名为 .gitignore

3. 条件文件生成

1function getTargetPath(file: string, targetDir: string, options: ProjectOptions): string {
2  // ...文件名处理...
3  
4  // 如果选择了Storybook,跳过demo目录
5  if (filename.startsWith('demo') && options.needDocs) {
6    return '';  // 返回空字符串表示跳过
7  }
8  
9  // 根据样式选择过滤文件
10  if (filename.endsWith('.less') && !options.styles.includes('less')) {
11    return '';
12  }
13  
14  if (filename.includes('tailwind') && !options.styles.includes('tailwind')) {
15    return '';
16  }
17  
18  // Card组件只在选择了tailwind时生成
19  if (filename.includes('components/Card') && !options.styles.includes('tailwind')) {
20    return '';
21  }
22  
23  return path.join(targetDir, filename);
24}
25

设计思想

  • 根据用户选择动态生成文件
  • 避免生成无用文件
  • 保持项目清爽

文件生成机制【核心】

1. 核心生成流程

1// src/utils/generate.ts
2
3export async function generateProject(
4  targetDir: string, 
5  options: ProjectOptions
6): Promise<void> {
7  
8  // 1. 定位模板目录
9  const templateDir = path.resolve(
10    __dirname,
11    '../../templates',
12    options.projectType
13  );
14
15  // 2. 确保模板目录存在
16  if (!fs.existsSync(templateDir)) {
17    throw new Error(`找不到模板目录:${templateDir}`);
18  }
19
20  // 3. 读取所有模板文件
21  const templateFiles = await glob('**/*', {
22    cwd: templateDir,
23    dot: true,
24    ignore: ['**/node_modules/**', '**/.git/**']
25  });
26
27  // 4. 准备模板数据
28  const templateData: TemplateData = {
29    projectName: options.projectName,
30    needDocs: options.needDocs,
31    hasLess: options.styles.includes('less'),
32    hasTailwind: options.styles.includes('tailwind'),
33    hasCssModules: options.styles.includes('css-modules'),
34    year: new Date().getFullYear(),
35    packageManager: options.packageManager
36  };
37
38  // 5. 遍历并处理每个文件
39  for (const file of templateFiles) {
40    const sourcePath = path.join(templateDir, file);
41    const targetPath = getTargetPath(file, targetDir, options);
42    
43    // 跳过不需要的文件
44    if (targetPath === '') continue;
45    
46    // 处理目录
47    if (fs.statSync(sourcePath).isDirectory()) {
48      await fs.ensureDir(targetPath);
49      continue;
50    }
51
52    // 读取文件内容
53    const content = await fs.readFile(sourcePath, 'utf8');
54    
55    // 6. EJS 模板渲染
56    if (file.endsWith('.ejs')) {
57      const renderedContent = ejs.render(content, templateData);
58      // 去掉 .ejs 后缀
59      await fs.outputFile(
60        targetPath.replace(/\.ejs$/, ''),
61        renderedContent
62      );
63    } else {
64      // 非模板文件直接复制
65      await fs.outputFile(targetPath, content);
66    }
67  }
68
69  // 7. 生成 package.json
70  await generatePackageJson(targetDir, options);
71}
72

2. package.json 动态生成

1async function generatePackageJson(
2  targetDir: string, 
3  options: ProjectOptions
4): Promise<void> {
5  
6  // 基础配置
7  const packageJson: any = {
8    name: options.projectName,
9    version: '0.1.0',
10    private: false,
11    scripts: {
12      dev: 'vite',
13      build: 'vite build && tsc --emitDeclarationOnly',
14      lint: 'eslint src --ext .ts,.tsx',
15      test: 'jest'
16    },
17    files: ['dist'],
18    devDependencies: {
19      "husky": "^9.0.7",
20      "typescript": "^5.2.2",
21      "eslint": "^8.52.0",
22      "terser": "^5.24.0"
23    }
24  };
25  
26  // 根据项目类型定制
27  switch (options.projectType) {
28    case 'component-lib':
29      // 组件库配置
30      packageJson.type = 'module';
31      packageJson.main = './dist/index.js';
32      packageJson.module = './dist/index.js';
33      packageJson.types = './dist/index.d.ts';
34      packageJson.exports = {
35        '.': {
36          import: './dist/index.js',
37          require: './dist/index.cjs'
38        }
39      };
40      
41      packageJson.peerDependencies = {
42        react: "^18.0.0",
43        "react-dom": "^18.0.0"
44      };
45      
46      packageJson.devDependencies = {
47        ...packageJson.devDependencies,
48        "vite": "^5.0.0",
49        "@vitejs/plugin-react": "^4.2.0",
50        "@types/react": "^18.2.0",
51        "@types/react-dom": "^18.2.0",
52        "react": "^18.2.0",
53        "react-dom": "^18.2.0",
54        "autoprefixer": "^10.4.16",
55        "postcss": "^8.4.31"
56      };
57      
58      // Storybook 支持
59      if (options.needDocs) {
60        packageJson.scripts.dev = 'storybook dev -p 6006';
61        packageJson.scripts.storybook = 'storybook dev -p 6006';
62        packageJson.scripts['build-storybook'] = 'storybook build';
63        
64        packageJson.devDependencies = {
65          ...packageJson.devDependencies,
66          "@storybook/addon-essentials": "^7.5.3",
67          "@storybook/react": "^7.5.3",
68          "@storybook/react-vite": "^7.5.3",
69          "storybook": "^7.5.3"
70        };
71      } else {
72        // 使用 demo 作为开发环境
73        packageJson.scripts.dev = 'vite demo --open';
74      }
75      
76      // 样式依赖
77      if (options.styles.includes('less')) {
78        packageJson.devDependencies.less = "^4.2.0";
79      }
80      
81      if (options.styles.includes('tailwind')) {
82        packageJson.devDependencies.tailwindcss = "^3.3.5";
83      }
84      break;
85      
86    case 'tool-lib':
87      // 工具库配置
88      packageJson.type = 'module';
89      packageJson.main = './dist/index.js';
90      packageJson.module = './dist/index.js';
91      packageJson.types = './dist/index.d.ts';
92      
93      packageJson.scripts.dev = 'vite build --mode watch';
94      packageJson.scripts.docs = 'typedoc --out docs src/index.ts';
95      
96      packageJson.devDependencies = {
97        ...packageJson.devDependencies,
98        "vite": "^5.0.0",
99        "typedoc": "^0.25.3"
100      };
101      break;
102      
103    case 'types-lib':
104      // 类型库配置
105      packageJson.scripts.build = 'tsc --emitDeclarationOnly';
106      packageJson.scripts.dev = 'tsc --emitDeclarationOnly --watch';
107      packageJson.scripts['test:types'] = 'tsc --noEmit';
108      
109      packageJson.main = './dist/index.d.ts';
110      packageJson.types = './dist/index.d.ts';
111      
112      packageJson.devDependencies = {
113        "husky": "^9.0.7",
114        "typescript": "^5.2.2",
115        "eslint": "^8.52.0",
116        "tsd": "^0.30.0",
117        "@types/node": "^20.8.10"
118      };
119      break;
120  }
121  
122  // 写入文件
123  await fs.writeFile(
124    path.join(targetDir, 'package.json'),
125    JSON.stringify(packageJson, null, 2)
126  );
127}
128

亮点

  • 动态配置:根据项目类型和用户选择生成不同的依赖和脚本
  • 双模式支持:同时支持 ESM 和 CommonJS
  • 按需加载:只添加用户选择的功能的依赖

打包与发布

1. Rollup 打包配置

1// rollup.config.js
2
3import resolve from '@rollup/plugin-node-resolve';
4import commonjs from '@rollup/plugin-commonjs';
5import typescript from '@rollup/plugin-typescript';
6import json from '@rollup/plugin-json';
7import terser from '@rollup/plugin-terser';
8
9export default {
10  // 入口文件
11  input: 'src/index.ts',
12  
13  // 输出配置
14  output: [{
15    dir: 'lib',              // 输出目录
16    format: 'esm',           // ES Module 格式
17    sourcemap: true,         // 生成 source map
18    preserveModules: true,   // 保留模块结构
19    entryFileNames: '[name].js'
20  }],
21  
22  // 外部依赖(不打包)
23  external: [
24    'fs', 'path', 'os', 'util', 'child_process',  // Node.js 内置模块
25    'commander', 'chalk', 'ora',                   // CLI 依赖
26    '@inquirer/prompts', 'fs-extra', 'ejs',       // 工具库
27    'fast-glob', 'execa', 'semver'
28  ],
29  
30  // 插件
31  plugins: [
32    resolve({
33      extensions: ['.ts', '.js']
34    }),
35    commonjs(),
36    json(),
37    typescript({
38      tsconfig: './tsconfig.json',
39      outputToFilesystem: true
40    }),
41    terser()  // 压缩代码
42  ]
43};
44

为什么使用 Rollup?

  • Tree Shaking:更好的死代码消除
  • 模块保留preserveModules: true 保持源码结构
  • 更小的包体积:相比 Webpack 更适合库的打包

2. TypeScript 配置

1{
2  "compilerOptions": {
3    "target": "ES2020",
4    "module": "NodeNext",           // Node.js ESM 支持
5    "moduleResolution": "NodeNext",
6    "esModuleInterop": true,
7    "strict": true,
8    "declaration": true,            // 生成 .d.ts
9    "resolveJsonModule": true,
10    "skipLibCheck": true,
11    "outDir": "lib",                // 输出到 lib 目录
12    "baseUrl": ".",
13    "paths": {
14      "@/*": ["src/*"]
15    }
16  },
17  "include": ["src/**/*", "bin/**/*"],
18  "exclude": ["node_modules", "src/templates/**/*"]
19}
20

3. npm 发布配置

package.json 关键字段
1{
2  "name": "objectx-cli",
3  "version": "0.1.0",
4  "type": "module",
5  
6  "bin": {
7    "objectx-cli": "./bin/index.js"
8  },
9  
10  "files": [
11    "bin",
12    "lib",
13    "templates"
14  ],
15  
16  "scripts": {
17    "build": "rollup -c",
18    "release": "bumpp && npm publish"
19  },
20  
21  "keywords": [
22    "cli", "react", "typescript", "vite",
23    "component-library", "tooling"
24  ],
25  
26  "engines": {
27    "node": ">=16.0.0"
28  }
29}
30

关键点解析

  1. files 字段
    • 只发布 binlibtemplates 目录
    • 不发布源码 src/,减小包体积
  2. engines 字段
    • 限制 Node.js 版本 >= 16
    • 确保 ESM 特性可用
  3. keywords 字段
    • 提升 npm 搜索排名
发布流程
1# 1. 构建
2pnpm build
3
4# 2. 版本管理(使用 bumpp)
5pnpm release
6
7# bumpp 会自动:
8# - 提示选择版本号(patch/minor/major)
9# - 更新 package.json
10# - 创建 git tag
11# - 推送到远程
12# - 发布到 npm
13

后续

好了,这就是一个完整的企业级脚手架搭建流程,后续我还会介绍webpack、vite的实现原理,eslint插件开发,babel实现原理,babel插件开发,感兴趣的可以关注下


【前端工程化】脚手架篇 - 模板引擎 & 动态依赖管理脚手架》 是转载文章,点击查看原文


相关推荐


在AI技术快速实现创意的时代,挖掘游戏开发框架新需求成为关键
qife1222025/10/6

内容描述 核心功能定位:该项目是一个专为经典游戏主机设计的增强型JavaScript运行环境,旨在为用户提供完整的自制软件开发工具包。它通过简化开发流程,让开发者能够使用JavaScript这一简单语言创建游戏和应用程序,无需编译过程,只需编写脚本即可快速测试。 关键应用场景:主要应用于经典游戏主机的自制软件开发,包括游戏创作、应用程序开发、图形渲染、音频处理、网络通信等多个领域。开发者可以利用该环境快速原型设计和开发各类交互式内容。 功能特性 多模块支持:系统提供丰富的功能模块,


【Matlab】matlab代码实现最小凸包
智慧浩海2025/10/4

实现最小凸包的算法有多种方法,其中一种常见的方法是使用Graham扫描算法。下面是用Matlab实现Graham扫描算法找到最小凸包的示例代码: function [convexHull] = grahamScan(points) % 找到包含所有点的最小凸包 n = length(points); % 找到y坐标最小的点作为起始点 [~,idx] = min(points(:,2)); startPoint = points(idx,:);


【深入浅出PyTorch】--3.1.PyTorch组成模块1
西柚小萌新吖(●ˇ∀ˇ●)2025/10/3

通过本节学习,你将掌握: 机器学习/深度学习任务的整体流程。各个阶段在任务中的作用与实现方式。深度学习与传统机器学习在实现上的关键差异。PyTorch 如何支持深度学习任务的模块化实现。 二、机器学习任务的标准流程 步骤内容说明1. 数据预处理- 统一数据格式 - 清除异常值 - 进行必要的数据变换(如归一化、标准化) - 划分数据集:训练集、验证集、测试集 - 常见方法:按比例随机划分、KFold 交叉验证 - 工具支持:sklearn.model_selection.train_t


PlantUML 完整教程:从入门到精通
对不起初见2025/10/2

什么是 PlantUML PlantUML 是一个开源工具,允许用户使用简单直观的文本描述来快速创建 UML 图表。它基于纯文本语法,能够生成多种类型的图表,包括时序图、用例图、类图、活动图、组件图、状态图等。 PlantUML 的核心理念是:用代码画图,让图表版本可控。 核心特点 • 文本驱动:使用简单的文本语法描述图表 • 版本控制友好:纯文本格式可轻松集成到 Git 等版本控制系统 • 多格式输出:支持 PNG、SVG、PDF、LaTeX 等多种输出格式 • 跨平台:基于 Java,可在


第6篇、Flask 表单处理与用户认证完全指南:从零到实战
何双新2025/10/2

标签:Python Flask Web开发 表单验证 Session Cookie 用户认证 安全编程 🎯 为什么选择这篇文章? 在Web开发的世界里,表单处理和用户认证是每个开发者必须掌握的核心技能。无论是构建电商网站、社交平台还是企业管理系统,都离不开用户登录、数据提交、状态保持这些基础功能。 Flask作为Python最轻量级的Web框架,以其简洁优雅的设计理念,让开发者能够快速构建功能完整的Web应用。本文将带你从零开始,深入理解Flask的表单处理机制,掌握Session和


微服务架构:从单机到分布式的革命性升级
chengooooooo10/2/2025

本文探讨了传统单机服务的缺陷及微服务架构的解决方案。单机模式存在单点故障、扩展性差、强耦合、技术栈受限等问题。微服务通过将系统拆分为独立服务,实现解耦、独立部署、技术多样性,并提供了分布式架构下的解决方案。SpringCloud提供了服务熔断、限流、服务注册与发现、配置中心、API网关等核心组件,同时介绍了分布式追踪、消息队列和分布式事务的实现方式。相比单机部署,微服务利用云计算实现动态资源分配和自动化管理,提高了系统的可用性和扩展性。


Excel文件瘦身指南:快速瘦身,告别卡顿-Excel易用宝
Excel_easy10/1/2025

明明工作簿文件中没有几行数据,打开表格的时候要等好一会儿,写个VLOOKUP函数公式鼠标指针都要转半天。其实这是由于文件中积累着各种冗余的信息导致的表格体积变大,计算卡顿等现象。单击【易用宝】→【工作簿】→【Excel文件瘦身】。表格文件超大,打开卡顿,闪烁,这种慢慢慢,烦人!我们只需给表格文件瘦个身就ok了。你有没有遇到过这种情况呢?


AI大模型学习(14)AI 小白入门!用 OpenCV+Python 打造人体姿态识别系统,超详细教程来了
icoder8889/30/2025

这篇教程介绍了如何使用Python+OpenCV+MediaPipe搭建入门级人体姿态识别系统。主要内容包括:人体姿态识别的应用场景(如健身APP、体感游戏)、开发环境搭建(安装Python、OpenCV和MediaPipe库)、核心代码实现(通过30行代码完成摄像头姿态检测),以及进阶功能开发(深蹲动作纠正算法和PPT体感控制器)。教程详细解析了MediaPipe的BlazePose模型架构和33个关键点的生理学意义,并提供了多线程优化等性能提升方案。该项目适合AI初学者实践,无需复杂算法即可实现实时人体


OSI 七层模型
日更嵌入式的打工靓仔2025/10/9

一、OSI 七层模型的核心定位与价值​ OSI 七层模型(Open Systems Interconnection Reference Model)是国际标准化组织(ISO)于 1984 年制定的网络通信体系结构标准,其核心目标是打破不同厂商设备的通信壁垒,通过分层化设计实现 “功能解耦、接口标准化”。该模型将网络通信的复杂流程拆解为七个逻辑层次,每层通过定义明确的 “服务原语”(Service Primitive)为上层提供服务,并通过 “协议数据单元(PDU)” 与下层交互,确保不同系统间


【Node】认识一下Node.js 中的 VM 模块
你的人类朋友2025/10/10

前言 今天介绍 Node.js 中的 VM(Virtual Machine)模块的基本概念和使用方法。 很多人不太了解他,比如在下 所以本文也不会过于深入,会偏向入门! 小目标:看完之后向自己解释一下:啥是 VM 模块?它有什么作用? 什么是 VM 模块 VM 模块是 Node.js 内置的模块,用于在 V8 虚拟机上下文中编译和执行 JavaScript 代码。 说人话就是,VM 模块允许你在隔离的环境中运行 JavaScript 代码。 核心功能 这边用代码进行举例子,后面会介绍具体的使用

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0