zoukankan      html  css  js  c++  java
  • vuecli3 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.js
    import 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-nocheck
    const 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 解决了!

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

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

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

  • 相关阅读:
    主元素 .
    Struts2中使用Session .
    不同的路径 .
    判断数独是否合法(LintCode) .
    最大子数组(LintCode) .
    不同的路径 II .
    删除元素(LintCode) .
    Hibernate 与Spring整合出现 hibernate.HibernateException: createCriteria is not valid without active transaction .
    子树(LintCode) .
    Surrounded Regions .
  • 原文地址:https://www.cnblogs.com/EnSnail/p/15707038.html
Copyright © 2011-2022 走看看