本系列文章过长会分开消化理解:
- 前端框架:vite2+vue3+typescript+axios+vant移动端 框架实战项目详解(一)
- 前端框架:vite2+vue3+typescript+axios+vant移动端 框架实战项目详解(二)
- 前端框架:vite2+vue3+typescript+axios+vant移动端 框架实战项目详解(三)
- 前端框架:vite2+vue3+typescript+axios+vant移动端 框架实战项目详解(四)
资源处理
引用静态资源
这块官网说的很清楚,可以查看:vitejs.cn/guide/asset…
- 相关: 公共基础路径
- 相关:
assetsInclude配置项
以相对和绝对路径方式引用
我们可以在*.vue 文件的template, style和纯.css文件中以相对和绝对路径方式引用静态资源。
<!-- 相对路径 -->
<img src="./assets/logo.png">
<!-- 绝对路径 -->
<img src="/src/assets/logo.png">
<style scoped>
#app {
background-image: url('./assets/logo.png');
}
</style>
图片动态引入
1、import
<img :src="imgUrlVal">
<script lang="ts" setup>
import { ref } from 'vue'
import imgUrl from './img.png'
const imgUrlVal = ref(imgUrl)
</script>
<style scoped>
#app {
background-image: url('./assets/logo.png');
}
</style>
2、new URL
import.meta.url 是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL:
创建一个新 URL 对象的语法:
new URL(url, [base])
- url —— 完整的 URL,或者仅路径(如果设置了 base),
- base —— 可选的 base URL:如果设置了此参数,且参数 url 只有路径,则会根据这个 base 生成 URL。
🍕例子:
const imgUrl = new URL('./img.png', import.meta.url).href
document.getElementById('hero-img').src = imgUrl
./img.png是相对路径,而import.meta.url 是base url (根链接)。
这在现代浏览器中能够原生使用 - 实际上,Vite 并不需要在开发阶段处理这些代码!
这个模式同样还可以通过字符串模板支持动态 URL:
function getImageUrl(name) {
return new URL(`./dir/${name}.png`, import.meta.url).href
}
在生产构建时,Vite 才会进行必要的转换保证 URL 在打包和资源哈希后仍指向正确的地址。
注意:无法在 SSR 中使用
如果你正在以服务端渲染模式使用 Vite 则此模式不支持,因为
import.meta.url在浏览器和 Node.js 中有不同的语义。服务端的产物也无法预先确定客户端主机 URL。new Url中无法使用别名,但是可以拼接路径
public目录
public 目录下可以存放在源码中引用的资源,它们会被留下且文件名不会有哈希处理。
这些文件会被原封不动拷贝到发布目录的根目录下。
<img src="/logo.png">
🙋问:什么样的文件适合放到public 目录下?
- 不会被源码引用(例如
robots.txt) - 必须保持原有文件名(没有经过 hash)
- …或者你只是不想为了获取 URL 而首先导入该资源
目录默认是 <root>/public,但可以通过 publicDir 选项 来配置。
😈注意避坑:
- 引入
public中的资源永远应该使用根绝对路径 - 举个例子,public/icon.png应该在源码中被引用为/icon.png。 public中的资源不应该被 JavaScript /ts文件引用。- 可以将该资源放在一个特别的
public目录中,它应位于你的项目根目录。该目录中的资源应该在开发时能直接通过/根路径访问到,并且打包时会被完整复制到目标目录的根目录下。
利用jest和@vue/test-utils测试组件
安装依赖
"jest": "^24.0.0",
"vue-jest": "^5.0.0-alpha.3",
"babel-jest": "^26.1.0",
"@babel/preset-env": "^7.10.4",
"@vue/test-utils": "^2.0.0-beta.9"
配置babel.config.js
module.exports = {
presets: [
[
"@babel/preset-env", {
targets: {
node: "current"
}
}
]
],
};
配置jest.config.js
module.exports = {
testEnvironment: "jsdom",
transform: {
"^.+\.vue$": "vue-jest",
"^.+\js$": "babel-jest",
},
moduleFileExtensions: ["vue", "js", "json", "jsx", "ts", "tsx", "node"],
testMatch: ["**/tests/**/*.spec.js", "**/__tests__/**/*.spec.js"],
moduleNameMapper: {
"^main(.*)$": "<rootDir>/src$1",
},
};
启动脚本
"test": "jest --runInBand"
测试代码,tests/example.spec.js
import HelloWorld from "main/components/HelloWorld.vue";
import { shallowMount } from "@vue/test-utils";
describe("aaa", () => {
test("should ", () => {
const wrapper = shallowMount(HelloWorld, {
props: {
msg: "hello,vue3",
},
});
expect(wrapper.text()).toMatch("hello,vue3");
});
});
lint配置添加jest环境,要不然会有错误提示:
module.exports = {
env: {
jest: true
},
}
将lint、test和git挂钩
npm i lint-staged yorkie -D
"gitHooks": {
"pre-commit": "lint-staged",
"pre-push": "npm run test"
},
"lint-staged": {
"*.{js,vue}": "eslint"
},
正常情况下安装 yorkie 后会自动安装提交钩子 如果提交钩子未生效可以手动运行
node node_modules/yorkie/bin/install.js来安装。 当然,你也可以运行node node_modules/yorkie/bin/uninstall.js来卸载提交钩子。
模式和环境变量
模式和环境变量
使用模式做多环境配置,
vite serve时模式默认是development,vite build时是production。当需要将应用部署到生产环境时,只需运行
vite build命令。默认情况下,它使用<root>/index.html作为构建入口点,并生成一个适合通过静态部署的应用包。查看 部署静态站点 获取常见服务的部署指引。
多环境配置,在之前的系列内容中我们其实创建过
.env.development //开发环境配置文件
.env.production //生产环境配置文件
放在根目录,方便vite.config.ts读取
可以使用
export default ({ command, mode }) => {
return defineConfig({
});
};
代替
export default defineConfig({
});
✔创建配置文件.env.development
VITE_BASE_API=/api
VITE_BASE_URL=./
VITE_APP_OUT_DIR = dist
✔创建配置文件.env.production
VITE_BASE_API=/api
VITE_BASE_URL=./
VITE_APP_OUT_DIR = dist
✔在env.d.ts中声明(便于有智能提示和使用时无需再次断言)
// 环境变量提示
interface ImportMetaEnv{
VITE_BASE_API:string
VITE_BASE_URL:string
VITE_APP_OUT_DIR:string
}
✔代码中读取
import.meta.env.VITE_BASE_API
import.meta.env.VITE_BASE_URL
😈注意避坑:
🙋1、import.meta.env.XXx 在.env[mode]中定义的变量不能访问
解决方式:要用 loadEnv函数代替
✔修改vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import { resolve } from "path";
import styleImport, { VantResolve } from "vite-plugin-style-import";
import { viteMockServe } from "vite-plugin-mock";
import { loadEnv } from "vite";
export default ({ command, mode }) => {
return defineConfig({
resolve: {
alias: {
"@": resolve(__dirname, "src"),
comps: resolve(__dirname, "src/components"),
apis: resolve(__dirname, "src/apis"),
views: resolve(__dirname, "src/views"),
utils: resolve(__dirname, "src/utils"),
routes: resolve(__dirname, "src/routes"),
styles: resolve(__dirname, "src/styles"),
},
},
plugins: [
vue(),
vueJsx(),
styleImport({
resolves: [VantResolve()],
}),
viteMockServe({
mockPath: "mock", // ↓解析根目录下的mock文件夹
supportTs: false, // 打开后,可以读取 ts 文件模块。 请注意,打开后将无法监视.js 文件。
}),
],
server: {
host: "0.0.0.0", // 解决 Network: use --host to expose
// port: 4000, //启动端口
open: true,
// proxy: {
// "^/api": {
// target: "https://baidu.com",
// changeOrigin: true,
// ws: true,
// rewrite: pathStr => pathStr.replace(/^\/api/, ""),
// },
// },
// cors: true,
},
/**
* 在生产中服务时的基本公共路径。
* @default '/'
*/
base: "./",
build: {
//target: 'modules', // 构建目标格式,默认是modules,也可以是esbuild配置项,https://esbuild.github.io/api/#target
outDir: loadEnv(mode, process.cwd()).VITE_APP_OUT_DIR, // 构建输出路径
assetsDir:"static", //静态资源文件夹,和outDir同级 默认assets
sourcemap: false, // map文件
assetsInlineLimit: 4096, // kb, 小于此值将内联base64格式
// rollupOptions: {
// output: {
// chunkFileNames: 'static/js1/[name]-[hash].js',
// entryFileNames: 'static/js2/[name]-[hash].js',
// assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
// },
// },
// brotliSize: false, // 不统计
// minify: 'esbuild' // 混淆器,terser构建后文件体积更小
// terserOptions: {
// compress: {
// drop_console: true,
// },
// },
},
});
};
### 🛢打包
使用npm run build执行打包
自定义构建
构建过程可以通过多种 构建配置选项 来自定义。特别地,你可以通过
build.rollupOptions直接调整底层的 Rollup 选==项:
vite.config.ts中
build: {
rollupOptions: {
// https://rollupjs.org/guide/en/#big-list-of-options
}
}
🍕示例:1、多页面应用模式
假设你有下面这样的项目文件结构
├── package.json
├── vite.config.js
├── index.html
├── main.js
└── nested
├── index.html
└── nested.js
在开发中,简单地导航或链接到 /nested/ - 将会按预期工作,就如同一个正常的静态文件服务器。
在构建中,你要做的只有指定多个 .html 文件作为入口点:
vite.config.ts中
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
nested: resolve(__dirname, 'nested/index.html')
}
}
}
🍕示例:2、库模式
当你开发面向浏览器的库时,你可能会将大部分时间花在该库的测试/演示页面上。使用 Vite,你可以使用 index.html 。
当需要构建你的库用于发布时,请使用 build.lib 配置项,请确保将你不想打包进你库中的依赖进行外部化,例如 vue 或 react:
vite.config.ts中
build: {
lib: {
entry: resolve(__dirname, 'lib/main.js'),
name: 'MyLib'
},
rollupOptions: {
// 请确保外部化那些你的库中不需要的依赖
external: ['vue'],
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
vue: 'Vue'
}
}
}
}
问答时间
🙋问:1、vite无法使用require,require is not defined
答:原因:
- node.js不是内置对象的一部分,如果想用typescript写Node.js,则需要引入第三方声明文件
- vue无法识别require,浏览器不支持cjs
处理方式:如果使用的是 typeScript2.x 那么只需要安装以下包即可
npm install @types/node --save-dev
处理方式:1、使用import引用替换require
🍕例如: 我们想引入一个图片:require('./logo.png') 是会报错的
<template>
<img :src="imgUrl" alt="">
<img :src="imgUrl2" alt="">
</template>
<script setup lang="ts">
import {ref, onMounted} from "vue";
import imgUrl from './logo.png'
const imgUrl2 = ref('');
const handleImgSrc = async()=>{
let m = await import('./logo.png');
imgUrl2.value = m.default;
};
handleImgSrc()
</script>
2、new URL
require('./assets/home.png')
// 等价于
new URL('./assets/home.png', import.meta.url).href
require在vite中是不可用的
而import()在vite环境下可用,但是在css in js不可用.
2、使用import.meta.globEager() 或者import.meta.glob
注意,路径需为以 ./ 开头)或绝对路径(以 / 开头,相对于项目根目录解析
- 这只是一个 Vite 独有的功能而不是一个 Web 或 ES 标准
- 该 Glob 模式会被当成导入标识符:必须是相对路径(以
./开头)或绝对路径(以/开头,相对于项目根目录解析)。- Glob 匹配是使用
fast-glob来实现的 —— 阅读它的文档来查阅 支持的 Glob 模式。- 你还需注意,glob 的导入不接受变量,你应直接传递字符串模式。
- glob 模式不能包含与包裹引号相同的引号字符串(其中包括
',",``),例如,如果你想实现'/Tom's files/ '的效果,请使用"/Tom's files/ "` 代替。
import.meta.glob 为过动态导入,构建时,会分离为独立的 chunk
import.meta.globEager 为直接引入
🍕例子:
export function getAssetsImagesUrl(path:string) {
return import.meta.globEager("/src/assets/images/*/*")[`/src/assets/images/${path}`].default
}
🍕或者某个文件夹下的文件
export function loadLottieApidataFile(path: string, meta: IdefaultObject) {
return new Promise((resolve, reject) => {
const modules = import.meta.glob(`/src/scripts/lottie/*/*.ts`);
meta = meta || {};
// 加载
modules[`/src/${path}`]()
.then(
(data) => {
meta = { ...meta, ...data.default };
meta.reload = !meta.reload; // 添加重新绑定的开关
resolve(data.default);
},
(err) => {
reject(err);
}
)
.catch((err) => {
reject(err);
});
});
}
🍕动态导入多个vue页面
import { App, Component } from 'vue'
interface FileType {
[key: string]: Component
}
// 导入 globComponents 下面的 所有 .vue文件
const files: Record<string, FileType> = import.meta.globEager("/src/components/golbComponents/*.vue")
export default (app: App): void => {
// 因为通过 import.meta.globEager 返回的列表不能迭代所以直接使用 Object.keys 拿到 key 遍历
Object.keys(files).forEach((c: string) => {
const component = files[c]?.default
// 挂载全局控件
app.component(component.name as string, component)
})
}
如果直接使用import.meta.glob,vscode会报类型ImportMeta上不存在属性“glob”的错误,需要在tsconfig文件下添加类型定义vite/client
{
"compilerOptions": {
"types": ["vite/client"]
}
}
ps: module.exports = { 这种的也最好改为 export default {
🙋问:2、启动页空白,怎么办?
答:有可能的原因:
- 检查兼容性、
- 注意入口文件index.html,需要放置项目根目录、
- 在服务器中还是空白,在
vite.config.ts文件添加base:'./'(因为 vue 打包后的路径默认是根路径,而在 vite 里面的配置文件是 vite.config.ts)
🙋问:3、vite环境下默认没有process.env,怎么办?
答:可通过define定义全局变量,在vite.config.ts中使用
自定义全局变量process.env
在vite.config.ts中使用
define: {
'process.env': {}
}
🙋问:4、子组件中defineProps如何设置特殊的类型
答:PropType使用
假如我有一个TitleScore组件,需要scoreData信息属性,需要准守 ITitleScore interface。
1、 引入
import { PropType } from 'vue'
2、 定义接口
// 评分组件
export interface ITitleScore {
title: string
score: number
}
3、 属性验证
const { scoreData } = defineProps({
scoreData: {
type: Object as PropType<ITitleScore>
}
})
🙋问:5、子组件中defineProps如何设置默认值
答:使用withDefaults
withDefaults作用是给
defineProps绑定默认值的api
如在问题4的基础上在加上默认值
父组件
<template>
<TitleScore :scoreData="{ title: `评分`, score: 98 }" />
</template>
子组件
<template>
<!-- 发质评分组件 -->
<div class="titlescore-container display-center-align-items">
<span class="titlescore-text">{{scoreData.title}}:</span>
<div class="score-bg all-score" v-for="item in totalScore" :key="item">
<div v-if="scoreData.score >= item" class="score-bg current-score"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
interface ITitleScore {
title: string
score: number
}
withDefaults(defineProps<ITitleScore>(),{
title:'发质评分',
score:100
})
</script>
<style lang="scss" scoped>
.titlescore-container{
width: 100%;
height: 15px;
margin-bottom: 15px;
.titlescore-text{
font-size: 15px;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #000000;
}
.score-bg{
background-image: url('../../assets/images/common/iconBg.png');
background-repeat: no-repeat;
background-size: 180px auto;
}
.all-score{
width: 16px;
height: 15px;
background-position: -46px -163px;
margin-right: 6px;
position: relative;
}
.current-score{
width: 100%;
height: 100%;
background-position: -15px -163px;
position: absolute;
left: 0;
top: 0;
}
}
</style>
注意:默认值为引用类型的,需要包装一个函数 return 出去。
<script lang='ts' setup>
interface Props {
child: string|number,
title?: string, // 未设置默认值,为 undefined
strData: string,
msg?: string
labels?: string[],
obj?:{a:number}
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two'],
obj: () => { return {a:2} }
})
</script>
🙋问:6、打包生成的index.html直接在浏览器打开会报跨域的错误
答:Vite 默认输出 <script type=module>,也就是 ES Modules,它是不支持文件系统访问的,我们可以使用 VScode 的 open with live server 打开html文件
ps:其他问题正在总结中。。。
可查看仓库:github.com/lixiaoyanle…