vue-cli3 vue2 保留 webpack 支持 vite 成功实践

vue-cli3 vue2 保留 webpack 支持 vite 成功实践

大家好!

文本是为了提升开发效率及体验实践诞生的。

项目背景

  • 脚手架:vue-cli3,具体为 "@vue/cli-service": "^3.4.1"
  • 库:vue2,具体为:"vue": "2.6.12"
  • 备注:没有 typescript , 非 ssr

痛点:随着时间的推移、业务的不断迭代,依赖、功能、代码越来越多,项目本地启动比较慢、开发热更新比较慢。

改进目标:保留原来的 webpack, 支持 vite。并且尽可能的减少改动,减少维护成本。

考虑:

  • vite 生产环境用的是 rollup,rollup 更适合打包库。
  • vite 打包提效并没有提供太多。
  • 保留原来的 webpack 方式,尽可能的保证生产的稳定与安全。

实践

主要是处理三方面:

  • 配置文件需根据 vue.config.js 增加 vite.config.js
  • 入口文件 index.html 支持 vite 的方式
  • 配置 vite 启动命令
  • 可能需要
    • 路由懒加载,vite 需要特殊处理
    • 解决 commonjs 与 esModule 的引入与混用

增加 vite.config.js

在项目根目录创建一个 vite.config.js

安装所需依赖

npm i vite vite-plugin-vue2 -D

根据 vue.config.jsvite.config.js 中增加对应配置

// 若改了该文件逻辑,请对照着改一下 vue.config.jsimport path from 'path'import fs from 'fs'import { defineConfig } from 'vite'import config from './config'import { createVuePlugin } from 'vite-plugin-vue2'import { injectHtml } from 'vite-plugin-html'const resolve = dir => path.join(__dirname, dir)const alias = {  vue: 'vue/dist/vue.esm.js',  '@': resolve('src'),}const publicPath = '/'const mode = 'development'// https://vitejs.dev/config/export default defineConfig({  base: publicPath,  plugins: [    createVuePlugin(),   ],  // 这些是注入项目中的变量,若有 process 开头的,都需要在这儿注入,vite 默认不会注入 process 相关的变量及值  define: {    'process.env.NODE_ENV': JSON.stringify(mode),    'process.env.publicPath': JSON.stringify(publicPath),  },  resolve: {    // 配置别名    alias,    // 导入时想要省略的扩展名列表,必须加入.vue,因为 vite 默认不支持省略 .vue 后缀    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']  },  server: {    // 允许跨域    cors: true,    proxy: {      // 可直接拷贝 vue.config.js 中对应的 proxy    }  }})

sass 相关

配置特殊说明:若项目中有用是 sass处理器, 且用到变量了。

需要安装 sass@1.32.13,不能安装比 1.32 更高的版本,跟高版本存在这个问题Sass报错: Using / for division is deprecated

npm i sass@1.32.13  -D
export default defineConfig({  ...  css: {    // 给 sass 传递选项    preprocessorOptions: {      scss: {        charset: false, // 最新版sass才支持        additionalData: `@import "src/sass/common/var.scss";`,      }    },  },  ...})

备注:如果生产环境用 vite 打包,采用 sass@1.32.13 将会遇到这个问题vite2打包出现警告,"@charset" must be the first,该如何消除呢?,但是 sass@1.32.13 不支持文档中的配置。所以这可以算是生产环境不用 vite 的一个原因

还需全局替换 /deep/::v-deep

入口文件 index.html 支持 vite 的方式

由于 vite 的项目要求 index.html 文件在根目录,且有入口文件配置。

所以将 index.html 从 public 目录移到根目录。并加入

<% if (typeof isVite === 'boolean' && isVite) { %>  <script type="module" src="/src/main.js"></script><% } %>

这样配置是为了能让 webpack、vite 方式都都支持,共用一个文件

而为了在 index.html 文件注入 isVite 变量,需要安装

npm i vite-plugin-html -D

需要在 vite.config.js 中配置

...import { injectHtml } from 'vite-plugin-html'export default defineConfig({  ...  plugins: [    ...    injectHtml({      data: {        title: 'vite-plugin-html-example',        isVite: true      },    }),  ],  define: {    'process.env.isVite': JSON.stringify(true)  },  ...})

配置 vite 启动命令

最后在 package.json 中增加脚本

"scripts": {  "vite-start": "vite"}

路由懒加载,vite 需要特殊处理

vue 实现路由懒加载的方式是这样的

  const Home = import(/* webpackChunkName: "[home]" */ `@/page/home.vue`)  const routes = [    {      path: `/home`,      name: 'home',      component: Home    },  ]

但是 vite 不支持,解决

const modules = import.meta.glob('@/page/*.vue')const Home = modules['@/page/home.vue']
const modules = import.meta.glob('@/page/*.vue')// vite 会生成代码const modules = {  '@/page/home.vue': () => import('@/page/home.vue'),  '@/page/page1.vue': () => import('@/page/page1.vue'),  '@/page/page2.vue': () => import('@/page/page2.vue'),  ...}

参考:vite-Glob 导入

所以可封装一下:

function loadPage (view) {  if (process.env.isVite) {    const modules = import.meta.glob('../pages/*.vue')    return modules[`../pages/${view}.vue`]  }    return () => import(/* webpackChunkName: "[request]" */ `@/pages/${view}`)}// 使用:const routes = [  {    path: `/home`,    name: 'home',    component: loadPage('home'),  },  {    path: `/404`,    name: '404',    component: loadPage('404'),  },]

但是 webpack 不支持 import.meta,需要 loader 处理。解决:在本地建一个文件 webpack-import-meta-loader.js。

// 来源:https://github.com/KmjKoishi/webpack-import-meta-loader-fixed// 是对 @open-wc/webpack-import-meta-loader 这个的修复// 主要是修复 当 this.rootContext 不存在的判断处理。构建生产环境时不存在/* eslint-disable */// @ts-nocheckconst path = require('path');function toBrowserPath(filePath, _path = path) {  return filePath.replace(new RegExp(_path.sep === '\\' ? '\\\\' : _path.sep, 'g'), '/');};const regex = /import\.meta/g;/** * Webpack loader to rewrite `import.meta` in modules with url data to the source code file location. * * @example * return import.meta; * // becomes: return ({ url: `${window.location.protocol}//${window.location.host}/relative/path/to/file.js` }); * * return import.meta.url; * // becomes: return ({ url: `${window.location.protocol}//${window.location.host}/relative/path/to/file.js` }).url; */module.exports = function (source) {  const path = require('path');  const relativePath = this.context.substring(    this.context.indexOf(this.rootContext) + (this.rootContext && this.rootContext.length >= 0 ? (this.rootContext.length + 1) : 0),    this.resource.lastIndexOf(path.sep) + 1,  );  const browserPath = toBrowserPath(relativePath);  const fileName = this.resource.substring(this.resource.lastIndexOf(path.sep) + 1);  let found = false;  let rewrittenSource = source.replace(regex, () => {    found = true;    return `({ url: getAbsoluteUrl('${browserPath}/${fileName}') })`;  });  if (found) {    return `      function getAbsoluteUrl(relativeUrl) {        const publicPath = __webpack_public_path__;        let url = '';        if (!publicPath || publicPath.indexOf('://') < 0) {          url += window.location.protocol + '//' + window.location.host;        }        if (publicPath) {          url += publicPath;        } else {          url += '/';        }        return url + relativeUrl;      }${rewrittenSource}`;  } else {    return source;  }};

vue.config.js 修改配置:

const resolve = dir => require('path').join(__dirname, dir)module.exports = {  ...  configureWebpack: {    ...    module: {      rules: {        ...        {          test: /index.js$/,          use: [            resolve('webpack-import-meta-loader'),            'babel-loader'          ],          include: [resolve('src/router')]        }      }    }  }  ...}

解决 commonjs 与 esModule 的引入与混用

混用

webpack 方式下,若你的 src 项目源码中存在混用 commonjs 与 esModule。

方案一:不改源码,在 vite.config.js 中加配置,把 commonjs 转换为 esModule

安装 npm i cjs2esmodule -D

在 vite.config.js 中加配置

export default defineConfig({  plugins: [cjs2esmVitePlugin()]})

如果这个方案,能让你的项目运行正常。否则,可能需要采用方案二。

方案二:把 src 代码中的 commonjs 语法自己手动改为 esModule

引入

如果你的项目在有一个 config.js, 被 vue.config.js 中使用。那么你可能需要处理。

vue.config.js 必须是 commonjs 语法的文件,才能被使用,否则会报错。

vite.config.js 既可以 esModule 语法,也可以 commonjs 语法。默认是 esModule 语法。

若上面混用,你是采用方案二,而且 src 代码中也用了 config.js。那么你只能把 config.js 改成 esModule 的方式。此时 vue.config.js 不支持了,采用的方案是根据 config.js 自动生成一个 config-cjs.js。

目的是减少后期维护成本。

// transformConfig2cjs.js// 运行项目的时候,会根据config.js自动生成文件:config-cjs.js, 以便让 vue-config.js 能直接使用const {transformAsync} = require('@babel/core');const plugin = require('@babel/plugin-transform-modules-commonjs')const fs = require('fs')const resolve = dir => require('path').join(__dirname, dir)async function transfrom () {  const inputPath = resolve('config.js')  const input = fs.readFileSync(inputPath, 'utf-8')  const {code} = await transformAsync(input, {    sourceType: 'module',    plugins: [ plugin ]  });    fs.writeFileSync(resolve('./config-cjs.js'), code);}transfrom()

然后在 vue.config.js 原来引入的 config.js 的地方改为 config-cjs.

最后在 package.json 中改变下脚本,每次都重新生成一个最新的配置。

"scripts": {  "transformConfig2cjs": "node transformConfig2cjs.js",  "serve": "npm run transformConfig2cjs && vue-cli-service serve",  "build": "npm run transformConfig2cjs && vue-cli-service build",}

总结

遇到很多坑,都是语法相关的,最终都 11 解决了!

也尝试过一些其他方案,但是不能用,我的项目没有生效:

  • wp2vite

支持了之后,开发是真的很高效!

希望这篇文章对有需要的有所帮助。

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部