Skip to content

Vite

什么是构建工具?


构建工具它让我们可以不用每次都关心我们的代码在浏览器如何运行,我们只需要首次给构建工具提供一个配置文件(这个配置文件也不是必须的,如果你不给它,它会默认去帮你处理)。有了这个集成的配置文件以后,我们就可以在下次需要更新的时候调用一次对应的命令就好了。如果我们再结合热更新,我们就更加不需要关心任何东西。这就是构建工具要做的工作!它让我们不用关心我们编写代码转换的工作。只需要我们去完成自己的开发工作。

企业级项目里都可能会具备一些功能

  1. typescript:如果遇到ts文件我们需要使用tsc将typescript文件转换成javascript代码
  2. React/vue:安装react-compiler/vue-complier,将我们写的jsx文件或者vue文件转换为render函数
  3. less/sass/postcss/component-style:我们又需要安装less-loader, sass-loader等一系列编译工具
  4. 语法降级: babel 将es的心愿降级为浏览器可识别的代码
  5. 体积优化:uglifyjs 将代码进行压缩,编程体积小性能高的文件

打包

概念:将我们写的浏览器不认识的代码 交给构建工具进行编译处理的过程就叫做打包,打包完成以后会给我们一个浏览器可以认识的文件。

开发者使用构建工具的目的

构建工具承担着开发者不需要干的一些工作:

  1. 模块化支持:支持直接从node_modules里引入代码+多重模块化支持
  2. 处理代码兼容性:比如babel语法降级,less, ts 语法转换(语法转换不是构造工具做的,构建工具将这些语法对应的处理工具集成进来自动化处理)
  3. 提高项目性能:压缩文件,代码分割。
  4. 优化开发体验:构建工具会帮助你自动监听文件的变化,当文件变化以后会自动帮你调用对应的集成工具进行重新打包,然后在浏览器重新运行(整个过程叫做热更新,hot replacement)
    1. 开发服务器:跨域问题,用react-cli create-react-element vue-cli 解决跨域的问题

国内主流的构建工具

  • webpack
  • vite
  • esbuild

国际主流的构建工具

  • webpack
  • vite
  • esbuild
  • parcel
  • rollup
  • grunt
  • gulp

vite与webpack比较的优势

当我们构建越来越大型的应用时,需要处理javascript代码量也呈现指数级增长。包含数千个模块的大型项目相当普遍。当我们遇到性能瓶颈时,使用javascript开发的工具通常需要很长时间(甚至是几分钟)才能启动开发服务器,即使使用HMR(热更新),文件修改后的效果也需要几秒钟才能在浏览器上反映出来。如果循环往复,迟钝的反馈会极大影响开发者的开发效率和心情。

webpack支持多种模块化,它一开始就必须要统一模块化代码,所以它就意味着需要把所有的依赖读取一遍。所以导致项目越大花费的时间长、开发者体验差。

vite是基于es_modules的,它和webpack的侧重点不一样,webpack更关注兼容性,而vite关注浏览器端的开发体验。

开发者必须理解的vite脚手架和vite


vite官网搭建vite项目文档教程:https://vitejs.dev/guide/#scaffolding-your-first-vite-project

bash
$ yarn create vite

create-vite和vite的关系

create-vite内置了vite,就像vue-cli内置了webpack

帮助理解vue-cli和crate-vite的功能:

预设:买房子:毛坯房(我们的工程)无装修 精装房 搭建好了

我们自己搭建一个项目:下载vite、vue、post-css、less、babel

vue-cli/create-react-app(开发商提供的精装房模板)帮你把react/vue都下载好了,同时帮助你配置调整到最佳实践。

create-vite(开发商提供的精装房模板) 下载了vite、vue、post-css、less、babel,同时帮助你配置调整到最佳实践。

vite启动项目


开发者不需要任何额外的配置就可以使用vite来帮助处理构建工作

安装vite

shell
yarn add vite

vite的预加载

在处理的过程中如果说看到了有绝对路径或者相对路径的引用,它会尝试开启路径补全。

bash
import _ from "lodash";

路径补全

bash
import _ from "/node_modules/lodash";
import __vite__cjsImport0_lodash from "/node_modules/.vite/deps/lodash.js?v=3b357916";

找寻依赖的过程中是自当前目录以此向上查找的过程,直到搜寻到根目录或者搜寻到对应依赖为止。

生产和开发时的区别

开发

每次依赖预构建所重新构建的相对路径都是正确的

bash
yarn dev

生产

vite会全权交给一个叫rollup的库去完成生产环境的打包。

bash
yarn build
# package.json

{
  "dependencies": {
    "n": "^9.1.0",
    "vite": "^4.4.4"
  },
  "script":{
    "dev": "vite",
    "build":"vite build",
  },
}

依赖预构建

首先vite会找到对应的依赖,然后调用esbuild(对js语法进行处理的一个库)。将其他规范的代码转换成esmodule规范,然后放到当前目录下的node_modules/.vite/deps。

它解决的问题:

  1. 不同的第三方包会有不同的导出格式(这个是vite没有办法约束别的开发者的)
  2. 对路径的处理上可以直接使用.vite/deps,方便路径重写
  3. 叫做路径多包传输的性能问题(也是原生esmodule规范不干支持node_modules的原因之一)

它的好处:

vite有了依赖预构建以后无论有多少的额外的export和import,vite都会尽可能的将它们进行集成,只生成一个或者几个模块。

vite配置文件处理细节

  1. vite配置文件的语法提示

    1. 如果你使用的是webstorm,你可以得到很好的语法补全
    2. 如果你使用的是vscode,则需要做一些特殊处理。
  2. 为了方便其他开发者对开发的项目都有语法补全,需要做特殊处理

    1. 编辑vite.config.js文件
    2. 输入代码框信息
    bash
    # vite.config.js
    import {defineConfig} from "vite";
    
    export default definConfig{
    	optimizeDeps:{
    		exclude:[], //将指定数组的依赖不进行依赖预构建
    }.
    }

或者(优先级第一)

bash
# vite.config.js

/**@type import("vite").UserCOnfig */
const viteConfig = {
    optimizeDeps:{
        exclude:[], 
    },
}

export default viteConfig;
# vite.config.js
export default{
	optimizeDeps:{
		exclude:[],  //将指定数组中的依赖不进行依赖预构建	
},
}

关于环境的处理

  1. 过去我们使用webpack的时候,我们要区分配置文件的一个环境

    1. webpack.dev.config
    2. webpack.prod.config
    3. webpack.base.config
    4. webpackmerge
  2. 现在我们使用vite的时候,我们需要下面这种配置

    1. 第一种写法

      bash
      # vite.config.js
      import { defineConfig } from "vite";
      
      export default defineConfig(({command})=>{
          if(command === 'build'){
              // build configuration
          }else{
              // serve configuration
          }
      })
    2. 第二种写法(策略模式)

      bash
      import { defineConfig } from "vite";
      import viteBaseConfig from "./vite.base.config";
      import viteDevConfig from "./vite.dev.config";
      import viteProdConfig from "./vite.prod.config";
      
      const envResolver = {
      	"build": () => Object.assign({},viteBaseConfig, viteProdConfig),
      	"serve": () => Object.assign({},viteBaseConfig, viteDevConfig),
      }
      
      export default defineConfig(({command})=>{
          return envResolver[command]();
      })
    3. 第三种写法

      bash
      import { defineConfig } from "vite";
      import viteBaseConfig from "./vite.base.config";
      import viteDevConfig from "./vite.dev.config";
      import viteProdConfig from "./vite.prod.config";
      
      const envResolver = {
      	"build": () => ({...viteBaseConfig, ...viteProdConfig}),
      	"serve": () => ({...viteBaseConfig, ...viteDevConfig}),
      }
      
      export default defineConfig(({command})=>{
          return envResolver[command]();
      })
    4. 第四种写法

      bash
      import { defineConfig } from "vite";
      import viteBaseConfig from "./vite.base.config";
      import viteDevConfig from "./vite.dev.config";
      import viteProdConfig from "./vite.prod.config";
      
      const envResolver = {
      	"build": () => {
              return ({...viteBaseConfig, ...viteProdConfig});
          },
      	"serve": () => {
              return({...viteBaseConfig, ...viteDevConfig});
          },
      }
      
      export default defineConfig(({command})=>{
          return envResolver[command]();
      })

是build还是serve主要取决于我们敲的命令是开启开发环境的还是生产环境的。

vite中针对环境变量的配置

环境变量:会根据当前代码环境产生值的变化的变量叫做环境变量 代码环境:开发环境、测试环境、预发布环境、灰度环境、生产环境。

vite使用了dotenv第三方库,vite内置了dotenv的第三方库。不需要开发者再去下载了!

dotenv的功能是去项目的根目录下去找.env的文件,并解析这个文件中的环境变量。并将其注入到process对象下(但是vite考虑到和其他配置的一些冲突问题,它不会直接注入到process对象下)。

这涉及到vite.config.js中的一些配置:

  • root
  • envDir:用来配置当前环境变量的文件地址

vite给我们提供了一些补偿措施,我们可以调用vite的loadEnv的方法去设置配置文件。

当前env文件所在的目录

  • process.cwd方法:返回当前node进程的工作目录
  • .env:所有环境都需要用到的环境变量
  • .env.development:开发环境需要用到的环境变量(默认情况下vite将开发环境取名为development)
  • .env.production:生产环境需要用到的环境变量(默认情况下vite将生产环境取名为production)

开发者只要启动时写命令

bash
yarn dev --mode development  #会将mode设置为development传递过去

如果开发者想要使用生产环境

bash
yarn dev --mode production  # 会将mode设置为production传递过去

当我们调用loadEnv方法的时候,它会做如下几件事情:

1.直接找到.env文件,并解析其中的环境变量,并放进一个对象里。

2.会将传进来的mode这个变量的值进行拼接:.env.development, 并根据开发者提供的目录去取对应的配置文件并进行解析,并放入一个对象。

3.我们可以理解为它覆盖了默认的对象。

bash
const baseEnvConfig = 读取.env的位置;
const modeEnvConfig = 读取.env相关配置;
const loadEnvConfig = {...baseEnvConfig, ...modeEnvConfig};

如果是客户端,vite会将对应的环境变量注入到import.meta.env中,所以它做了一层拦截,如果你的环境变量不是以vite开头的,它不会注入到客户端中去。如果开发者想要更改这个前缀,可以去使用在vue.config.js文件里修改envPrefix配置或者用户自己新建的vue.base.config.js文件去修改envPrefix配置

bash
# package.json
{
  "name": "my-app",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vite --mode test"
  },
  "dependencies": {
    "n": "^9.1.0",
    "vite": "^4.4.4"
  }
}
# vite.config.js
import { defineConfig, loadEnv } from "vite";
import viteBaseConfig from "./vite.base.config";
import viteDevConfig from "./vite.dev.config";
import viteProdConfig from "./vite.prod.config";

const envResolver = {
	"build": () => {
        return ({...viteBaseConfig, ...viteProdConfig});
    },
	"serve": () => {
        return({...viteBaseConfig, ...viteDevConfig});
    },
}

export default defineConfig(({command, mode})=>{
    console.log('command', command);
    console.log('mode', mode);
    const env = loadEnv(mode, process.cwd(), '')
    console.log(env);
    return envResolver[command]();
})
# .env.development
ENV_APP_KEY = 110
# .env.production
ENV_APP_KEY = 112
# vite.dev.config.js
import { defineConfig } from "vite";

export default defineConfig({mode: 'development'})
# vite.base.config.js
import { defineConfig } from "vite";

export default defineConfig({
    optimizeDeps:{
        exclude:[], //将指定数组中的依赖不进行依赖预构建
    },
    envPrefix:"ENV_", //配置vite注入客户端环境变量校验的env前缀
})

为什么vite.config.js可以书写成es module的形式

因为vite在读取这个vite.config.js文件的时候会先node这个文件语法,如果是es module规范会直接将你的es module规范进行替换,变成commanjs规范。

读取vite.config.js文件时读取到的数据是字符串,所以可以使用字符串的replace方法进行替换。它将字符串中所有的export default 都替换成 model.exports。

【原理篇】vite是怎么让浏览器可以识别.vue文件的

bash
yarn install vite

yarn create vite my-vue-app --template vue

yarn create 实际上就等于在安装create-vite脚手架 然后使用脚手架的指令去构建项目

我们来实现一套简单的vite的开发服务器

  1. 解决我们刚刚的这个问题
  2. 让大家爱对开发服务器的原理层有一个基础的简单的认识
  3. 会涉及到一些node的一些知识
  4. koa:node端的一个框架

vite将数据解析,然后使用js方法去替换数据

vite处理css

vite读取main.js中引用的index.css,直接去使用fs模块去读取index.css中文件内容。直接创建一个style标签,将index.css中文件内容直接copy进style标签里。

将style标签插入到index.html的head中,将该css文件的内容直接替换为js脚本(方便热更新或者css模块化),同时设置Content-Type为js,从而让浏览器以js脚本的形式去执行该css后缀的文件。

场景:

  • 一个组件最外层的元素类名一般取名:wrapper
  • 一个组件最底层的元素类名一般取名:footer

协同开发中,容易出现css样式设置被复用的问题。 cssmodule能解决这个问题。它的功能是让css模块化。

cssmodule的原理可以认为是给你设置的css的类名增加了一些随机码

它需要开发者命名css文件的时候要写成这样 xx.module.css 它会获取文件名称,当它知道文件名里有module.css他就认为是一个模块化文件,它就会对文件里面所有的类进行一定规则的替换工作。将替换过的内容塞进style标签里放入head标签中。将xx.module.css内容进行全部抹除,替换成js脚本。将创建的映射对象在脚本中进行默认导出。

vite.config.js中css配置(module)

bash
import { defineConfig } from "vite";

export default defineConfig({
    optimizeDeps:{
        exclude:[], //将指定数组中的依赖不进行依赖预构建
    },
    envPrefix:"ENV_", //配置vite注入客户端环境变量校验的env前缀
    css:{ //
        modules:{
            localsConvention:"cammlCaseOnly",
            scopeBehaviour:"local",
            generateScopedName:(name, filename, css) =>{
                return `${name}-${filename}-${css}`
            },
            hashPrefix:'hello',
            globalModulePaths:["path1"]
        }
    }
})

localsConvention:修改对象的key的展示(驼峰还是中划线展示)

scopeBehaviour:配置当前的模块化行为是模块化还是全局化

hashPrefix:生成hash会根据你的类名+一些其他字符串去生成

globalModulePaths:代表你不想 参与到的css模块化的路径

vite配置文件中css配置流程(preprocessorOption)

主要用来配置css

bash
import { defineConfig } from "vite";

export default defineConfig({
    optimizeDeps:{
        exclude:[], //将指定数组中的依赖不进行依赖预构建
    },
    envPrefix:"ENV_", //配置vite注入客户端环境变量校验的env前缀
    css:{ //
        modules:{
            localsConvention:"cammlCaseOnly",
            scopeBehaviour:"local",
            generateScopedName:(name, filename, css) =>{
                return `${name}-${filename}-${css}`
            },
            hashPrefix:'hello',
            globalModulePaths:["path1"]
        },
        preprocessorOptions:{
            less:{},
            sass:{},
        },
        }
})
  1. 使用yarn创建vite项目

    bash
    yarn create vite
    ? Project name: » vite-project
    ? Select a framework: » - Use arrow-keys. Return to submit.
        Vanilla
    >   Vue
        React
        Preact
        Lit
        Svelte
        Solid
        Qwik
        Others
    ? Select a framework: » - Use arrow-keys. Return to submit.
        Vanilla
    >   Vue
        React
        Preact
        Lit
        Svelte
        Solid
        Qwik
        Others
    cd vite-project
      yarn
      yarn dev
  2. 进入vite-project

    bash
    cd vite-project
  3. 执行yarn

    bash
    yarn
  4. 安装tailwindcss

    bash
    yarn add tailwindcss postcss autoprefixer
    
    npx tailwindcss init -p
  5. 设置tainwindowss.config.js

    bash
    /** @type {import('tailwindcss').Config} */
    export default {
      content: [
        "./index.html",
        "./src/**/*.{js,ts,jsx,tsx}",
      ],
      theme: {
        extend: {},
      },
      plugins: [],
    }
  6. 新建 ./src/idnex.css

    bash
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  7. main.ts引入./src/index.cs

    bash
    import { createApp } from 'vue'
    import './style.css'
    import App from './App.vue'
    
    createApp(App).mount('#app')
  8. 编写App.vue

    bash
    <template>
    
    <div class="w-screen h-screen bg-slate-900 text-8xl">Hello world</div>
    
    </template>
    <script setup lang='ts'>
    import {ref, reactive} from 'vue'
    </script>
    <style scopred>
    
    </style>
  9. 启动项目,查看网页效果(结果无效, class是设定上了,但是css效果没有产生)

    bash
    yarn dev

跨域

什么是跨域?

浏览器的同源策略限制,它是浏览器最核心也是最基本的安全功能。

当一个请求url的协议、域名、端口三者之间任意一个与当前页面的url不同即为跨域。

例如:xxxx.comxxxx.com 存在跨域 协议不同

例如:127.x.x.x:8001→ 127.x.x.x:8002 存在跨域 端口不同

例如:www.xxx.comwww.yyyy.com 存在跨域 域名不同

如何解决跨域?

jsonp这种方式在之前很常见,基本原理是利用html的script元素标签没有跨域限制,动态创建script标签,将src作为服务器地址,服务器返回一个callback接受返回的参数。

bash
function clickButton(){
	let obj,s
	obj = {"table":"products", "limit": 10},
	s = document.createElement("script") //动态创建script
	s.src = "接口地址" + JSON.stringify(obj);
	document.body.appendChild(s);
}
function myFunc(myObj){
	document.getElementById("demo").innerHTML = myObj;
}

CORS设置允许跨域资源共享 需要后端设置

bash
{
	"Access-Control-Allow-Origin":"<http://web.xxx.com>" //可以指定地址
}

或者

bash
{
	"Access-Control-Allow-Origin":"*" //可以使用通配符,任何地址都能访问(安全性低)后端不需要这么做
}

或者

使用vite proxy或者node代理 或者webpack proxy他们三种方式都是代理

先创建一个接口使用express

shell
yarn add express -D
js
const express = reqire('express')
	const app = express()
	
	app.get('/xm', (req,res)=>{
		res.json({
			code:200,
			message: '请求成功'
		})		
	})
	app.listen(9001)

Released under the MIT License.