Skip to content

本地开发工程化改造优化

约 1591 字大约 5 分钟

vitewebpack重构

2024-08-22

1. 背景及结果:

1.1 背景

  • 当前系统功能复杂,模快依赖多,目前的依赖模块有 6000+ 个,加上自身的源码,总的模块依赖数量超过 7000+,导致每次启动的时候项目特别慢,需要 120S 左右,同时也影响了热更新速度。
  • 所以想通过项目改造来提升启动速度和热更新速度,提升开发效率。

1.2 结果

对项目进行改造后, 项目启动时缩短为 10S以内,启动效率提升 90%

1.3 统计依赖的模块数量:6000+

npm ls | wc -l

1.4 统计本地文件数量: 1000+

// 安装 tree 命令
brew install tree

// 统计模块数量
tree -fi src | grep -E '\.js$|\.css$|.vue$|.scss$|' | wc -l

2. 解决方案

将 webpack 构建转为 vite 构建。

3. 过程中需要解决的问题

3.1 依赖项变化

3.1.1 基础依赖

  1. 需要新增的依赖
"@vitejs/plugin-vue": "^1.6.1", // 支持 Vue 2.7 或以上版本
"@vitejs/plugin-vue2-jsx": "^1.1.0", // 支持 jsx
"vite": "^4.4.9",
// 或者
"vite-plugin-vue2" : "1.9.0" // 仅支持 Vue 2.6 或更早版本
"vite": "^2.9.18",
npm i vite-plugin-vue2@1.9.0 vite@2.9.18 -D

3.1.2 插件依赖

  1. 需要新增的依赖
"vite-plugin-dynamic-import": "^1.5.0", // 增强 Vite 内置的 dynamic import
"vite-plugin-env-compatible": "^1.1.1", // inject to process.env like vue-cli and also define client process.env.XXX for you.
"vite-plugin-node-polyfills": "^0.7.0", // 用于在浏览器环境中填充 Node 的核心模块
"@originjs/vite-plugin-commonjs": "^1.0.3", // Support commonJS to esm in vite
"@originjs/vite-plugin-require-context": "^1.0.9", // Support require.context in vite
"@rollup/plugin-alias": "^5.1.0",
"path-browserify": "^1.0.1",
npm i vite-plugin-dynamic-import@1.5.0 vite-plugin-env-compatible@1.1.1 vite-plugin-node-polyfills@0.7.0 @originjs/vite-plugin-commonjs@1.0.3 @originjs/vite-plugin-require-context@1.0.9 @rollup/plugin-alias@5.1.0 path-browserify@1.0.1 -D
  1. 自定义插件
// 替换不兼容的 npm 包
function transformNpmModules() {
  return {
    name: "vite-plugin-transform-npm-modules",
    enforce: "pre",
    transform(src, id) {
      // 替换不兼容的包
      if (/\.(js|vue)(\?)_/.test(id) && !id.includes("node_modules")) {
        const keyword = [{ src: "path", target: "path-browserify" }];
        keyword.forEach((item) => {
          src = src
            .replace(
              new RegExp(`from\\s+(['"])${item.src}(['"])`, "gi"),
              `from $1${item.target}$2`
            )
            .replace(
              new RegExp(`require\\((['"])${item.src}(['"])\\)`, "gi"),
              `require($1${item.target}$2)`
            );
        });
        return {
          code: src,
        };
      }
    },
  };
}
// 兼容 scss 旧语法
function transformScss() {
  return {
    name: "vite-plugin-transform-scss",
    enforce: "pre",
    transform(src, id) {
      if (
        /\.(js|vue)(\?)_/.test(id) &&
        id.includes("lang.scss") &&
        !id.includes("node_modules")
      ) {
        return {
          code: src.replace(/(\/deep\/|>>>)/gi, "::v-deep"),
        };
      }
      // 单独的 css 文件内出现 vue 深度选择器将不起作用,webpack 直接忽略,而 vite 会报错,这里屏蔽这个错误
      if (/\.(scss|css)$/.test(id) && !id.includes("node_modules")) {
        return {
          code: src.replace(
            /(\/deep\/|::v-deep|>>>)/gi,
            "**vite_remove_useless_vue_deep**"
          ),
        };
      }
    },
  };
}

3.2 文件变化

3.2.1 新增 index.html

对原 index.html 进行处理

  • 手动添加入口文件: <script type="module" src="/src/main.js"></script>
  • 显示书写 title: 替换 <%= webpackConfig.name %>

3.2.2 新增 vite.config.js

需要手动配置的:

  • 代理
  • 别名
  • 基础路径
import { defineConfig } from "vite";
import { createVuePlugin } from "vite-plugin-vue2";
import { resolve } from "path";
import alias from "@rollup/plugin-alias";
import envCompatible from "vite-plugin-env-compatible";
import { viteCommonjs } from "@originjs/vite-plugin-commonjs";
import viteRequireContext from "@originjs/vite-plugin-require-context";
import dynamicImport from "vite-plugin-dynamic-import";
import { nodePolyfills } from "vite-plugin-node-polyfills";

// 替换不兼容的 npm 包
function transformNpmModules() {
  return {
    name: "vite-plugin-transform-npm-modules",
    enforce: "pre",
    transform(src, id) {
      // 替换不兼容的包
      if (/\.(js|vue)(\?)_/.test(id) && !id.includes("node_modules")) {
        const keyword = [{ src: "path", target: "path-browserify" }];
        keyword.forEach((item) => {
          src = src
            .replace(
              new RegExp(`from\\s+(['"])${item.src}(['"])`, "gi"),
              `from $1${item.target}$2`
            )
            .replace(
              new RegExp(`require\\((['"])${item.src}(['"])\\)`, "gi"),
              `require($1${item.target}$2)`
            );
        });
        return {
          code: src,
        };
      }
    },
  };
}
// 兼容 scss 旧语法
function transformScss() {
  return {
    name: "vite-plugin-transform-scss",
    enforce: "pre",
    transform(src, id) {
      if (
        /\.(js|vue)(\?)_/.test(id) &&
        id.includes("lang.scss") &&
        !id.includes("node*modules")
      ) {
        return {
          code: src.replace(/(\/deep\/|>>>)/gi, "::v-deep"),
        };
      }
      // 单独的 css 文件内出现 vue 深度选择器将不起作用,webpack 直接忽略,而 vite 会报错,这里屏蔽这个错误
      if (/\.(scss|css)$/.test(id) && !id.includes("node_modules")) {
        return {
          code: src.replace(
            /(\/deep\/|::v-deep|>>>)/gi,
            "**vite_remove_useless_vue_deep**"
          ),
        };
      }
    },
  };
}
// https://vitejs.dev/config/
export default defineConfig({
  // build: {
  // rollupOptions: {
  // input: 'public2/index.html',
  // },
  // },
  plugins: [
    // 兼容 scss 旧语法
    transformScss(),
    // transformNpmModules(),
    // 支持.vue 文件, Vue2.6
    createVuePlugin({
      jsx: true, // 支持 vue jsx 语法(需要同时把.js 改为.jsx 或者 script 标签加属性 lang="jsx")
    }),
    dynamicImport(),
    viteCommonjs(),
    viteRequireContext(),
    envCompatible(),
    nodePolyfills(),
  ],
  envPrefix: ["VUE_APP*"], // 兼容 VUE*APP*前缀
  base: "/test/",
  server: {
    host: "me.xx.com",
  },
  resolve: {
    extensions: [".js", ".vue", ".json"],
    alias: {}, //
  },
  define: {
    global: "globalThis",
  },
});

4. 可能会遇到的问题:

4.1 JSX 相关

  • 实例化 Vue2 插件的时候传入{jsx: true}开启 JSX 支持
  • 在用到的组件上加上 jsx 标识:<script lang="jsx"> </script>

4.2 全局 SCSS 变量

在 Vite 中我们可以通过 css.preprocessorOptions 进行配置。

css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@import './src/variables/index.scss';`,
    },
  },
}

4.3 CSS module 相关

任何以.module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件。导入这样的文件会返回一个相应的模块对象.Vite 中,如果你希望使用 CSS Modules 功能来局部化组件的样式,确实需要按照特定的命名约定来命名你的 SCSS 文件。通常,这个约定是将文件命名为 \*.module.scss,这样 Vite 和其他构建工具就能够识别这些文件并作为 CSS Modules 来处理它们。

SCSS 导出的变量在 JS 文件找不到,如下:

:export {
  menuText: $menuText;
}

或者, 配置所有的 .scss 文件都使用 CSS Modules, 使所有的样式默认使用 CSS Modules

defineConfig({
  css: {
    modules: {
      // 配置所有的 .scss 文件都使用 CSS Modules
      scopeBehaviour: "local", // 使所有的样式默认使用 CSS Modules
    },
    preprocessorOptions: {
      scss: {
        // additionalData: `@import "src/styles/variables.scss";`, // 如果有全局变量文件
      },
    },
  },
});

4.4 /deep/ 相关

在 Vite 中,使用 /deep/ 选择器是可以的,但需要注意它的非标准性和未来兼容性问题。Vite 本身没有对 /deep/ 选择器做特殊处理,它依赖于 PostCSS 插件(如 postcss-preset-env)来解析和处理这些非标准选择器。为了确保 /deep/ 选择器在 Vite 项目中正常工作,按照以下步骤进行配置:

4.4.1. 安装必要的 PostCSS 插件:

npm install postcss postcss-preset-env --save-dev

4.4.2. 配置 PostCSS:

在项目根目录下创建一个 postcss.config.js 文件,并进行如下配置:

module.exports = {
  plugins: [
    require("postcss-preset-env")({
      stage: 0,
      features: {
        "nesting-rules": true, // 支持嵌套规则
      },
    }),
  ],
};

5. 快捷的脚手架

方便大家进行项目改造,提供一个脚手架为大家完成基础的配置,大家根据项目再做个性化配置即可。

webpack2vite-cli

5.1 安装

npm i webpack2vite-cli -D

5.2 使用

w2v

5.3 启动

npm run vite

6. 参考资料

© 2024 图图 📧 email