自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

「Create-?」每個(gè)前端開發(fā)者都可以擁有屬于自己的命令行腳手架

開發(fā) 前端
最近一直在搞Strve.js生態(tài),在自己搗鼓框架的同時(shí)也學(xué)到了很多東西。所以就本篇文章給大家介紹一種更加方便靈活的命令行腳手架工具,以及如何發(fā)布到NPM上。

[[441093]]

前言

為什么要寫這篇文章呢?是因?yàn)樽罱恢痹诟鉙trve.js生態(tài),在自己搗鼓框架的同時(shí)也學(xué)到了很多東西。所以就本篇文章給大家介紹一種更加方便靈活的命令行腳手架工具,以及如何發(fā)布到NPM上。

之前,我也寫過類似的開發(fā)命令行工具的文章,但是核心思想都是通過代碼遠(yuǎn)程拉取Git倉庫中的項(xiàng)目模板代碼。有時(shí)候會因?yàn)榫W(wǎng)速的原因?qū)е吕∈。M(jìn)而會初始化項(xiàng)目失敗。

那么,有沒有比這個(gè)更好的方案呢?那么本篇就來了。

最近,使用Vite工具開發(fā)了很多項(xiàng)目。不得不佩服尤老師驚人的代碼能力,創(chuàng)建了這么好的開發(fā)工具,開發(fā)體驗(yàn)非常絲滑。尤其是你剛初始化項(xiàng)目時(shí),只需要執(zhí)行一行命令,也不用全局安裝什么工具。然后,自定義選擇需要的模板進(jìn)行初始化項(xiàng)目,就大功告成了!這種操作著實(shí)把我驚到了!我在想,如果我把create-vite的這種思路應(yīng)用到我自己的腳手架工具中是不是很Nice!

實(shí)戰(zhàn)

所以,二話不說,就抓緊打開ViteGitHub地址。

https://github.com/vitejs

找了大半天,終于找到了命令行工具核心代碼。

https://github.com/vitejs/vite/tree/main/packages/create-vite

映入眼簾的是很多以template-開頭的文件夾,打開幾個(gè)都看了一下,都是框架項(xiàng)目模板。那么,可以先放在一邊。

下一步,我們就打開index.js文件看下什么內(nèi)容。我列下代碼,大家可以簡單看一下,不用深究。

  1. #!/usr/bin/env node 
  2.  
  3. // @ts-check 
  4. const fs = require('fs'
  5. const path = require('path'
  6. // Avoids autoconversion to number of the project name by defining that the args 
  7. // non associated with an option ( _ ) needs to be parsed as a string. See #4606 
  8. const argv = require('minimist')(process.argv.slice(2), { string: ['_'] }) 
  9. // eslint-disable-next-line node/no-restricted-require 
  10. const prompts = require('prompts'
  11. const { 
  12.   yellow, 
  13.   green, 
  14.   cyan, 
  15.   blue, 
  16.   magenta, 
  17.   lightRed, 
  18.   red 
  19. } = require('kolorist'
  20.  
  21. const cwd = process.cwd() 
  22.  
  23. const FRAMEWORKS = [ 
  24.   { 
  25.     name'vanilla'
  26.     color: yellow, 
  27.     variants: [ 
  28.       { 
  29.         name'vanilla'
  30.         display: 'JavaScript'
  31.         color: yellow 
  32.       }, 
  33.       { 
  34.         name'vanilla-ts'
  35.         display: 'TypeScript'
  36.         color: blue 
  37.       } 
  38.     ] 
  39.   }, 
  40.   { 
  41.     name'vue'
  42.     color: green, 
  43.     variants: [ 
  44.       { 
  45.         name'vue'
  46.         display: 'JavaScript'
  47.         color: yellow 
  48.       }, 
  49.       { 
  50.         name'vue-ts'
  51.         display: 'TypeScript'
  52.         color: blue 
  53.       } 
  54.     ] 
  55.   }, 
  56.   { 
  57.     name'react'
  58.     color: cyan, 
  59.     variants: [ 
  60.       { 
  61.         name'react'
  62.         display: 'JavaScript'
  63.         color: yellow 
  64.       }, 
  65.       { 
  66.         name'react-ts'
  67.         display: 'TypeScript'
  68.         color: blue 
  69.       } 
  70.     ] 
  71.   }, 
  72.   { 
  73.     name'preact'
  74.     color: magenta, 
  75.     variants: [ 
  76.       { 
  77.         name'preact'
  78.         display: 'JavaScript'
  79.         color: yellow 
  80.       }, 
  81.       { 
  82.         name'preact-ts'
  83.         display: 'TypeScript'
  84.         color: blue 
  85.       } 
  86.     ] 
  87.   }, 
  88.   { 
  89.     name'lit'
  90.     color: lightRed, 
  91.     variants: [ 
  92.       { 
  93.         name'lit'
  94.         display: 'JavaScript'
  95.         color: yellow 
  96.       }, 
  97.       { 
  98.         name'lit-ts'
  99.         display: 'TypeScript'
  100.         color: blue 
  101.       } 
  102.     ] 
  103.   }, 
  104.   { 
  105.     name'svelte'
  106.     color: red, 
  107.     variants: [ 
  108.       { 
  109.         name'svelte'
  110.         display: 'JavaScript'
  111.         color: yellow 
  112.       }, 
  113.       { 
  114.         name'svelte-ts'
  115.         display: 'TypeScript'
  116.         color: blue 
  117.       } 
  118.     ] 
  119.   } 
  120.  
  121. const TEMPLATES = FRAMEWORKS.map( 
  122.   (f) => (f.variants && f.variants.map((v) => v.name)) || [f.name
  123. ).reduce((a, b) => a.concat(b), []) 
  124.  
  125. const renameFiles = { 
  126.   _gitignore: '.gitignore' 
  127.  
  128. async function init() { 
  129.   let targetDir = argv._[0] 
  130.   let template = argv.template || argv.t 
  131.  
  132.   const defaultProjectName = !targetDir ? 'vite-project' : targetDir 
  133.  
  134.   let result = {} 
  135.  
  136.   try { 
  137.     result = await prompts( 
  138.       [ 
  139.         { 
  140.           type: targetDir ? null : 'text'
  141.           name'projectName'
  142.           message: 'Project name:'
  143.           initial: defaultProjectName, 
  144.           onState: (state) => 
  145.             (targetDir = state.value.trim() || defaultProjectName) 
  146.         }, 
  147.         { 
  148.           type: () => 
  149.             !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm'
  150.           name'overwrite'
  151.           message: () => 
  152.             (targetDir === '.' 
  153.               ? 'Current directory' 
  154.               : `Target directory "${targetDir}"`) + 
  155.             ` is not empty. Remove existing files and continue?` 
  156.         }, 
  157.         { 
  158.           type: (_, { overwrite } = {}) => { 
  159.             if (overwrite === false) { 
  160.               throw new Error(red('✖') + ' Operation cancelled'
  161.             } 
  162.             return null 
  163.           }, 
  164.           name'overwriteChecker' 
  165.         }, 
  166.         { 
  167.           type: () => (isValidPackageName(targetDir) ? null : 'text'), 
  168.           name'packageName'
  169.           message: 'Package name:'
  170.           initial: () => toValidPackageName(targetDir), 
  171.           validate: (dir) => 
  172.             isValidPackageName(dir) || 'Invalid package.json name' 
  173.         }, 
  174.         { 
  175.           type: template && TEMPLATES.includes(template) ? null : 'select'
  176.           name'framework'
  177.           message: 
  178.             typeof template === 'string' && !TEMPLATES.includes(template) 
  179.               ? `"${template}" isn't a valid template. Please choose from below: ` 
  180.               : 'Select a framework:'
  181.           initial: 0, 
  182.           choices: FRAMEWORKS.map((framework) => { 
  183.             const frameworkColor = framework.color 
  184.             return { 
  185.               title: frameworkColor(framework.name), 
  186.               value: framework 
  187.             } 
  188.           }) 
  189.         }, 
  190.         { 
  191.           type: (framework) => 
  192.             framework && framework.variants ? 'select' : null
  193.           name'variant'
  194.           message: 'Select a variant:'
  195.           // @ts-ignore 
  196.           choices: (framework) => 
  197.             framework.variants.map((variant) => { 
  198.               const variantColor = variant.color 
  199.               return { 
  200.                 title: variantColor(variant.name), 
  201.                 value: variant.name 
  202.               } 
  203.             }) 
  204.         } 
  205.       ], 
  206.       { 
  207.         onCancel: () => { 
  208.           throw new Error(red('✖') + ' Operation cancelled'
  209.         } 
  210.       } 
  211.     ) 
  212.   } catch (cancelled) { 
  213.     console.log(cancelled.message) 
  214.     return 
  215.   } 
  216.  
  217.   // user choice associated with prompts 
  218.   const { framework, overwrite, packageName, variant } = result 
  219.  
  220.   const root = path.join(cwd, targetDir) 
  221.  
  222.   if (overwrite) { 
  223.     emptyDir(root) 
  224.   } else if (!fs.existsSync(root)) { 
  225.     fs.mkdirSync(root) 
  226.   } 
  227.  
  228.   // determine template 
  229.   template = variant || framework || template 
  230.  
  231.   console.log(`\nScaffolding project in ${root}...`) 
  232.  
  233.   const templateDir = path.join(__dirname, `template-${template}`) 
  234.  
  235.   const write = (file, content) => { 
  236.     const targetPath = renameFiles[file] 
  237.       ? path.join(root, renameFiles[file]) 
  238.       : path.join(root, file) 
  239.     if (content) { 
  240.       fs.writeFileSync(targetPath, content) 
  241.     } else { 
  242.       copy(path.join(templateDir, file), targetPath) 
  243.     } 
  244.   } 
  245.  
  246.   const files = fs.readdirSync(templateDir) 
  247.   for (const file of files.filter((f) => f !== 'package.json')) { 
  248.     write(file) 
  249.   } 
  250.  
  251.   const pkg = require(path.join(templateDir, `package.json`)) 
  252.  
  253.   pkg.name = packageName || targetDir 
  254.  
  255.   write('package.json', JSON.stringify(pkg, null, 2)) 
  256.  
  257.   const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent) 
  258.   const pkgManager = pkgInfo ? pkgInfo.name : 'npm' 
  259.  
  260.   console.log(`\nDone. Now run:\n`) 
  261.   if (root !== cwd) { 
  262.     console.log(`  cd ${path.relative(cwd, root)}`) 
  263.   } 
  264.   switch (pkgManager) { 
  265.     case 'yarn'
  266.       console.log('  yarn'
  267.       console.log('  yarn dev'
  268.       break 
  269.     default
  270.       console.log(`  ${pkgManager} install`) 
  271.       console.log(`  ${pkgManager} run dev`) 
  272.       break 
  273.   } 
  274.   console.log() 
  275.  
  276. function copy(src, dest) { 
  277.   const stat = fs.statSync(src) 
  278.   if (stat.isDirectory()) { 
  279.     copyDir(src, dest) 
  280.   } else { 
  281.     fs.copyFileSync(src, dest) 
  282.   } 
  283.  
  284. function isValidPackageName(projectName) { 
  285.   return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test( 
  286.     projectName 
  287.   ) 
  288.  
  289. function toValidPackageName(projectName) { 
  290.   return projectName 
  291.     .trim() 
  292.     .toLowerCase() 
  293.     .replace(/\s+/g, '-'
  294.     .replace(/^[._]/, ''
  295.     .replace(/[^a-z0-9-~]+/g, '-'
  296.  
  297. function copyDir(srcDir, destDir) { 
  298.   fs.mkdirSync(destDir, { recursive: true }) 
  299.   for (const file of fs.readdirSync(srcDir)) { 
  300.     const srcFile = path.resolve(srcDir, file) 
  301.     const destFile = path.resolve(destDir, file) 
  302.     copy(srcFile, destFile) 
  303.   } 
  304.  
  305. function isEmpty(path) { 
  306.   return fs.readdirSync(path).length === 0 
  307.  
  308. function emptyDir(dir) { 
  309.   if (!fs.existsSync(dir)) { 
  310.     return 
  311.   } 
  312.   for (const file of fs.readdirSync(dir)) { 
  313.     const abs = path.resolve(dir, file) 
  314.     // baseline is Node 12 so can't use rmSync :( 
  315.     if (fs.lstatSync(abs).isDirectory()) { 
  316.       emptyDir(abs
  317.       fs.rmdirSync(abs
  318.     } else { 
  319.       fs.unlinkSync(abs
  320.     } 
  321.   } 
  322.  
  323. /** 
  324.  * @param {string | undefined} userAgent process.env.npm_config_user_agent 
  325.  * @returns object | undefined 
  326.  */ 
  327. function pkgFromUserAgent(userAgent) { 
  328.   if (!userAgent) return undefined 
  329.   const pkgSpec = userAgent.split(' ')[0] 
  330.   const pkgSpecArr = pkgSpec.split('/'
  331.   return { 
  332.     name: pkgSpecArr[0], 
  333.     version: pkgSpecArr[1] 
  334.   } 
  335.  
  336. init().catch((e) => { 
  337.   console.error(e) 
  338. }) 

看到上面這么多代碼是不是不想繼續(xù)閱讀下去了?不要慌!我們其實(shí)就用到里面幾個(gè)地方,可以放心的繼續(xù)閱讀下去。

這些代碼算是Create Vite核心代碼了,我們會看到常量FRAMEWORKS定義了一個(gè)數(shù)組對象,另外數(shù)組對象中都是一些我們初始化項(xiàng)目時(shí)需要選擇安裝的框架。所以,我們可以先ViteGithub項(xiàng)目Clone下來,試試效果。

然后,將項(xiàng)目Clone下來之后,我們找到/packages/create-vite這個(gè)文件夾,我們現(xiàn)在就只關(guān)注這個(gè)文件夾。

我用的Yarn依賴管理工具,所以我首先使用命令初始化依賴。

  1. yarn  

然后,我們可以先打開根目錄下的package.json文件,會發(fā)現(xiàn)有如下命令。

  1.   "bin": { 
  2.     "create-vite""index.js"
  3.     "cva""index.js" 
  4.   } 

我們可以在這里起一個(gè)自己模板的名字,比如我們就叫demo,

  1.   "bin": { 
  2.     "create-demo""index.js"
  3.     "cvd""index.js" 
  4.   } 

然后,我們先在這里使用yarn link命令來將此命令在本地可以運(yùn)行。

然后再運(yùn)行create-demo命令·。

 會顯示一些交互文本,會發(fā)現(xiàn)非常熟悉,這正是我們創(chuàng)建Vite項(xiàng)目時(shí)所看到的。我們在前面說到我們想實(shí)現(xiàn)一個(gè)屬于自己的項(xiàng)目模板,現(xiàn)在我們也找到了核心。所以就開始干起來吧!

我們會看到在根目錄下有很多template-開頭的文件夾,我們打開一個(gè)看一下。比如template-vue。

原來模板都在這!但是這些模板文件都是以template-開頭,是不是有什么約定?所以,我們打算回頭再去看下index.js文件。

  1. // determine template 
  2. template = variant || framework || template 
  3.  
  4. console.log(`\nScaffolding project in ${root}...`) 
  5.  
  6. const templateDir = path.join(__dirname, `template-${template}`) 

果真,所以模板都必須以template-開頭。

那么,我們就在根目錄下面建一個(gè)template-demo文件夾,里面再放一個(gè)index.js文件,作為示例模板。

我們在執(zhí)行初始化項(xiàng)目時(shí)發(fā)現(xiàn),需要選擇對應(yīng)的模板,那么這些選項(xiàng)是從哪里來的呢?我們決定再回去看下根目錄下的index.js文件。

會發(fā)現(xiàn)有這么一個(gè)數(shù)組,里面正是我們要選擇的框架模板。

  1. const FRAMEWORKS = [ 
  2.   { 
  3.     name'vanilla'
  4.     color: yellow, 
  5.     variants: [ 
  6.       { 
  7.         name'vanilla'
  8.         display: 'JavaScript'
  9.         color: yellow 
  10.       }, 
  11.       { 
  12.         name'vanilla-ts'
  13.         display: 'TypeScript'
  14.         color: blue 
  15.       } 
  16.     ] 
  17.   }, 
  18.   { 
  19.     name'vue'
  20.     color: green, 
  21.     variants: [ 
  22.       { 
  23.         name'vue'
  24.         display: 'JavaScript'
  25.         color: yellow 
  26.       }, 
  27.       { 
  28.         name'vue-ts'
  29.         display: 'TypeScript'
  30.         color: blue 
  31.       } 
  32.     ] 
  33.   }, 
  34.   { 
  35.     name'react'
  36.     color: cyan, 
  37.     variants: [ 
  38.       { 
  39.         name'react'
  40.         display: 'JavaScript'
  41.         color: yellow 
  42.       }, 
  43.       { 
  44.         name'react-ts'
  45.         display: 'TypeScript'
  46.         color: blue 
  47.       } 
  48.     ] 
  49.   }, 
  50.   { 
  51.     name'preact'
  52.     color: magenta, 
  53.     variants: [ 
  54.       { 
  55.         name'preact'
  56.         display: 'JavaScript'
  57.         color: yellow 
  58.       }, 
  59.       { 
  60.         name'preact-ts'
  61.         display: 'TypeScript'
  62.         color: blue 
  63.       } 
  64.     ] 
  65.   }, 
  66.   { 
  67.     name'lit'
  68.     color: lightRed, 
  69.     variants: [ 
  70.       { 
  71.         name'lit'
  72.         display: 'JavaScript'
  73.         color: yellow 
  74.       }, 
  75.       { 
  76.         name'lit-ts'
  77.         display: 'TypeScript'
  78.         color: blue 
  79.       } 
  80.     ] 
  81.   }, 
  82.   { 
  83.     name'svelte'
  84.     color: red, 
  85.     variants: [ 
  86.       { 
  87.         name'svelte'
  88.         display: 'JavaScript'
  89.         color: yellow 
  90.       }, 
  91.       { 
  92.         name'svelte-ts'
  93.         display: 'TypeScript'
  94.         color: blue 
  95.       } 
  96.     ] 
  97.   } 

所以,可以在后面數(shù)組后面再添加一個(gè)對象。

  1.     name'demo'
  2.     color: red, 
  3.     variants: [ 
  4.       { 
  5.         name'demo'
  6.         display: 'JavaScript'
  7.         color: yellow 
  8.       } 
  9.     ] 

好,你會發(fā)現(xiàn)我這里會有個(gè)color屬性,并且有類似顏色值的屬性值,這是依賴kolorist導(dǎo)出的常量。kolorist是一個(gè)將顏色放入標(biāo)準(zhǔn)輸入/標(biāo)準(zhǔn)輸出的小庫。我們在之前那些模板交互文本會看到它們顯示不同顏色,這正是它的功勞。

  1. const { 
  2.   yellow, 
  3.   green, 
  4.   cyan, 
  5.   blue, 
  6.   magenta, 
  7.   lightRed, 
  8.   red 
  9. } = require('kolorist'

我們,也將模板對象添加到數(shù)組里了,那么下一步我們執(zhí)行命令看下效果。

 

會發(fā)現(xiàn)多了一個(gè)demo模板,這正是我們想要的。

我們繼續(xù)執(zhí)行下去。

我們會看到根目錄下已經(jīng)成功創(chuàng)建了demo1文件夾,并且里面正是我們想要的demo模板。

上圖顯示的Error,是因?yàn)槲覜]有在demo模板上創(chuàng)建package.json文件,所以這里可以忽略。你可以在自己的模板里創(chuàng)建一個(gè)package.json文件。

雖然,我們成功在本地創(chuàng)建了自己的一個(gè)模板,但是,我們只能本地創(chuàng)建。也就是說你換臺電腦,就沒有辦法執(zhí)行這個(gè)創(chuàng)建模板的命令。

所以,我們要想辦法去發(fā)布到云端,這里我們發(fā)布到NPM上。

首先,我們重新新建一個(gè)項(xiàng)目目錄,將其他模板刪除,只保留我們自己的模板。另外,將數(shù)組中的其他模板對象刪除,保留一個(gè)自己的模板。

我以自己的模板create-strve-app為例。

然后,我們打開package.json文件,需要修改一些信息。

以create-strve-app為例:

  1.   "name""create-strve-app"
  2.   "version""1.3.3"
  3.   "license""MIT"
  4.   "author""maomincoding"
  5.   "bin": { 
  6.     "create-strve-app""index.js"
  7.     "cs-app""index.js" 
  8.   }, 
  9.   "files": [ 
  10.     "index.js"
  11.     "template-*" 
  12.   ], 
  13.   "main""index.js"
  14.   "private"false
  15.   "keywords": ["strve","strvejs","dom","mvvm","virtual dom","html","template","string","create-strve","create-strve-app"], 
  16.   "engines": { 
  17.     "node"">=12.0.0" 
  18.   }, 
  19.   "repository": { 
  20.     "type""git"
  21.     "url""git+https://github.com/maomincoding/create-strve-app.git" 
  22.   }, 
  23.   "bugs": { 
  24.     "url""https://github.com/maomincoding/create-strve-app/issues" 
  25.   }, 
  26.   "homepage""https://github.com/maomincoding/create-strve-app#readme"
  27.   "dependencies": { 
  28.     "kolorist""^1.5.0"
  29.     "minimist""^1.2.5"
  30.     "prompts""^2.4.2" 
  31.   } 

注意,每次發(fā)布前,version字段必須與之前不同,否則發(fā)布失敗。

最后,我們依次運(yùn)行如下命令。

切換到npm源

  1. npm config set registry=https://registry.npmjs.org 

登錄NPM(如果已登錄,可忽略此步)

  1. npm login 

發(fā)布NPM

  1. npm publish 

我們可以登錄到NPM(https://www.npmjs.com/)

查看已經(jīng)發(fā)布成功!

 

以后,我們就可以直接運(yùn)行命令下載自定義模板。這在我們重復(fù)使用模板時(shí)非常有用,不僅可以提升效率,而且還可以避免犯很多不必要的錯(cuò)誤。

結(jié)語

另外,此篇舉例的 Create Strve App 是一套快速搭建Strve.js項(xiàng)目的命令行工具。如果你對此感興趣,可以訪問以下地址查看源碼:

https://github.com/maomincoding/create-strve-app

熬夜奮戰(zhàn)二個(gè)多月,Strve.js生態(tài)初步已經(jīng)建成,以下是Strve.js 最新文檔地址,歡迎瀏覽。

https://maomincoding.github.io/strvejs-doc/

 

責(zé)任編輯:姜華 來源: 前端歷劫之路
相關(guān)推薦

2019-09-19 09:02:52

開發(fā)者技能工具

2021-12-23 06:07:21

前端技術(shù)編程

2023-11-21 17:36:04

OpenFeignSentinel

2021-12-16 23:40:33

部署ReactTypeScript

2022-04-20 06:56:33

Strve.js前端

2021-08-17 21:52:04

工具

2018-01-29 20:12:11

python翻譯命令行

2021-05-21 05:22:52

腳手架工具項(xiàng)目

2022-03-07 10:27:03

Linux開源社區(qū)

2019-12-25 15:20:48

前端腳手架命令

2021-01-07 05:34:07

腳手架JDK緩存

2016-07-05 16:30:10

碳云智能數(shù)字化生命

2020-04-09 10:25:18

Java 開發(fā)者神器

2018-08-30 16:08:37

Node.js腳手架工具

2018-06-11 14:39:57

前端腳手架工具node.js

2014-08-15 09:36:06

2010-05-21 18:30:02

2016-06-30 11:25:52

VisualNET開發(fā)

2019-08-09 10:52:58

Linux內(nèi)核Spark

2023-08-10 08:31:53

工具實(shí)用網(wǎng)站
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號