阅读 1409

前端框架: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   }, } 复制代码

linttestgit挂钩

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时模式默认是developmentvite 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文件

image.png

ps:其他问题正在总结中。。。


作者:草履虫的思考
链接:https://juejin.cn/post/7062277850224656397

文章分类
前端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐