代替Vue Cli的全新脚手架工具create vue示例解析

目录
  • 前言
    • npm init
    • npx
  • 源码
    • 主流程入口
    • 获取参数
    • 对话选项
    • 默认值
    • emptyDir函数
    • 模板写入
    • 简述
  • 快照
  • 总结

前言

美国时间 2021 年 10 月 7 日早晨,Vue 团队等主要贡献者举办了一个 Vue Contributor Days 在线会议,蒋豪群(知乎胖茶,Vue.js 官方团队成员,Vue-CLI 核心开发),在会上公开了create-vue,一个全新的脚手架工具。

create-vue 使用 npm init vue 一行命令就能快速的创建基于Vite的Vue3项目

npm init

$ npm init vue

以前我们初始化Vue-Cli项目时太多通过全局的形式安装, 然后通过vue create project-name命令进行项目安装,为什么npm init 也可以可以直接初始化一个项目且不需要全局安装?

本质是 npx 命令的语法糖,它在调用时是会转换为npx 命令

npm init vue@next -> npx create-vue@next
npm init @harexs -> npx @harexs/create
npm init @harexs/test -> npx @harexs/create-test

看完这三个例子应该就明白了 npm init 的作用了

npx

从本地或者远程npm包运行命令

npx 就是一种调用npm包的命令,如果没提供-c或者--call命令则默认从我们指定的包中,查找package.json中的 bin字段,从而确定要执行的文件

{
      "name": "create-vue",
      "version": "3.3.4",
      "description": "An easy way to start a Vue project",
      "type": "module",
      "bin": {
        "create-vue": "outfile.cjs" //关键
      }
  }

npm init vue 完整的解析就是 本地或者远程寻找 create-vue 这个包,然后查找package.jsonbin字段值的可执行文件,最终就是运行了outfile.cjs这个文件.

源码

这里使用川哥的create-vue-analysis仓库,仓库的版本便于我们学习其思路和实现,对应的是3.0.0-beta.6版本。 最新版本已经到了3.3.4, 但核心功能以及实现思路是不变的.

仓库地址 create-vue-analysis

主流程入口

//index.js
async function init() {
 ///
}
init().catch((e) => {
  console.error(e)
})

先不看其他部分, 先关注入口这里 就是 自调用了异步函数 init

获取参数

const cwd = process.cwd() //获取当前运行环境项目目录
//process.argv.slice(2) 用来获取 npx create-vue 后面传入的参数 值为数组
//minimist 用来格式化获取传入的参数
const argv = minimist(process.argv.slice(2), {
    alias: {
      typescript: ['ts'],
      'with-tests': ['tests', 'cypress'],
      router: ['vue-router']
    },
    // all arguments are treated as booleans
    boolean: true
  })
  //通过minimist获取的argv结果是个对象,通过对象属性去判断 是否有传入参数
  const isFeatureFlagsUsed =
    typeof (argv.default || argv.ts || argv.jsx || argv.router || argv.vuex || argv.tests) ===
    'boolean'
  //argv._ 这个_ 属性获取的是 没有-或者--开头的参数
  //比如 npm init create-vue xxx --a 那么argv就是 {_:['xxx'],a:true}
  let targetDir = argv._[0] // argv._[0] 假如对应{_:['xxx'],a:true} 就是 xxx
  //给一会的选项用的 默认项目名称 defaultProjectName 默认取targetDir
  const defaultProjectName = !targetDir ? 'vue-project' : targetDir
  const forceOverwrite = argv.force

接着是第二部分,主要就是获取 运行目录 以及 判断 命令调用时 有没有传入指定参数

对话选项

try {
result = await prompts(
      [
        {
        //name 参数就是一会要收集的对应变量
          name: 'projectName',
          //判断targetDir 有没有值 有值的话 就是null  null会跳过这个对话
          type: targetDir ? null : 'text',
          message: 'Project name:',
          initial: defaultProjectName, //默认结果值 获取参数部分已经说过这个变量了
          //onState 完成回调,让targetDir 取 用户输入的内容 没输入直接回车的话 取defaultProjectName
          onState: (state) => (targetDir = String(state.value).trim() || defaultProjectName)
        },
        {
          name: 'shouldOverwrite',
          //canSafelyOverwrite 判断是否是空目录并可以写入 否则判断有没有参数--force 目录
          // 有一个条件有效就为null 就跳过写入对话, 否则为confirm 确认框 y/n
          type: () => (canSafelyOverwrite(targetDir) || forceOverwrite ? null : 'confirm'),
          message: () => {
          //提示文本
            const dirForPrompt =
              targetDir === '.' ? 'Current directory' : `Target directory "${targetDir}"`
            return `${dirForPrompt} is not empty. Remove existing files and continue?`
          }
        },
        {
          name: 'overwriteChecker',
          //检查是否写入, type这里的函数 prev 是上一个选项的值, values 是整个对象
          //如果 shouldOverwrite 阶段 type变为  confirm  并且还选了 no
          //那么这一阶段判断后就会直接退出 不再执行 抛出异常
          type: (prev, values = {}) => {
            if (values.shouldOverwrite === false) {
              throw new Error(red('') + ' Operation cancelled')
            }
            return null
          }
        },
        {
          name: 'packageName',
          //正则验证 是否符合 package.json name 的值,不符合则让用户输入
          type: () => (isValidPackageName(targetDir) ? null : 'text'),
          message: 'Package name:',
          //没输入则取默认值 将targetDir 通过函数转换为符合packageName的格式
          initial: () => toValidPackageName(targetDir),
          //校验函数,如果 用户输入的包名无法通过 则提示Invalid package.json name 重新输入
          validate: (dir) => isValidPackageName(dir) || 'Invalid package.json name'
        },
        {
          name: 'needsTypeScript',
          type: () => (isFeatureFlagsUsed ? null : 'toggle'),
          message: 'Add TypeScript?',
          initial: false,
          active: 'Yes',
          inactive: 'No'
        },
        {
          name: 'needsJsx',
          type: () => (isFeatureFlagsUsed ? null : 'toggle'),
          message: 'Add JSX Support?',
          initial: false,
          active: 'Yes',
          inactive: 'No'
        },
        {
          name: 'needsRouter'
          //toggle 和confirm 无异  isFeatureFlagsUsed 如果有指定某一参数 则跳过后面所有对话,
          type: () => (isFeatureFlagsUsed ? null : 'toggle'),
          message: 'Add Vue Router for Single Page Application development?',
          initial: false,
          active: 'Yes',
          inactive: 'No'
        },
        {
          name: 'needsVuex',
          type: () => (isFeatureFlagsUsed ? null : 'toggle'),
          message: 'Add Vuex for state management?',
          initial: false,
          active: 'Yes',
          inactive: 'No'
        },
        {
          name: 'needsTests',
          type: () => (isFeatureFlagsUsed ? null : 'toggle'),
          message: 'Add Cypress for testing?',
          initial: false,
          active: 'Yes',
          inactive: 'No'
        }
      ],
      {
        onCancel: () => {
          throw new Error(red('') + ' Operation cancelled')
        }
      }
    )
  } catch (cancelled) {
    console.log(cancelled.message)
    process.exit(1)
  }

这一部分 使用了prompts这个库, 它提供了 命令行对话选项的能力, 这里主要收集用户的选择以及输入,

默认值

//取出前面对话选项后的值, 如果没有的话 就取 argv上的默认值
const {
    packageName = toValidPackageName(defaultProjectName),
    shouldOverwrite,
    needsJsx = argv.jsx,
    needsTypeScript = argv.typescript,
    needsRouter = argv.router,
    needsVuex = argv.vuex,
    needsTests = argv.tests
  } = result
   //root为 命令运行位置 + targetDir 得到 项目路径
  const root = path.join(cwd, targetDir)
  //如果之前判断的文件目录可写入 则执行一次emptyDir
  if (shouldOverwrite) {
    emptyDir(root)
     // 再判断目录不存在则创建这个目录
  } else if (!fs.existsSync(root)) {
    fs.mkdirSync(root)
  }
  console.log(`\nScaffolding project in ${root}...`)
  const pkg = { name: packageName, version: '0.0.0' }
    //往root目录  写入初始化 package.json文件
  fs.writeFileSync(path.resolve(root, 'package.json'), JSON.stringify(pkg, null, 2))

emptyDir函数

function emptyDir(dir) {
  postOrderDirectoryTraverse(
    dir,
    (dir) => fs.rmdirSync(dir),
    (file) => fs.unlinkSync(file)
  )
}

emptyDir 内部调用 postOrderDirectoryTraverse 函数,它来自utils下 我们接着看

export function postOrderDirectoryTraverse(dir, dirCallback, fileCallback) {
  //fs.readdirSync(dir) 返回一个数组 包含当前目录下的文件名 列表
  for (const filename of fs.readdirSync(dir)) {
    //遍历列表 得到 文件的完整路径
    const fullpath = path.resolve(dir, filename)
    if (fs.lstatSync(fullpath).isDirectory()) {
      // 如果这个文件 也是个目录 则递归继续遍历
      //因为删除目录的话 必须要先删除所有文件
      postOrderDirectoryTraverse(fullpath, dirCallback, fileCallback)
      //执行记dirCallback 回调 也就是fs.rmdirSync(dir) 移除目录
      dirCallback(fullpath)
      continue
    }
    //否则调用第二个回调 就是移除文件
    fileCallback(fullpath)
  }
}

emptyDir 就是对目录 递归遍历,遇到目录就继续递归遍历然后删除目录,文件就直接删除

模板写入

const templateRoot = path.resolve(__dirname, 'template')
  const render = function render(templateName) {
    const templateDir = path.resolve(templateRoot, templateName)
    renderTemplate(templateDir, root)
  }
  // Render base template
  render('base')
  // Add configs.
  if (needsJsx) {
    render('config/jsx')
  }
  if (needsRouter) {
    render('config/router')
  }
  if (needsVuex) {
    render('config/vuex')
  }
  if (needsTests) {
    render('config/cypress')
  }
  if (needsTypeScript) {
    render('config/typescript')
  }
   // Render code template.
  // prettier-ignore
  const codeTemplate =
    (needsTypeScript ? 'typescript-' : '') +
    (needsRouter ? 'router' : 'default')
  render(`code/${codeTemplate}`)
  // Render entry file (main.js/ts).
  if (needsVuex && needsRouter) {
    render('entry/vuex-and-router')
  } else if (needsVuex) {
    render('entry/vuex')
  } else if (needsRouter) {
    render('entry/router')
  } else {
    render('entry/default')
  }

先看第一部分

// work around the esbuild issue that `import.meta.url` cannot be correctly transpiled
  // when bundling for node and the format is cjs
  // const templateRoot = new URL('./template', import.meta.url).pathname
  const templateRoot = path.resolve(__dirname, 'template')
  //需要区分的是  templateDir取的是 对应当前执行文件环境中的文件地址
  // root变量 path.join(cwd, targetDir) process.cwd() 也就是取的命令执行时的地址
  //到时候对应的可能就是这样:
  //C:xxx/xxxx/npm-cache/_npx/xxxx/.bin/create-vue/template
  //D:/xxx/projectDir/vue-project
const render = function render(templateName) {
    const templateDir = path.resolve(templateRoot, templateName)
    renderTemplate(templateDir, root)
  }

需要注意 __dirname 是不存在会报错的,作者在注释也留有信息, 因为我们的项目环境是ESM,原先CJS的环境变量不能用了, 所以我们要换成这种写法

import path from "node:path";
import url from "node:url";
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
console.log(__filename, __dirname);

node:xxx 这种写法是Node提供的, 我在 promiseify 文章中也有讲到过

接下里看 renderTemplate函数

./utils/renderTemplate.js

function renderTemplate(src, dest) {
//src  是 npx拉取到create-vue本地缓存目录中的 模板目录地址
//dest 是 用户命令执行时的 项目地址
  const stats = fs.statSync(src)
  //statSync 返回一个文件类对象 可以看下面的截图
  if (stats.isDirectory()) {
    // if it's a directory, render its subdirectories and files recusively
    //如果src 是一个目录  则 在对应dest 的位置创建一个目录
    //recursive: true 允许递归创建目录 也就是允许 a/b/c 这种形式来创建
    fs.mkdirSync(dest, { recursive: true })
    for (const file of fs.readdirSync(src)) {
     //遍历src中所有文件, 递归自身,传入的参数变为 src/file 也即是每个文件名
      //第二个参数 对应的就是 dest/file
      renderTemplate(path.resolve(src, file), path.resolve(dest, file))
    }
    return
  }
   //这一步就是循环遍历 将src中的每个文件包目录都写入到 我们声明的project(dest)项目中
    //如果src不是一个目录 则来到这里 先取出文件名
  const filename = path.basename(src)
    //判断文件名是否为 package.json 并且 dest 也对应存在这个文件
  if (filename === 'package.json' && fs.existsSync(dest)) {
    // merge instead of overwriting
    //读取两个package.json的内部
    const existing = JSON.parse(fs.readFileSync(dest))
    const newPackage = JSON.parse(fs.readFileSync(src))
     //合并两个文件的内并 并重新排序  得到新的 package.json内容
    const pkg = sortDependencies(deepMerge(existing, newPackage))
    //重新写入到 dest下
    fs.writeFileSync(dest, JSON.stringify(pkg, null, 2) + '\n')
    return
  }
  if (filename.startsWith('_')) {
    // rename `_file` to `.file`
    //resolve 合并名字
    //dirname 取目录名字
    dest = path.resolve(path.dirname(dest), filename.replace(/^_/, '.'))
  }
//拷贝文件
  fs.copyFileSync(src, dest)
}

接下来解析合并以及排序package.json的工具函数

//判断是不是对象
const isObject = (val) => val && typeof val === 'object'
//合并数组值 通过new Set 去除重复项
const mergeArrayWithDedupe = (a, b) => Array.from(new Set([...a, ...b]))
/**
 * Recursively merge the content of the new object to the existing one
 * @param {Object} target the existing object
 * @param {Object} obj the new object
 */
function deepMerge(target, obj) {
  for (const key of Object.keys(obj)) {
    const oldVal = target[key]
    const newVal = obj[key]
    if (Array.isArray(oldVal) && Array.isArray(newVal)) {
      //合并数组项
      target[key] = mergeArrayWithDedupe(oldVal, newVal)
    } else if (isObject(oldVal) && isObject(newVal)) {
      //如果是对象则递归自身 继续遍历
      target[key] = deepMerge(oldVal, newVal)
    } else {
      //否则直接覆盖值
      target[key] = newVal
    }
  }
  return target
}
export default deepMerge

比较简单的深拷贝函数

export default function sortDependencies(packageJson) {
  // packageJson json的内容对象
  // sorted排序字段
  const sorted = {}
  //需要排序的类型
  const depTypes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
  for (const depType of depTypes) {
    //如果json中包含了 这个字段
    if (packageJson[depType]) {
      //赋值sorted对应的 depType 为空对象
      sorted[depType] = {}
      Object.keys(packageJson[depType]) //得到packageJson depType所有key的数组
        .sort() //使用默认排序
        .forEach((name) => {
          //遍历这个key的数组  然后将对应的值 重新赋值到sorted depType name
          sorted[depType][name] = packageJson[depType][name]
        })
    }
  }
  //ES6 展开对象语法 重复的key 会被覆盖 达到排序的效果
  return {
    ...packageJson,
    ...sorted
  }
}

sortDependencies 核心就是通过声明指定字段数组 然后取出来给到新的对象,然后通过ES6的展开语法 同样的key 新key覆盖旧key的应用实现了排序效果

又学到一招,展开语法真好使啊~

 // Add configs.
  if (needsJsx) {
    render('config/jsx')
  }
  if (needsRouter) {
    render('config/router')
  }
  if (needsVuex) {
    render('config/vuex')
  }
  if (needsTests) {
    render('config/cypress')
  }
  if (needsTypeScript) {
    render('config/typescript')
  }
  // Render code template.
  // prettier-ignore
  const codeTemplate =
    (needsTypeScript ? 'typescript-' : '') +
    (needsRouter ? 'router' : 'default')
  render(`code/${codeTemplate}`)
  // Render entry file (main.js/ts).
  if (needsVuex && needsRouter) {
    render('entry/vuex-and-router')
  } else if (needsVuex) {
    render('entry/vuex')
  } else if (needsRouter) {
    render('entry/router')
  } else {
    render('entry/default')
  }

在看这一段,基本能明白主要是在做什么操作。通过前面收集的变量进行render

 if (needsTypeScript) {
    // rename all `.js` files to `.ts`
    // rename jsconfig.json to tsconfig.json
    //前面说过 preOrderDirectoryTraverse函数,前面是通过遍历的形式 去移除文件和目录
    //这里的调用只传入第二个参数就是针对非目录的文件
    preOrderDirectoryTraverse(
      root,
      () => {},
      (filepath) => {
        //看作者的注释也很好理解,遇到.js结尾文件 重写为ts
        if (filepath.endsWith('.js')) {
          fs.renameSync(filepath, filepath.replace(/\.js$/, '.ts'))
          //遇到jsconfig.json 重写为 tsconfig.json
        } else if (path.basename(filepath) === 'jsconfig.json') {
          fs.renameSync(filepath, filepath.replace(/jsconfig\.json$/, 'tsconfig.json'))
        }
      }
    )
    // Rename entry in `index.html
    //取到 index.html 文件路径
    const indexHtmlPath = path.resolve(root, 'index.html')
    //通过utf-8 读取 文件内容
    const indexHtmlContent = fs.readFileSync(indexHtmlPath, 'utf8')
    //将原本引用的 main.js 重写为 main.ts  这里也很好理解
    fs.writeFileSync(indexHtmlPath, indexHtmlContent.replace('src/main.js', 'src/main.ts'))
  }
export function preOrderDirectoryTraverse(dir, dirCallback, fileCallback) {
  for (const filename of fs.readdirSync(dir)) {
    //遍历dir
    const fullpath = path.resolve(dir, filename)
    if (fs.lstatSync(fullpath).isDirectory()) {
      dirCallback(fullpath)
      //如果是目录 则 调用dirCallback
      // in case the dirCallback removes the directory entirely
      //执行完后再判断 目录是否还存在 再递归自身继续调用
      if (fs.existsSync(fullpath)) {
        preOrderDirectoryTraverse(fullpath, dirCallback, fileCallback)
      }
      continue
    }
    fileCallback(fullpath)
  }
}

preOrderDirectoryTraverse稍有不同,对于目录级的回调会先执行然后再判断目录是否还存在,在进行递归

 if (!needsTests) {
    // All templates assumes the need of tests.
    // If the user doesn't need it:
    // rm -rf cypress **/__tests__/
    preOrderDirectoryTraverse(
      root,
      (dirpath) => {
        //对于目录 得到目录名
        const dirname = path.basename(dirpath)
        //如果目录名为cypress || __tests__
        if (dirname === 'cypress' || dirname === '__tests__') {
          emptyDir(dirpath) //执行 清空目录操作
          fs.rmdirSync(dirpath) //最后移除这个目录
        }
      },
      () => {}
    )
  }
  //通过npm_execpath来获取当前执行的包管理器绝对路径
  //得到路径后 通过 关键 字符去匹配
  const packageManager = /pnpm/.test(process.env.npm_execpath)
    ? 'pnpm'
    : /yarn/.test(process.env.npm_execpath)
    ? 'yarn'
    : 'npm'
  // README generation
  //生产README  generateReadme 也比较简单不展开讲它了
  fs.writeFileSync(
    path.resolve(root, 'README.md'),
    generateReadme({
      projectName: result.projectName || defaultProjectName,
      packageManager,
      needsTypeScript,
      needsTests
    })
  )
  console.log(`\nDone. Now run:\n`)
  if (root !== cwd) {
    console.log(`  ${bold(green(`cd ${path.relative(cwd, root)}`))}`)
  }
  console.log(`  ${bold(green(getCommand(packageManager, 'install')))}`)
  console.log(`  ${bold(green(getCommand(packageManager, 'dev')))}`)
  console.log()

简述

至此,整个源码就解析完毕了,刚开始确实感觉很复杂,毕竟Node很多API都不是很熟悉, 然后就一一拆解开来对着文档慢慢看了,读源码还是很需要耐心~

大体的流程:

  • 收集用户指定的参数 以及 项目名
  • 通过对话选项卡确定用户的配置
  • 根据对话选项卡后用户的配置匹配模板目录下的文件,一一写入到项目文件夹中
  • 再判断是否需要Ts/测试 对文件做修改
  • 生成其他文件 流程结束

快照

项目中还有个snapshot.js文件, 它主要是通过const featureFlags = ['typescript', 'jsx', 'router', 'vuex', 'with-tests']组合生成 31种加上default共计 32种组合,然后通过子线程命令spawnSync 调用 bin 然后循环把 我们组合好的 参数传给它执行,也就是相当于执行了npm init vue这一步操作并传入组合好的参数

最后生成不同的模板在 playground目录中

关于这个命令: spawnSync

总结

在写完本文的时候,把源码大概梳理了2-3遍, 读源码很需要耐心, 遇到不懂的API还要翻文档, 但是等你完全弄明白里面的原理和实现思路之后,就有一种油然而生的开心, 而且以后要开发类似的脚手架时 也可以有一定的思路和想法去实现它!

以上就是代替Vue Cli的全新脚手架工具create vue示例解析的详细内容,更多关于Vue Cli脚手架工具create vue的资料请关注我们其它相关文章!

(0)

相关推荐

  • VUE3中h()函数和createVNode()函数的使用解读

    目录 h()函数和createVNode()函数的使用 使用方法 VUE3中h方法和createVnode的实现 在公共包shared里写上ShapeFlags 在runtime-core模块里创建vnode.ts文件专门处理虚拟节点 为了后续的diff算法,我们要给这个虚拟节点上加一些属性和标识 h的用法 创建h.ts文件来写h方法 h()函数和createVNode()函数的使用 使用方法 h(标签, {属性},内容) h(标签, {属性},[可以继续嵌套h()]) createVNode(

  • vue中关于click.stop的用法

    目录 关于click.stop的用法 @click.stop与@click.prevent 一.@click.stop 二.@click.prevent 关于click.stop的用法 click.stop 阻止点击事件继续传播 场景: 在table中使用,点击当前行,当前行被勾选,但是点击当前行中按钮或点击事件时,使用此方法,则在触发当前点击事件后,阻止行的选中事件 使用: html <el-table ref="tableRef" :data="tableData&

  • VUE使用vue create命令创建vue2.0项目的全过程

    目录 前言 第一步,打开命令行后,首先进入我们想要创建项目的目录下 第二步,执行 vue create xxxx 命令 总结 前言 为了保证创建过程中避免出现因权限不足的原因 从而 导致创建失败的问题,我们使用 管理员身份 打开命令行 第一步,打开命令行后,首先进入我们想要创建项目的目录下 g: 表示切换进入G盘 cd git 表示打开 当前盘下的 git 文件夹 大家可以根据以上两个命令进入自己想要保存项目的目录下,我这里是保存在 G:\Git 文件夹 第二步,执行 vue create xx

  • vue中this.$createElement方法的使用

    目录 vue this.$createElement方法 关于createElement使用实例 参数说明 使用示例 源码解读 vue this.$createElement方法 element ui中的slider的marks属性中使用到this.$createElement方法设置标记样式: 上面虽然只用到两个参数,实际上,此方法有三个参数: ①第一个参数为标签,即创建的节点元素的标签是什么 ②第二个参数是属性配置,如class.style等 ③第三个参数是节点元素的内容 this.$cre

  • Vue中使用 Echarts5.0 遇到的一些问题(vue-cli 下开发)

    目录 Vue使用Echarts5.0的一些问题 问题 解决方案一 解决方案二 为什么会出现这种情况? vue使用echarts 5.0“export ‘default‘ (imported as ‘echarts‘) was not found in ‘echarts‘ Vue使用Echarts5.0的一些问题 问题 最新版的 Echarts5.0 使用 import echarts from 'echarts' 导入,会发现导出的 echarts 是 undefined 的情况,无法正常使用.

  • Vue如何给组件添加点击事件 @click.native

    目录 给组件添加点击事件 @click.native 问题 结论 vue中@click.native使用 @click.native是给组件绑定原生事件 给组件添加点击事件 @click.native 问题 毕设项目中有个产品展示列表,当初用组件写的,今天想要点击获取当前选中的产品的数据,刚开始直接使用@click写的,但是点击并没有生效. 我尝试在组件中添加点击事件,点击图片,控制台输出1. 结果是可以实现的. 结论 给vue组件绑定事件时候,必须加上native ,否则会认为监听的是来自It

  • 代替Vue Cli的全新脚手架工具create vue示例解析

    目录 前言 npm init npx 源码 主流程入口 获取参数 对话选项 默认值 emptyDir函数 模板写入 简述 快照 总结 前言 美国时间 2021 年 10 月 7 日早晨,Vue 团队等主要贡献者举办了一个 Vue Contributor Days 在线会议,蒋豪群(知乎胖茶,Vue.js 官方团队成员,Vue-CLI 核心开发),在会上公开了create-vue,一个全新的脚手架工具. create-vue 使用 npm init vue 一行命令就能快速的创建基于Vite的Vu

  • 详解Vue CLI 3.0脚手架如何mock数据

    前后端分离的开发模式已经是目前前端的主流模式,至于为什么会前后端分离的开发我们就不做过多的阐述,既然是前后端分离的模式开发肯定是离不开前端的数据模拟阶段. 我们在开发的过程中,由于后台接口的没有完成或者没有稳定之前我们都是采用模拟数据的方式去进行开发项目,这样会使我们的前后端会同时的进行,提高我们的开发效率. 因为最近自己在自学 Vue 也在自己撸一个项目,肯定会遇到使用数据的情况,所以就想着如何在前端做一些 mock 数据的处理,因为自己的项目使用的是 vue/cli 3.0 与 vue/cl

  • 详解使用vue脚手架工具搭建vue-webpack项目

    对于Vue.js来说,如果你想要快速开始,那么只需要在你的html中引入一个<script>标签,加上CDN的地址即可.但是,这并不算是一个完整的vue实际应用.在实际应用中,我们必须要一系列的工具,包括:模块化,转译,预处理,热加载,静态检测和自动化测试等.对于一个需要长期维护和大型的项目而言,这些工具是必不可少的,但是尝试配置初始化这些很痛苦.这就是我们发布vue官方提供的脚手架工具的原因,一个简单的构建工具,通过几个默认的步骤帮助你快速的构建Vue.js项目. 1.安装node环境 可以

  • vue cli 3.0通用打包配置代码,不分一二级目录

    1.项目根目录下新建vue.config.js,进行如下配置即可 module.exports={ publicPath:'', }; 补充知识:Vue-CLI3.0更改打包配置 在实际项目开发中,我们一般会直接使用vue.vue-cli来搭建项目.vue框架的宗旨就是让初学者轻松上手,所以,对于打包配置的一些东西,vue的脚手架已经帮我们做好了完美的封装,让我们达到安装既用的效果,也不用担心太多不会做打包配置的问题. 在前期使用Vue-CLI2.0搭建项目时,我们可以在build目录下,直接修

  • 详解基于node.js的脚手架工具开发经历

    前言 我们团队的前端项目是基于一套内部的后台框架进行开发的,这套框架是基于vue和ElementUI进行了一些定制化包装,并加入了一些自己团队设计的模块,可以进一步简化后台页面的开发工作. 这套框架拆分为基础组件模块,用户权限模块,数据图表模块三个模块,后台业务层的开发至少要基于基础组件模块,可以根据具体需要加入用户权限模块或者数据图表模块.尽管vue提供了一些脚手架工具vue-cli,但由于我们的项目是基于多页面的配置进行开发和打包,与vue-cli生成的项目结构和配置有些不一样,所以创建项目

  • Vue CLI 3搭建vue+vuex最全分析(推荐)

    一.介绍 Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统.有三个组件: CLI:@vue/cli 全局安装的 npm 包,提供了终端里的vue命令(如:vue create .vue serve .vue ui 等命令) CLI 服务:@vue/cli-service是一个开发环境依赖.构建于 webpack和 webpack-dev-server之上(提供 如:serve.build 和 inspect 命令) CLI 插件:给Vue 项目提供可选功能的 npm 包 (如:

  • 浅谈Vue CLI 3结合Lerna进行UI框架设计

    当前大部分UI框架设计的Webpack配置都相对复杂,例如 Element. Ant Design Vue和Muse-UI等Vue组件库.例如Element,为了实现业务层面的两种引入形式( 完整引入 和 按需引入 ),以及抛出一些可供业务层面通用的 utils . i18n 等,Webpack配置变得非常复杂.为了简化UI框架的设计难度,这里介绍一种简单的UI框架设计,在此之前先简单介绍一下 Element 的构建流程,以便对比新的UI框架设计. 一般组件库的设计者将引入形式设计成 完整引入

  • Vite和Vue CLI的优劣

    Vue 生态系统中有一个名为 Vite 的新构建工具,它的开发服务器比 Vue CLI 快 10-100 倍. 这是否意味着 Vue CLI 已经过时了?在本文中,我将比较这两种构建工具,并说明它们的优缺点,以便你可以决定哪一种适合你的下一个项目. Vue CLI 概述 大多数 Vue 开发人员都知道,Vue CLI 是使用标准构建工具和最佳实践配置快速建立基于 Vue 的项目的不可或缺的工具. 其主要功能包括: 工程脚手架 带热模块重载的开发服务器 插件系统 用户界面 在本讨论中需要注意的是,

  • 使用Vue CLI创建typescript项目的方法

    使用最新的Vue CLI @vue/cli创建typescript项目,使用vue -V查看当前的vue cli版本 安装命令 npm install -g @vue-cli 创建项目 vue create my-vue-typescript 上下键选择,空格键确定 接下来是一些常规选项 下面是询问要不要记录这次配置以便后面直接使用,我们选择y 当确定配置后会在C:\Users\Administrator\.vuerc下生成一个刚选好的配置记录 { "useTaobaoRegistry"

  • 使用vue-cli脚手架工具搭建vue-webpack项目

    最近更新了webpack配置详解,可移步vue-cli webpack详解 对于Vue.js来说,如果你想要快速开始,那么只需要在你的html中引入一个<script>标签,加上CDN的地址即可.但是,这并不算是一个完整的vue实际应用.在实际应用中,我们必须要一系列的工具,包括:模块化,转译,预处理,热加载,静态检测和自动化测试等.对于一个需要长期维护和大型的项目而言,这些工具是必不可少的,但是尝试配置初始化这些很痛苦.这就是我们发布vue官方提供的脚手架工具的原因,一个简单的构建工具,通过

随机推荐