vue-cli 替换为 rsbuild 遇到的问题

最近给 @vue/cli 4.x 的 vue2 项目替换为 rsbuild. 发现 rsbuild 兼容性很好, 但是遇到几个问题

  • 没有找到现成的 compression-webpack-plugin 替代, 所以自己编写了一个插件来进行多线程压缩
  • 在配置 output.assetPrefix./ 时, css 中加载图片和字体的文件路径不是相对 css 文件本身的相对路径. 找官网文档说是不推荐使用. 但实际项目需求存在多个前端被放到一个 nginx 下的情况, 在各种内网环境没办法开很多端口进行映射(甚至多个开发商共用一个端口),路径前缀不可预计;虽然可以修改配置重新打包,但有时候把文件传进内网需要审核得花费数天;目前每个系统也不需要完全融合,不值得进行微前端改造.

不知道有没有人知道如何配置 assetPrefix ?

typescript 复制代码
// rsbuild.config.ts
import { defineConfig, loadEnv, rspack } from '@rsbuild/core';
import { pluginVue2 } from '@rsbuild/plugin-vue2';
import { pluginSass } from '@rsbuild/plugin-sass';
import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin';
import path from 'path';
import { pluginCompress } from './compress';

process.env.VUE_APP_TIME = Date.now() + '';
const { publicVars } = loadEnv({ prefixes: ['VUE_APP_'] });

export default defineConfig({
    output: {
       filename: {
          image: '[contenthash:8][ext]',
       },
    },
    html: {
       template: './public/index.html',
       templateParameters: {
          VUE_APP_TIME: process.env.VUE_APP_TIME,
       },
       favicon: './public/favicon.png',
    },
    tools: {
       bundlerChain: (chain, { CHAIN_ID }) => {
          if (process.env.RSDOCTOR) {
             chain.plugin('Rsdoctor').use(RsdoctorRspackPlugin, [
                {
                   // 插件选项
                },
             ]);
          }
       },
    },
    plugins: [
       pluginVue2(),
       pluginSass({
          sassLoaderOptions: {
             additionalData: `@import "~@/styles/var.scss";`,
             sassOptions: {
                silenceDeprecations: ['import'],
             },
          },
       }),
       pluginCompress({
          enableGzip: true,
          enableBrotli: false,
       }),
    ],
    source: {
       define: publicVars,
       // 指定入口文件
       entry: {
          index: './src/main.ts',
       },
    },
    resolve: {
       alias: {
          '@': path.resolve(__dirname, 'src'),
          '~@': path.resolve(__dirname, 'src'),
          '~': '',
       },
    },
    dev: {},
    server: {
       proxy: {
          '/api': {
             target: 'http://proxy-target/',
             changeOrigin: true,
             ws: true,
          },
       },
       port: 8080,
    },
});
typescript 复制代码
// plugin-compress.ts
import type { RsbuildPlugin } from '@rsbuild/core';
import type { RspackPluginInstance } from '@rspack/core';
import { Worker } from 'worker_threads';
import os from 'os';
import path from 'path';
import fs from 'fs';

export interface CompressPluginOptions {
    enableGzip?: boolean;
    enableBrotli?: boolean;
    test?: RegExp;
    threshold?: number;
}

export const pluginCompress = (
    options: CompressPluginOptions = {},
): RsbuildPlugin => {
    const {
       enableGzip = true,
       enableBrotli = true,
       test = /.(js|css|html|svg)$/,
       threshold = 1024,
    } = options;

    const maxWorkers = Math.max(1, os.cpus().length - 1);
    const queue: Promise<any>[] = [];

    return {
       name: 'rsbuild-plugin-compress',

       setup(api) {
          if (api.context.action !== 'build') {
             api.logger.info(`rsbuild-plugin-compress: ${api.context.action} mode, skip compression.`);
             return;
          } else {
             api.logger.info(`rsbuild-plugin-compress: ${api.context.action} mode, start compression.`);
          }
          api.modifyRsbuildConfig((config) => {
             // nothing here, we modify rspack config below
          });

          api.modifyRspackConfig((config) => {
             if (!config.plugins) {
                config.plugins = [];
             }
             config.plugins.push({
                apply(compiler) {
                   compiler.hooks.afterEmit.tapPromise(
                      'rsbuild-plugin-compress',
                      async (compilation) => {
                         const outDir = compiler.outputPath;

                         for (const filename of Object.keys(compilation.assets)) {
                            if (!test.test(filename)) continue;

                            const asset = compilation.getAsset(filename);
                            const source = asset?.source?.source?.()?.toString('utf-8');
                            if (!source || source.length < threshold) continue;

                            const worker = new Worker(
                               path.resolve(__dirname, './compress.worker.js'),
                               {
                                  workerData: { source, enableGzip, enableBrotli },
                               },
                            );

                            const job = new Promise<void>((resolve, reject) => {
                               worker.on('message', async (result: Record<string, Buffer>) => {
                                  const filePath = path.join(outDir, filename);

                                  if (result.gzip) {
                                     await fs.promises.writeFile(`${filePath}.gz`, result.gzip);
                                  }
                                  if (result.brotli) {
                                     await fs.promises.writeFile(`${filePath}.br`, result.brotli);
                                  }
                                  resolve();
                               });
                               worker.on('error', reject);
                            });

                            queue.push(job);

                            // 控制最大并发
                            while (queue.length >= maxWorkers) {
                               await Promise.race(queue);
                               queue.splice(0, 1);
                            }
                         }

                         await Promise.all(queue);
                      },
                   );
                },
             } as RspackPluginInstance);
          });
       },
    };
};
ini 复制代码
// compress.worker.js
import { parentPort, workerData } from 'worker_threads';
import { promisify } from 'util';
import zlib from 'zlib';

const gzip = promisify(zlib.gzip);
const brotliCompress = promisify(zlib.brotliCompress);

async function run() {
    const { source, enableGzip, enableBrotli } = workerData;
    const result = {};

    if (enableGzip) {
       result.gzip = await gzip(source, { level: 9 });
    }

    if (enableBrotli) {
       result.brotli = await brotliCompress(source, {
          params: {
             [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
          },
       });
    }

    parentPort?.postMessage(result);
}

run();
相关推荐
weibkreuz2 小时前
初始React@1
前端·react.js·前端框架
Coder_Boy_2 小时前
前端和后端软件系统联调经典问题汇总
java·前端·驱动开发·微服务·状态模式
小皮虾2 小时前
别再封装 Axios 了!用 RPC 像调用本地函数一样写接口(支持 Vue/React/Node)
前端·rpc·全栈
PieroPC2 小时前
NiceGUI .classes() 完整列表教程
前端
月巴月巴白勺合鸟月半2 小时前
一个医学编码的服务
服务器·前端·javascript
JS_GGbond2 小时前
给DOM元素加超能力:Vue自定义指令入门指南
前端·vue.js
T___T2 小时前
用 Vite 搭建现代化 Vue 3 项目:从零到工程化入门
前端·vue.js
ycgg2 小时前
深入理解 DOM 的 dispatchEvent API
前端
方也_arkling2 小时前
【JS】定时器的使用(点击开始计时,再次点击停止计时)
开发语言·前端·javascript