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 解决了!

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

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

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

  • 相关阅读:
    陶瓷电容的结构、工艺、失效模式
    Vue.js最佳实践
    Vue 超快速学习
    CSS 小技巧
    HTML5 Canvas
    webkit下面的CSS设置滚动条
    Some untracked working tree files would be overwritten by checkout. Please move or remove them before you can checkout. View them
    JSCS: Please specify path to 'JSCS' package
    React中ref的使用方法
    React 60S倒计时
  • 原文地址:https://www.cnblogs.com/EnSnail/p/15707038.html
Copyright © 2011-2022 走看看