目的:使用vite创建vue3项目记录细节点
上篇vue2/vue3 + webpack多页面遇到的问题和思考我们使用vue-cli搭建项目都是使用webpack打包的,现在对比一下vite感受一下极速开发体验
增:下一篇vite + vue3 多页面实战优化续集:eslint+lint-staged+husky+stylelint
说到vue3,不得不提ts,说到ts,一定要先到 官方文档了解tsconfig.json配置的意思,这里我觉得有意思的就是references/lib。
我们通过处理一些警告来了解配置:
例如你使用了vue自动导入插件:unplugin-auto-import/vite, 组件自动导入unplugin-vue-components/vite,可能会遇到以下问题
在自定义ts文件中引入资源,ts无法识别别名@

要在tsconfig.json配置paths
记得将以上自动导入插件生成的文件auto-imports.d.ts,components.d.ts放入到 tsconfig.json的include数组中
// vite.config.ts
import { resolve } from 'path'
export default defineConfig({
plugins,
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
})
// tsconfig.json
"compilerOptions": {
"baseUrl": ".",
"paths":{
"@": ["src"],
"@/*": ["src/*"],
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "auto-imports.d.ts",
"components.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }],
},

这个单独拆分出来的配置文件include包含vite.config.ts,说明只是负责编译 vite 的配置文件。
我在根目录新建了一个build文件夹,拆分了plugin的引入和多页面配置,这里红色警告提示要在tsconfig.node.json的include中加入文件
// tsconfig.node.json
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts", "build/*.ts"]
}
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
// 更多环境变量...
}
src/env.d.ts 中的三斜线指令官方文档说明 /// <reference types="vite/client" />
三斜线引用告诉编译器在编译过程中用types形式引入的额外的文件vite/client.d.ts,这个文件里面就是vite帮我们定义的各种常用类型定义,比如css,图片等。下图可以看到又用path的形式引入了./types/importMeta.d.ts,还引入了dom相关声明<reference lib="dom" />
想一想:这个lib在tsconfig.json/compilerOptions配置项目中也有

继续深入到types/importMeta.d.ts发现了可以全局使用的一些声明ImportMetaEnv,GlobOptions
// vite/client/types/importMeta.d.ts
interface ImportMetaEnv {
[key: string]: any
BASE_URL: string
MODE: string
DEV: boolean
PROD: boolean
SSR: boolean
}
综上我们可以发现
新增环境变量记得VITE_ 开头
// 项目根目录新增的 .env.development文件
VITE_PROJECT_ENV = 'development'
VITE_APP_URL = "https://app-api-0.com/"
<reference lib="dom" /> 中用到的lib,types 和tsconfig.json配置文件中的references/compilerOptions中的"lib": ["esnext", "dom"],types属性一样的,只是不同文件场景不同的使用方式而已。(1) 在d.ts文件中
声明对某个包的依赖用到了types: /// <reference types="vite/client" />
用路径声明依赖的用到了path: /// <reference path="./types/importMeta.d.ts" />
(2) 在.ts文件里声明一个对@types包的依赖,可以使用–types命令行选项或在tsconfig.json里指定
/// <reference types="node" />
官方文档-编译选项告诉我们:target 选项用来指定最终代码运行环境所支持的 JavaScrpt 语法的版本; lib 选项的值默认继承自 target:es5,默认注入target值版本es5库和dom 库。所以我们才能在默认项目中看到vite帮我们配置了"lib": ["esnext", "dom"]
关于配置文件references,我们可以看到在vite项目拆分了出了tsconfig.node.json配置文件,专门用来负责vite.config.ts配置相关编译工作,结构清晰。
它更重要的作用references官方文档已经告诉我们了: 一套配置,多个工程,修改时只编译子项目本身,显著地加速类型检查和编译。 更细致的举例文章
所以:拆开后,vite项目中修改vite.config.ts配置并不会导致整个src项目重新编译ts,仅仅只是触发reload
vite.config.ts中使用环境变量,要用vite提供的loadEnv(mode, process.cwd())
单页面应用在src目录下项目中用环境变量可以使用
import.meta.env查看.env文件中的自定义环境变量,或者使用import.meta.glob(类似webpack的require.context)读取文件,但是在vite.config.ts配置文件中import.meta是空的
loadEnv的第一参数mode哪里有?两种方式
(1)官网环境变量告诉我们可以直接在vite.config.ts中将defineConfig参数写成函数,函数就有mode参数可用
下面代码中有define,我们在下面讲
export default defineConfig(({ mode }) => {
// 根据当前工作目录中的 `mode` 加载 .env 文件
return {
// vite config
define: {
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
'process.env': loadEnv(mode, process.cwd(), '')
}
}
})
(2)mode值也可以配合scripts命令从process.argv中拿,实际上defineConfig的mode参数也是拿的这个
tips:
a. 注意下面的 --mode development命令在最后面,方便process.argv取其值;
b. 注意 --mode development和新建的.env.development文件同名
// package.json
"scripts": {
"dev": "vite --host --mode development",
"test": "vue-tsc --noEmit && vite build --mode test",
"build": "vue-tsc --noEmit && vite build --mode production",
"preview": "vite preview"
}
import { loadEnv } from 'vite'
const mode = process.argv[process.argv.length - 1]
console.log(mode)
const env = loadEnv(mode, process.cwd())
// env
// {
// VITE_PROJECT_ENV: 'development',
// VITE_APP_URL: 'https://app-api-0.com/'
// }
在多页面中你会发现:process没有,报错:process is not defined,import.meta.env也没有合并自定义的变量
define: {
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
'process.env': loadEnv(mode, process.cwd(), '')
}
全局使用
const {VITE_APP_URL} = process.env
目录结构还是一样的,views下面多个页面都有自己的main.ts/index.vue
├── public
└── build // 多页面生成
└── src
├── api // 请求
├── assets // 静态资源
├── components // 公共组件
├── config // 公用类
└── views // 页面
└── home // 页面
├── lang // 多语言
├── icons // svg图标
├── components // 组件
├── router // 路由,选择性需要
├── store // 选择性需要
├── main.ts //
├── index.vue //
//getPages.ts多页面入口html生成
import glob from "glob";
import fs from "fs";
import { resolve } from 'path'
const input = {}
const mainEntry = []
const iconDirs = []
function getPages() {
// 遍历文件夹中含有main.ts的文件夹路径
const allEntry = glob.sync("./src/views/**/main.ts");
// 获取模板
const temp = fs.readFileSync("./index.html");
console.log('allEntry', allEntry)
// 创建多页面模板
allEntry.forEach((entry: string) => {
const pathArr = entry.split("/");
const name = pathArr[pathArr.length - 2];
// 判断文件是否存在
try {
fs.accessSync(`./src/views/${name}.html`);
} catch (err) {
console.log(`创建${name}.html文件`);
const index = temp.toString().indexOf("</body>");
const content =
temp.toString().slice(0, index) +
`<script type="module" src="./${name}/main.ts"></script>` +
temp.toString().slice(index);
fs.writeFile(`./src/views/${name}.html`, content, err => {
if (err) console.log(err);
});
}
// input中的配置
input[name] = resolve(`src/views/${name}.html`);
mainEntry.push(resolve(`src/views/${name}/main.ts`))
iconDirs.push(resolve(process.cwd(), `src/views/${name}/svg`))
});
};
getPages()
console.log(input, mainEntry, iconDirs)
export {input, mainEntry, iconDirs}
关于路径小记:
1. process.cwd() 永远是项目根目录,也就是package.json那一层
2. __dirname 是当前工作目录,以上就是指build文件夹,例如以下代码在我新建的build文件夹中的getPage.ts
以上代码
我们以根目录的index.html为模版在src/views目录下生成了input(入口html路径), mainEntry(入口main.ts文件,给VConsole插件用), iconDirs(icon图标,vite-plugin-svg-icons插件要用)
在每个页面html中插入script,引入自己的ts文件<script type="module" src="./${name}/main.ts"></script>
记得删除模版,也就是根目录的index.html中的script标签先
我们看打印的效果

插件代码
import vue from '@vitejs/plugin-vue'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import styleImport, { VantResolve } from 'vite-plugin-style-import';
import {visualizer} from "rollup-plugin-visualizer";
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import viteCompression from "vite-plugin-compression";
import WindiCSS from 'vite-plugin-windicss'
import { viteVConsole } from 'vite-plugin-vconsole';
import {mainEntry, iconDirs} from './getPage'
export const getPlugins = (mode) => {
console.log('mode', mode)
return [
vue(),
WindiCSS({
scan: {
dirs: ['.'], // 当前目录下所有文件
fileExtensions: ['vue', 'js', 'ts'], // 同时启用扫描vue/js/ts
}
}),
visualizer(),
AutoImport({
imports: ['vue']
}),
Components(),
styleImport({
resolves: [VantResolve()],
}),
viteCompression({
ext: ".gz",
algorithm: "gzip",
deleteOriginFile: false
}),
viteVConsole({
entry: mainEntry,
localEnabled: mode !== 'production',
enabled: mode !== 'production',
config: {
maxLogNumber: 1000,
theme: 'dark'
}
}),
createSvgIconsPlugin({
iconDirs,
symbolId: 'icon-[dir]-[name]'
})
]
}
上面的vite-plugin-style-import 版本低于2.0.0
2.0.0最新的要修改一下,还会提示你没有consla这个依赖,安装一下
import { VantResolve, createStyleImportPlugin } from 'vite-plugin-style-import';
styleImport 没了,换成下面的。
createStyleImportPlugin({
resolves: [
VantResolve(),
],
libs: [
// If you don’t have the resolve you need, you can write it directly in the lib, or you can provide us with PR
{
libraryName: 'vant',
esModule: true,
resolveStyle: (name) => {
return `../es/${name}/style`
},
},
],
}),
vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
import { input } from './build/getPage'
import postCssPxToRem from "postcss-pxtorem"
import { getPlugins } from './build/plugins'
import { loadEnv } from 'vite'
// const mode = process.argv[process.argv.length - 1]
// https://vitejs.dev/config/
export default defineConfig(({mode}) => {
const plugins = getPlugins(mode)
return {
base: './',
root: './src/views/',
publicDir: resolve(__dirname, 'public'),
plugins,
define: {
'process.env': loadEnv(mode, process.cwd())
},
css: {
postcss: {
plugins: [
postCssPxToRem({
rootValue: 37.5, // 1rem的大小
propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
selectorBlackList: ['.ig-','.dzg-header-','.za-'], // 忽略的选择器 .ig- 表示 .ig- 开头的都不会转换
})
]
}
},
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
build: {
outDir: '../../dist', // 输出路径
assetsDir: 'static', // 静态文件目录
// 默认情况下 若 outDir 在 root 目录下, 则 Vite 会在构建时清空该目录。
emptyOutDir: true,
rollupOptions: {
input,
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
manualChunks(id) {
// 单独打包第三方依赖
if (id.includes("node_modules")) {
return id
.toString()
.split("node_modules/")[1]
.split("/")[0]
.toString();
}
}
}
}
}
}
})
有了以上的两步走修改,项目就可以正常跑起来访问了。
配置上,我们重点看几个配置root,publicDir,outDir, rollupOptions /manualChunks
由于在第一步我们以根目录下的index.html为模板,在src/views下生成了相应页面的html,那么项目root就不是指向根目录了,我们改为新生成的index.html目录 root: './src/views/', 这样启动项目就会自动打开这个新生成的src/views/index.html了
同理:root变了,放静态资源的publicDir也要改一改,否则就获取到不放在public的静态资源了;outDir要改,不然打包生成的dist就跑到views下面了
manualChunks这个就类似我们在webpack中用到的splitchunks,可以单独打包依赖项目。我们利用插件rollup-plugin-visualizer看一看打包后的包分布情况,
未配置manualChunks时看看分布图,以入口页面为维度打包了相应的一个js,我们可以看到被至少两个页面使用的,例如axios,vue-i18n等被打包到了virtual_svg-icons-register-150ef7c7.js ,vue被单独拆出来到windi-79315b5a.js, 这两个命名是随机取的你的依赖名称(真的狗)

我们再看配置manualChunks将依赖全部单个打包,每个依赖清晰可见。其中一个叫intlify的包被多个依赖引用,也被单独拆出来了

我们也可以根据需要将依赖包打包到一起,例如我将'vue', 'vue-i18n', 'axios'打包到一起名称叫做vandor,将vant单独打包,代码如下:
manualChunks: {
vandor: ['vue', 'vue-i18n', 'axios'],
vant: ['vant']
},

我们看分布图有四个大模块(最左侧的一条是其他页面js):
其他小点:
- less支持,只需要安装
yarn add less -D即可- 项目如果提示没有glob,安装一个glob和@types/glob(类型支持)
yarn add glob @types/glob -D
src/views,要改两个地方,不然不会生效哦(1)windi.config.ts 也要移动到src/views目录下
(2)plugins中windi.css的扫描范围scan也改一下
WindiCSS({
scan: {
dirs: ['.'], // 当前目录下所有文件
fileExtensions: ['vue', 'js', 'ts'], // 同时启用扫描vue/js/ts
}
}),
至于其他,vue-router,pinia,eslint配置这里就不做记录了
以上就是所有实践记录,代码我放到gitee中。vite-vue3-multip-master
我有一个在Linux服务器上运行的ruby脚本。它不使用rails或任何东西。它基本上是一个命令行ruby脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa
之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m
注意:本文主要掌握DCN自研无线产品的基本配置方法和注意事项,能够进行一般的项目实施、调试与运维AP基本配置命令AP登录用户名和密码均为:adminAP默认IP地址为:192.168.1.10AP默认情况下DHCP开启AP静态地址配置:setmanagementstatic-ip192.168.10.1AP开启/关闭DHCP功能:setmanagementdhcp-statusup/downAP设置默认网关:setstatic-ip-routegeteway192.168.10.254查看AP基本信息:getsystemgetmanagementgetmanaged-apgetrouteAP配
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
1.1.1 YARN的介绍 为克服Hadoop1.0中HDFS和MapReduce存在的各种问题⽽提出的,针对Hadoop1.0中的MapReduce在扩展性和多框架⽀持⽅⾯的不⾜,提出了全新的资源管理框架YARN. ApacheYARN(YetanotherResourceNegotiator的缩写)是Hadoop集群的资源管理系统,负责为计算程序提供服务器计算资源,相当于⼀个分布式的操作系统平台,⽽MapReduce等计算程序则相当于运⾏于操作系统之上的应⽤程序。 YARN被引⼊Hadoop2,最初是为了改善MapReduce的实现,但是因为具有⾜够的通⽤性,同样可以⽀持其他的分布式计算模
我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).
我正在尝试使用“updated_at”字段的日期时间范围查询数据库。前端在JSON数组中发送查询:["2015-09-0100:00:00","2015-10-0223:00:00"]在RailsController中,我使用以下方法将两个字符串解析为DateTime:start_date=DateTime.parse(params[:date_range_arr][0])end_date=DateTime.parse(params[:date_range_arr][1])#...@events=@events.where('updated_atBETWEEN?AND?,start_d