一步一步打造属于自己的脚手架

【高频考点精讲】前端脚手架开发指南,如何打造自己的create-react-app? 🧑‍🏫:全栈老李📅:2025 年 5 月🧑‍💻:前端初学者、进阶开发者🚀:本文由全栈老李原创,转载请注明出处。最近在后台收到不少同学提问:"老李啊,现在各种脚手架用起来是方便,但总感觉黑盒操作心里没底,能不能教我们从头撸一个?" 这不,今天咱们就来手把手实现一个极简版create-react-app,让你彻底搞懂脚手架背后的魔法。(全栈老李出品,盗版必究) 阅读详情

预备知识

  • 本地安装卸载包

    在项目根目录下运行下面命令

    # 安装
    npm link
    
    # 卸载
    npm unlink
    
  • 命令行命令指定运行文件

    package.json文件中bin字段指定

项目依赖包

  • chalk 打印出彩色字

  • commander 完整的 node.js 命令行解决方案

  • download-git-repo 下载仓库代码

  • handlebars 编译文件

  • inquirer 交互式命令行界面

  • ora 加载动画

  • update-notifier 更新通知

  • figlet 炫酷文字

    字体示例

支持的命令

da --help
da -version
da update
da add
da delete
da list
da create
da site add
da site delete
da site list

命令详情介绍以及项目地址点击传送门

动起来

1. 新建文件夹da-cli
2. 打开命令行工具,进入文件夹,运行初始化仓库
cd ./da-cli
npm init -y
3. 安装依赖
npm i chalk commander download-git-repo handlebars inquirer ora update-notifier figlet -S
  • handlebars 不是一定要用,可以使用node读写文件,本项目使用是多介绍一种修改文件内容方案,项目中会使用两种:原生node读写文件、使用handlebars修改文件内容。
  • figlet 不是一定要用,主要为了炫酷,装一下
  • ora 不是一定要用,为了体验优化
4. 修改package.json
  • 脚手架会有多个命令,每个命令会有对应的处理逻辑,一般是会分模块写,这样增加可读性、方便维护

  • 命令执行文件,可以由命令根据已经配置的一个文件自己匹配文件,也可以自己注册

    例如: da init和da add两个命令(da是自定义的,必须和bin中键保持一致)

    匹配文件模式:(自己编的模式)

    // package.json文件
    {
        ...
        "bin": {
            "da": "./bin/da.js"
        },
        ...
    }
    

    ./bin/da.js文件中不会写 da init和da add两个命令处理逻辑

    当运行da init时,会去bin文件夹下面找da-init.js文件(也可以在bin中增加da-init键指定执行文件)

    当运行da add时,会去bin文件夹下面找da-add.js文件

    自己注册模式:(自己编的模式)

    // package.json文件
    {
        ...
        "bin": {
            "da": "./bin/index.js"
        }
        ...
    }
    

    ./bin/index.js文件中会写 da init和da add两个命令处理逻辑

    本项目采用自己注册模式

  • package.json内容

    {
      "name": "da-cli",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "bin": {
        "da": "./bin/index.js"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
    
5. 新建文件夹da-cli/bin和da-cli/lib,新建文件da-cli/bin/index.js
6. 编写da-cli/bin/index.js,首先处理兼容问题
#!/usr/bin/env node 
  • 惊叹、有病,环境是node方便记忆
7.实现da -version,编写da-cli/bin/index.js
#!/usr/bin/env node

const program = require('commander')

program.name('da').version(require('../package').version, '-v -version', 'output the current version').usage('<command> [options]')

program.parse(process.argv)
  • program.parse(process.argv)必须有这个,一直放到最后一行用于解析命令行参数

  • <>包裹的参数一般是必传的

  • .name('da')可以去除

  • 此时可以使用以下命令(执行命令前,根项目下运行npm link,这样可以全局使用)

    da -v
    da -version
    
    da -h
    da -help
    
8. 实现da update

新建编写da-cli/lib/update/update.js

const updateNotifier = require('update-notifier')
const chalk = require('chalk')
const pkg = require('../../package.json')

const notifier = updateNotifier({
	pkg,
	updateCheckInterval: 1000,
})

function updateVer() {
	if (notifier.update) {
		console.log(`New version ${chalk.green(notifier.update.latest)} found, please update`)
		notifier.notify()
	} else {
		console.log('There is no updatable version')
	}
}

module.exports = updateVer

编写da-cli/bin/index.js

#!/usr/bin/env node

const program = require('commander')

program.name('da').version(require('../package').version, '-v -version', 'output the current version').usage('<command> [options]')

// update
program
	.command('update')
  .alias('u')
	.description('update version')
	.action(() => {
		updateVer()
	})

program.parse(process.argv)
9. 增加配置文件,方便下面命令实现

新增文件夹da-cli/config

增加da-cli/config/codebase.json

// 存放模板仓库
{
  "loading": {
    "tplOwner": "lydxwj", // 项目仓库所有者
    "tplName": "vue-loading", // 项目仓库名
    "site": "gitee" // 项目仓库地址类型
  }
}

增加da-cli/config/config.json

{
  "sites": { // 全部项目仓库地址类型,支持用户自定义扩展
    "github": {
      "name": "github",
      "value": "github",
      "short": "github",
      "url": "github:{tplOwner}/{tplName}"
    },
    "gitlab": {
      "name": "gitlab",
      "value": "gitlab",
      "short": "gitlab",
      "url": "gitlab:{tplOwner}/{tplName}"
    },
    "gitee": {
      "name": "gitee",
      "value": "gitee",
      "short": "gitee",
      "url": "direct:https://gitee.com/{tplOwner}/{tplName}"
    }
  },
  "frames": { // 创建项目框架选项,以及对应url,不支持用户自定义扩展
    "vue": {
      "url": "direct:https://gitee.com/lydxwj/vue-create"
    }
  }
}
10. 实现da add(增加模板)

新增文件夹da-cli/lib/template

增加da-cli/lib/template/add.js

const inquirer = require('inquirer')
const chalk = require('chalk')
const fs = require('fs')
// 现有模板,用于判断是否重复添加和新增后的数据拼接
const codeBase = require(`${__dirname}/../../config/codebase`)
// 用于生成模板URL地址类型选择
const sites = require(`${__dirname}/../../config/config`).sites

// 生成模板URL地址类型
const sitesChoices = [];
Object.keys(sites).forEach(item => {
  sitesChoices.push(sites[item]);
})
function add() {
  // 交互式命令
  let question = [
    {
      name: "name",
      type: 'input',
      message: "请输入模板名称(英文字母)",
      validate (val) {
        if (val === '') {
          return 'Name is required!'
        } else if (!/^[a-zA-Z]+$/.test(val)) {
          return 'It can only contain English letters!'
        } else if (codeBase[val]) {
          return 'Template has already existed!'
        } else {
          return true
        }
      }
    },
    {
      type: 'list',
      message: '请选择模板仓库地址类型:',
      name: 'site',
      choices: sitesChoices,
    },
    {
      name: "tplOwner",
      type: 'input',
      message: "请输入仓库所属",
      validate (val) {
        if (val === '') return 'The tplOwner is required!'
        return true
      }
    },
    {
      name: "tplName",
      type: 'input',
      message: "请输入仓库名",
      validate (val) {
        if (val === '') return 'The tplName is required!'
        return true
      }
    }
  ]
  
  inquirer
    .prompt(question).then(answers => {
      let { name, tplOwner, tplName, site } = answers;
      // 拼接增加新的模板
      codeBase[name] = {
        tplOwner,
        tplName,
        site
      };
      // 写文件,更新模板
      fs.writeFile(`${__dirname}/../../config/codebase.json`, JSON.stringify(codeBase, null, 2), 'utf-8', err => {
        if (err) {
          console.log(chalk.red('Error\n'))
          console.log(err)
          return
        }
        console.log('\n')
        console.log(chalk.green('Added successfully!\n'))
        console.log(chalk.grey('The latest template list is: \n'))
        // 更新成功并打印最新的模板列表
        Object.keys(codeBase).forEach(item => {
          console.log(chalk.green('   ' + item + ':  \n'));
          console.log('     仓库地址类型:' + sites[codeBase[item].site].name + ' \n');
          console.log('     仓库所属:' + codeBase[item].tplOwner + ' \n');
          console.log('     仓库名:' + codeBase[item].tplName + ' \n');
        })
        console.log('\n')
      })
    })
}
module.exports = add

编写da-cli/bin/index.js

...
const tplAdd = require('../lib/template/add')

...
// add a new template
program
  .command('add')
  .description('add a new template')
  .alias('a')
  .action(() => {
    tplAdd()
  })
...
11. 实现da delete(删除模板)

本命令的实现和上一个十分类似,只是把变量增加键值对变成删除键

增加da-cli/lib/template/delete.js

const inquirer = require('inquirer')
const chalk = require('chalk')
const fs = require('fs')
const codeBase = require(`${__dirname}/../../config/codebase`)
const sites = require(`${__dirname}/../../config/config`).sites
function tplDelete() {
  let question = [{
    name: "name",
    message: "请输入要删除的模板名称",
    validate(val) {
      if (val === '') {
        return 'Name is required!'
      } else if (!codeBase[val]) {
        return 'Template does not exist!'
      } else {
        return true
      }
    }
  }]
  
  inquirer
    .prompt(question).then(answers => {
      let {
        name
      } = answers;
      delete codeBase[name]
      fs.writeFile(`${__dirname}/../../config/codebase.json`, JSON.stringify(codeBase, null, 2), 'utf-8', err => {
        if (err) {
          console.log(chalk.red('Error\n'))
          console.log(err)
          return
        }
        console.log('\n')
        console.log(chalk.green('Deleted successfully!\n'))
        console.log(chalk.grey('The latest template list is: \n'))
        Object.keys(codeBase).forEach(item => {
          console.log(chalk.green('   ' + item + ':  \n'));
          console.log('     仓库地址类型:' + sites[codeBase[item].site].name + ' \n');
          console.log('     仓库所属:' + codeBase[item].tplOwner + ' \n');
          console.log('     仓库名:' + codeBase[item].tplName + ' \n');
        })
        console.log('\n')
      })
    })
}

module.exports = tplDelete

编写da-cli/bin/index.js

...
const tplDelete = require('../lib/template/delete')

...
// delete a template
program
  .command('delete')
  .description('delete a template')
  .alias('d')
  .action(() => {
    tplDelete()
  })
...
12. 实现da list(模板列表)

此功能在增加和删除命令中已经实现

增加da-cli/lib/template/list.js

const chalk = require('chalk')
const codeBase = require(`${__dirname}/../../config/codebase`)
const sites = require(`${__dirname}/../../config/config`).sites
function tplList() {
  Object.keys(codeBase).forEach(item => {
    console.log(chalk.green('   ' + item + ':  \n'));
    console.log('     仓库地址类型:' + sites[codeBase[item].site].name + ' \n');
    console.log('     仓库所属:' + codeBase[item].tplOwner + ' \n');
    console.log('     仓库名:' + codeBase[item].tplName + ' \n');
  })
}
module.exports = tplList

编写da-cli/bin/index.js

...
const tplList = require('../lib/template/list')

...
// list all the templates
program
  .command('list')
  .description('list all the templates')
  .alias('l')
  .action(() => {
    tplList()
  })
...
13. 实现da init(初始化项目)

有了模板,现在就是用起来

增加da-cli/lib/template/init.js

const program = require('commander')
const chalk = require('chalk')
const ora = require('ora')
const download = require('download-git-repo')
const inquirer = require('inquirer')
const path = require('path')
const fs = require('fs')
const codeBase = require(`${__dirname}/../../config/codebase`)
const sites = require(`${__dirname}/../../config/config`).sites

const promptList = [
  {
    type: 'input',
    message: '请输入项目简介',
    name: 'description',
    default: '项目简介'
  },
  {
    type: 'input',
    message: '请输入项目版本',
    name: 'version',
    default: '1.0.0'
  }
]
// 读写文件,修改package的name,description,version
function setProjectDescription(projectName, options) {
  const packagePath = `${projectName}/package.json`
  const packageJson = fs.readFileSync(packagePath, 'utf-8')
  const packageResult = JSON.stringify(Object.assign({}, JSON.parse(packageJson), options), null, 2)
  fs.writeFileSync(packagePath, packageResult)
}
function tplInit(templateName, projectName) {
  // 判断模板是否存在
  if (!codeBase[templateName]) {
    console.log(chalk.red('\n Template name does not exit! \n '))
    return;
  }
  if (!projectName) {
    console.log(chalk.red('\n Project name should not be empty! \n '))
    return program.help();
  }
  if (program.args.length < 2) return program.help()
  // 判断是否已经存在这个文件夹,存在则报错(不判断会造成仓库代码下载不成功)
  try {
    const stat = fs.statSync(path.resolve('./', projectName));
    if (stat.isDirectory()) {
      console.log(chalk.red('\n The folder already exists! Please change the project name! \n '))
      return;
    };
  } catch (err) {}
  const tplObj = codeBase[templateName];
  if (!sites[tplObj.site]) {
    console.log(chalk.red('\n Code base address type does not exist! \n '))
    return;
  }
  // 拼接仓库地址
  let url = sites[tplObj.site].url;
  url = url.replace('{tplOwner}', tplObj.tplOwner);
  url = url.replace('{tplName}', tplObj.tplName);
  const spinner = ora("Downloading...");
  inquirer.prompt(
    promptList
  ).then(answers => {
    console.log(chalk.white('\n Start generating... \n'))
    spinner.start()
    // 下载仓库代码
    download(
      url,
      projectName,
      {
        clone: url.startsWith('direct:') ? true : false,
      },
      err => {
        if (err) {
          spinner.fail()
          console.log(chalk.red(`Generation failed. ${err}`))
          return
        }
        const { description, version } = answers
        setProjectDescription(projectName, { name: projectName, description, version })
        spinner.succeed()
        console.log(chalk.cyan('\n Generation completed!'))
        console.log(chalk.cyan('\n To get started'))
        console.log(chalk.cyan(`\n    cd ${projectName}`))
        console.log(chalk.cyan(`\n    npm install \n`))
      }
    )
  });
}
module.exports = tplInit

编写da-cli/bin/index.js

...
const tplInit = require('../lib/template/init')

...
// generate a new project from a template
program
  .command('init')
  .arguments('<templateName> <projectName>')
  .description('generate a new project from a template', {
    templateName: 'existing template name',
    projectName: 'folder that does not exist'
  })
  .alias('i')
  .action((templateName, projectName) => {
    tplInit(templateName, projectName)
  })
...
14. 实现da site list(URL地址类型列表),da site add(URL地址类型增加),da site delete(URL地址类型删除)

因为上面已经实现了类似的,这里就一次搞定三个

新建文件夹da-cli/lib/site

增加da-cli/lib/site/list.js

const chalk = require('chalk')
const sites = require(`${__dirname}/../../config/config`).sites
function siteList() {
  Object.keys(sites).forEach(item => {
    console.log(chalk.green('   ' + item + ':  \n'));
    console.log('     仓库地址类型名:' + sites[item].name + ' \n');
    console.log('     仓库地址URL:' + sites[item].url + ' \n');
  })
  console.log('\n')
}
module.exports = siteList

增加da-cli/lib/site/add.js

const chalk = require('chalk')
const inquirer = require('inquirer')
const fs = require('fs')
const configJson = require(`${__dirname}/../../config/config`)
const sites = configJson.sites
function siteAdd() {
  let question = [
    {
      name: "siteName",
      type: 'input',
      message: "请输入仓库地址类型名",
      validate (val) {
        if (val === '') {
          return 'siteName is required!'
        } else {
          return true
        }
      }
    },
    {
      name: "siteKey",
      type: 'input',
      message: "请输入存储仓库地址类型的键(key)",
      validate (val) {
        if (val === '') return 'siteKey is required!'
        if (sites[val]) return 'siteKey has already existed!'
        if (!/^[a-zA-Z]+$/.test(val)) {
          return 'It can only contain English letters!'
        }
        return true
      }
    },
    {
      name: "siteUrl",
      type: 'input',
      message: "请输入仓库地址URL",
      suffix: '例如:https://my.sitecode.com',
      validate (val) {
        if (val === '') return 'The siteUrl is required!'
        if (!/(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/.test(val)) return 'Please enter the correct URL!'
        return true
      }
    }
  ]
  inquirer.prompt(question).then(answers => {
    let { siteName, siteKey, siteUrl } = answers;
    const newSite = {
      name: siteName,
      value: siteKey,
      short: siteName,
      url: 'direct:' + siteUrl + '/{tplOwner}/{tplName}.git'
    }
    sites[siteKey] = newSite
    fs.writeFile(`${__dirname}/../../config/config.json`, JSON.stringify({ ...configJson, sites }, null, 2), 'utf-8', err => {
      if (err) {
        console.log(chalk.red('Error\n'))
        console.log(err)
        return
      }
      console.log('\n')
      console.log(chalk.green('Added successfully!\n'))
      console.log(chalk.grey('The latest sites list is: \n'))
      Object.keys(sites).forEach(item => {
        console.log(chalk.green('   ' + item + ':  \n'));
        console.log('     仓库地址类型名:' + sites[item].name + ' \n');
        console.log('     仓库地址URL:' + sites[item].url + ' \n');
      })
      console.log('\n')
    })
  })
}
module.exports = siteAdd

增加da-cli/lib/site/delete.js

const chalk = require('chalk')
const inquirer = require('inquirer')
const fs = require('fs')
const configJson = require(`${__dirname}/../../config/config`)
const sites = configJson.sites
function siteDelete() {
  let question = [
    {
      name: "siteKey",
      type: 'input',
      message: "请输入要删除的存储仓库地址类型键(key)",
      validate (val) {
        if (val === '') {
          return 'siteKey is required!'
        } else if (!sites[val]) {
          return 'siteKey does not exist!'
        } else if (val == 'github' || val == 'gitlab' || val == 'gitee') {
          return 'no permission!'
        }
        return true
      }
    },
  ]
  inquirer.prompt(question).then(answers => {
    let { siteKey } = answers;
    delete sites[siteKey]
    fs.writeFile(`${__dirname}/../../config/config.json`, JSON.stringify({ ...configJson, sites }, null, 2), 'utf-8', err => {
      if (err) {
        console.log(chalk.red('Error\n'))
        console.log(err)
        return
      }
      console.log('\n')
      console.log(chalk.green('Deleted successfully!\n'))
      console.log(chalk.grey('The latest sites list is: \n'))
      Object.keys(sites).forEach(item => {
        console.log(chalk.green('   ' + item + ':  \n'));
        console.log('     仓库地址类型名:' + sites[item].name + ' \n');
        console.log('     仓库地址URL:' + sites[item].url + ' \n');
      })
      console.log('\n')
    })
  })
}
module.exports = siteDelete

编写da-cli/bin/index.js

...
const siteList = require('../lib/site/list')
const siteAdd = require('../lib/site/add')
const siteDelete = require('../lib/site/delete')

...
// 由于是两个指令参数,所以要配合addCommand
// configure site
function makeSiteCommand() {
  const site = new program.Command('site');

  // list all the sites
  site
    .alias('s')
    .usage('<command>')
    .command('list')
    .alias('l')
    .description('list all the sites')
    .action(() => {
      siteList()
    });

  // add a new site
  site
    .command('add')
    .alias('a')
    .description('add a new site')
    .action(() => {
      siteAdd()
    });

  // delete a site
  site
    .command('delete')
    .alias('d')
    .description('delete a site')
    .action(() => {
      siteDelete()
    });
  return site;
}
program.addCommand(makeSiteCommand())
...
15. 实现da create

这是最后一个命令,是定制化的生成项目,需要自己建一个用于生成定制的项目。本项目使用的是https://gitee.com/lydxwj/vue-create,没有什么特别的只是需要修改的文件含有模板引擎的代码,所以本命令使用了模板引擎插件,并且使用了figlet 炫酷文字。

新建文件夹da-cli/lib/create

增加da-cli/lib/create/create.js

const program = require('commander')
const chalk = require('chalk')
const figlet = require('figlet')
const inquirer = require('inquirer')
const ora = require('ora')
const download = require('download-git-repo')
const handlebars = require('handlebars')
const fs = require('fs')
const path = require('path')
const frames = require(`${__dirname}/../../config/config`).frames

// 生成框架选择数组
const framesChoices = [];
Object.keys(frames).forEach(item => {
  framesChoices.push(item);
})

const questions = [
  {
    type: 'list',
    message: '请选择框架:',
    name: 'frame',
    choices: framesChoices,
  },
  {
    type: 'input',
    message: '请输入项目简介',
    name: 'description',
    default: '项目简介'
  },
  {
    type: 'input',
    message: '请输入项目版本',
    name: 'version',
    default: '1.0.0'
  }
]

// 修改文件
function renderFiles(targetPath, globalData) {
  // 需要修改的文件列表
  const fileList = [
    `${targetPath}/package.json`,
    `${targetPath}/README.md`,
  ]
  return new Promise(async (resolve, reject) => {
    for (let i = 0; i < fileList.length; i++) {
      try {
        const fileContent = await fs.readFileSync(fileList[i], 'utf8')
        // 使用模板引擎编译文件
        const fileRendered = await handlebars.compile(fileContent)(globalData)
        await fs.writeFileSync(fileList[i], fileRendered)
        resolve()
      } catch (err) {
        chalk.red('\n craete project failed. \n')
        chalk.red(`\n ${err} \n`)
        reject(err)
      }
    }

  })
}

function createPro(projectName) {
  if (!projectName) {
    console.log(chalk.red('\n Project name should not be empty! \n '))
    return program.help()
  }
  if (program.args.length < 1) return program.help()
  try {
    const stat = fs.statSync(path.resolve('./', projectName));
    if (stat.isDirectory()) {
      console.log(chalk.red('\n The folder already exists! Please change the project name! \n '))
      return;
    };
  } catch (err) {}
  // 生成炫酷文字
  const text = figlet.textSync('da cli', {
    font: 'isometric1',
    horizontalLayout: 'default',
    verticalLayout: 'default',
    width: 80,
    whitespaceBreak: true
  })
  console.log(chalk.green(text));
  const spinner = ora("Generating...");
  inquirer.prompt(questions).then(answers => {
    console.log(chalk.white('\n Start generating... \n'))
    spinner.start()
    let { frame, description, version } = answers;
    const url = frames[frame].url;

    download(
      url,
      projectName,
      {
        clone: url.startsWith('direct:') ? true : false,
      },
      err => {
        if (err) {
          spinner.fail()
          console.log(chalk.red(`Generation failed. ${err}`))
          return
        }
        renderFiles(projectName, { projectName, description, version }).then(() => {
          spinner.succeed()
          console.log(chalk.cyan('\n Generation completed!'))
          console.log(chalk.cyan('\n To get started'))
          console.log(chalk.cyan(`\n    cd ${projectName}`))
          console.log(chalk.cyan(`\n    npm install \n`))
        }).catch(() => {
          process.exit()
        })
      }
    )
  })
}
module.exports = createPro

编写da-cli/bin/index.js

...
const createPro = require('../lib/create/create')

...
// generate an official project
program
  .command('create')
  .arguments('<projectName>')
  .description('generate an official project', {
    projectName: 'folder that does not exist'
  })
  .alias('c')
  .action((projectName) => {
    createPro(projectName)
  })
...
16. 最后增加帮助附加

编写da-cli/bin/index.js

...
program.addHelpText('after', `

Example call:
  $ da --help
  $ da -version
  $ da update
  $ da add
  $ da delete
  $ da list
  $ da create
  $ da site add
  $ da site delete
  $ da site list`);
...

完结

以上是整个项目的实现步骤,代码中有少量注释,没有具体详细讲解,如果有什么不懂得欢迎关注公众号询问。

在这里插入图片描述

打造专属 React 脚手架:从 0 到 1 开发 CLI 工具 本文介绍如何开发一个React脚手架CLI工具,实现一键生成标准化项目模板。通过commander解析命令、download-git-repo拉取GitHub模板、child_process执行终端命令,实现"拉取模板+初始化Git+自动安装依赖"全流程。核心功能包括:定义create命令下载模板、自动执行git init和npm install、提供友好提示。文章还介绍了本地测试、发布到npm的方法,并建议扩展多模板选择、错误处理等优化方向。该工具可帮助团队统一技术栈,提升开发效率,实 阅读详情

相关推荐

nuxt-vue-multiselect:使用vue-multiselect的Nuxt.js的单选选择插件

nuxt-vue-multiselect 利用 。 设置 将带有yarn或npm的nuxt-vue-multiselect依赖项添加到您的项目中 添加nuxt-vue-multiselect到modules的部分nuxt.config.js 配置它: { modules : [ 'nuxt-vue-multiselect' ] } 发展 克隆此存储库 使用yarn install或npm install安装依赖项 使用npm run dev启动开发服务器 执照 版权所有

学习笔记-前端脚手架cli实现

学习笔记-前端脚手架(cli)实现 commander.js,可以自动的解析命令和参数,用于处理用户输入的命令。 download-git-repo,下载并提取 git 仓库,用于下载项目模板。 inquirer.js,通用的命令行用户界面集合,用于和用户进行交互。 handlebars.js,模板引擎,将用户提交的信息动态填充到文件中。 ora,下载过程久的话,可以用于显示下载中的动画效果。 chalk,可以给终端的字体加上颜色。 log-symbols,可以在终端上显示出 √ 或 × 等的图标。 ——

jiao512的专栏 270

私彩视频开奖动画直播源码内部核心技术解析及修改可增加后台操控-持续更新文章!

其实在此很感谢能做出H5开奖系统的码农们,而我本人也喜欢分解此类的东西。 因为特殊原因,该东西本人就不做图片展示,以防他人商业化。 借用开奖摇奖代码首先分解首页index文件。代码如下:(后面持续在原有的作品上添加后台操作) 代码里写的//.....是我的注明代码的所需。 ​ &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;...

yugua3837的博客 5995

从零开始搭建一个自己的前端脚手架(一):基础篇

文章目录一、前期基础知识1.1 脚手架本质1.2 脚手架工作流程1.3 依赖工具库二、实现输入命令解析三、实现从输入到下载模板的过程四、实现模板替换 一、前期基础知识 1.1 脚手架本质 1.2 脚手架工作流程 解析用户命令—》 commander 根据用户输入替换内容 —》 metalsmith, handlebars 模板 拉取远端模板 —》 download-git-repo 文件操作 —》增删改 fs-extra 结束初始化 1.3 依赖工具库 commander 用于帮助快速

Sophie_U的博客 5907

PyTorch+C3D实战:用UCF101数据集训练你的第一个视频动作识别模型(附完整代码)

本文详细介绍了如何使用PyTorch和C3D模型在UCF101数据集上构建视频动作识别系统。从环境配置、数据预处理到模型训练与优化,提供了完整的代码实战指南,帮助开发者快速掌握视频行为识别技术。文章特别强调了3D卷积核设计、混合精度训练等关键技术要点,并分享了模型部署优化经验。

weixin_29233857的博客 218

一种基于频域滤波法消除干扰项与角谱法重构技术的数字全息显微台阶形貌测量实例分析

一种基于频域滤波法消除干扰项与角谱法重构技术的数字全息显微台阶形貌测量实例分析

简单光学 2034

解密 Vue CLI,打造属于自己的vue项目脚手架工具

解密vue-cli,打造属于自己的vue项目脚手架工具

wanghe1111_的博客 1088

TI电量计BQ40Z50R2循环学习实战:从量产文件生成到关键标志位解析

本文详细解析了TI电量计BQ40Z50R2的循环学习实战流程,从量产文件生成到关键标志位解析。通过实战案例和易错分析,帮助工程师避开常见陷阱,提升电量计配置效率。重点介绍了循环学习前的准备工作、量产文件生成步骤及关键标志位的深度解读,适用于电池管理系统开发人员。

weixin_30505225的博客 439

搭建cli命令行脚手架

cli (Command-line interface)简称命令行界面,又叫脚手架。比如vue-cli、create-react-app、gulp-cli、node脚本、shell脚本等 作用:帮你减少做重复性工作的工具 前端常用:创建项目、打包构建上传OSS、刷新CDN 常用到的几个库 commander:用来编写指令和处理命令行的 const program = require("commander"); // 定义指令 program .version('0.0.1') .command(

越思考越清晰 2281

新手如何学习车载底层嵌入式软件开发(AutoSar)

汽车嵌入式开发

ACarSoft的博客 4219

知识图谱的数据挖掘与分析

1. 背景介绍 1.1 数据挖掘的崛起 随着互联网的普及和大数据技术的发展,数据挖掘已经成为了计算机科学领域的热门研究方向。数据挖掘是从大量的、不完全的、有噪声的、模糊的、随机的实际应用数据中,通过计算机科学、统计学、信息论等多学科交叉的方法,挖掘出有用信息和知识的过程。数据挖掘的目标是从数据中发现有

AI天才研究院 1600

用vue 创建工程或者是创建组件注意事项

背景:原来都是一直在使用react,现在换成了vue因为中途进来的项目,一直没有从头开始理清楚,今天花了一些时间梳理了一下。 @vue/cli 全局安装就不说了 这里主要说下创建vue组件和创建vue项目 刚开始的时候一直在用 vue create projectname 每次创建之后发现和自己的工程结构不一样 后来发现 Usage: vue &amp;amp;lt;command&amp;amp;gt; [options...

点滴 2870

6.3 节深度拆解:Hermes Agent 多 Agent 协同执行链路的 4 层设计逻辑

拆解 Hermes 多 Agent 协同的 4 层工程化设计(协议层、编排层、执行层、适配层),聚焦协议层的强约束消息 Schema 如何解决 Agent 间语义不一致问题,并提供 `soul.md` 配置、GLM-4.7Flash 结构化输出验证及 v0.13.0/v0.14.0 版本实操避坑指南

weixin_42520239的博客 122

vue脚手架实现选项卡_打造属于自己的项目脚手架工具----Vue CLI

一、初始化——把脚本映射为命令1.vue-cli演示在这里我们希望可以像vue-cli那样 在终端输入vue 有对应的命令显示2.创建自己的脚手架文件(pgx-cli)index.js文件中写入#!/usr/bin/env node console.log('pgx-cli脚手架工具'); 使用Node开发命令行工具所执行的javascript 脚本必须在顶部加入#!/usr/bin/env n...

weixin_39746382的博客 208

置顶必读(2) | 《YOLOv11实战:从入门到深度优化》目录导航篇(持续更新中)

🏆 本文收录于 《YOLOv11实战:从入门到深度优化》 专栏。 本专栏系统梳理并持续复现全网各类 YOLOv11 / YOLO系列改进方案与实战案例,目前已覆盖 分类、检测、分割、追踪、关键点、OBB 检测 等多个方向。 内容坚持 持续更新 + 深度拆解 + 工程落地导向,不仅讲“怎么改”,更讲“为什么这样改、如何真正落地到项目中”。

**My Coding Family** 11万+

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

vue 引入的方法 用在template_?【有手就行】轻松打造属于自己的Vue工程化脚手架工具...

前言 为什么闲得蛋疼要自己做脚手架 官方脚手架工具创建的项目不能马上进行搬砖? 每次创建新项目就得复制粘贴旧项目的配置再CRUD? 能不能自己造个劳资御用的个性化定制版脚手架? 以Vue举例来说,虽然vue-cli已经非常牛批,可以快速灵活的创建各种各样配置搭配的初始项目模板,但这对于能够开始搬砖还远远不够,我们还得在此基础上加上一个又一个的配置,并且这些配置散乱无章又大多互不关联,很容易这...

weixin_42499800的博客 2071

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

使用Node.js打造交互式脚手架,简化模板下载与项目创建

在 packages/utils/lib 下创建一个名为 npm.js 的文件,并定义获取 npm 包信息和获取最新版本号的方法。然后,在 packages/init/lib/createTemplate.js 文件中定义获取用户选择的 npm 包的最新版本号的逻辑。首先,我们需要获取用户需要创建的项目类型。当用户执行 npx @ice-basic-cli/cli 命令时,init 文件中的 action 方法会被执行,触发交互式问答流程,用户可以根据提示选择初始化类型(例如创建项目或页面)。

weixin_55010563的博客 595

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

vue json解析_?【有手就行】轻松打造属于自己的Vue工程化脚手架工具

前言 为什么闲得蛋疼要自己做脚手架 官方脚手架工具创建的项目不能马上进行搬砖? 每次创建新项目就得复制粘贴旧项目的配置再CRUD? 能不能自己造个劳资御用的个性化定制版脚手架? 以Vue举例来说,虽然vue-cli已经非常牛批,可以快速灵活的创建各种各样配置搭配的初始项目模板,但这对于能够开始搬砖还远远不够,我们还得在此基础上加上一个又一个的配置,并且这些配置散乱无章又大多互不关联,很容易这...

weixin_39637614的博客 263

vuecli-脚手架,安装使用及目录详解

文章目录遇到的错误安装脚手架错误vue ui启动错误一、安装脚手架二、脚手架21.初始化2.工程配置说明package.jsonbuild命令解析dev命令解析package.json-lock.jsonconfig目录static目录.babelrc.editorconfig忽略配置文件eslint检查规则文件css转化配置文件(一般不会修改)index.html三、代码检查四、runtimecompiler与runtimeonly区别区别Vue程序运行过程runtimeonly,render方法详解五、

zhaohan___的博客 3150

如何打造自己的脚手架[转]

前言在实际的开发过程中,从零开始建立项目的结构是一件让人头疼的事情,所以各种各样的脚手架工具应运而生。笔者使用较多的yoeman,express-generator和vue-cli便是当中之一。它们功能丰富,但最核心的功能都是能够快速搭建一个完整的项目的结构,开发者只需要在生成的项目结构的基础上进行开发即可,非常简单高效。作为一个不折腾会死星人,在熟悉了使用方法以后就开始琢磨起它们的原理来了。经过...

weixin_33705053的博客 142

Android动画开发利器:MotionLayoutExamples最佳实践与案例分析

MotionLayoutExamples是一个专注于Android平台动画效果实现的开源项目,它基于MotionLayout提供了丰富的动画示例,帮助开发者快速掌握复杂动画的设计与实现方法。无论是折叠工具栏、多元素协同动画还是场景过渡效果,该项目都提供了可直接复用的代码模板和视觉参考。 ## 为什么选择MotionLayout进行动画开发? MotionLayout作为ConstraintLa

gitblog_00710的博客 1096

【前端】Vue2 脚手架模块化开发 -快速入门

搭建Vue2脚手架CLI不仅是一门工具的使用,更是一种高效、便捷开发的方式的选择。借助Vue的强大功能、丰富的生态系统以及众多优秀的插件和扩展库,我们能够以更高的效率开发出精美、功能丰富的Web应用程序。希望我的这篇帖子能够给你带来启发和帮助,让我们一起打造令人瞩目的开发利器吧!

dandelionl_的博客 4424

从0到1打造一套适合自己接单的脚手架03用户登录注册

我们本篇介绍了用户登录注册服务的一个开发过程,nestjs有点像我们常规的java后台开发,需要创建模块,添加service,开发实体、dto以及controller。完整的代码写好了之后我们又要求ai帮我们自动完成了测试用例的编写并且执行了测试。借助ai的能力基本上可以把业务逻辑完整的生成,过程中也是需要人为干预解决各种不符合预期的问题,也算是能帮不少忙。

低代码布道师的博客 1312
上一篇: 微前端-多页应用
下一篇: 开始工作就差一个双击
xiaojinglyd
博客等级 码龄9年 6粉丝 29原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值