preface

Recently, I have been studying some things of front-end engineering, so I need to have my own set of scaffolding, so I took aim at vue-CLI, and also looked at create-React-app. I felt that VUe-CLI was more in line with my expectation, so I stroked through the source code and wrote a small demo scaffolding.

Offer source address: source

Experience method:

$ npm i masoneast-cli -g
$ masoneast init my-project
Copy the code

Now, let’s see what vue-CLI does for us, allowing us to generate a project with a single command.

The whole process

Let’s take a look at how to use vue-CLI, and then go through the implementation of each step.

Vue-cli provides a variety of templates, we use the WebPack template as an example here.

  • Install: NPM install vue-cli -g

  • Use:

    1. Download directly to use:vue init webpack my-project
    2. Offline use:vue init webpack my-projiect --offline
    3. Clone using:vue init webpack my-projiect --clone

In this way, we can get a vue initial project in the current directory.

When we use vue-cli, we rely on two things: the vue-cli command line and the Vue-template template, which is used to generate the project.

Process:

  1. When we install vue-CLI globally, we register environment variables and generate a soft connection so that we can use the command from any path on the command line.
  2. When weKnock downvue init webpack my-projectwhen.vue-cliYou will be prompted that you are downloading the template.

In this case, vue-cli is a Download of the WebPack template from github hosted code. The git address for the corresponding WebPack template is here: WebPack template

The concatenation URL code is this:

function getUrl (repo, clone) {
    var url

    // Get origin with protocol and add trailing slash or colon (for ssh)
    var origin = addProtocol(repo.origin, clone)
    if (/^git\@/i.test(origin))
        origin = origin + ':'
    else
        origin = origin + '/'

    // Build url
    if (clone) {
        url = origin + repo.owner + '/' + repo.name + '.git'
    } else {
        if (repo.type === 'github')
            url = origin + repo.owner + '/' + repo.name + '/archive/' + repo.checkout + '.zip'
        else if (repo.type === 'gitlab')
            url = origin + repo.owner + '/' + repo.name + '/repository/archive.zip? ref=' + repo.checkout
        else if (repo.type === 'bitbucket')
            url = origin + repo.owner + '/' + repo.name + '/get/' + repo.checkout + '.zip'
    }

    return url
}
Copy the code
  1. After the template is downloaded.vue-cliWill keep it in your local directory so you can use it offline to build projects later/Users/xxx/.vue-templatesIf you’ve used it beforevue-cliGenerated project, should be in your administrator path can find the corresponding.vue-templatesFolder. The webpack file is the same as the code in the git address above.

Note: The. Folder is hidden by default, you need to show it to see it.

  1. Ask interaction

Next, Vue-CLI will ask you a bunch of questions, the questions you answer it will save their answers, and in the next generation, it will render the corresponding file based on your answers.

  1. File selection

After you have answered the question, vue-CLI will filter the useless files from the WebPack template according to your needs and delete them, not from your local deletion, but from the generated project for you.

  1. Template rendering

In the template, your SRC/app.vue looks like this:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    {{#router}}
    <router-view/>
    {{else}}
    <HelloWorld/>
    {{/router}}
  </div>
</template>

<script>
{{#unless router}}
import HelloWorld from './components/HelloWorld'

{{/unless}}
export default {
  name: 'App'{{#router}}{{else}},
  components: {
    HelloWorld
  }{{/router}}
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
Copy the code

If you choose yes when choosing whether to route, the app. vue generated in your project will look like this:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
Copy the code

It will render different files to you according to your requirements.

  1. Files are generated

After rendering, the next step is to generate the corresponding file in your current directory, so the vue-cli is done.

implement

Now that we know how vue-CLI works, we can make a simpler CLI by ourselves.

The command register

Generate your package.json file with NPM init and add bin to it

  "bin": {
    "xxx": "bin/index.js"
  },
Copy the code

This way, you will register your XXX command in the environment variable when you load the package globally.

Next comes bin/index.js.

usecommanderThe command on the cli is completed

program
   .command('init [project-name]')
   .description('create a project')
   .option("-c, --clone".`it will clone from ${tmpUrl}`)
   .option('--offline'.'use cached template')
   .action(function (name, options) {
       console.log('we are try to create "%s".... ', name);
       downloadAndGenerate(name, options)
   }).on('--help'.function () {
       console.log(' ');
       console.log('Examples:');
       console.log(' ');
       console.log(' $ masoneast init my-project');
       console.log(`  $ path: ${home}`);
   });

program.parse(process.argv)
Copy the code

With this code, you have the init command, clone, and offline arguments, and you have:

$ masoneast init my-project
$ masoneast init my-project --clone
$ masoneast init my-project --offline
Copy the code

For details on the use of the Commander package, see commander

Implement download and Clone templates

Here you need to have a template address for you to download and clone, if you are just playing around you can also directly use vue template address, or my template address: Template

Download the implementation code:

This relies on two libraries: git-Clone and Download.

function download (name, clone, fn) {
    if (clone) {
        gitclone(tmpUrl, tmpPath, err => {
            if (err) fn(err)
            rm(tmpPath + '/.git')
            fn()
        })
    } else {
        const url = tmpUrl.replace(/\.git*/.' ') + '/archive/master.zip'
        console.log(url)
        downloadUrl(url, tmpPath, { extract: true.strip: 1.mode: '666'.headers: { accept: 'application/zip' } })
            .then(function (data) {
                fn()
            })
            .catch(function (err) {
                fn(err)
            })
    }
}
Copy the code

Implementation of inquiry interaction

The implementation of the interaction relies heavily on the Inquirer library.

function askQuestion (prompts) {                    // Ask for interaction
    return (files, metalsmith, done) = > {
        async.eachSeries(Object.keys(prompts), (key, next) => {
            prompt(metalsmith.metadata(), key, prompts[key], next)
        }, done)
    }
}
Copy the code

Store the answer to the question for later rendering

function prompt (data, key, prompt, done) {                    // Store user actions to metaData
    inquirer.prompt([{
        type: prompt.type,
        name: key,
        message: prompt.message || prompt.label || key,
        default: prompt.default,
        choices: prompt.choices || [],
        validate: prompt.validate || (() = > true)
    }]).then(answers= > {
        if (Array.isArray(answers[key])) {
            data[key] = {}
            answers[key].forEach(multiChoiceAnswer= > {
                data[key][multiChoiceAnswer] = true})}else if (typeof answers[key] === 'string') {
            data[key] = answers[key].replace(/"/g.'\ \ "')}else {
            data[key] = answers[key]
        }
        done()
    }).catch(done)
}
Copy the code

Implement template rendering

Template rendering relies on handleBar, the front-end template engine, and Consolidate, the library of the parsing template engine. {{#router}} = handlebar;}

function renderTemplateFiles () {

    return (files, metalsmith, done) = > {

        const keys = Object.keys(files)
        const metalsmithMetadata = metalsmith.metadata()            // This is where data from previous user operations are stored
        async.each(keys, (file, next) => {                          // Iterate through the template to find the file that needs to be rendered
            const str = files[file].contents.toString()
            if (!/{{([^{}]+)}}/g.test(str)) {                       // Regex matches the contents of the file. If not, go to the next file without modifying the file
                return next()
            }
            render(str, metalsmithMetadata, (err, res) => {
                if (err) {
                    err.message = ` [${file}] ${err.message}`
                    return next(err)
                }
                files[file].contents = new Buffer(res)
                next()
            })
        }, done)
    }
}
Copy the code

Implementation writes files from local to your project directory

A core library is used here: MetalSmith. Its main function is to read your file, and through a series of middleware to your file processing, and then write to the path you want. It is through this library that our processes are linked together to achieve the transformation of the template and write the project you want.

    metalsmith.use(askQuestion(options.prompts))                            // This section is the essence of generator, which processes the template selected by the user through various middleware
        .use(filterFiles(options.filters))                                  // File filtering
        .use(renderTemplateFiles())                                         // Render the template internal variables
        .source('. ')
        .destination(projectPath)                                            // The path where the project was created
        .build((err, files) = > {
            if (err) console.log(err)

        })
Copy the code

The latter

Here I realize the demo is vue-CLI compact version, the main functions are:

    1. Download and Clone the project template from Git
    1. Save the template to the local PC for offline use
    1. Ask questions and customize templates to meet user needs

Vue-cli also has a lot of fault tolerance, as well as other templates, download sources and so on switching I haven’t done here.

This Masoneast-CLI is what I learned from reading vue-CLI source code, here is a summary. If it is helpful to everyone, conveniently give a star✨.