Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于vite的使用感受 #7

Open
SepVeneto opened this issue Jul 26, 2021 · 0 comments
Open

关于vite的使用感受 #7

SepVeneto opened this issue Jul 26, 2021 · 0 comments

Comments

@SepVeneto
Copy link
Owner

SepVeneto commented Jul 26, 2021

目前最大的感受就是一个字,快!

相较于webpack,以rollup为基础的vite,基于浏览器的模块化无论是冷启动还是打包都是异常的快速。
之前大大小小也在三四个项目上使用过vite,体验下来还有局限性的。

1.兼容性

这个问题主要是因为vite的核心是es module,简单的说就是依赖浏览器的模块化功能(import和export)
image
这里可以简单的看一下目前各浏览器对module的支持程度,很明显对于IE等传统浏览器打包后的代码是无法运行的。但是这个问题目前可以通过官方插件@vitejs/plugin-legacy来支持。

2.社区生态不够成熟

相较于webpack成熟的社区生态,大部分问题都可以找到对应的库来解决,但是在这一点上,刚刚起步的vite很明显是做不到的。现阶段,如果想要在项目上使用,需要做好造轮子的心里准备。
举一个简单的例子,原先webpack的项目针对svg的加载,都是通过svg-sprite-loader来实现的。

const platform = process.platform;
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(path.join(__dirname, 'src/assets/icon'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        // symbolId: 'icon-[name]'
        symbolId: function(paths) {
          const res = paths.substr(path.join(__dirname, 'src/assets/icon').length + 1).split(platform === 'win32' ? '\\' : '/');
          let symbolId = '';
          res.forEach((item, index) => {
            if (index === 0) {
              symbolId += item;
            } else {
              symbolId += item.replace(/^./, item.slice(0, 1).toUpperCase());
            }
          })
          return `icon-${path.basename(symbolId, '.svg')}`;
        }
      });
  }
}

而vite很明显不能这样实现svg的动态加载。

import { Plugin } from 'vite';
import fs from 'fs';
import path from 'path';

function collectSvg(dir: string): string[] {
  const svgList = [];
  const dirents = fs.readdirSync(dir, { withFileTypes: true })
  dirents.forEach(dirent => {
    if (dirent.isDirectory()) {
      svgList.push(...collectSvg(path.resolve(dir, dirent.name)));
    } else {
      const content = fs.readFileSync(path.resolve(dir, dirent.name))
        .toString()
        .replace(/<svg([^>+].*?)/, ($1, $2) => {
          return `<symbol id=icon-${dirent.name.replace('.svg', '')} ${$2}`
        })
        .replace('</svg>', '</symbol>');
      svgList.push(content)
    }
  })
  return svgList;
}

export function svgLoader(svgPath: string): Plugin {
  return {
    name: 'svg-loader',
    transformIndexHtml(html): string {
      const res = collectSvg(svgPath);
      return html.replace('<body>', 
      `
        <body>
          <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
            ${res.join('')}
          </svg>
      `)
    }
  }
}

然后在vite.config中使用

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { svgLoader } from './plugins/svgLoader';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), svgLoader('./src/assets/icon/svg')],
  resolve: {
    alias: {
      '@': 'src'
    }
  }
})

3.对于第三方库的外部导入不太友好

在webpack中,为了提高首屏加载的速度或者是打包速度,包体大小会选择将部分比较重的第三方库抽离出来,通过cdn的方式外部导入。

module.exports = {
  configureWebpack: {
    externals: {
      vue: 'Vue',
      'vue-router': 'VueRouter',
      'element-ui': 'ELEMENT',
    },
  },
}

配置后webpack会在打包阶段剔除这些包,并根据上述配置做一个映射,至于这一步的理由是因为从外部导入的是commonjs规范,会在全局变量上暴露一个名称,vue的话是Vueelement-ui对应的是ELEMENT
换句话说,设置externals后,原代码里书写的import,from会被改写成从全局变量上获取。
上面也说过,vite是面向现代浏览器原生的es module功能,也就是说import from并不会被改写,那么想要做到第三方库的抽离就很简单了,以vue为例,只需要做到以下两点

  1. 在打包时忽略vue
  2. import ... from 'vue'改写成对应的cdn地址
export default defineConfig(({ command }) => {
  const config = command === 'serve' ? {} : {
    resolve: {
      alias: {
        'vue': 'https://unpkg.com/vue@3.1.5/dist/vue.runtime.esm-browser.prod.js'
      }
    }
  };
  return {
    plugins: [vue(), vueJsx()],
    ...config,
  }
})

和别名的写法一致。

import {defineComponent as e, createVNode as t, ref as n, resolveComponent as l, openBlock as s, createBlock as r, createApp as a} from "https://unpkg.com/vue@3.1.5/dist/vue.runtime.esm-browser.prod.js";

打包后会直接替换掉原来的vue
看上去很简单,但是实际上这种做法对于有依赖关系的第三方库是行不通的。
如果你想抽离vue-router,会得到这样一个报错

Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".

从字面上很容易理解,导入的应该是一个路径,而不是简单的一个模块名。
涉及到vue的有两个地方,一个是项目本身,另一个就是刚刚引入的vue-router

import { getCurrentInstance, inject, onUnmounted, onDeactivated, onActivated, computed, unref, defineComponent, reactive, watchEffect, h, provide, ref, watch, shallowRef, nextTick } from 'vue';

很明显,vue-router并没有对vue做处理。
[ESM browser module distribution depends on unsupported import](https://github.com/vuejs/vue-router-next/issues/694)
同样的问题在vuex中也是存在的,因为4.x的版本依赖了vue,采用了import { xxx } from 'vue'的方案导入,而这种方法是不符合esm规范的。
简单的说,现阶段没办法在vite上对vue-router@nextvuex@next进行第三方抽离。
总结起来有如下几点原因

  1. vite依赖浏览器的esm规范,因此引入的库也必须是esm规范的。
  2. 目前4.0版本的vue-router@nextvuex@next虽然有esm的包,但是因为采用import { xxx } from 'vue',不符合esm规范,导致无法通过cdn引入。

综上所述,vite未来可期。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant