preface

Some time ago to see some VUE-CLI source code, the harvest is quite deep. I wanted to find a time to update an article, but there were too many things recently, so I didn’t have time to sort out these things. Take advantage of these two days idle down, then sorted out, and then share with you. If you read it, like I harvest a lot of words, but also hope you friends more like collection support oh.

Vue – cli is introduced

Vue-cli is an excellent tool for quickly building Vue-based Web applications. Unlike tools such as Creat-React-app, developers only need to focus on the code of the project logic, and don’t need to worry about webpack packing, starting Node services, and so on. Vue-cli is a development tool based on templates, is to copy other people’s project structure, all the configuration is exposed, you can do some configuration modification according to the actual situation, more flexible and free. Of course, this puts more demands on the front end engineers, and more things to think about. Vue-cli, however, is about to be released in version 3.0. Vue-cli will be completely changed, following the model of tools like Creat-React-app, where developers only need to focus on the code of the project logic. But 3.0 has not yet come out, so this source analysis I use the v2.9.3 source code, that is, 2.0 code. Boys in the back should pay attention to the following when reading.

Vue-cli project structure

The directory structure of the entire project is shown in the figure above, and I’ll give you an overview of what each folder does.

  • Bin (vue command files such as vue init are controlled from here.)

  • Docs (some things to note, not important directory, can be ignored.)

  • Lib (this is where some of the custom methods needed for vue-CLI are stored.)

  • Node_modules (here should not need me to say more, I believe we all know, don’t know if you can go to the wall! Low – low)

  • Test (unit test development vue-CLI tools will be used, we can read the source code can be directly ignored.)

  • Miscellaneous stuff (esLint configuration,.gitignore, LICENSE, etc.) can be ignored without disturbing the source code.

  • Package. json/ readme. md Low – low)

In general, we only need to focus on the bin and lib source code, the rest can be ignored. Start your reading journey

Vue-cli source code reading tour

Before I start reading the source code, I’ll first introduce a tool (Commander) that works with the command line. The use of specific methods to view lot README. Md https://github.com/tj/commander.js. Before you read the following, it is recommended that you know something about Commander. We will not go into the details of Commander here. Vue-cli is written in the Git style of Commander. Vue files process vue commands, vue-init processes vue init commands, and so on. Then we went through it one command at a time.

vue

Introduced packages:

  • Commander (for processing the command line.)

Vue this file code is very little, I directly posted out.

#! /usr/bin/env node

require('commander')
  .version(require('.. /package').version)
  .usage('<command> [options]')
  .command('init'.'generate a new project from a template')
  .command('list'.'list available official templates')
  .command('build'.'prototype a new project')
  .parse(process.argv)
Copy the code

This file is mainly used to display the instructions of parameters on the terminal when the user enters “vue”. Specific writing may refer to https://github.com/tj/commander.js.

vue build

Introduced packages:

  • Chalk (used to highlight information printed from the terminal.)

The vue build command has been removed from vue-cli. There’s not much code, so I’ll just post it.


const chalk = require('chalk')

console.log(chalk.yellow(
  '\n' +
  ' We are slimming down vue-cli to optimize the initial installation by ' +
  'removing the `vue build` command.\n' +
  '  Check out Poi (https://github.com/egoist/poi) which offers the same functionality!' +
  '\n'
))
Copy the code

vue list

#! /usr/bin/env node
const logger = require('.. /lib/logger')
const request = require('request')
const chalk = require('chalk')

/**
 * Padding.
 */

console.log()
process.on('exit', () => {
  console.log()
})

/**
 * List repos.
 */

request({
  url: 'https://api.github.com/users/vuejs-templates/repos',
  headers: {
    'User-Agent': 'vue-cli'
  }
}, (err, res, body) => {
  if (err) logger.fatal(err)
  const requestBody = JSON.parse(body)
  if (Array.isArray(requestBody)) {
    console.log(' Available official templates:')
    console.log()
    requestBody.forEach(repo => {
      console.log(
        ' ' + chalk.yellow('★') +
        ' ' + chalk.blue(repo.name) +
        The '-' + repo.description)
    })
  } else {
    console.error(requestBody.message)
  }
})
Copy the code

Introduced packages:

  • Request (tool for sending HTTP requests.)
  • Chalk (used to highlight the information printed from console.log.)
  • Logger (Custom tool – for log printing.)

Function: When entering “vue list” (we can directly enter “bin/vue-list” on the terminal under the current source file directory), VUe-CLI will request the interface to obtain the official template information, and then do some processing, display the template name and corresponding description on the terminal.

The effect is as follows:

Available official templates: ★ Browserify - A full-featured Browserify + vueify setup with hot-reload, Linting & Unit testing. ★ Browserify -simple - A simple Browserify + vueify setupfor★ PWA-PWA templateforVue-cli based on the webpack Template ★ Simple - the simplest possible vue setupinA single HTML file ★ Webpack-A full-featured Webpack + Vue -loader setup with hot Reload, Linting, Testing & CSS Extraction. ★ Webpack-simple - A simple Webpack + VUe-loader setupfor quick prototyping.
Copy the code

vue init

“Vue init” is the command used to build the project, which is also the core file of vuE-CLI. The above three are very simple commands, which are an appetizer for us to read the source code, the real feast here.

The working process

Before getting into the code, we’ll go through the flow of the vuE-CLI initial project, and then we’ll walk through the flow step by step.

The general flow of vue Init is shown in the diagram above, which should be relatively easy to understand. Here I will outline the general process.

  1. Vue-cli determines whether your template is in a remote Github repository or in a local file. If it is in a local folder, vue-CLI immediately jumps to step 3, and if it is in a local folder, it jumps to Step 2.

  2. Step 2 determines whether it is an official template, which will be downloaded from the official Github repository to the local default repository, the root directory. Vue-templates

  3. The third step is to read the meta. Js or meta. Json file in the template directory.

  4. Based on the template content and the developer’s responses, the project structure is rendered and generated to the specified directory.

Source content

There’s a lot of code in vue-init, so I’m just going to break it up a little bit. First of all, I listed the structure of the entire document for the convenience of subsequent reading.

/** * const program = require()'commander')... /** * Select */ program.usage ('<template-name> [project-name]')
      .option('-c, --clone'.'use git clone')
      .option('--offline'.'use cached template'/** * Defines commanderhelpCheck method * / program.'--help', () => {
      console.log(' Examples:')
      console.log()
      console.log(chalk.gray(' # create a new project with an official template'))
      console.log(' $ vue init webpack my-project')
      console.log()
      console.log(chalk.gray(' # create a new project straight from a github template'))
      console.log(' $ vue init username/repo my-project')
      console.log()
    })
    
    
    function help () {
      program.parse(process.argv)
      if (program.args.length < 1) returnProgram.help () // If no parameter is entered, the terminal displays help}help() /** * defines a bunch of variables */lettemplate = program.args[0] ... /** * Determine whether to enter the project name yes - directly execute the run function no - ask the developer whether to generate the project in the current directory, the developer answer "yes" also execute the run function otherwise do not execute the run function */ ** * define the main function run */function run() {... } /** * defines the function downloadAndGenerate */ to download the template and produce the projectfunction downloadAndGenerate(){
          ...
      }
Copy the code

The general outline of the entire file is shown above, and we will look at it piece by piece later.

A bunch of packages introduced

const download = require('download-git-repo') // GitHub, GitLab, Bitbucket const program = require('commander'// const exists = require('fs'ExistsSync // The existsSync method in the fs module of node is used to check whether a path exists. Const path = require(const path = require('path'Const ora = require(const ora = require() const ora = require()'ora'Const home = require() const home = require()'user-home'Const tildify = require() const tildify = require()'tildify'Const chalk = require() const chalk = require('chalk'Const inquirer = require(const inquirer = require()'inquirer'Const rm = require(const rm = require()'rimraf').sync // equivalent to UNIX "rm -rf" const logger = require('.. /lib/logger'Const generate = require(const generate = require()'.. /lib/generate') // Custom tools - for building projects based on templates const checkVersion = require('.. /lib/check-version'Const warnings = require(const warnings = require(const warnings = require())'.. /lib/warnings') // Custom tool - warning for templates constlocalPath = require('.. /lib/local-path') // Custom tools - for handling paths const isLocalPath =localIsLocalPath // Check whether it is a local Path const getTemplatePath =localPath.getTemplatePath // Obtain the absolute Path of the local templateCopy the code

A bunch of variables defined

letTemplate = program.args[0] // hasSlash = template.indexof ('/'< span style = "box-sizing: border-box; color: RGB (51, 51, 51); line-height: 20px; font-size: 13px! Important; white-space: normal;inPlace = ! rawName || rawName ==='. '// Do not write or ". Const name = denotes the build project under the current directoryinPlace ? path.relative('.. / 'Process. CWD ()) : rawName / / if construction projects under the current directory, the directory called project build directory name, or the current directory as part of the directory for the project build directory name rawName 】 【 const to = path. Resolve (rawName | |'. ') // The absolute path to the project build directory constclone = program.clone || false// Whether to usecloneMode, const TMP = path.join(home,'.vue-templates', template.replace(/[\/:]/g, The '-') // Remote template download path to localCopy the code

Main logic

if (inPlace || exists(to)) {
  inquirer.prompt([{
    type: 'confirm',
    message: inPlace
      ? 'Generate project in current directory? '
      : 'Target directory exists. Continue? ',
    name: 'ok'
  }]).then(answers => {
    if (answers.ok) {
      run()
    }
  }).catch(logger.fatal)
} else {
  run()
}
Copy the code

For the above code, vue-CLI determines inPlace and exists(to), asks the developer if true, executes run when the developer answers “yes”, otherwise executes run directly. Here are two questions for developers:

  • Generate project in current directory? // Whether to build the project under the current directory

  • Target directory exists. Continue? // The build directory already exists

These two problems rely on the variable inPlace, and let’s look at where the variable inPlace comes from.

Const rawName = program.args[1] //rawName is the second argument to the command line (relative to the project build directory) constinPlace = ! rawName || rawName ==='. '//rawName exists or is ". Is considered to be built under the current directoryCopy the code

If inPlace is true, Generate project in current directory will be displayed. If inPlace is false, Target directory exists(to) must be true. .

The Run function

Logic:

Source:

function run () {
  // check if template is local
  ifConst templatePath = getTemplatePath(template) // Obtain the absolute pathifGenerate (name, templatePath, to, err => {if (err) logger.fatal(err)
        console.log()
        logger.success('Generated "%s".', name)
      })
    } else{// Prints an error log indicating that the local template does not exist logger.fatal('Local template "%s" not found.', template)
    }
  } else{checkVersion(() => {// Check the version numberif(! HasSlash) {// Use official templates // Use official templates // Download git-repo https://github.com/vuejs-templates const officialTemplate ='vuejs-templates/' + template
        if (template.indexOf(The '#')! == -1) {// Whether the template name is included"#"DownloadAndGenerate (officialTemplate)else {
          if (template.indexOf('2.0')! == -1) {// Yes"2.0"/ / warning warnings. V2SuffixTemplatesDeprecated (template,inPlace ? ' ' : name)
            return
          }

          // warnings.v2BranchIsNowDefault(template, inPlace ? ' ': name) downloadAndGenerate(officialTemplate)// Download template}}else{downloadAndGenerate(template)}}}Copy the code

DownloadAndGenerate function

function downloadAndGenerate (template) {
  const spinner = ora('downloading template'Spinner.start ()// display load status // Removeif local template exists
  if(exists(TMP)) rm(TMP) // Whether the template exists in the current template library. If the template exists, delete it. // Download the template template-template name tmp-template pathclone- Whether to use GitcloneErr - Error message download(template, TMP, {clone}, err => {spinner.stop() // Hide the loading status // Print the error log if there is an errorif (err) logger.fatal('Failed to download repo ' + template + ':'+ err.message.trim()) generate(name, TMP, to, err => {if (err) logger.fatal(err)
      console.log()
      logger.success('Generated "%s".', name)
    })
  })
}
Copy the code

lib

The generate. Js (u)

Lib file under the most important JS file, he is the most important part of our construction project, according to the template rendering into the project we need. This is where we need to focus our attention.

const chalk = require('chalk')
const Metalsmith = require('metalsmith')
const Handlebars = require('handlebars')
const async = require('async')
const render = require('consolidate').handlebars.render
const path = require('path')
const multimatch = require('multimatch')
const getOptions = require('./options')
const ask = require('./ask')
const filter = require('./filter')
const logger = require('./logger') / / register handlebars helper registered handlebars helper handlebars. RegisterHelper ('if_eq'.function (a, b, opts) {
  return a === b
    ? opts.fn(this)
    : opts.inverse(this)
})

Handlebars.registerHelper('unless_eq'.function (a, b, opts) {
  return a === b
    ? opts.inverse(this)
    : opts.fn(this)
})

/**
 * Generate a template given a `src` and `dest`.
 *
 * @param {String} name
 * @param {String} src
 * @param {String} dest
 * @param {Function} done
 */

module.exports = function generate (name, src, dest, done) {const opts = getOptions(name, SRC) // get configuration const metalsmith = metalsmith (path.join(SRC, SRC)'template'// Initialize Metalsmith const data = object.assign (metalsmith.metadata(), {destDirName: name,inPlace: dest === process.cwd(),
    noEscape: true})// Add some variables to metalsmith, Helper opts.helpers && object.keys (opts.helpers).map(key => { Handlebars. RegisterHelper (key, opts. Helpers [key])}) const helpers = {chalk, logger} / / configuration object before function, is performedif (opts.metalsmith && typeof opts.metalsmith.before === 'function') { opts.metalsmith.before(metalsmith, opts, Help metalsmith.use(askQuestions(opts.prompts)). Use (filterFiles(opts.filters)) .use(renderTemplateFiles(opts.skipinterpolation)) // Render template file // Configure object after function if yes, executeif (typeof opts.metalsmith === 'function') {
    opts.metalsmith(metalsmith, opts, helpers)
  } else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
    opts.metalsmith.after(metalsmith, opts, helpers)
  }

  metalsmith.clean(false) 
    .source('. ') // start from template root instead of `./src` which is Metalsmith's default for `source` .destination(dest) .build((err, files) => { done(err) if (typeof opts.complete === 'functionConst helpers = {chalk, logger, files} opts.complete(data, Helpers)} else {helpers) Execute logMessage function logMessage(opts.completeMessage, data) } }) return data } /** * Create a middleware for asking questions. * * @param {Object} prompts * @return {Function} */ function askQuestions (prompts) { return (files, metalsmith, done) => { ask(prompts, metalsmith.metadata(), done) } } /** * Create a middleware for filtering files. * * @param {Object} filters * @return {Function} */ function filterFiles (filters) { return (files, metalsmith, done) => { filter(files, filters, metalsmith.metadata(), done) } } /** * Template in place plugin. * * @param {Object} files * @param {Metalsmith} metalsmith * @param {Function}  done */ function renderTemplateFiles (skipInterpolation) { skipInterpolation = typeof skipInterpolation === 'string'? [skipInterpolation] : SkipInterpolation // skipInterpolation is an array return (files, metalsmith, Done) => {const keys = object.keys (files) // Get all keys of files const metalsmithMetadata = metalsmith.metadata() Async. Each (keys, (file, Next) => {// TMP files with skipaccommodation // skipping files with skipaccommodation // Skip file if with skipaccommodation (skipInterpolation && multimatch([file], skipInterpolation, { dot: Const STR = files[file].contents.tostring () // Do not attempt to render Files that do not have mustaches // skip file if (! ([^ / {{{}] +)}} / g.t est (STR)) {return next ()} / / render files render (STR, metalsmithMetadata, (err, res) => { if (err) { err.message = `[${file}] ${err.message}` return next(err) } files[file].contents = new Buffer(res) next() }) }, done) } } /** * Display template complete message. * * @param {String} message * @param {Object} data */ function logMessage (message, data) { if (! Render (message, data, (err, res) => {if (err) {console.error('\n   Error when rendering template complete message: '+ err.message.trim()) // Render error prints error message} else {console.log(')\n' + res.split(/\r? \n/g).map(line => '   ' + line).join('\n')) // Render successfully prints final render results}})}Copy the code

Introduced packages:

  • Chalk (used to highlight information printed from the terminal.)
  • Metalsmith (Static web site builder.)
  • Handlebars (a well-known template engine)
  • Async (very powerful asynchronous processing tool)
  • Consolidate (supports various template engines for rendering.)
  • Path (Node has its own path module for processing paths.)
  • Multimatch (allows for matching of multiple conditions.)
  • Options (Custom tool – used to get template configuration.)
  • Ask (Custom tool – for asking developers.)
  • Filter (Custom tool – for file filtering.)
  • Logger (Custom tool – for log printing.)

The main logic:

Get the template configuration –> initialize Metalsmith –> add some variables to Metalsmith –> Handlebars template registration helper –> Config object if there is before function, If yes, execute –> Ask questions –> Filter file –> Render template file –> Whether there is after function in the configuration object, execute –> Finally build project content –> Complete, successful if there is complete function in the configuration object, execute. Otherwise print the completeMessage message in the configuration object, and execute the callback done(err) if there is an error.

Other functions:

  • askQuestions
  • FilterFiles: filters files
  • RenderTemplateFiles: renderTemplateFiles
  • LogMessage: Prints information when the build is successful

Metalsmith plug-in format:

function <function name> {
  return (files,metalsmith,done)=>{// logic code... }}Copy the code

options.js

const path = require('path')
const metadata = require('read-metadata')
const exists = require('fs').existsSync
const getGitUser = require('./git-user')
const validateName = require('validate-npm-package-name')

/**
 * Read prompts metadata.
 *
 * @param {String} dir
 * @return {Object}
 */

module.exports = function options (name, dir) {
  const opts = getMetadata(dir)

  setDefault(opts, 'name', name)
  setValidateName(opts)

  const author = getGitUser()
  if (author) {
    setDefault(opts, 'author', author)
  }

  return opts
}

/**
 * Gets the metadata from either a meta.json or meta.js file.
 *
 * @param  {String} dir
 * @return {Object}
 */

function getMetadata (dir) {
  const json = path.join(dir, 'meta.json')
  const js = path.join(dir, 'meta.js')
  let opts = {}

  if (exists(json)) {
    opts = metadata.sync(json)
  } else if (exists(js)) {
    const req = require(path.resolve(js))
    if(req ! == Object(req)) { throw new Error('meta.js needs to expose an object')
    }
    opts = req
  }

  return opts
}

/**
 * Set the default value for a prompt question
 *
 * @param {Object} opts
 * @param {String} key
 * @param {String} val
 */

function setDefault (opts, key, val) {
  if (opts.schema) {
    opts.prompts = opts.schema
    delete opts.schema
  }
  const prompts = opts.prompts || (opts.prompts = {})
  if(! prompts[key] || typeof prompts[key] ! = ='object') {
    prompts[key] = {
      'type': 'string'.'default': val
    }
  } else {
    prompts[key]['default'] = val
  }
}

function setValidateName (opts) {
  const name = opts.prompts.name
  const customValidate = name.validate
  name.validate = name => {
    const its = validateName(name)
    if(! its.validForNewPackages) { const errors = (its.errors || []).concat(its.warnings || [])return 'Sorry, ' + errors.join(' and ') + '. '
    }
    if (typeof customValidate === 'function') return customValidate(name)
    return true}}Copy the code

Introduced packages:

  • Path (Node has its own path module for processing paths.)
  • Read-metadata (used to read json or YAML metadata files and return an object.)
  • ExistsSync (The existsSync method of node’s fs module is used to check whether a path exists.)
  • Git-user (Get the local Git configuration.)
  • Validate-npm-package-name (whether the name used for the NPM package is valid or not.)

Function:

  • Main method: Step 1: Obtain the configuration file information of the template. Step 2: Set the name field and check whether the name is valid; Step 3: Just the Author field.
  • GetMetadata: Get configuration information from meta. Js or meta. Json
  • SetDefault: Used to add a default field to the configuration object
  • SetValidateName: Used to check whether the Name field in the configuration object is valid

git-user.js

const exec = require('child_process').execSync

module.exports = () => {
  let name
  let email

  try {
    name = exec('git config --get user.name')
    email = exec('git config --get user.email')
  } catch (e) {}

  name = name && JSON.stringify(name.toString().trim()).slice(1, -1)
  email = email && ('<' + email.toString().trim() + '>')
  return (name || ' ') + (email || ' ')}Copy the code

Introduced packages:

  • ExecSync (The execSync method in node’s child_process module is used to open a new shell, execute the corresponding command, and return the corresponding output.)

Git git git git git git git git git git git git git git git git git git git

eval.js

const chalk = require('chalk')

/**
 * Evaluate an expression in meta.json in the context of
 * prompt answers data.
 */

module.exports = function evaluate (exp, data) {
  /* eslint-disable no-new-func */
  const fn = new Function('data'.'with (data) { return ' + exp + '} ')
  try {
    return fn(data)
  } catch (e) {
    console.error(chalk.red('Error when evaluating filter condition: ' + exp))
  }
}
Copy the code

Introduced packages:

  • Chalk (used to highlight information printed from the terminal.)

Function: Executes an exp expression in the scope of data and returns the value of its execution

ask.js

const async = require('async')
const inquirer = require('inquirer')
const evaluate = require('./eval')

// Support types from prompt-for which was used before
const promptMapping = {
  string: 'input',
  boolean: 'confirm'
}

/**
 * Ask questions, return results.
 *
 * @param {Object} prompts
 * @param {Object} data
 * @param {Function} done*/ /** * prompts meta-js or meta-json * data metalsmith.metadata() *donePass to the next metalsmith plugin */ module.exports =function ask (prompts, data, doneAsync. EachSeries (Object.keys(prompts), (key, next) => {prompt(data, key, prompt [key], next) },done)
}

/**
 * Inquirer prompt wrapper.
 *
 * @param {Object} data
 * @param {String} key
 * @param {Object} prompt
 * @param {Function} done* /function prompt (data, key, prompt, done) {
  // skip prompts whose when condition is not met
  if(prompt.when && ! evaluate(prompt.when, data)) {return done} // Get the default valuelet promptDefault = prompt.default
  if (typeof prompt.default === 'function') {
    promptDefault = function () {
      returnPrompt. The default. The bind (this) (data)}} / / set problem, specific usage can go to https://github.com/SBoudrias/Inquirer.js to check the inquirer. Prompt ([{type: promptMapping[prompt.type] || prompt.type,
    name: key,
    message: prompt.message || prompt.label || key,
    default: promptDefault,
    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(/ when the answer is a string"/g, '\\"')} else {// other cases data[key] = answers[key]} done()}. Catch (done)}Copy the code

Introduced packages:

  • Async (asynchronous processing tool)
  • Inquirer (command line interaction with the user.)
  • Eval (returns the value of an expression under an action.)

Prompts can be interpreted as a prompt in meta-js or meta-json.

filter.js

const match = require('minimatch')
const evaluate = require('./eval'/** * files All files in the template * filters meta. Js or meta. Json The filters field * data metalsmith.metadata() *doneModule. Exports = (files, filters, data, etc.)done) = > {if(! Filters) {//meta. Js or meta. Json will skip the filters field and pass it to the next metalsmith pluginreturn doneConst fileNames = object.keys (files) // Iterate over all fields in meta-js or meta-.json without filters Object. Keys (filters).foreach (glob => {// Iterate over all fileNames. ForEach (file => {// If any filename matches one of the fields under the filtersif (match(file, glob, { dot: true })) {        
        const condition = filters[glob]
        if(! Evaluate (condition, data) {evaluate(condition, data) {evaluate(condition, data) {evaluate(condition, data) {evaluate(condition, data) {evaluate(condition, data) {done()}Copy the code

Introduced packages:

  • Minimatch (character matching tool)
  • Eval (returns the value of an expression under an action.)

What metalsmith.metadata() does: Removes unwanted template files based on metalsmith.metadata(), while metalsmith.metadata() changes mainly in ask.js, meaning that the user needs to be retrieved from ask.js.

logger.js

const chalk = require('chalk')
const format = require('util').format

/**
 * Prefix.
 */

const prefix = ' vue-cli'
const sep = chalk.gray(', ')

/**
 * Log a `message` to the console.
 *
 * @param {String} message
 */

exports.log = function(... args) { const msg = format.apply(format, args) console.log(chalk.white(prefix), sep, msg) } /** * Log an error `message` to the console and exit. * * @param {String} message */ exports.fatal =function(... args) {if (args[0] instanceof Error) args[0] = args[0].message.trim()
  const msg = format.apply(format, args)
  console.error(chalk.red(prefix), sep, msg)
  process.exit(1)
}

/**
 * Log a success `message` to the console and exit.
 *
 * @param {String} message
 */

exports.success = function(... args) { const msg = format.apply(format, args) console.log(chalk.white(prefix), sep, msg) }Copy the code

Introduced packages:

  • Chalk (used to highlight information printed from the terminal.)
  • Format (the format method in node’s util module.)

Logger. js provides three methods: log, FATAL, and SUCCESS. Each of these methods is fairly simple, and I don’t want to go into too much detail.

local-path.js

const path = require('path')

module.exports = {
  isLocalPath (templatePath) {
    return /^[./]|(^[a-zA-Z]:)/.test(templatePath)
  },

  getTemplatePath (templatePath) {
    return path.isAbsolute(templatePath)
      ? templatePath
      : path.normalize(path.join(process.cwd(), templatePath))
  }
}
Copy the code

Introduced packages:

  • Path (Node’s built-in path handling tool.)

Function:

  • IsLocalPath: UNIX (with “. Or “/”) WINDOWS(starts with something like “C:”).
  • GetTemplatePath: Whether templatePath is an absolute path. If yes, return templatePath; otherwise, convert templatePath to an absolute path and normalize templatePath.

check-version.js

const request = require('request')
const semver = require('semver')
const chalk = require('chalk')
const packageConfig = require('.. /package.json')

module.exports = done => {
  // Ensure minimum supported node version is used
  if(! semver.satisfies(process.version, packageConfig.engines.node)) {return console.log(chalk.red(
      ' You must upgrade node to >=' + packageConfig.engines.node + '.x to use vue-cli'
    ))
  }

  request({
    url: 'https://registry.npmjs.org/vue-cli',
    timeout: 1000
  }, (err, res, body) => {
    if(! err && res.statusCode === 200) { const latestVersion = JSON.parse(body)['dist-tags'].latest
      const localVersion = packageConfig.version
      if (semver.lt(localVersion, latestVersion)) {
        console.log(chalk.yellow(' A newer version of vue-cli is available.'))
        console.log()
        console.log(' latest: ' + chalk.green(latestVersion))
        console.log(' installed: ' + chalk.red(localVersion))
        console.log()
      }
    }
    done()})}Copy the code

Introduced packages:

  • Request (HTTP request tool.)
  • Semver (version number processing tool)
  • Chalk (used to highlight information printed from the terminal.)

Function:

  • Json file. If the node version is lower than the node version specified in nodepackage.json file, directly ask the developer to update their own node version. If not, proceed to step 2.
  • Step 2: request https://registry.npmjs.org/vue-cli to obtain the latest version of the vue – cli, with package. The json version of the field, if the local version number is less than the latest version number, the tip can be updated with the latest version. It is important to note that checking the version number here does not affect subsequent processes, even if the local VUe-CLI version is not up to date, nor does it affect the build, just as a reminder.

warnings.js

const chalk = require('chalk')

module.exports = {
  v2SuffixTemplatesDeprecated (template, name) {
    const initCommand = 'vue init ' + template.replace('2.0'.' ') + ' ' + name

    console.log(chalk.red('This template is deprecated, as the original template now uses Vue 2.0 by default.'))
    console.log()
    console.log(chalk.yellow(' Please use this command instead: ') + chalk.green(initCommand))
    console.log()
  },
  v2BranchIsNowDefault (template, name) {
    const vue1InitCommand = 'vue init ' + template + '# 1.0' + ' ' + name

    console.log(chalk.green(' This will install Vue 2.x version of the template.'))
    console.log()
    console.log(chalk.yellow(' For Vue 1.x use: ') + chalk.green(vue1InitCommand))
    console.log()
  }
}
Copy the code

Introduced packages:

  • Chalk (used to highlight information printed from the terminal.)

Function:

  • V2SuffixTemplatesDeprecated: tips to take “2.0” template has been abandoned, the official template by default in 2.0. It is no longer necessary to use “-2.0” to distinguish vue1.0 from VUe2.0.
  • V2BranchIsNowDefault: This method is commented out in vue-init and is no longer used. It was used during the transition from Vue1.0 to VUe2.0, and is now the default 2.0.

conclusion

Because the code is more, a lot of code I will not go into detail, some relatively simple or not very important JS files, I will only explain its role. But the key JS file, I still add a lot of annotations in the above. In my opinion, the most important files are vue-init, generate.js, options.js, ask.js and filter.js. These five files constitute the main process of vue-CLI construction project, so we need to spend more time on them. In addition, as we read the source code, we must have a clear understanding of what the entire build process is like, and have a spectrum in mind. After reading the whole VUE-CLI, I also made a scaffold tool according to the vue-CLI process, just for your reference. The address is as follows:

https://github.com/ruichengping/asuna-cli

Finally, I wish you can go better and better on the road of the front end! If you like my article, please remember to follow me! More high-quality articles will be launched in the future, please look forward to it!