preface

Hello, everyone, I am Ajiang. I know that my friends may know that I have been building a scaffolding called VUe3-viet-CLI. Now I have summed up some experience and would like to share it with other partners who need it.

In fact, the initial vuite-vuUE-CLI project was actually built by learning vue3 + Vite + TS project demo, followed by watching the source code of Vite and the built-in project template replication process and transformed into a VUe3 can directly use the project template.

In this way, we can not only practice but also build a set of generic project templates that can be installed by NPM (i.e., scaffolding mode). This record down is actually a technical point for the purpose of the record, and also hope to build their own scaffolding small partners use!

Finally, a set of empty project templates can be directly built and uploaded to the project. NPM can be used anytime and anywhere. I probably won’t start with the scaffolding I created on Vite, since it will involve a major change at the project level, so I’ll start with the scaffolding I created after the project level changed.

start

Create a project

npm init
Copy the code

Readers can refer to this form to fill in their own.So we’ve introduced the package manager! Next, I create them one by one.gitgnoreREADME.mdThese two standard documents are used forGit upload ignoredanddocumentation

Configure.gitgnore and readme.md

The.gitgnore file can be configured according to the following configuration, in general, ignoring dependencies and so on.

node_modules
dist
*.local
.idea
.vscode
Copy the code

Readme. md This is a documentation file that describes the project level, features, and usage steps as simply as possible. Makedown and HTML are supported

Here is a recommended generation that readers might useA portal that internally relies on third-party library badges:shields

NPM scaffolding instructions

Create a bin folder in the package.json sibling file

 mkdir bin
Copy the code

This step is to create a soft connection under NPM in preparation for quick invocation of instructions!

Next you need to create npmrc.js in the newly created bin folder. This is the NPM configuration file, similar to vue.config.js used to configure some NPM CLI configuration or NPM config configuration items, proxy proxy, etc

This paper is just a simple copy of the project template we have configured, and these detailed configurations are not needed for the time being. If you need to ensure that the dependent downloads of scaffolding are consistent, you can use the config configuration source in npmrc.js, etc., but there is no more description here.

npmrc.js

#! /usr/bin/env node
require('.. /packages/create/index')
Copy the code

#! /usr/bin/env node, in order to solve the problem of different user node path, can have the system to dynamically find node to execute your script file. require(‘.. /packages/create/index’).js: /packages/create/index’

Add create instruction

Next we add the following code to package.json

"bin": {
  "create-cli": "./bin/npmrc.js"
}
Copy the code

Here bin is the folder we used to create nPM-aware directives, and create-cli is the directive we used to run npmrc.js, that is, js to create scaffolding projects!

Create scaffold templates

This is the scaffolding template, that is, through vue-CLI itself added some common modules such as background management system, encapsulated custom components, common verification instructions, custom tool function library, Webpack multi-module configuration, permission verification, AXIos packaging, etc., extracted and encapsulated into a project template and put in the scaffolding. You can name the template as follows: template-xxx-xxx-xx template name, create the template in the create folder, convenient for us to copy 👇

Js to create the scaffold project

Create the Packages folder in the package.json sibling directory

mkdir packages
Copy the code

Create the create folder under the packages folder you just created

mkdir create
Copy the code

Used to indicate the creation of the project content, readers can also according to their own custom instructions to adjust.

The next step is to create index.js, which is the js used to create the scaffolding project. The core methods in index.js are annotated below and the entire code is posted at the end for readers to understand.

Introducing corresponding dependencies

#! /usr/bin/env node
// Introduce the query operation library prompt
const { prompt } = require('enquirer');
//node handles the file manipulation module
const fs =require('fs');
// Node handles the file path module
const path = require('path');
// The terminal path of the calling instruction
const cwd=process.cwd();
// A library that executes terminal instructions by creating child processes
const execa=require('execa');
// Progress bar library for terminal display
let progressBar = require('progress');
// Single line log through control flow
let log=require('single-line-log').stdout;
// Introduce terminal colorful colors
require('colors');
Copy the code

Package. json, because NPM will only download the dependencies in the outermost package.json by default after uploading, otherwise it will not find dependencies when calling the command after uploading!

Write the steps and select the project template

let projectname = await prompt({
    type: 'input'.name: 'projectName'.message:'projectName/ projectName '.initial:"vue3-vite-cli",})// contains an override query to verify that the name-security checksum and instruction call hierarchy has the same file name.
const projectName=await checkProjectName(projectname.projectName)
let selectTemplate =  await prompt({
    type: 'select'.name: 'ProjectTemplate'.message: 'project-template/Select Project template'.initial:"vue3-vite-cli".choices: [{name: Templates - 'XXX'},// Use the template name shown to the user
        { name: Templates - 'XXX'}]});Copy the code

Note that the name of the template must be as clear as possible, and must be the same as XXX of the template name created by create. Then the user does the selection directly after the same name splicing copy corresponding project template folder.

const templateDir = path.join(__dirname, `template-${selectTemplate.ProjectTemplate}`)
let root =path.join(cwd,projectName);// Get the call instruction and the concatenation address of the passed project name
console.log(`\nScaffolding project in ${projectName}. / create${projectName}In the project... `)
emptyDir(root); // Empty the folders and files of the incoming address
Copy the code

Copy progress bar implementation

calculateCount(path.join(templateDir)); // Count the quantity
bar =new progressBar('Current creation progress: :bar :percent ', { total: copyCount ,
    complete: "█".incomplete:"░".width: 30});Copy the code

Copy Template file

await copy(path.join(templateDir), root);
Copy the code

Determine which package manager the user is currently downloading

const pkg = require(path.join(templateDir, `package.json`))
pkg.name = projectName
const pkgManager = /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm'
console.log('\nDone. Now run/ over. Now run: ')
console.log(`\nDownloading dependencies... / Downloading dependencies... `)
let downShell=pkgManager === 'yarn' ? ' ' : 'install';
console.log('\nrunning/ running:${pkgManager+""+downShell}` );
Copy the code

Print dependent download process results through EXECl

const downResult = await execa(`${pkgManager}`, [downShell],{cwd:path.relative(cwd, root),stdio:'inherit'});
if(downResult.failed){
    console.error('\nFailed to download dependencies/);
    console.log(`${pkgManager === 'yarn' ? `yarn` : `npm install`}\n`)}else{
    console.log(`Depend on the download is complete! / Dependency download complete! 🥳 `)}Copy the code

Synchronize the child thread logs that perform the download dependent to the parent thread through the parent-child thread communication

The complete code

#! /usr/bin/env node const { prompt } = require('enquirer'); const fs =require('fs'); const path = require('path'); const cwd=process.cwd(); const execa=require('execa'); let progressBar = require('progress'); let log=require('single-line-log').stdout; require('colors'); let copyCount=0; // Let copySchedule=0; // Copy progress let bar; Async function init() {try{const renameFiles = {_gitignore: async function init() {try{const renameFiles = {_gitignore: '.gitignore' } let projectname = await prompt({ type: 'input', name: 'projectName', message:'projectName/ projectName', initial:"vue3-vite-cli", }) const projectName=await checkProjectName(projectname.projectName) let selectTemplate = await prompt({ type: 'select', name: 'ProjectTemplate', message: 'project-template/select Project template ', initial:" vue3-viet-cli ", choices: [{name: 'vue3-ts-initial'}, { name: 'webpack-protist-js'}, ] }); const templateDir = path.join(__dirname, `template-${selectTemplate.ProjectTemplate}`) let root =path.join(cwd,projectName); console.log(`\nScaffolding project in ${projectName}... / create ${projectName} project... `) emptyDir(root); calculateCount(path.join(templateDir)); Bar =new progressBar('Current creation progress: :bar :percent ', {total: copyCount, complete: "█", incomplete:" Self-destruct ", width: 30,}); await copy(path.join(templateDir), root); const pkg = require(path.join(templateDir, `package.json`)) pkg.name = projectName const pkgManager = /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'NPM' console.log(' \nDone. Now run/ finished. Now run: ') console.log(' \nDownloading dependencies... / Downloading dependencies... `) let downShell=pkgManager === 'yarn' ? '' : 'install'; Console. log(' \nrunning/ running :${pkgManager+" "+downShell} '); const downResult = await execa(`${pkgManager}`, [downShell],{cwd:path.relative(cwd, root),stdio:'inherit'}); If (downresult. failed){console.error('\nFailed to download dependencies '); console.log(`${pkgManager === 'yarn' ? `yarn` : `npm install`}\n`) }else{ console.log(`Depend on the download is complete! / Dependency download complete! 🥳 ')} if (root! == cwd) { console.log(`\ncd ${path.relative(cwd, root)}`.green) } console.log(`${pkgManager === 'yarn' ? `yarn dev` : `npm run dev`}\n`.green) }catch (e) { } } async function copy(src, Dest) {const stat = fs.statsync (SRC) if (stat.isdirectory ()) {// Is a directory instead of a file. copyDir(src, dest) } else { copySchedule++; Await fs.copyfilesync (SRC, dest)} if (bar.complete) {log('\nCreated/ created completed \n'.green); }else{ bar.tick(copySchedule/(copyCount/100)); } } async function checkProjectName(projectName) { const packageNameRegExp = /^(? :@[a-z0-9-*~][a-z0-9-*._~]*/)? [a-z0-9-~][a-z0-9-._~]*$/ if (packageNameRegExp.test(projectName)) { console.log(path.join(cwd, projectName)); if(fs.existsSync(path.join(cwd,projectName))){ let coverQuerySelect = await prompt({ type: 'select', name: 'coverQuery', message: 'The current file name already exists, do you want to overwrite it? / The file name already exists. Do you want to overwrite it? 'initial: "yes/confirmed," choices: [{name:' yes'}, {name: 'no'},]}); if (coverQuerySelect.coverQuery == 'no') { prompt.stop(); } } return projectName } else { const suggestedPackageName = projectName .trim() .toLowerCase() .replace(/\s+/g, '-') .replace(/^[._]/, '') .replace(/[^a-z0-9-~]+/g, '-') /** console.log(`@type {{ inputPackageName: string }} */ const { inputPackageName } = await prompt({ type: 'input', name: 'inputPackageName', message: `Package name:`, initial: suggestedPackageName, validate: (input) => packageNameRegExp.test(input) ? true : 'Invalid package.json name' }) return inputPackageName } } 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)}} /** * calculateCount(srcDir){if (fs.statsync (srcDir).isdirectory ()) {// If is a directory Not documents. */ function dirCount(srcDir) {copyCount+= fs.readdirsync (srcDir).length; for (const file of fs.readdirSync(srcDir)) { const srcFile = path.resolve(srcDir, file) calculateCount(srcFile) } } function emptyDir(dir) { if (! fs.existsSync(dir)) { return } for (const file of fs.readdirSync(dir)) { const abs = path.resolve(dir, file) if (fs.lstatSync(abs).isDirectory()) { emptyDir(abs) fs.rmdirSync(abs) } else { fs.unlinkSync(abs) } } } init();Copy the code

Subsequent readers can then create a template using the above format and run Node index.js to try to copy the project template. Of course, this is not our final result, we want to directly create-CLI to call our scaffolding creation instructions, the main game!

Upload the NPM

Modify the NPM source before uploading

npm config set registry https://registry.npmjs.org
Copy the code

If it is a newly created NPM account, please verify your email address first, otherwise 500 will be displayed when shell login

Then the NPM upload ignore file has its own.npmignore file with a higher priority than.gitignore but if it doesn’t, nPMignore uses.gitignore ignore

It is important to note that each upload requires an update of the packes. json version number, not the same as the last

NPM landing

Readers who do not have an NPM account can first register an account on the official website

Readers who already have an account go to the next step

npm login
Copy the code

You will need to enter your account number, password and email address.

If you still can’t log in, you may need VPN help

NPM package tests locally

It is recommended to test the NPM package to be uploaded locally before uploading it

// Go to the project directory to test
cd vue3-vite-cli // Here is the project name
npm link vue3-vite-cli // Deploy to local global // go to other project directory levels
cd test // Enter a test directory to test
npm link // Since I am local global so call my create-cli directly just ok
Copy the code

NPM upload

npm publish
Copy the code

Use scaffolding

After the NPM package is successfully uploaded, you can log in to the NPM official website to check whether the package is successfully uploaded

And then you can go through

    npm i vite-vue3-cli -g // Readers need to change to their own package name
Copy the code

The call comes after the scaffolding is downloaded and installed globally

 create-cli 
Copy the code

Instructions for creating projects in scaffolding

Expand knowledge

Next, if readers want to open source their own scaffolding, it is necessary to configure the following, such as open source agreement, scaffolding documentation official website, Github issues specifications, etc., these things will be explained below

MIT Open Source Agreement

Create an MIT certificate for the LICENSE in the package.json equivalent directory using the example below, replacing the time and account name. Here’s the MIT website

Begin license text.


Copyright <YEAR> <COPYRIGHT HOLDER>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


End license text.

Issues Template specification

If you want to open your scaffolding to Github and ensure that issues are represented accurately, you can create a. Github folder in your project’s root directory

Then create the ISSUE_TEMPLATE folder in the. Github folder and create xxx_issues.md file below to write the fixed issues format for users to fill in quickly. Examples are as follows:

-- Please submit your issues according to the template rules -- please submit issues strictly according to the following template, <! - Your environment -> for example: MAC XXX <! - Your problem -> For example: my page switch will be blank <! 2. Clicking 'my' TAB toggle will cause a white screen for one second while my code is as follows: js 666666666 <! - Expected result -> Example: There is no problem with screen cutting ~.Copy the code

You can now see the template you just uploaded in Github Create Issues

Use vitePress to build the official website

There will be a separate article about building the official website of vitePress later.

conclusion

Since then, the whole process of scaffolding construction has been described. In fact, the scaffolding construction is very different from the use of third-party libraries and business projects. As Yog said, open source requires a different skill tree. In addition to the overall project reference vite project source layer, is also part of the vite source code handwritten implementation. Finally, attached is my own scaffold git address :vue3-vite- CLI document address, I hope friends can dot star to support the author. I will bring you a better article later! Work together and progress together, I am Jiang!

Next announcement: vitePress official website construction