前端框架:vite2+vue3+typescript+axios+vant移动端 框架实战项目详解(四)
以相对和绝对路径方式引用
我们可以在*.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 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> 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>{{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> import { PropType } from 'vue'; interface ITitleScore { title: string score: number } withDefaults(defineProps<ITitleScore>(),{ title:'发质评分', score:100 }) </script> <style 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 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:其他问题正在总结中。。。
作者:草履虫的思考
链接:https://juejin.cn/post/7062277850224656397