background

In the usual work will encounter many small programs customized with the same template, so I want to build a scaffold tool to generate templates, based on the template to build the corresponding small program, and usually small programs are written with the MPVue framework, so first refer to the principle of VUE-CLI. After knowing the principle, to customize their own template scaffolding is certainly twice the result with half the effort.


To review the use of VUE-CLI before talking about the code, we usually use the WebPack template package and enter the following code.

vue init webpack [project-name]
Copy the code

After executing this code, the template package is automatically downloaded and we are asked questions such as template name, author, whether we need to use ESLint, build using NPM or YARN, etc. Once all the questions have been answered, the scaffolding project is generated.

We will download the source code, the source repository click here, the usual scaffolding is still 2.0, note that the default branch is dev, dev is 3.0.

Let’s take a look at package.json first. There’s a paragraph in the file that says this

{
  "bin": {
    "vue": "bin/vue"."vue-init": "bin/vue-init"."vue-list": "bin/vue-list"}}Copy the code

So the command vue init that we’re using is probably from the bin/vue-init file, so let’s take a look at what’s in that file


bin/vue-init

const download = require('download-git-repo')
const program = require('commander')
const exists = require('fs').existsSync
const path = require('path')
const ora = require('ora')
const home = require('user-home')
const tildify = require('tildify')
const chalk = require('chalk')
const inquirer = require('inquirer')
const rm = require('rimraf').sync
const logger = require('.. /lib/logger')
const generate = require('.. /lib/generate')
const checkVersion = require('.. /lib/check-version')
const warnings = require('.. /lib/warnings')
const localPath = require('.. /lib/local-path')
Copy the code

The path module provides a number of utility functions. The path module provides the following functions: /Users/sindresorhus/dev → ~/dev inquirer; / /Users/ sindResorhus /dev → ~/dev inquirer Rimraf is a module that can use the UNIX command rm -rf. The rest of the local path modules are actually utility classes that we’ll talk about when we need to use them


// Check whether the template path is local./ '
const isLocalPath = localPath.isLocalPath
If the path parameter is absolute, it is directly returned. If the path parameter is relative, it is spliced according to the current path
const getTemplatePath = localPath.getTemplatePath
Copy the code
/** * Usage. */

program
  .usage('<template-name> [project-name]')
  .option('-c, --clone'.'use git clone')
  .option('--offline'.'use cached template')

/** * Help. */

program.on('--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()
})

/** * Help. */
function help () {
  program.parse(process.argv)
  if (program.args.length < 1) return program.help()
}
help()
Copy the code

This section of code declares the use of vue init. If you type vue init –help in the terminal or if the parameter following vue init is less than 1 in length, the following description will be printed

  Usage: vue-init <template-name> [project-name]

  Options:

    -c, --clone  use git clone
    --offline    use cached template
    -h, --help   output usage information
  Examples:

    # create a new project with an official template
    $ vue init webpack my-project

    # create a new project straight from a github template
    $ vue init username/repo my-project
Copy the code

The next step is to get some variables

/** * Settings. */
// Template path
let template = program.args[0]
const hasSlash = template.indexOf('/') > - 1
// Project name
const rawName = program.args[1]
constinPlace = ! rawName || rawName ==='. '
// Name takes the name of the current folder if no project name exists or the '.' entered for the project name
const name = inPlace ? path.relative('.. / ', process.cwd()) : rawName
// Output path
const to = path.resolve(rawName || '. ')
Git clone is required
const clone = program.clone || false

// TMP is the path of the local template. If the path is offline, set it to the local path
const tmp = path.join(home, '.vue-templates', template.replace(/[\/:]/g.The '-'))
if (program.offline) {
  console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
  template = tmp
}
Copy the code

The next step is to download and produce the template based on the template name. If it is a local template path, it is directly generated.

/** * Check, download and generate the project. */

function run () {
  // Check whether it is a local template path
  if (isLocalPath(template)) {
    // Get the template address
    const templatePath = getTemplatePath(template)
    // Start generating templates if the local template path exists
    if (exists(templatePath)) {
      generate(name, templatePath, to, err => {
        if (err) logger.fatal(err)
        console.log()
        logger.success('Generated "%s".', name)
      })
    } else {
      logger.fatal('Local template "%s" not found.', template)
    }
  } else {
    // Check the version first for non-local template paths
    checkVersion((a)= > {
      // Whether the path contains '/'
      // If not, enter this logic
      if(! hasSlash) {Vuejs-tempalte 'is the official template package
        const officialTemplate = 'vuejs-templates/' + template
        // If there is a '#' in the path, download directly
        if (template.indexOf(The '#')! = =- 1) {
          downloadAndGenerate(officialTemplate)
        } else {
          // If there is no -2.0 string, the template will be discarded
          if (template.indexOf('2.0')! = =- 1) {
            warnings.v2SuffixTemplatesDeprecated(template, inPlace ? ' ' : name)
            return
          }

          // Download and produce the template
          downloadAndGenerate(officialTemplate)
        }
      } else {
        // Download and generate the build template
        downloadAndGenerate(template)
      }
    })
  }
}
Copy the code

Let’s take a look at the downloadAndGenerate method

/** * Download a generate from a template repo. * * @param {String} template */

function downloadAndGenerate (template) {
  // Perform the loading animation
  const spinner = ora('downloading template')
  spinner.start()
  // Remove if local template exists
  // Delete a local template
  if (exists(tmp)) rm(tmp)
  // The template parameter is the target address. TMP is the download address
  download(template, tmp, { clone }, err => {
    // Finish loading the animation
    spinner.stop()
    // If downloading error output log
    if (err) logger.fatal('Failed to download repo ' + template + ':' + err.message.trim())
    // After the template is downloaded successfully, it enters the production template method
    generate(name, tmp, to, err => {
      if (err) logger.fatal(err)
      console.log()
      logger.success('Generated "%s".', name)
    })
  })
}
Copy the code

So far, bin/vue-init is done. One of the main things this file does is download the generated template based on the template name, but the methods for downloading and generating the template are not init.

Download the template

The download method used to download the template belongs to the download-Git-repo module.

The first parameter is the repository address, the second is the output address, the third parameter is whether you need git clone, and the fourth parameter is the callback parameter

download('flipxfx/download-git-repo-fixture'.'test/tmp', {clone: true }, function (err) {
  console.log(err ? 'Error' : 'Success')})Copy the code

The # string mentioned in the run method above is actually how this module downloads the branch module

download('bitbucket:flipxfx/download-git-repo-fixture#my-branch'.'test/tmp', { clone: true }, function (err) {
  console.log(err ? 'Error' : 'Success')})Copy the code

Generate template

In generate.js, let’s go ahead and look at the generate method


generate.js

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')

Copy the code

Metalsmith is a library for generating static websites (blogs, projects). Handlerbars is a template compiler that outputs HTML async modules via templates and JSON. Kind of like making method into a thread consolidate template engine consolidate library multimatch a string array matching library options is a self-defined configuration item file

Two renderers were subsequently registered, similar to vif Velse’s conditional rendering in VUE

// register 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)
})
Copy the code

Next, look at the key generate method

module.exports = function generate (name, src, dest, done) {
  // add the name auther(current git user) to opts
  const opts = getOptions(name, src)
  // concatenates the directory SRC /{template} under which to produce static files
  const metalsmith = Metalsmith(path.join(src, 'template'))
  // Merge meta and three attributes in metalsmitch to form data
  const data = Object.assign(metalsmith.metadata(), {
    destDirName: name,
    inPlace: dest === process.cwd(),
    noEscape: true
  })
  Helpers helpers helpers helpers helpers helpers helpers helpers helpers helpers helpers helpers
  // If_or and template_version are specified, respectively
  opts.helpers && Object.keys(opts.helpers).map(key= > {
    Handlebars.registerHelper(key, opts.helpers[key])
  })

  const helpers = { chalk, logger }

  // 将metalsmith metadata 数据 和 { isNotTest, isTest 合并 }
  if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
    opts.metalsmith.before(metalsmith, opts, helpers)
  }

  // askQuestions are questions that are asked at the terminal
  Prompts. Prompt. // What does the author ask for in meta-js's opts.prompts
  // filterFiles is used to filterFiles
  RenderTemplateFiles is a rendering plugin
  metalsmith.use(askQuestions(opts.prompts))
    .use(filterFiles(opts.filters))
    .use(renderTemplateFiles(opts.skipInterpolation))

  if (typeof opts.metalsmith === 'function') {
    opts.metalsmith(metalsmith, opts, helpers)
  } else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
    opts.metalsmith.after(metalsmith, opts, helpers)
  }

  The clean method sets whether to delete the original destination directory before writing. The default is true
  // the source method is to set the original path
  The destination method sets the output directory
  The build method performs the build
  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 === 'function') {
        // Execute the opts.complete method in meta.js when the generation is complete
        const helpers = { chalk, logger, files }
        opts.complete(data, helpers)
      } else {
        logMessage(opts.completeMessage, data)
      }
    })

  return data
}
Copy the code

meta.js

Now look at the following complete methods

complete: function(data, { chalk }) {
    const green = chalk.green
    // The existing PackageJoson dependency declarations will be reordered
    sortDependencies(data, green)

    const cwd = path.join(process.cwd(), data.inPlace ? ' ' : data.destDirName)
    // Whether we need to install this automatically was our choice in the pre-build query
    if (data.autoInstall) {
      // Run the install command on the terminal
      installDependencies(cwd, data.autoInstall, green)
        .then((a)= > {
          return runLintFix(cwd, data, green)
        })
        .then((a)= > {
          printMessage(data, green)
        })
        .catch(e= > {
          console.log(chalk.red('Error:'), e)
        })
    } else {
      printMessage(data, chalk)
    }
  }
Copy the code

Build custom templates

After seeing how vue-init works, customizing a custom template is easy. There are only two things we need to do

  • First we need to have a template project of our own
  • If you need to customize some variables, you need to do so in the templatemeta.jsThe custom of

Since the download module uses the Download-Git-repo module, which can be downloaded from Github, Gitlab, and Bitucket, we only need to put the customized template project into git remote repository.

Since I need to define the development template for the applet, MPvue itself has a QuickStart template, so we will customize it based on it. First we will fork it down, create a new custom branch, and customize it on this branch.

Where we need to customize the useful dependency libraries, we need to use less and wxParse in addition, so we added it in template/package.json

{/ /... Part of the omitted"dependencies": {
    "mpvue": "^ 1.0.11"{{#vuex}},
    "vuex": "^ 3.0.1." "{{/vuex}}
  },
  "devDependencies": {/ /... Omit // This is the added package"less": "^ 3.0.4"."less-loader": "^ 4.1.0." "."mpvue-wxparse": "^ 0.6.5"}}Copy the code

We also need to customize the ESLint rule, so we can remove Airbnb-style queries in meta.js since we only use Standard

"lintConfig": {
  "when": "lint"."type": "list"."message": "Pick an ESLint preset"."choices": [{"name": "Standard (https://github.com/feross/standard)"."value": "standard"."short": "Standard"
    },
    {
      "name": "none (configure it yourself)"."value": "none"."short": "none"}}]Copy the code

.eslinttrc.js

'rules': {{{#if_eq lintConfig "standard"}}
    "camelcase": 0,
    // allow paren-less arrow functions
    "arrow-parens": 0."space-before-function-paren": 0,
    // allow async-await
    "generator-star-spacing": 0,
    {{/if_eq}}
    {{#if_eq lintConfig "airbnb"}}
    // don't require .vue extension when importing 'import/extensions': ['error', 'always'{'js':'never', 'vue':'never' }], // allow optionalDependencies 'import/no-extraneous-dependencies': ['error'{'optionalDependencies': ['test/unit/index.js'] }], {{/if_eq}} // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production'? 2:0}Copy the code

Finally, in the build time question, we will set a question for the name of the small program, and this name will be set in the navigation title. Questions are added to meta.js

"prompts": {
    "name": {
      "type": "string"."required": true."message": "Project name"}, // Add a question"appName": {
      "type": "string"."required": true."message": "App name"}}Copy the code

main.json

{
  "pages": [
    "pages/index/main"."pages/counter/main"."pages/logs/main"]."window": {
    "backgroundTextStyle": "light"."navigationBarBackgroundColor": "#fff"// Set the title according to the question"navigationBarTitleText": "{{appName}}"."navigationBarTextStyle": "black"}}Copy the code

Finally, let’s try our own template

vue init Baifann/mpvue-quickstart#custom min-app-project
Copy the code

conclusion

The above template customization is very simple, in the actual project is certainly more complex, but according to this idea should be feasible. For example, some self-encapsulated components are also placed in the project, which I won’t go into here. The principle analysis is based on VUE-CLI 2.0, but in fact 3.0 is ready to go, if there is a chance, after in-depth understanding, and then share with you, thank you.


Refer to the article

  • Vue-how does cli work]6

Spread the word

This article is published in Front of Mint Weekly. Welcome to Watch & Star.

Welcome to the discussion. Give it a thumbs up before we go. ◕ ‿ ◕. ~