最近给 @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();