Принципиальный анализ Vue-cli

внешний интерфейс Командная строка JavaScript Vue.js

задний план

В повседневной работе есть много небольших программ, настроенных с использованием одного и того же шаблона, поэтому я хочу создать инструмент для создания шаблонов и создать соответствующие небольшие программы на основе шаблона, а обычные небольшие программы написаны с использованием фреймворка mpvue, поэтому сначала обратитесь к принципу Vue-cli. Зная принцип, изготовление опалубочных лесов по индивидуальному заказу, безусловно, будет более эффективным.


Прежде чем говорить о коде, давайте сначала рассмотрим использование Vue-cli, Обычно мы используем пакет шаблонов webpack и вводим следующий код.

vue init webpack [project-name]

После выполнения этого кода система автоматически загрузит пакет шаблона, а затем задаст нам несколько вопросов, таких как имя шаблона, автор, использовать ли eslint, использовать ли npm или yarn для сборки и т. д. Когда на все вопросы будут даны ответы , мы начнем Генерировать проект строительных лесов.

Скачиваем исходники, нажимаем на репозиторий исходниковздесь, скаффолдинг, который обычно используется, все еще версии 2.0.Следует отметить, что ветка по умолчанию находится на dev, а версия 3.0 — на dev.

Давайте сначала посмотрим на 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. командир может выводить текст на терминал fs — модуль для чтения и записи файлов узла Модуль path предоставляет служебные функции для работы с путями к файлам и каталогам. ora Этот модуль используется для отображения анимации загрузки в терминале. user-home Получить путь к домашнему каталогу пользователя tildify преобразует абсолютные пути в волнистые пути, например./Users/sindresorhus/dev → ~/devinquirer — модуль ответов командной строки, вы можете сами задавать терминальные вопросы, а затем давать этим ответам соответствующую обработку 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-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 — это модуль, который может изменить цвет вывода терминала. Metalsmith — библиотека генерации статических сайтов (блогов, проектов) handlerbars — это компилятор шаблонов, черезtemplateа такжеjson, вывести html async Модуль асинхронной обработки, что-то вроде превращения метода в поток. объединить библиотеку интеграции механизма шаблонов multimatch Библиотека для сопоставления строковых массивов options - это самоопределяемый файл элемента конфигурации

Затем было прописано 2 рендерера, аналогично условному рендерингу виф велсе в вью

// 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: 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 также имеетшаблон быстрого запуска, затем на его основе настраиваем.Сначала делаем форк, создаем новую кастомную ветку и на этой ветке кастомизируем.

Там, где нам нужно настроить полезную библиотеку зависимостей, нам нужно дополнительно использовать 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.Так как используется только стандарт, мы находимся в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

image_1cj0ikq141je51ii31eek25t18il19.png-31.4kB

image_1cj0m986t1qdu1j97lruh4kjgo9.png-36.2kB

Суммировать

Настройка вышеперечисленных шаблонов очень проста, и это определенно сложнее в реальных проектах, но в соответствии с этой идеей это должно быть осуществимо. Например, в проекте также размещены некоторые самоинкапсулированные компоненты и т. д., поэтому я не буду здесь вдаваться в подробности. Принципиальный анализ основан на vue-cli 2.0, но на самом деле 3.0 тоже готов к работе.Если будет шанс в будущем, после глубокого понимания, я поделюсь им с вами, спасибо.


Справочная статья

  • Как работает vue-cli]6

Широкая реклама

Эта статья была опубликована вЕженедельный выпуск Mint Front End, Добро пожаловать в Watch & Star ★, пожалуйста, указывайте источник при перепечатке.

Добро пожаловать, чтобы обсудить, поставить лайк и перейти 。◕‿◕。~