Skip to content

[WIP] Migrate webpack to rspack - node22#182

Open
liaoyu wants to merge 1 commit into
qiniu:masterfrom
liaoyu:feat/rspack
Open

[WIP] Migrate webpack to rspack - node22#182
liaoyu wants to merge 1 commit into
qiniu:masterfrom
liaoyu:feat/rspack

Conversation

@liaoyu

@liaoyu liaoyu commented May 29, 2026

Copy link
Copy Markdown
Collaborator
  • node 版本要求 >=22.12.0

迁移 generate/serve 与 webpack 配置到 rspack,新增 ESM 动态加载适配层,并更新相关依赖与 Node 版本要求。

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the builder from Webpack to Rspack v2+, updating dependencies, dev server configurations, and plugins to their Rspack equivalents. It also introduces a dynamic ESM loader utility to bridge CommonJS and ESM modules. The review feedback highlights several critical issues: a configuration error in css-loader where localIdentHashDigest was used instead of localIdentHashFunction, an incorrectly structured quality option in ImageMinimizerPlugin, a fragile dynamic file-resolution implementation for webpackbar, and the use of the deprecated url.parse() API instead of the modern new URL() constructor.

Comment thread src/webpack/transform.ts
Comment on lines +155 to +156
// Node 17+ / OpenSSL 3 不再支持 MD4,需使用 xxhash64
localIdentHashDigest: 'xxhash64',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

xxhash64 is a hash function, not a digest. In css-loader, the hash function is configured via localIdentHashFunction, while localIdentHashDigest is used for the digest type (e.g., base64).

If you set localIdentHashDigest: 'xxhash64', it will be treated as an invalid digest, and css-loader will still default to using md4 as the hash function, which will crash on Node 17+ / OpenSSL 3.

Please change this to localIdentHashFunction: 'xxhash64'.

Suggested change
// Node 17+ / OpenSSL 3 不再支持 MD4,需使用 xxhash64
localIdentHashDigest: 'xxhash64',
// Node 17+ / OpenSSL 3 不再支持 MD4,需使用 xxhash64
localIdentHashFunction: 'xxhash64',

Comment thread src/webpack/index.ts
Comment on lines +154 to +158
new ImageMinimizerPlugin({
use: 'jpeg',
test: /\.(?:jpg|jpeg|jpe)$/i,
quality: 65
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In @rsbuild/plugin-image-compress's ImageMinimizerPlugin, the minimizer-specific options (such as quality for JPEG) must be wrapped inside the options property. Passing quality as a top-level option will have no effect.

      new ImageMinimizerPlugin({
        use: 'jpeg',
        test: /\.(?:jpg|jpeg|jpe)$/i,
        options: {
          quality: 65
        }
      }),

Comment on lines +1 to +64
import fs from 'fs'
import path from 'path'
import type { rspack as Rspack, RspackPluginInstance } from '@rspack/core'
import type { WebpackBarOptions } from 'webpackbar'

type RspackProgressInfo = {
builtModules?: number
moduleIdentifier?: string
}

type WebpackBarInstance = {
apply(compiler: unknown): void
updateProgress(percent: number, message: string, details: string[]): void
}

type WebpackBarConstructor = new (options?: WebpackBarOptions) => WebpackBarInstance

function loadWebpackBarClass(): WebpackBarConstructor {
const sharedDir = path.join(
path.dirname(require.resolve('webpackbar/rspack')),
'shared'
)
const sharedBundle = fs.readdirSync(sharedDir).find(
name => name.startsWith('webpackbar.') && name.endsWith('.cjs')
)
if (!sharedBundle) {
throw new Error('Cannot find webpackbar shared bundle')
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require(path.join(sharedDir, sharedBundle)).WebpackBar
}

function toProgressDetails(info: unknown): string[] {
if (typeof info === 'string') {
return [info]
}
if (Array.isArray(info)) {
return info.filter((item): item is string => typeof item === 'string')
}
if (info != null && typeof info === 'object' && 'moduleIdentifier' in info) {
const { moduleIdentifier } = info as RspackProgressInfo
return typeof moduleIdentifier === 'string' ? [moduleIdentifier] : []
}
return []
}

/** webpackbar/rspack 与 Rspack 2 ProgressPlugin 的 info 对象格式不兼容,在此适配 */
export function createWebpackBarRspackPlugin(
rspack: typeof Rspack,
options?: WebpackBarOptions
): RspackPluginInstance {
const WebpackBar = loadWebpackBarClass()
const webpackbar = new WebpackBar(options)
const progressPlugin = new rspack.ProgressPlugin((percent, message, info) => {
webpackbar.updateProgress(percent, message, toProgressDetails(info))
})

return {
apply(compiler) {
progressPlugin.apply(compiler)
webpackbar.apply(compiler)
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of loadWebpackBarClass dynamically scans the node_modules/webpackbar directory to find a .cjs file in the shared folder. This is extremely fragile and highly likely to break with minor or patch updates of webpackbar if its internal packaging structure changes.

Since WebpackBar is exported as the default export of the webpackbar package, you can import it directly instead of resolving it through internal paths. This also allows you to remove the unused fs and path imports.

import type { rspack as Rspack, RspackPluginInstance } from '@rspack/core'
import WebpackBar, { type WebpackBarOptions } from 'webpackbar'

type RspackProgressInfo = {
  builtModules?: number
  moduleIdentifier?: string
}

function toProgressDetails(info: unknown): string[] {
  if (typeof info === 'string') {
    return [info]
  }
  if (Array.isArray(info)) {
    return info.filter((item): item is string => typeof item === 'string')
  }
  if (info != null && typeof info === 'object' && 'moduleIdentifier' in info) {
    const { moduleIdentifier } = info as RspackProgressInfo
    return typeof moduleIdentifier === 'string' ? [moduleIdentifier] : []
  }
  return []
}

/** webpackbar/rspack 与 Rspack 2 ProgressPlugin 的 info 对象格式不兼容,在此适配 */
export function createWebpackBarRspackPlugin(
  rspack: typeof Rspack,
  options?: WebpackBarOptions
): RspackPluginInstance {
  const webpackbar = new WebpackBar(options)
  const progressPlugin = new rspack.ProgressPlugin((percent, message, info) => {
    webpackbar.updateProgress(percent, message, toProgressDetails(info))
  })

  return {
    apply(compiler) {
      progressPlugin.apply(compiler)
      webpackbar.apply(compiler)
    }
  }
}

Comment thread src/serve.ts
Comment on lines +152 to +173
proxyReq(proxyReq) {
// add header `X-Real-IP`
const origin = proxyReq.getHeader('origin') as (string | undefined)
if (origin) {
proxyReq.setHeader(
"X-Real-IP",
url.parse(origin).hostname!
)
)
}
},

onProxyRes(proxyRes) {
// 干掉 set-cookie 中的 secure 设置,因为本地开发 server 是 http 的
// TODO: 考虑支持 https dev server?
if (proxyRes.headers['set-cookie']) {
proxyRes.headers['set-cookie'] = proxyRes.headers['set-cookie'].map(
cookie => cookie.replace('; Secure', '')
)
}

// fix `referer` to avoid csrf detect
const referer = proxyReq.getHeader('referer') as (string | undefined)
if (referer) {
proxyReq.setHeader(
'referer',
referer.replace(
url.parse(referer).host!,
proxyReq.getHeader('host') as string
)
)
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the Node.js engine requirement has been upgraded to >=22.12.0 in this PR, you should use the standard, modern new URL() API instead of the legacy and deprecated url.parse().

Additionally, wrapping the URL parsing in a try-catch block ensures that invalid or malformed headers do not crash the dev server.

    proxyReq(proxyReq) {
      // add header `X-Real-IP`
      const origin = proxyReq.getHeader('origin') as (string | undefined)
      if (origin) {
        try {
          const originUrl = new URL(origin)
          proxyReq.setHeader("X-Real-IP", originUrl.hostname)
        } catch {
          // ignore invalid URL
        }
      }

      // fix `referer` to avoid csrf detect
      const referer = proxyReq.getHeader('referer') as (string | undefined)
      if (referer) {
        try {
          const refererUrl = new URL(referer)
          proxyReq.setHeader(
            'referer',
            referer.replace(
              refererUrl.host,
              proxyReq.getHeader('host') as string
            )
          )
        } catch {
          // ignore invalid URL
        }
      }
    },

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary:

  • Found two noteworthy migration regressions: one CSS Modules hashing option issue and one image-compression/docs mismatch.
  • Performance and security passes found no additional noteworthy issues.

Comment thread src/webpack/transform.ts
? {
localIdentName: '[local]_[hash:base64:5]',
// Node 17+ / OpenSSL 3 不再支持 MD4,需使用 xxhash64
localIdentHashDigest: 'xxhash64',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localIdentHashDigest controls the digest encoding (for example hex/base64), not the hash algorithm. Setting it to xxhash64 leaves the hash function on the default md4 and then asks the digest step to use an invalid encoding, so CSS Modules builds can still fail under OpenSSL 3. This should be localIdentHashFunction: 'xxhash64' (and keep the digest as base64/default as needed).

Comment thread src/webpack/index.ts
quality: 65
}),
new ImageMinimizerPlugin({
use: 'svg',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration now only installs JPEG and SVG image minimizers, but optimization.compressImage previously also compressed GIFs and the user-facing docs still advertise GIF compression. If GIF support is no longer available with the new plugin, the docs/config behavior should be updated; otherwise add a GIF minimizer equivalent so existing projects don't silently lose that optimization.

@liaoyu liaoyu changed the title [WIP] Migrate webpack to rspack [WIP] Migrate webpack to rspack - node22 Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant