<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 12989字,需瀏覽 26分鐘

           ·

          2021-12-24 20:57

          前言

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

          之前,我也寫過類似的開發(fā)命令行工具的文章,但是核心思想都是通過代碼遠程拉取Git倉庫中的項目模板代碼。有時候會因為網速的原因導致拉取失敗,進而會初始化項目失敗。

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

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

          實戰(zhàn)

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

          https://github.com/vitejs

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

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


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

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

          #!/usr/bin/env?node

          //?@ts-check
          const?fs?=?require('fs')
          const?path?=?require('path')
          //?Avoids?autoconversion?to?number?of?the?project?name?by?defining?that?the?args
          //?non?associated?with?an?option?(?_?)?needs?to?be?parsed?as?a?string.?See?#4606
          const?argv?=?require('minimist')(process.argv.slice(2),?{?string:?['_']?})
          //?eslint-disable-next-line?node/no-restricted-require
          const?prompts?=?require('prompts')
          const?{
          ??yellow,
          ??green,
          ??cyan,
          ??blue,
          ??magenta,
          ??lightRed,
          ??red
          }?=?require('kolorist')

          const?cwd?=?process.cwd()

          const?FRAMEWORKS?=?[
          ??{
          ????name:?'vanilla',
          ????color:?yellow,
          ????variants:?[
          ??????{
          ????????name:?'vanilla',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'vanilla-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'vue',
          ????color:?green,
          ????variants:?[
          ??????{
          ????????name:?'vue',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'vue-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'react',
          ????color:?cyan,
          ????variants:?[
          ??????{
          ????????name:?'react',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'react-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'preact',
          ????color:?magenta,
          ????variants:?[
          ??????{
          ????????name:?'preact',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'preact-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'lit',
          ????color:?lightRed,
          ????variants:?[
          ??????{
          ????????name:?'lit',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'lit-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'svelte',
          ????color:?red,
          ????variants:?[
          ??????{
          ????????name:?'svelte',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'svelte-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??}
          ]

          const?TEMPLATES?=?FRAMEWORKS.map(
          ??(f)?=>?(f.variants?&&?f.variants.map((v)?=>?v.name))?||?[f.name]
          ).reduce((a,?b)?=>?a.concat(b),?[])

          const?renameFiles?=?{
          ??_gitignore:?'.gitignore'
          }

          async?function?init()?{
          ??let?targetDir?=?argv._[0]
          ??let?template?=?argv.template?||?argv.t

          ??const?defaultProjectName?=?!targetDir???'vite-project'?:?targetDir

          ??let?result?=?{}

          ??try?{
          ????result?=?await?prompts(
          ??????[
          ????????{
          ??????????type:?targetDir???null?:?'text',
          ??????????name:?'projectName',
          ??????????message:?'Project?name:',
          ??????????initial:?defaultProjectName,
          ??????????onState:?(state)?=>
          ????????????(targetDir?=?state.value.trim()?||?defaultProjectName)
          ????????},
          ????????{
          ??????????type:?()?=>
          ????????????!fs.existsSync(targetDir)?||?isEmpty(targetDir)???null?:?'confirm',
          ??????????name:?'overwrite',
          ??????????message:?()?=>
          ????????????(targetDir?===?'.'
          ????????????????'Current?directory'
          ??????????????:?`Target?directory?"${targetDir}"`)?+
          ????????????`?is?not?empty.?Remove?existing?files?and?continue?`
          ????????},
          ????????{
          ??????????type:?(_,?{?overwrite?}?=?{})?=>?{
          ????????????if?(overwrite?===?false)?{
          ??????????????throw?new?Error(red('?')?+?'?Operation?cancelled')
          ????????????}
          ????????????return?null
          ??????????},
          ??????????name:?'overwriteChecker'
          ????????},
          ????????{
          ??????????type:?()?=>?(isValidPackageName(targetDir)???null?:?'text'),
          ??????????name:?'packageName',
          ??????????message:?'Package?name:',
          ??????????initial:?()?=>?toValidPackageName(targetDir),
          ??????????validate:?(dir)?=>
          ????????????isValidPackageName(dir)?||?'Invalid?package.json?name'
          ????????},
          ????????{
          ??????????type:?template?&&?TEMPLATES.includes(template)???null?:?'select',
          ??????????name:?'framework',
          ??????????message:
          ????????????typeof?template?===?'string'?&&?!TEMPLATES.includes(template)
          ????????????????`"${template}"?isn't?a?valid?template.?Please?choose?from?below:?`
          ??????????????:?'Select?a?framework:',
          ??????????initial:?0,
          ??????????choices:?FRAMEWORKS.map((framework)?=>?{
          ????????????const?frameworkColor?=?framework.color
          ????????????return?{
          ??????????????title:?frameworkColor(framework.name),
          ??????????????value:?framework
          ????????????}
          ??????????})
          ????????},
          ????????{
          ??????????type:?(framework)?=>
          ????????????framework?&&?framework.variants???'select'?:?null,
          ??????????name:?'variant',
          ??????????message:?'Select?a?variant:',
          ??????????//?@ts-ignore
          ??????????choices:?(framework)?=>
          ????????????framework.variants.map((variant)?=>?{
          ??????????????const?variantColor?=?variant.color
          ??????????????return?{
          ????????????????title:?variantColor(variant.name),
          ????????????????value:?variant.name
          ??????????????}
          ????????????})
          ????????}
          ??????],
          ??????{
          ????????onCancel:?()?=>?{
          ??????????throw?new?Error(red('?')?+?'?Operation?cancelled')
          ????????}
          ??????}
          ????)
          ??}?catch?(cancelled)?{
          ????console.log(cancelled.message)
          ????return
          ??}

          ??//?user?choice?associated?with?prompts
          ??const?{?framework,?overwrite,?packageName,?variant?}?=?result

          ??const?root?=?path.join(cwd,?targetDir)

          ??if?(overwrite)?{
          ????emptyDir(root)
          ??}?else?if?(!fs.existsSync(root))?{
          ????fs.mkdirSync(root)
          ??}

          ??//?determine?template
          ??template?=?variant?||?framework?||?template

          ??console.log(`\nScaffolding?project?in?${root}...`)

          ??const?templateDir?=?path.join(__dirname,?`template-${template}`)

          ??const?write?=?(file,?content)?=>?{
          ????const?targetPath?=?renameFiles[file]
          ????????path.join(root,?renameFiles[file])
          ??????:?path.join(root,?file)
          ????if?(content)?{
          ??????fs.writeFileSync(targetPath,?content)
          ????}?else?{
          ??????copy(path.join(templateDir,?file),?targetPath)
          ????}
          ??}

          ??const?files?=?fs.readdirSync(templateDir)
          ??for?(const?file?of?files.filter((f)?=>?f?!==?'package.json'))?{
          ????write(file)
          ??}

          ??const?pkg?=?require(path.join(templateDir,?`package.json`))

          ??pkg.name?=?packageName?||?targetDir

          ??write('package.json',?JSON.stringify(pkg,?null,?2))

          ??const?pkgInfo?=?pkgFromUserAgent(process.env.npm_config_user_agent)
          ??const?pkgManager?=?pkgInfo???pkgInfo.name?:?'npm'

          ??console.log(`\nDone.?Now?run:\n`)
          ??if?(root?!==?cwd)?{
          ????console.log(`??cd?${path.relative(cwd,?root)}`)
          ??}
          ??switch?(pkgManager)?{
          ????case?'yarn':
          ??????console.log('??yarn')
          ??????console.log('??yarn?dev')
          ??????break
          ????default:
          ??????console.log(`??${pkgManager}?install`)
          ??????console.log(`??${pkgManager}?run?dev`)
          ??????break
          ??}
          ??console.log()
          }

          function?copy(src,?dest)?{
          ??const?stat?=?fs.statSync(src)
          ??if?(stat.isDirectory())?{
          ????copyDir(src,?dest)
          ??}?else?{
          ????fs.copyFileSync(src,?dest)
          ??}
          }

          function?isValidPackageName(projectName)?{
          ??return?/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
          ????projectName
          ??)
          }

          function?toValidPackageName(projectName)?{
          ??return?projectName
          ????.trim()
          ????.toLowerCase()
          ????.replace(/\s+/g,?'-')
          ????.replace(/^[._]/,?'')
          ????.replace(/[^a-z0-9-~]+/g,?'-')
          }

          function?copyDir(srcDir,?destDir)?{
          ??fs.mkdirSync(destDir,?{?recursive:?true?})
          ??for?(const?file?of?fs.readdirSync(srcDir))?{
          ????const?srcFile?=?path.resolve(srcDir,?file)
          ????const?destFile?=?path.resolve(destDir,?file)
          ????copy(srcFile,?destFile)
          ??}
          }

          function?isEmpty(path)?{
          ??return?fs.readdirSync(path).length?===?0
          }

          function?emptyDir(dir)?{
          ??if?(!fs.existsSync(dir))?{
          ????return
          ??}
          ??for?(const?file?of?fs.readdirSync(dir))?{
          ????const?abs?=?path.resolve(dir,?file)
          ????//?baseline?is?Node?12?so?can't?use?rmSync?:(
          ????if?(fs.lstatSync(abs).isDirectory())?{
          ??????emptyDir(abs)
          ??????fs.rmdirSync(abs)
          ????}?else?{
          ??????fs.unlinkSync(abs)
          ????}
          ??}
          }

          /**
          ?*?@param?{string?|?undefined}?userAgent?process.env.npm_config_user_agent
          ?*?@returns?object?|?undefined
          ?*/

          function?pkgFromUserAgent(userAgent)?{
          ??if?(!userAgent)?return?undefined
          ??const?pkgSpec?=?userAgent.split('?')[0]
          ??const?pkgSpecArr?=?pkgSpec.split('/')
          ??return?{
          ????name:?pkgSpecArr[0],
          ????version:?pkgSpecArr[1]
          ??}
          }

          init().catch((e)?=>?{
          ??console.error(e)
          })

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

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

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

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

          yarn?

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

          {
          ??"bin":?{
          ????"create-vite":?"index.js",
          ????"cva":?"index.js"
          ??}
          }

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

          {
          ??"bin":?{
          ????"create-demo":?"index.js",
          ????"cvd":?"index.js"
          ??}
          }

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

          然后再運行create-demo命令·。

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

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

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

          //?determine?template
          template?=?variant?||?framework?||?template

          console.log(`\nScaffolding?project?in?${root}...`)

          const?templateDir?=?path.join(__dirname,?`template-${template}`)

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

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

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

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

          const?FRAMEWORKS?=?[
          ??{
          ????name:?'vanilla',
          ????color:?yellow,
          ????variants:?[
          ??????{
          ????????name:?'vanilla',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'vanilla-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'vue',
          ????color:?green,
          ????variants:?[
          ??????{
          ????????name:?'vue',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'vue-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'react',
          ????color:?cyan,
          ????variants:?[
          ??????{
          ????????name:?'react',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'react-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'preact',
          ????color:?magenta,
          ????variants:?[
          ??????{
          ????????name:?'preact',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'preact-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'lit',
          ????color:?lightRed,
          ????variants:?[
          ??????{
          ????????name:?'lit',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'lit-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??},
          ??{
          ????name:?'svelte',
          ????color:?red,
          ????variants:?[
          ??????{
          ????????name:?'svelte',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????},
          ??????{
          ????????name:?'svelte-ts',
          ????????display:?'TypeScript',
          ????????color:?blue
          ??????}
          ????]
          ??}
          ]

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

          {
          ????name:?'demo',
          ????color:?red,
          ????variants:?[
          ??????{
          ????????name:?'demo',
          ????????display:?'JavaScript',
          ????????color:?yellow
          ??????}
          ????]
          }

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

          const?{
          ??yellow,
          ??green,
          ??cyan,
          ??blue,
          ??magenta,
          ??lightRed,
          ??red
          }?=?require('kolorist')

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

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

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

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

          上圖顯示的Error,是因為我沒有在demo模板上創(chuàng)建package.json文件,所以這里可以忽略。你可以在自己的模板里創(chuàng)建一個package.json文件。

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

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

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

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

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

          create-strve-app為例:

          {
          ??"name":?"create-strve-app",
          ??"version":?"1.3.3",
          ??"license":?"MIT",
          ??"author":?"maomincoding",
          ??"bin":?{
          ????"create-strve-app":?"index.js",
          ????"cs-app":?"index.js"
          ??},
          ??"files":?[
          ????"index.js",
          ????"template-*"
          ??],
          ??"main":?"index.js",
          ??"private":?false,
          ??"keywords":?["strve","strvejs","dom","mvvm","virtual?dom","html","template","string","create-strve","create-strve-app"],
          ??"engines":?{
          ????"node":?">=12.0.0"
          ??},
          ??"repository":?{
          ????"type":?"git",
          ????"url":?"git+https://github.com/maomincoding/create-strve-app.git"
          ??},
          ??"bugs":?{
          ????"url":?"https://github.com/maomincoding/create-strve-app/issues"
          ??},
          ??"homepage":?"https://github.com/maomincoding/create-strve-app#readme",
          ??"dependencies":?{
          ????"kolorist":?"^1.5.0",
          ????"minimist":?"^1.2.5",
          ????"prompts":?"^2.4.2"
          ??}
          }

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

          最后,我們依次運行如下命令。

          1. 切換到npm源
          npm?config?set?registry=https://registry.npmjs.org
          1. 登錄NPM(如果已登錄,可忽略此步)
          npm?login
          1. 發(fā)布NPM
          npm?publish

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

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

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

          結語

          謝謝你對此篇的閱讀,希望可以幫到你。如果在操作時有任何疑問,可以向我留言。

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

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

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

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



          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  婷婷色色五月天 | 国产精品视频在线播放 | 黃色一級片黃色一級片尖叫声 | 日韩无码一区二区三 | 成人无码视频成 |