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();
相关推荐
Nan_Shu_61415 分钟前
学习: Threejs (2)
前端·javascript·学习
G_G#23 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界39 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路1 小时前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug1 小时前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu121381 小时前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子2 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端