19,vue-cli原理,请求,真实的模块开发,一步一不到实战脚手架开发。

22 篇文章 0 订阅
18 篇文章 1 订阅

2021【前端VUE框架】最新/最全/最细实战课程,真实的模块开发,一步一不到实战脚手架开发

学习目标:

提示:2021【前端VUE框架】最新/最全/最细实战课程,VUE56节进阶学习
重磅来袭:3个月掌握 vue 入门知识到实战学代码


学习内容:

提示:本节第17节内容:vue-cli知识点
学习本节视频需要掌握的知识点:
1.HTML
2.CSS
3.JavaScript
那么关于上面三个知识点,视频学习地方在:html5+css3+js+jquery课程

学习时间:

提示:2020现在-2021.1月
一。无基础的同学,有充足的事件可以一天学习两节内容,要多写代码,一个案例和知识点最少要敲代码3到4遍:
二。有基础的同学可以自由安排事件哈:
1、 周一至周五晚上 7 点—晚上9点
2、 周六上午 9 点-上午 11 点
3、 周日下午 3 点-下午 6 点


学习产出:

提示:在网站上
例如:
1、 技术笔记 2 遍 ,在你自己的电脑学代码。
2、tofacebook.com, 发布技术文章 1 篇

本节知识点内容如下:

背景

在平时工作中会有遇到许多以相同模板定制的小程序,因此想自己建立一个生成模板的脚手架工具,以模板为基础构建对应的小程序,而平时的小程序都是用mpvue框架来写的,因此首先先参考一下Vue-cli的原理。知道原理之后,再定制自己的模板脚手架肯定是事半功倍的。


在说代码之前我们首先回顾一下Vue-cli的使用,我们通常使用的是webpack模板包,输入的是以下代码。

vue init webpack [project-name]

在执行这段代码之后,系统会自动下载模板包,随后会询问我们一些问题,比如模板名称,作者,是否需要使用eslint,使用npm或者yarn进行构建等等,当所有问题我们回答之后,就开始生成脚手架项目。

我们将源码下载下来,源码仓库点击这里,平时用的脚手架还是2.0版本,要注意,默认的分支是在dev上,dev上是3.0版本。

我们首先看一下package.json,在文件当中有这么一段话

{
  "bin": {
    "vue": "bin/vue",
    "vue-init": "bin/vue-init",
    "vue-list": "bin/vue-list"
  }
}

由此可见,我们使用的命令 vue init,应该是来自bin/vue-init这个文件,我们接下来看一下这个文件中的内容


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

download-git-repo 一个用于下载git仓库的项目的模块

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

fs 是node的文件读写的模块

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

fs 是node的文件读写的模块

path 模块提供了一些工具函数,用于处理文件与目录的路径

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

fs 是node的文件读写的模块

path 模块提供了一些工具函数,用于处理文件与目录的路径

ora 这个模块用于在终端里有显示载入动画

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

fs 是node的文件读写的模块

path 模块提供了一些工具函数,用于处理文件与目录的路径

ora 这个模块用于在终端里有显示载入动画

user-home 获取用户主目录的路径

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

fs 是node的文件读写的模块

path 模块提供了一些工具函数,用于处理文件与目录的路径

ora 这个模块用于在终端里有显示载入动画

user-home 获取用户主目录的路径

tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

fs 是node的文件读写的模块

path 模块提供了一些工具函数,用于处理文件与目录的路径

ora 这个模块用于在终端里有显示载入动画

user-home 获取用户主目录的路径

tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev

inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

fs 是node的文件读写的模块

path 模块提供了一些工具函数,用于处理文件与目录的路径

ora 这个模块用于在终端里有显示载入动画

user-home 获取用户主目录的路径

tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev

inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理

rimraf 是一个可以使用 UNIX 命令 rm -rf的模块

download-git-repo 一个用于下载git仓库的项目的模块

commander 可以将文字输出到终端当中

fs 是node的文件读写的模块

path 模块提供了一些工具函数,用于处理文件与目录的路径

ora 这个模块用于在终端里有显示载入动画

user-home 获取用户主目录的路径

tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev

inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理

rimraf 是一个可以使用 UNIX 命令 rm -rf的模块

剩下的本地路径的模块其实都是一些工具类,等用到的时候我们再来讲


// 是否为本地路径的方法 主要是判断模板路径当中是否存在 `./`
const isLocalPath = localPath.isLocalPath
// 获取模板路径的方法 如果路径参数是绝对路径 则直接返回 如果是相对的 则根据当前路径拼接
const getTemplatePath = localPath.getTemplatePath

/**
 * 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()

这部分代码声明了vue init用法,如果在终端当中 输入 vue init --help或者跟在vue init 后面的参数长度小于1,也会输出下面的描述

  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

接下来是一些变量的获取

/**
 * Settings.
 */
// 模板路径

let template = program.args[0]
const hasSlash = template.indexOf('/') > -1
// 项目名称
const rawName = program.args[1]
const inPlace = !rawName || rawName === '.'
// 如果不存在项目名称或项目名称输入的'.' 则name取的是 当前文件夹的名称
const name = inPlace ? path.relative('../', process.cwd()) : rawName
// 输出路径
const to = path.resolve(rawName || '.')
// 是否需要用到 git clone

const clone = program.clone || false

// tmp为本地模板路径 如果 是离线状态 那么模板路径取本地的
const tmp = path.join(home, '.vue-templates', template.replace(/[\/:]/g, '-'))
if (program.offline) {
  console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
  template = tmp
}

接下来主要是根据模板名称,来下载并生产模板,如果是本地的模板路径,就直接生成。

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

function run () {
  // 判断是否是本地模板路径
 if (isLocalPath(template)) {
    // 获取模板地址
   const templatePath = getTemplatePath(template)

   // 如果本地模板路径存在 则开始生成模板
    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 {
    // 非本地模板路径 则先检查版本
    checkVersion(() => {
      // 路径中是否 包含'/'
      // 如果没有 则进入这个逻辑
      if (!hasSlash) {
        // 拼接路径 'vuejs-tempalte'下的都是官方的模板包
        const officialTemplate = 'vuejs-templates/' + template

        // 如果路径当中存在 '#'则直接下载

        if (template.indexOf('#') !== -1) {
		downloadAndGenerate(officialTemplate)
        } else {

          // 如果不存在 -2.0的字符串 则会输出 模板废弃的相关提示

          if (template.indexOf('-2.0') !== -1) {

            warnings.v2SuffixTemplatesDeprecated(template, inPlace ? '' : name)

            return

          }

          // 下载并生产模板
          downloadAndGenerate(officialTemplate)
        }
      } else {
        // 下载并生生成模板
        downloadAndGenerate(template)
      }

    })

  }
}

我们来看下 downloadAndGenerate这个方法

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

function downloadAndGenerate (template) {
  // 执行加载动画
  const spinner = ora('downloading template')
  spinner.start()
  // Remove if local template exists
  // 删除本地存在的模板
  if (exists(tmp)) rm(tmp)
  // template参数为目标地址 tmp为下载地址 clone参数代表是否需要clone
  download(template, tmp, { clone }, err => {
    // 结束加载动画
    spinner.stop()
    // 如果下载出错 输出日志
    if (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)
    })
  })
}

到这里为止,bin/vue-init就讲完了,该文件做的最主要的一件事情,就是根据模板名称,来下载生成模板,但是具体下载和生成的模板的方法并不在里面。

下载模板

下载模板用的download方法是属于download-git-repo模块的。

最基础的用法为如下用法,这里的参数很好理解,第一个参数为仓库地址,第二个为输出地址,第三个是否需要 git clone,带四个为回调参数

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

在上面的run方法中有提到一个#的字符串实际就是这个模块下载分支模块的用法

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

生成模板

模板生成generate方法在generate.js当中,我们继续来看一下


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

chalk 是一个可以让终端输出内容变色的模块

chalk 是一个可以让终端输出内容变色的模块

Metalsmith是一个静态网站(博客,项目)的生成库

chalk 是一个可以让终端输出内容变色的模块

Metalsmith是一个静态网站(博客,项目)的生成库

handlerbars 是一个模板编译器,通过template和json,输出一个html

chalk 是一个可以让终端输出内容变色的模块

Metalsmith是一个静态网站(博客,项目)的生成库

handlerbars 是一个模板编译器,通过template和json,输出一个html

async 异步处理模块,有点类似让方法变成一个线程

chalk 是一个可以让终端输出内容变色的模块

Metalsmith是一个静态网站(博客,项目)的生成库

handlerbars 是一个模板编译器,通过template和json,输出一个html

async 异步处理模块,有点类似让方法变成一个线程

consolidate 模板引擎整合库

chalk 是一个可以让终端输出内容变色的模块

Metalsmith是一个静态网站(博客,项目)的生成库

handlerbars 是一个模板编译器,通过template和json,输出一个html

async 异步处理模块,有点类似让方法变成一个线程

consolidate 模板引擎整合库

multimatch 一个字符串数组匹配的库

chalk 是一个可以让终端输出内容变色的模块

Metalsmith是一个静态网站(博客,项目)的生成库

handlerbars 是一个模板编译器,通过template和json,输出一个html

async 异步处理模块,有点类似让方法变成一个线程

consolidate 模板引擎整合库

multimatch 一个字符串数组匹配的库

options 是一个自己定义的配置项文件

随后注册了2个渲染器,类似于vue中的 vif velse的条件渲染

// 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)
})

接下来看关键的generate方法

module.exports = function generate (name, src, dest, done) {
  // 读取了src目录下的 配置文件信息, 同时将 name auther(当前git用户) 赋值到了 opts 当中
  const opts = getOptions(name, src)

  // 拼接了目录 src/{template} 要在这个目录下生产静态文件

  const metalsmith = Metalsmith(path.join(src, 'template'))

  // 将metalsmitch中的meta 与 三个属性合并起来 形成 data

  const data = Object.assign(metalsmith.metadata(), {
    destDirName: name,
    inPlace: dest === process.cwd(),
    noEscape: true
  })

  // 遍历 meta.js元数据中的helpers对象,注册渲染模板数据
  // 分别指定了 if_or 和   template_version内容
  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是会在终端里询问一些问题
  // 名称 描述 作者 是要什么构建 在meta.js 的opts.prompts当中
  // filterFiles 是用来过滤文件
  // renderTemplateFiles 是一个渲染插件
  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)
  }
  // clean方法是设置在写入之前是否删除原先目标目录 默认为true
  // source方法是设置原路径
  // destination方法就是设置输出的目录
  // 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') {
        // 当生成完毕之后执行 meta.js当中的 opts.complete方法
        const helpers = { chalk, logger, files }
        opts.complete(data, helpers)
      } else {
        logMessage(opts.completeMessage, data)
      }
    })

  return data
}

meta.js

接下来看以下complete方法

complete: function(data, { chalk }) {
    const green = chalk.green
    // 会将已有的packagejoson 依赖声明重新排序
    sortDependencies(data, green)
    const cwd = path.join(process.cwd(), data.inPlace ? '' : data.destDirName)
    // 是否需要自动安装 这个在之前构建前的询问当中 是我们自己选择的
    if (data.autoInstall) {
     // 在终端中执行 install 命令
     installDependencies(cwd, data.autoInstall, green)
        .then(() => {
          return runLintFix(cwd, data, green)
        })
        .then(() => {
          printMessage(data, green)
        })
        .catch(e => {
         console.log(chalk.red('Error:'), e)
      })
    } else {
      printMessage(data, chalk)
    }
  }

构建自定义模板

在看完vue-init命令的原理之后,其实定制自定义的模板是很简单的事情,我们只要做2件事

  • 首先我们需要有一个自己模板项目
  • 如果需要自定义一些变量,就需要在模板的meta.js当中定制

由于下载模块使用的是download-git-repo模块,它本身是支持在github,gitlab,bitucket上下载的,到时候我们只需要将定制好的模板项目放到git远程仓库上即可。

由于我需要定义的是小程序的开发模板,mpvue本身也有一个quickstart的模板,那么我们就在它的基础上进行定制,首先我们将它fork下来,新建一个custom分支,在这个分支上进行定制。

我们需要定制的地方有用到的依赖库,需要额外用到less以及wxparse

我们需要定制的地方有用到的依赖库,需要额外用到less以及wxparse

因此我们在 template/package.json当中进行添加

{
  // ... 部分省略
  "dependencies": {
    "mpvue": "^1.0.11"{{#vuex}},
    "vuex": "^3.0.1"{{/vuex}}
 },
  "devDependencies": {
   // ... 省略
    // 这是添加的包
    "less": "^3.0.4",
    "less-loader": "^4.1.0",
    "mpvue-wxparse": "^0.6.5"
  }
}

除此之外,我们还需要定制一下eslint规则,由于只用到standard,因此我们在meta.js当中 可以将 airbnb风格的提问删除

"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"
    }
  ]
}

.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
  }

最后我们在构建时的提问当中,再设置一个小程序名称的提问,而这个名称会设置到导航的标题当中。

最后我们在构建时的提问当中,再设置一个小程序名称的提问,而这个名称会设置到导航的标题当中。

提问是在meta.js当中添加

"prompts": {
    "name": {
     "type": "string",
      "required": true,
      "message": "Project name"
    },
    // 新增提问
    "appName": {
      "type": "string",
      "required": true,
      "message": "App name"
    }
}

main.json

{
  "pages": [
    "pages/index/main",
    "pages/counter/main",
    "pages/logs/main"
  ],
  "window": {
    "backgroundTextStyle": "light",
   "navigationBarBackgroundColor": "#fff",
    // 根据提问设置标题
    "navigationBarTitleText": "{{appName}}",
    "navigationBarTextStyle": "black"
  }
}

最后我们来尝试一下我们自己的模板

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

总结

以上模板的定制是十分简单的,在实际项目上肯定更为复杂,但是按照这个思路应该都是可行的。比如说将一些自行封装的组件也放置到项目当中等等,这里就不再细说。原理解析都是基于vue-cli 2.0的,但实际上 3.0也已经整装待发,如果后续有机会,深入了解之后,再和大家分享,谢谢大家。

背景

在平时工作中会有遇到许多以相同模板定制的小程序,因此想自己建立一个生成模板的脚手架工具,以模板为基础构建对应的小程序,而平时的小程序都是用mpvue框架来写的,因此首先先参考一下Vue-cli的原理。知道原理之后,再定制自己的模板脚手架肯定是事半功倍的。


在说代码之前我们首先回顾一下Vue-cli的使用,我们通常使用的是webpack模板包,输入的是以下代码。

vue init webpack [project-name]

在执行这段代码之后,系统会自动下载模板包,随后会询问我们一些问题,比如模板名称,作者,是否需要使用eslint,使用npm或者yarn进行构建等等,当所有问题我们回答之后,就开始生成脚手架项目。

我们将源码下载下来,源码仓库点击这里,平时用的脚手架还是2.0版本,要注意,默认的分支是在dev上,dev上是3.0版本。

我们首先看一下package.json,在文件当中有这么一段话

{
  "bin": {
    "vue": "bin/vue",
    "vue-init": "bin/vue-init",
    "vue-list": "bin/vue-list"
  }
}

由此可见,我们使用的命令 vue init,应该是来自bin/vue-init这个文件,我们接下来看一下这个文件中的内容


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

download-git-repo 一个用于下载git仓库的项目的模块

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径 tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径 tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径 tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理 rimraf 是一个可以使用 UNIX 命令 rm -rf的模块

download-git-repo 一个用于下载git仓库的项目的模块 commander 可以将文字输出到终端当中 fs 是node的文件读写的模块 path 模块提供了一些工具函数,用于处理文件与目录的路径 ora 这个模块用于在终端里有显示载入动画 user-home 获取用户主目录的路径 tildify 将绝对路径转换为波形路径 比如/Users/sindresorhus/dev → ~/dev inquirer 是一个命令行的回答的模块,你可以自己设定终端的问题,然后对这些回答给出相应的处理 rimraf 是一个可以使用 UNIX 命令 rm -rf的模块 剩下的本地路径的模块其实都是一些工具类,等用到的时候我们再来讲


// 是否为本地路径的方法 主要是判断模板路径当中是否存在 `./`
const isLocalPath = localPath.isLocalPath
// 获取模板路径的方法 如果路径参数是绝对路径 则直接返回 如果是相对的 则根据当前路径拼接
const getTemplatePath = localPath.getTemplatePath
/**
 * 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()

    这部分代码声明了vue init用法,如果在终端当中 输入 vue init --help或者跟在vue init 后面的参数长度小于1,也会输出下面的描述

      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

    接下来是一些变量的获取

    /**
  • Settings.
    */
    // 模板路径

let template = program.args[0]
const hasSlash = template.indexOf('/') > -1
// 项目名称
const rawName = program.args[1]
const inPlace = !rawName || rawName === '.'
// 如果不存在项目名称或项目名称输入的'.' 则name取的是 当前文件夹的名称
const name = inPlace ? path.relative('…/', process.cwd()) : rawName
// 输出路径
const to = path.resolve(rawName || '.')
// 是否需要用到 git clone

const clone = program.clone || false

// tmp为本地模板路径 如果 是离线状态 那么模板路径取本地的
const tmp = path.join(home, ‘.vue-templates’, template.replace(/[/:]/g, ‘-’))
if (program.offline) {
console.log(&gt; Use cached template at ${chalk.yellow(tildify(tmp))})
template = tmp
}


接下来主要是根据模板名称,来下载并生产模板,如果是本地的模板路径,就直接生成。

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

function run () {
// 判断是否是本地模板路径
if (isLocalPath(template)) {
// 获取模板地址
const templatePath = getTemplatePath(template)

// 如果本地模板路径存在 则开始生成模板
if (exists(templatePath)) {
generate(name, templatePath, to, err => {
if (err) logger.fatal(err)

   console.log()
  logger.success(&#39;Generated &quot;%s&quot;.&#39;, name)
  })

} else {
  logger.fatal(&#39;Local template &quot;%s&quot; not found.&#39;, template)
}

} else {
// 非本地模板路径 则先检查版本
checkVersion(() => {
// 路径中是否 包含'/'
// 如果没有 则进入这个逻辑
if (!hasSlash) {
// 拼接路径 'vuejs-tempalte'下的都是官方的模板包
const officialTemplate = 'vuejs-templates/' + template

    // 如果路径当中存在 &#39;#&#39;则直接下载

    if (template.indexOf(&#39;#&#39;) !== -1) {
	downloadAndGenerate(officialTemplate)
    } else {

      // 如果不存在 -2.0的字符串 则会输出 模板废弃的相关提示

      if (template.indexOf(&#39;-2.0&#39;) !== -1) {

        warnings.v2SuffixTemplatesDeprecated(template, inPlace ? &#39;&#39; : name)

        return

      }

      // 下载并生产模板
      downloadAndGenerate(officialTemplate)
    }
  } else {
    // 下载并生生成模板
    downloadAndGenerate(template)
  }

})

}
}

我们来看下 downloadAndGenerate这个方法

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

function downloadAndGenerate (template) {
// 执行加载动画
const spinner = ora(‘downloading template’)
spinner.start()
// Remove if local template exists
// 删除本地存在的模板
if (exists(tmp)) rm(tmp)
// template参数为目标地址 tmp为下载地址 clone参数代表是否需要clone
download(template, tmp, { clone }, err => {
// 结束加载动画
spinner.stop()
// 如果下载出错 输出日志
if (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)
})
})
}

到这里为止,bin/vue-init就讲完了,该文件做的最主要的一件事情,就是根据模板名称,来下载生成模板,但是具体下载和生成的模板的方法并不在里面。

下载模板

下载模板用的download方法是属于download-git-repo模块的。

最基础的用法为如下用法,这里的参数很好理解,第一个参数为仓库地址,第二个为输出地址,第三个是否需要 git clone,带四个为回调参数

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

在上面的run方法中有提到一个#的字符串实际就是这个模块下载分支模块的用法

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

生成模板

模板生成generate方法在generate.js当中,我们继续来看一下


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

chalk 是一个可以让终端输出内容变色的模块

chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库

chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过templatejson,输出一个html

chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过templatejson,输出一个html
async 异步处理模块,有点类似让方法变成一个线程

chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过templatejson,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库

chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过templatejson,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库
multimatch 一个字符串数组匹配的库

chalk 是一个可以让终端输出内容变色的模块
Metalsmith是一个静态网站(博客,项目)的生成库
handlerbars 是一个模板编译器,通过templatejson,输出一个html
async 异步处理模块,有点类似让方法变成一个线程
consolidate 模板引擎整合库
multimatch 一个字符串数组匹配的库
options 是一个自己定义的配置项文件

随后注册了2个渲染器,类似于vue中的 vif velse的条件渲染

// 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)
})

接下来看关键的generate方法

module.exports = function generate (name, src, dest, done) {
// 读取了src目录下的 配置文件信息, 同时将 name auther(当前git用户) 赋值到了 opts 当中
const opts = getOptions(name, src)

// 拼接了目录 src/{template} 要在这个目录下生产静态文件

const metalsmith = Metalsmith(path.join(src, 'template'))

// 将metalsmitch中的meta 与 三个属性合并起来 形成 data

const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})

// 遍历 meta.js元数据中的helpers对象,注册渲染模板数据
// 分别指定了 if_or 和 template_version内容
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是会在终端里询问一些问题
// 名称 描述 作者 是要什么构建 在meta.js 的opts.prompts当中
// filterFiles 是用来过滤文件
// renderTemplateFiles 是一个渲染插件
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)
}
// clean方法是设置在写入之前是否删除原先目标目录 默认为true
// source方法是设置原路径
// destination方法就是设置输出的目录
// build方法执行构建
metalsmith.clean(false)
.source('.') // start from template root instead of ./src which is Metalsmith's default for source
.destination(dest)

.build((err, files) =&gt; {

 done(err)
  if (typeof opts.complete === &#39;function&#39;) {
    // 当生成完毕之后执行 meta.js当中的 opts.complete方法
    const helpers = { chalk, logger, files }
    opts.complete(data, helpers)
  } else {
    logMessage(opts.completeMessage, data)
  }
})

return data
}

meta.js

接下来看以下complete方法

complete: function(data, { chalk }) {
const green = chalk.green
// 会将已有的packagejoson 依赖声明重新排序
sortDependencies(data, green)
const cwd = path.join(process.cwd(), data.inPlace ? ‘’ : data.destDirName)
// 是否需要自动安装 这个在之前构建前的询问当中 是我们自己选择的
if (data.autoInstall) {
// 在终端中执行 install 命令
installDependencies(cwd, data.autoInstall, green)
.then(() => {
return runLintFix(cwd, data, green)
})
.then(() => {
printMessage(data, green)
})
.catch(e => {
console.log(chalk.red(‘Error:’), e)
})
} else {
printMessage(data, chalk)
}
}

构建自定义模板

在看完vue-init命令的原理之后,其实定制自定义的模板是很简单的事情,我们只要做2件事

  • 首先我们需要有一个自己模板项目
  • 如果需要自定义一些变量,就需要在模板的meta.js当中定制

由于下载模块使用的是download-git-repo模块,它本身是支持在github,gitlab,bitucket上下载的,到时候我们只需要将定制好的模板项目放到git远程仓库上即可。

由于我需要定义的是小程序的开发模板,mpvue本身也有一个quickstart的模板,那么我们就在它的基础上进行定制,首先我们将它fork下来,新建一个custom分支,在这个分支上进行定制。

我们需要定制的地方有用到的依赖库,需要额外用到less以及wxparse

我们需要定制的地方有用到的依赖库,需要额外用到less以及wxparse
因此我们在 template/package.json当中进行添加

{
// … 部分省略
“dependencies”: {
“mpvue”: “^1.0.11”{{#vuex}},
“vuex”: “^3.0.1”{{/vuex}}
},
“devDependencies”: {
// … 省略
// 这是添加的包
“less”: “^3.0.4”,
“less-loader”: “^4.1.0”,
“mpvue-wxparse”: “^0.6.5”
}
}

除此之外,我们还需要定制一下eslint规则,由于只用到standard,因此我们在meta.js当中 可以将 airbnb风格的提问删除

"lintConfig": {
“when”: “lint”,
“type”: “list”,
“message”: “Pick an ESLint preset”,
“choices”: [
{
“name”: “Standard (https://github.com/feross/standard)”,
“value”: “standard”,
“short”: “Standard”
},
{
  &quot;name&quot;: &quot;none (configure it yourself)&quot;,
  &quot;value&quot;: &quot;none&quot;,
  &quot;short&quot;: &quot;none&quot;
}

]
}

.eslinttrc.js

’rules’: {
{{#if_eq lintConfig “standard”}}
&quot;camelcase&quot;: 0,
// allow paren-less arrow functions
&quot;arrow-parens&quot;: 0,
&quot;space-before-function-paren&quot;: 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
}

最后我们在构建时的提问当中,再设置一个小程序名称的提问,而这个名称会设置到导航的标题当中。

最后我们在构建时的提问当中,再设置一个小程序名称的提问,而这个名称会设置到导航的标题当中。
提问是在meta.js当中添加

"prompts": {
“name”: {
“type”: “string”,
“required”: true,
“message”: “Project name”
},
// 新增提问
“appName”: {
“type”: “string”,
“required”: true,
“message”: “App name”
}
}

main.json

{
“pages”: [
“pages/index/main”,
“pages/counter/main”,
“pages/logs/main”
],
“window”: {
“backgroundTextStyle”: “light”,
“navigationBarBackgroundColor”: “#fff”,
// 根据提问设置标题
“navigationBarTitleText”: “{{appName}}”,
“navigationBarTextStyle”: “black”
}
}

最后我们来尝试一下我们自己的模板

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

image_1cj0ikq141je51ii31eek25t18il19.png-31.4k\

image_1cj0m986t1qdu1j97lruh4kjgo9.png-36.2k\

总结

以上模板的定制是十分简单的,在实际项目上肯定更为复杂,但是按照这个思路应该都是可行的。比如说将一些自行封装的组件也放置到项目当中等等,这里就不再细说。原理解析都是基于vue-cli 2.0的,但实际上 3.0也已经整装待发,如果后续有机会,深入了解之后,再和大家分享,谢谢大家。

加我微信号:欢迎你来撩,谢谢!在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值