Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Vite真香之路

Vite真香之路

作者头像
小小杨
发布于 2022-12-13 08:01:03
发布于 2022-12-13 08:01:03
2.8K00
代码可运行
举报
文章被收录于专栏:下落木下落木
运行总次数:0
代码可运行

一、开始

近期将几个项目的脚手架从 Vue-CLI 替换成了 Vite,直呼真香,原来冷启动2分多钟,现在只要几秒,对于需要频繁切项目的人来说,真的是开发利器。

当前 Vite 的优点不止于此,这篇文章不探讨 Vite 的优势,只记录下从 Vue-CLI 转 Vite 踩的一些坑。

二、问题记录

提前说明下,以下问题的解决方法可能有多种,这里选用的是对业务库改动最小的,原因是:

  1. 一个项目往往有多个开发者,不希望改动会对之前的 Vue-CLI 启动或者打包造成影响
  2. 配置文件会抽取到基础库中,使用的项目会有很多,如果改动大意味着成本会很高,出错的概率也更大

1. 环境变量

Vite 在一个特殊的 import.meta.env 对象上暴露环境变量,Vue-CLI 是基于webpack,它是在 process.env 上挂载的。

此外,还有一个不同点是,原来的 vue.config.js 是能直接通过 process.env 拿到环境变量的,vite.config.js 却不能直接拿到,需要开发者自己调用 loadEnv 加载。

还有 Vite 只暴露以 VITE_ 开头的环境变量给客户端,Vue-CLI 中是 VUE_APP_ 开头。

对应的处理如下,通过 define 替换全局变量,这种方式目前来看是安全的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { loadEnv } from 'vite';

const ENV_PREFIX = ['VITE_', 'VUE_APP'];

export default ({ mode, serverProxy }) => {
  const envMap = loadEnv(mode, process.cwd(), ENV_PREFIX) || {};
  const appDir = envMap.VUE_APP_DIR;

  return defineConfig({
    root: `${path.resolve(curDirname, `./src/${appDir}`)}/`,
    define: {
      'process.env': {
        ...envMap,
        NODE_ENV: mode,
      },
    },
  })
}

2. index.html处理

Vite 中默认 index.html 在项目根目录下,也就是和 vite.config.js 同一层级,但是我们的大多数项目是 monorepo 模式,index.html 在 src/project/some-project下。

解决方法是设置root:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  root: `${path.resolve(curDirname, `./src/${appDir}`)}/`,
}

但是,只有这个还不行,默认的 index.html 中是没有 <script type="module" src="./src/project/some-project/main.js"></script> 这一句的,所以要写个插件,当加载 index.html 时,动态插入这一句。

打包的时候发现另一个问题,只打包出来 index.html,其他js等文件没有被打包,猜测是打包的时候找不到 main.js,于是给插件增加配置enforce: pre

下面是插件核心代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
return {
  name: 'vite-plugin-transform-html',
  enforce: 'pre',
  transformIndexHtml(html) {
    return html.replace(
      /<\/head>/,
      `<script type="module" src="./main.js"></script>
  </head>`,
    );
  },
};

后面发现,生产环境不会触发transformIndexHtml方法,上面代码并没有效果,于是优化成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const res = {
  name: 'vite-plugin-transform-html',
  enforce: 'pre',
};

if (mode === 'development') {
  return {
    ...res,
    transformIndexHtml(code) {
      return transformIndexHtml(code, mode);
    },
  };
}

return {
  ...res,
  transform(code, id) {
    if (id?.endsWith('.html')) {
      return transformIndexHtml(code, mode);
    }
  },
};

3. 预构建缓存问题

Vite有个预构建阶段,用于将commonjs/UMD模块转为ESM,和合并多个模块。就是把一些模块处理后放在node_modules/.vite/deps目录下,项目启动时直接引用这个目录下的内容。

值得注意的是,这一阶段是有缓存的,且存在两处缓存,一处是.vite/deps下的缓存,一处是浏览器的缓存。如果发现修改了插件,但是观察不到效果,可以尝试npx vite --fore,以及禁用浏览器缓存。

4. vue2/vue3并存

有个公共库是同时支持vue2/vue3的,比如有个extend-comp功能,用来扩展组件,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import Vue, { createApp } from 'vue';

export function extendComp(arg: ExtendCompParam) {
  if (Vue?.version?.startsWith?.('2')) {
    return extendV2(arg);
  }
  if (typeof createApp === 'function') {
    return extendV3(arg);
  }
  return extendV2(arg);
}

它的顶部会尝试引用 createApp,如果是vue2的项目,它会报错,之前的兼容方案是扩展下vue的类型声明:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import 'vue/types/index';

declare module 'vue/types/index' {
  function createApp(c: any, d?: any): any;
}

现在vite的预构建,会直接报错,因为vue依然没导出createApp,想到一个方式是写个插件在最底部加上createApp的导出,核心代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
return {
  transform(source, id) {
    if (id.indexOf('vue.js') > -1 || id.indexOf('vue.runtime.esm.js') > -1) {
      return `${source}

  export const createApp = () => {}
`;
    }
    return source;
  },
};

5. vue组件的动态导入

vue动态导入有多种方式,Vite可以支持 xxComp: ()=>import('xx.vue'),不支持 xxComp(resolve){ require(['xx.vue'], resolve) },可以手动改业务库,但我们的目标是尽可能少的改项目,所以也可以写个插件,用于替换源代码,核心代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
return {
  transform(source, id) {
    if (id.indexOf('.vue') === -1) {
      return source;
    }
    const reg = new RegExp(/([a-zA-Z]+?)\(resolve\)(?:\s*?)\{(?:\n\s*)require\(\['(.*?)'\],(?:\s*?)resolve\);(?:\n\s*)\}/, 'g');
    const match = source.match(reg);
    if (match?.[1] && match[2]) {
      const res = source.replace(reg, (match, originA, originB) => `${originA}: () => import('${originB}')`);
      return res;
    }
    return source;
  },
};

6. Vant样式按需加载

这个问题只要使用一下vite-plugin-style-import就可以。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import {
  createStyleImportPlugin,
  VantResolve,
} from 'vite-plugin-style-import';
// ...
plugins: [
  createStyleImportPlugin({
    resolves: [
      VantResolve(),
    ],
  }),
]

7. externals配置

关于external的Vite插件众多,这里用的是vite-plugin-externals

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { viteExternalsPlugin } from 'vite-plugin-externals';
// ...
plugins: [
  viteExternalsPlugin({
    vue: 'Vue',
    'vue-router': 'VueRouter',
    vuex: 'Vuex',
    axios: 'axios',
    'vue-lazyload': 'VueLazyload',
  }),
]

8. proxy配置

部分项目需要配置proxy,配置如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const serverProxy = {
  '/xxx-cgi': {
    target: 'http://localhost:3000',
    changeOrigin: true,
    ws: true,
    rewrite: path => path.replace(/^\/xxx-cgi/, ''),
  },
};
// ...
server: {
  proxy: {
    ...serverProxy,
  },
},

9. sass相关

Vite 中要想支持scss文件,需要安装sass,注意不是node-sass,这会引起另一个问题,/deep/会报错,需要将 /deep/ 换成 ::v-deep,这两个作用一样,都可以在scoped下修改子组件样式,一些文章说::v-deep性能更佳。

此外,某些项目有这种写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$--font-path: "~element-ui/lib/theme-chalk/fonts";

这种引用方式Vite默认情况下是无法识别的,最简单的方式是改成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$--font-path: "node_modules/element-ui/lib/theme-chalk/fonts";

10. BASE_URL

之前index.html中的这种写法会报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />

报错信息为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[vite] Internal server error: URI malformed

解决方法是写个插件替换下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
res = code.replace(/<%=\s+BASE_URL\s+%>/g, baseDir);

11. 编译时动态加载对应的样式

值得注意的是下面这行代码不会报错,所以当要找的样式文件不存在时,可以直接用空字符串替换。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<style lang="scss" scoped src=""></style>

如何判断要找的文件存不存在呢,如何判断当前操作的文件目录呢?用path.dirname(id)就可以,相关插件代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
transform(source, id) {
  let res = source;

  if (res.indexOf(STYLE_KEYWORD) !== -1) {
    const styleName = getStyleName(appDir);
    const curDir = path.dirname(id);

    let pureCSSLink = `./css/${styleName}.scss`;
    const cssLink = path.resolve(curDir, pureCSSLink);

    const isExist = fs.existsSync(cssLink);
    if (!isExist) {
      pureCSSLink = '';
    }

    res = res.replace(new RegExp(STYLE_KEYWORD, 'g'), pureCSSLink);
  }
  return res;
}

12. 分包策略

关于分包策略没有标准答案,每个项目都有自己的特点,目前我们项目采用的是这种:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const SPLIT_CHUNK_CONFIG = [
  {
    match: /[\\/]src[\\/]_?common(.*)/,
    output: 'chunk-common',
  },
  {
    match: /[\\/]src[\\/]_?component(.*)/,
    output: 'chunk-component',
  },
  {
    match: /[\\/]src[\\/]_?logic(.*)/,
    output: 'chunk-logic',
  },
];

const rollupOptions = {
  output: {
    chunkFileNames: 'assets/js/[name]-[hash].js',
    entryFileNames: 'assets/js/[name]-[hash].js',
    assetFileNames: 'assets/static/[name]-[hash].[ext]',
    manualChunks(id) {
      for (const item of SPLIT_CHUNK_CONFIG) {
        const { match, output } = item;

        if (match.test(id)) {
          return output;
        }
      }

      if (id.includes('node_modules')) {
        return id.toString().split('node_modules/')[1].split('/')[0].toString();
      }
    },
  },
},

13. Vue2中支持JSX

在Vue-CLI中是默认支持Vue2+JSX的,也就是不需额外配置,但是vite+vue2项目中,如果直接写jsx会报错,报错信息如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[vite] Internal server error: Failed to parse source for import analysis because the content contains invalid JS syntax. Install @vitejs/plugin-vue to handle .vue files.

尽管上面提示让你安装@vitejs/plugin-vue这个库,但是这个是for Vue3 版本的,如果加上它,会报额外的的错误,说这个库仅服务于Vue3。

那怎么办呢,很简单,在使用JSX的script的地方加上:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script lang="jsx">

然后在vite.config.js中,为vite-plugin-vue2这个插件增加jsx: true的选项。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { createVuePlugin } from 'vite-plugin-vue2';

// ...
plugins: [
  createVuePlugin({
    jsx: true,
  }),
  // ...
]

14. qrcodejs2报错

报错信息如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
TypeError: Cannot read properties of undefined (reading '_android')

这个问题其实是判断的不严谨,已经有很多issue了,比如159,也有人提了PR,甚至合了,但是没发布版本。。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (this._android && this._android <= 2.1) {
  var factor = 1 / window.devicePixelRatio;
  // ...
}

知道了问题,解决办法就很多了,可以fork下,自己发个包,也可以写个Vite插件转换下代码。

此外,有个问题是,在Vue-CLI中为什么不会报错呢?

因为Vite中使用的是ESM模块,默认会使用严格模式,“禁止this指向全局对象”。而Vue-CLI中使用的是UMD方式加载,在浏览器中会顶层的this等于window,所以不会报错。

15. 使用 path-browserify

不要在前端项目中使用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import path from 'path'

会报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Error in render: "Error: Module "path" has been externalized for browser compatibility and cannot be accessed in client code."

而应该使用path-browserify

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import path from 'path-browserify';

如果是用 path.resolve 方法,这样还是不行的,因为 resolve 方法里面使用了 process.cwd 方法,而 Vite 是没有注入 process 这个变量的。

有多个解决方法:

  1. 安装process包,然后在项目中执行 window.process = process,注意不要与vite.config.js中define变量冲突。
  2. 写个Vite插件用来转换源码,开发环境替换为真实的process.cwd()对应的字符串,生产环境替换成/
  3. 自己写path.resolve方法,不用第三方库
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 模拟path.resolve()
function resolve(...paths) {
  let resolvePath = '';
  let isAbsolutePath = false;
  let cwd;

  for (let i = paths.length - 1; i >= -1; i--) {
    let path;
    if (isAbsolutePath) {
      break;
    }
    if (i >= 0) {
      path = paths[i];
    } else {
      if (cwd === undefined) {
        cwd = process.cwd();
      }
      path = cwd;
    }
    if (!path) {
      continue;
    }
    resolvePath = `${path}/${resolvePath}`;
    isAbsolutePath = path.charCodeAt(0) === 47;
  }
  if (/^\/+$/.test(resolvePath)) {
    resolvePath = resolvePath.replace(/(\/+)/, '/');
  } else {
    resolvePath = resolvePath.replace(/(?!^)\w+\/+\.{2}\//g, '')
      .replace(/(?!^)\.\//g, '')
      .replace(/\/+$/, '');
  }
  return resolvePath;
}

console.log(resolve('/aa', '../bb', 'cc', 'dd')); // => /bb/cc/dd
console.log(resolve('/aa', '../bb', './cc', 'dd')); // =>  bb/cc/dd
console.log(resolve('/', '/system', 'user', 'userIndex')); // => /system/user/userIndex
console.log(resolve('', 'system', 'user', 'userIndex')); // => ${cwd}/system/user/userIndex

16. base设置

base是开发或生产环境服务的公共基础路径,也就是文件引用路径,默认是/。合法的值包括以下几种:

  • 绝对 URL 路径名,例如 /foo/
  • 完整的 URL,例如 https://foo.com/
  • 空字符串或 ./(用于开发环境)

我们项目会把静态文件上传到CDN,所以生产环境会应该是第二种——完整的URL,所以可以这么设置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
base: envMap.VUE_APP_PUBLIC_PATH || './'
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 下落木 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Vite 项目中如何去集成 Mock 环境 (插件:vite-plugin-mock)
之后在项目根目录创建 mock 文件夹,去创建我们需要的 Mock 数据和对应的接口
HelloWorldZ
2024/05/24
1.4K0
Vite 项目中如何去集成 Mock 环境 (插件:vite-plugin-mock)
vite基本配置教程
最近做项目要求将webpack打包方式换成vite,下面将详细讲解一下配置vite需要修改哪些文件,以及过程中踩到的奇葩坑。
程序媛夏天
2024/01/18
6040
vite基本配置教程
Vite项目当中的SVG图标的配置及图标全局组件的封装
在开发项目的时候经常会用到svg矢量图,而且我们使用SVG以后,页面上加载的不再是图片资源,这对页面性能来说是个很大的提升,而且我们SVG文件比img要小的很多。放在项目中几乎不占用资源。
HelloWorldZ
2024/05/24
4970
Vite项目当中的SVG图标的配置及图标全局组件的封装
大前端备战2021年,使用vite构建React !
写在开头 由于 vite这个构建工具被用在了vue3上门,而且它的构建思路我觉得优于webpack,底层也是使用了esbuild,性能上更优 那么为了照顾一些小伙伴之前没有学习过vite的,我们先来看看什么是vite 什么是vite Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用,支持热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打包 vite
Peter谭金杰
2020/12/29
8310
【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之vite初始化项目以及项目准备工作
相信绝大多数的前端小伙伴就业初期或多或少都了解或使用过花裤衩大佬的vue-element-admin,部分小伙伴还看过框架配套的文章——手摸手撸后台系列。但很多小伙伴上来就用框架,很多实现方法都不了解怎么实现的,比如权限管理怎么做的?标签切换怎么做的?暗黑模式自定义主题又是如何实现的?诸如此类的细节还有很多,像我之前就不是很懂,用是会用,但是框架出点什么毛病就很难去修改。所以趁着失业,正好静下心来学习一下,用vite+vue3+element-plus+Ts来从0开始写一个通用的后台管理模板,ts由于我也不是太熟,写着用着,以不报错为主,所以ts用法部分仅供参考。
十里青山
2023/04/28
1.1K3
【vite+vue3+Ts+element-plus】肩并肩带你写后台管理之vite初始化项目以及项目准备工作
用 vite 2 平滑升级 vue 2 + webpack 项目实战
之前的 webpack 命令加前缀(如:"webpack:build"),继续可用
PHP开发工程师
2022/03/10
1.6K0
用 vite 2 平滑升级 vue 2 + webpack 项目实战
vue 随记(6):构建的艺术
做过vue项目的人都知道,当项目越变越大,或者变成多页面应用时,热更新打包速度奇慢无比,每次保存都要几分钟。
一粒小麦
2020/07/28
1K0
vue 随记(6):构建的艺术
【Vue工程】001-Vite 创建 Vue-TypeScript 项目
@types/node 是一个 TypeScript 类型声明包,它包含 Node.js 中的所有类型定义。在 TypeScript 项目中,我们需要类型声明来提供类型信息,才能获得类型检查、自动补全等功能。但是 Node.js 本身的代码是由 JavaScript 编写的,没有类型信息。所以,@types/node 这个类型声明包为所有的 Node.js API 都提供了 TypeScript 的类型定义,类似:
訾博ZiBo
2025/01/06
1580
【Vue工程】001-Vite 创建 Vue-TypeScript 项目
一个基于vite构建的vue3+pinia+ts+elementUI plus的初始化开箱即用的项目模版
Vite 需要 Node.js 版本 >= 12.0.0。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
用户6297767
2023/11/21
9200
一个基于vite构建的vue3+pinia+ts+elementUI plus的初始化开箱即用的项目模版
深度解读 Vite 的依赖扫描?
当我们首次运行 Vite 的时候,Vite 会执行依赖预构建,目的是为了兼容 CommonJS 和 UMD,以及提升性能。
CandyTong
2022/08/07
1.4K0
vue-cli 将被 create-vue 替代?初始化基于 vite 的 vue3 项目为何如此简单?
美国时间 2021 年 10 月 7 日早晨,This Dot Media 邀请了 Vue 的核心成员和 Vue Community (例如 Quasar, Ionic 开发者等)的一些主要贡献者举办了一个 Vue Contributor Days 在线会议,长达两个半小时,会上 vue-cli 的核心贡献者胖茶也在同一天公开了全新的脚手架工具 create-vue[1],我也是看到 antfu 发推就关注了一下,看完直播回放[2]之后收获很大,这里做一些总结并且分析一下最新发布的 create-vue 的源码。
若川
2021/10/18
1K0
vue-cli 将被 create-vue 替代?初始化基于 vite 的 vue3 项目为何如此简单?
深度解读 Vite 的依赖扫描?
当我们首次运行 Vite 的时候,Vite 会执行依赖预构建,目的是为了兼容 CommonJS 和 UMD,以及提升性能。
CandyTong
2023/02/24
9840
深度解读 Vite 的依赖扫描?
如何开发一个完整的 Vite 插件?
Vite 插件与 Rollup 插件结构类似,为一个name和各种插件 Hook 的对象:
江拥羡橙
2023/11/20
1.3K0
如何开发一个完整的 Vite 插件?
Vue 团队公开快如闪电的全新脚手架工具,未来将替代 Vue-CLI,才300余行代码,学它!
create-vue使用npm init vue@next一行命令,就能快如闪电般初始化好基于vite的Vue3项目。
若川
2021/10/27
1.4K0
Vue 团队公开快如闪电的全新脚手架工具,未来将替代 Vue-CLI,才300余行代码,学它!
5分钟搭建 vite + vue3 工程,简单,实用!
本文主要介绍 vite + vue3 + vue-router4 + vuex4 + ant-design-vue2 + axios + mockjs 工程搭建。2021年,若还没有体验过 vite 的速度,要抓紧动手试一下啦!
coder_koala
2021/12/22
5.7K0
基于最新 Vite+Vue3+VantUI移动端应用项目搭建
这里记录下使用最新的Vite+vue3和有赞出品的Vant移动端UI库搭建移动端应用的过程。
杨永贞
2022/01/07
3.3K0
基于最新 Vite+Vue3+VantUI移动端应用项目搭建
三大前端构建工具横评,谁是性能之王!
而在Vite之前,还有Snowpack也同样采用了No-Bundler构建方案。那么No-Bundler模式与传统老牌构建工具Webpack孰优孰劣呢?能否实现平滑迁移和完美取代?
Nealyang
2021/03/26
2.2K0
三大前端构建工具横评,谁是性能之王!
深入浅出 Vite5 中依赖预构建
大多数同学提到 Vite ,会下意识的反应出 “快”、“noBundle”等关键词。
19组清风
2024/01/30
1.1K1
深入浅出 Vite5 中依赖预构建
搭建后台管理系统的思路
搭建后台管理系统最基础的是什么呢?个人的体会是整体的基础框架,这个是指最基础的框架,比如根 router-view, 侧边栏以及侧边栏的router-view,以及顶部栏,等基础布局的控制。
公众号---人生代码
2021/05/08
2.9K0
详解 Vite 依赖预构建流程
大家好,我是码农小余。我们知道,首次执行 vite 时,服务启动后会对 node_modules 模块和配置 optimizeDeps 的目标进行预构建。本节我们就去探索预构建的流程。
码农小余
2022/06/16
4.7K0
详解 Vite 依赖预构建流程
推荐阅读
相关推荐
Vite 项目中如何去集成 Mock 环境 (插件:vite-plugin-mock)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验