vue3学习 --- 脚手架

1,335 阅读9分钟

Vue CLI脚手架

在真实开发中我们不可能每一个项目从头来完成所有的webpack配置,这样显示开发的效率会大大的降低

所以我们会预先搭建好一个项目的模板,以后直接在这个项目模板的基础上进行二次开发即可,这个项目模板就通常就被称之为脚手架

Vue的脚手架就是Vue CLI:

  • CLI是Command-Line Interface, 翻译为命令行界面,

  • 我们可以通过CLI选择项目的配置和创建出我们的项目

  • Vue CLI已经内置了webpack相关的配置,我们不需要从零来配置

安装和使用

# 安装
npm install @vue/cli -g

# 升级
npm update @vue/cli -g

# 项目创建 --- 执行后,根据@vue/cli的提示,逐步进行配置即可
vue create 项目的名称

# 查看脚手架的版本
vue --version

我们可以查看package.json中配置的script来启动我们的vue3项目,或者对它进行打包操作

"scripts": {
  "serve": "vue-cli-service serve",
  "build": "vue-cli-service build"
}

vue-cli-service运行原理

以运行npm run serve为例

执行npm run serve的本质就执行了package.json中的vue-cli-service serve

也就是在命令行中执行了vue-cli-service serve,所以会去执行node_modules/.bin/vue-cli-service

.bin文件下的文件都是一个个软连接(symbol link), 所以实际执行的是@vue/cli-service这个包

@vue/cli-service的package.json中我们可以看到

"bin": {
  "vue-cli-service": "bin/vue-cli-service.js"
}

所以当我们执行vue-cli-service serve的时候,真正执行的文件是node_modules/@vue/cli-service/bin/vue-cli-service.js

vue-cli-service.js

// 核心的逻辑代码如下
const Service = require('../lib/Service')
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())

service.run(command, args, rawArgv).catch(err => {
  error(err)
  process.exit(1)
})

service.js

const PluginAPI = require('./PluginAPI')

constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
  this.commands = {}
  this.plugins = this.resolvePlugins(plugins, useBuiltIn)

  // this.plugins = [{ id: built-in:路径名, apply: 配置文件所暴露的对象 }, ...]
  resolvePlugins (inlinePlugins, useBuiltIn) {
    const idToPlugin = id => ({
      id: id.replace(/^.\//, 'built-in:'),
      apply: require(id)
    }) 

    const builtInPlugins = [
      './commands/serve',
      './commands/build',
      './commands/inspect',
      './commands/help',
      // config plugins are order sensitive
      './config/base',
      './config/css',
      './config/prod',
      './config/app'
    ].map(idToPlugin)
  }
  
  init() {
    this.plugins.forEach(({ id, apply }) => {
      apply(new PluginAPI(id, this), this.projectOptions)
    })
  }

  async run (name, args = {}, rawArgv = []) {
    // ...
    
    // 取出对应情况下的特有配置选项
    let command = this.commands[name]
    // ...
    const { fn } = command
    
    // 执行函数,将webpack对应配置插入公共配置并执行webpack打包
    return fn(args, rawArgv)
  }
}

./commands/serve

module.exports = (api, options) => {
  api.registerCommand('command', {
    // 描述对象
  }, async function serve (args) {

  api.chainWebpack(webpackConfig => {
    // 封装的webpack相关的配置
  })

  const webpackConfig = api.resolveWebpackConfig()

  // 生成webpack编译器
  const compiler = webpack(webpackConfig)

  // 生成devServer
  const server = new WebpackDevServer(compiler, {/* 配置选项 */})

  // 启动服务,监听用户构建
  server.listen(port, host, err => {
    if (err) {
      reject(err)
    }
  })
    
  resolveChainableWebpackConfig() {
    const chainableConfig = new Config()
    // apply chains
    this.webpackChainFns.forEach(fn => fn(chainableConfig))
    return chainableConfig
  }
    
  resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
   // ... 
   let config = chainableConfig.toConfig()
   
   // 合并用户自定义配置信息
   // 最后合并,目的是可以覆盖之前的配置项
   config = merge(config, fn)
   return config
  }
 })

./config/base

module.exports = (api, options) => {
  api.chainWebpack(webpackConfig => {
		// webpack相关配置信息
  }
}

PluginAPI,js

registerCommand (name, opts, fn) {
  if (typeof opts === 'function') {
    fn = opts
    opts = null
  }

  // 如果是特有的配置信息(如serve或build的时候特有的配置)
  // 会将执行webpack配置的操作封装成一个函数
  // 并存在于this.service.commands
  this.service.commands[name] = { fn, opts: opts || {}}
}

// 如果是公共配置,直接将包裹了webpack配置的函数存放入this.service.webpackChainFns
chainWebpack (fn) {
  this.service.webpackChainFns.push(fn)
}

resolveWebpackConfig (chainableConfig) {
  return this.service.resolveWebpackConfig(chainableConfig)
}

IXFYYu.jpg

Vite

Vite (法语意为 "快速的",发音 /vit/) 是一种新型前端构建工具,能够显著提升前端开发体验

官方的定位:下一代前端开发与构建工具

主要解决在随着项目越来越大,需要处理的JavaScript呈指数级增长,模块越来越多的情况下

构建工具需要很长的时间才能开启服务器,HMR也需要几秒钟才能在浏览器反应出来的问题

vite主要由两部分组成:

  • 一个开发服务器,它基于原生ES模块提供了丰富的内建功能,HMR的速度非常快速
  • 一套构建指令,它使用rollup打开我们的代码,并且它是预配置的,可以输出生成环境的优化过的静态资源

目前,现代的浏览器依旧可以很好的支持和识别模块化相关的代码

index.js

// 因为没有webpack之类的构建工具,帮助我们进行后缀名的补全
// 所以使用浏览器原生模块语法的时候,引入的模块是需要添加对应的后缀名
import { sum } from './sum.js'
console.log(sum(10, 20))

index.html

<!-- 在引入脚本的时候,添加上 type="module" 就告诉浏览器引入的是一个模块 -->
<!--
	1. 如果使用原生模块语法,需要在服务器环境运行,file协议会报错
  2. 所有的模块js的加载都是异步的
-->
<script src="./index.js" type="module"></script>

可见,在开发阶段,我们不需要借助构建工具来帮助我们进行模块化打包操作

因为开发的时候,我们的浏览器都是那些可以识别ESM的现代浏览器

我们只需要到发布阶段再将我们的项目进行打包操作已适配更多的浏览器

但是直接使用浏览器原生特性进行开发依旧是存在以下问题:

  1. 使用TypeScript、less、vue等文件的时候,浏览器无法直接识别,需要编译转换后浏览器才会认识

  2. 浏览器不会对第三方包的结构和依赖进行解析,我们需要手动引入包中的具体文件后才可以使用

    import _ from './node_modules/lodash-es/cloneDeep.js'
    
  3. 目前绝大多数的库都是基于CJS编写的,无法直接在浏览器环境下使用

所以在实际开发中,我们仍需要构建工具来帮助我们完成相应的构建,vite就是其中的一个工具

# 安装
npm install vite –g # 全局安装 
npm install vite –D # 局部安装
# 使用
# vite的编译底层依旧依赖于node,所以需要预先安装node环境

# 如果出现esbuild无法正确安装的bug的时候,可以执行以下命令
# node node_modules/esbuild/install.js

# 开发 vite会开启一个本地服务(类似于devServer)
# 默认端口3000 (每新开一个,如果之前的服务没有关闭会自动端口号加1)
vite

# 打包
vite build

# 预览打包后的内容
# 需要先build后才可以预览
# 默认端口5000 (每新开一个,如果之前的服务没有关闭会自动端口号加1)
vite preview

注意: vite是以index.html为入口,而不是诸如main.js之类的js文件

<body>
  <!-- 我们需要将对应的js文件,手动以模块的方式进行引入,否则打包出来的内容会不存在任何的内容 -->
  <script type="module" src="./src/main.js"></script>
</body>

资源处理

  1. vite可以直接支持css的处理,直接导入css即可;

  2. vite可以直接支持css预处理器,包括常见的less,sass,stylus等,但是vite依旧需要借助他们自己的编译工具进行打包操作

    所以依旧需要安装对应的预处理工具,例如我们编写sass文件的使用,需要手动安装sass,安装后无需再进行任何的配置,vite既可直接对sass文件进行处理

  3. vite直接支持postcss的转换,只需要安装postcss,并且配置 postcss.config.js 的配置文件即可

  4. vite对TypeScript是原生支持的,因为vite底层有依赖于esbuild,而esbuild对ts是可以直接解析的,所以直接导入对应的ts文件即可

  5. vite底层依赖的esbuild集成了babel的大部分功能,所以在绝大多数情况下,不在需要单独配置babel

  6. vite对vue提供第一优先级支持,

# 安装
npm install @vitejs/plugin-vue -D # Vue 3 单文件组件支持
npm i @vitejs/plugin-vue-jsx -D # Vue 3 JSX 支持

vite.config.js

import vue from '@vitejs/plugin-vue'

module.exports = {
  plugins: [
    vue()
  ]
}
  1. Vite 可以直接导入资源模块,无需任何的配置

  2. vite默认可以直接导入js和json资源模块,而不需要任何的配置(导入文件无需后缀名 因为vite有默认的后缀名)

但是如果需要引入vue文件,如果要省略后缀,还是需要进行相应的配置(不推荐)

  1. 在开发阶段,vue默认只会将我们的模块打包成ESM。因为vite默认开发浏览器都是可以直接解析和识别ESM的现代浏览器

  2. vite以index.html为入口文件进行构建,这个文件需要被存放于根目录,vite会基于这个文件构建本地服务

在vite1.x的时候,构建本地服务使用的是koa, 但是在vite2.x中,不在使用koa,而是使用Connect来搭建的服务器

这是因为vite的服务更多的功能是为我们进行转发操作,例如我们查看浏览器的connect会发现我们请求的依旧是scss文件,ts文件,但是点进去查看内容的时候,发现是编译后的js代码(包括样式文件,点进去查看内容依旧是js,而不是css)

这是因为vite中的服务器Connect会对我们的请求进行转发。从而将编译后的代码,给浏览器返回,浏览器可以直接进行解析

  1. 对于一些第三方库,vite会将这些库进行预打包,并将打包后的文件存放在node_modules/.vite中,从而提升打包效率,只有package.json中包的依赖发生了改变,vite才会重新对第三方包进行预打包操作

ESBuild

ESBuild的特点:

  • 超快的构建速度,并且不需要缓存
  • 支持ES6和CommonJS的模块化
  • 支持ES6和CommonJS的模块化
  • 支持Go、JavaScript的API (因为ESBuild是使用go语言来进行编写的)
  • 支持TypeScript、JSX等语法编译
  • 支持SourceMap
  • 支持代码压缩
  • 支持扩展其他插件

IXFoTE.png

ESBuild为什么这么快呢?

  • 使用Go语言编写的,可以直接转换成机器代码,而无需经过字节码
  • ESBuild可以充分利用CPU的多内核,尽可能让它们饱和运行
  • ESBuild的所有内容都是从零开始编写的,而不是使用第三方,所以不会因为第三方插件的性能而拖累ESBuild的构建速度
  • .......

vite针对于vue的脚手架

# 安装
$ npm install @vitejs/create-app -g

# 使用
$ create-app <项目名>

# 或者我们也可以将上述两步合并为如下指令
# 注意: 如下命令后不需要加上项目名
# 因为他们本质是交互式工具,项目名是在交互配置中进行设置的(类似于npm init或者使用@vue/cli进行vue项目搭建时候的操作)
$ npm init @vitejs/app

# 上述命名还可以写成
$ npm init vite@latest # 其实就是 npm init vite
# 
$ yarn create vite