Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: ['22', '24']
bundler: ['webpack', 'vite']
bundler: ['rspack', 'webpack', 'vite']

runs-on: ${{ matrix.os }}

Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"mdit",
"prefetch",
"preload",
"rspack",
"slugify",
"tinyglobby",
"unmount",
Expand Down
22 changes: 13 additions & 9 deletions e2e/docs/.vuepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import process from 'node:process'

import { rspackBundler } from '@vuepress/bundler-rspack'
import { viteBundler } from '@vuepress/bundler-vite'
import { webpackBundler } from '@vuepress/bundler-webpack'
import { defineUserConfig } from 'vuepress'
Expand Down Expand Up @@ -57,20 +58,23 @@ export default defineUserConfig({

markdown: {
assets: {
absolutePathPrependBase: E2E_BUNDLER === 'webpack',
absolutePathPrependBase:
E2E_BUNDLER === 'webpack' || E2E_BUNDLER === 'rspack',
},
},

bundler:
E2E_BUNDLER === 'webpack'
? webpackBundler()
: viteBundler({
viteOptions: {
optimizeDeps: {
include: ['@vuepress-e2e/conditional-exports'],
E2E_BUNDLER === 'rspack'
? rspackBundler()
: E2E_BUNDLER === 'webpack'
? webpackBundler()
: viteBundler({
viteOptions: {
optimizeDeps: {
include: ['@vuepress-e2e/conditional-exports'],
},
},
},
}),
}),

theme: e2eTheme(),

Expand Down
5 changes: 5 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@
"type": "module",
"scripts": {
"docs:build": "vuepress build docs --clean-cache --clean-temp",
"docs:build:rspack": "cross-env E2E_BUNDLER=rspack pnpm docs:build",
"docs:build:webpack": "cross-env E2E_BUNDLER=webpack pnpm docs:build",
"docs:clean": "rimraf docs/.vuepress/.temp docs/.vuepress/.cache docs/.vuepress/dist",
"docs:dev": "vuepress dev docs --clean-cache --clean-temp",
"docs:dev:rspack": "cross-env E2E_BUNDLER=rspack pnpm docs:dev",
"docs:dev:webpack": "cross-env E2E_BUNDLER=webpack pnpm docs:dev",
"docs:serve": "serve -l 9080 docs/.vuepress/dist",
"e2e:build": "cross-env E2E_COMMAND=build playwright test",
"e2e:build:rspack": "cross-env E2E_COMMAND=build E2E_BUNDLER=rspack playwright test",
"e2e:build:webpack": "cross-env E2E_COMMAND=build E2E_BUNDLER=webpack playwright test",
"e2e:dev": "cross-env E2E_COMMAND=dev playwright test",
"e2e:dev:rspack": "cross-env E2E_COMMAND=dev E2E_BUNDLER=rspack playwright test",
"e2e:dev:webpack": "cross-env E2E_COMMAND=dev E2E_BUNDLER=webpack playwright test"
},
"dependencies": {
"@vuepress-e2e/conditional-exports": "file:./modules/conditional-exports",
"@vuepress-e2e/style-exports": "file:./modules/style-exports",
"@vuepress/bundler-rspack": "workspace:*",
"@vuepress/bundler-vite": "workspace:*",
"@vuepress/bundler-webpack": "workspace:*",
"sass": "^1.99.0",
Expand Down
12 changes: 12 additions & 0 deletions packages/bundler-rspack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# @vuepress/bundler-rspack

[![npm](https://badgen.net/npm/v/@vuepress/bundler-rspack/next)](https://www.npmjs.com/package/@vuepress/bundler-rspack)
[![license](https://badgen.net/github/license/vuepress/core)](https://github.com/vuepress/core/blob/main/LICENSE)

## Documentation

https://vuepress.vuejs.org

## License

[MIT](https://github.com/vuepress/core/blob/main/LICENSE)
69 changes: 69 additions & 0 deletions packages/bundler-rspack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "@vuepress/bundler-rspack",
"version": "2.0.0-rc.29",
"description": "Bundler rspack package of VuePress",
"keywords": [
"bundler",
"rspack",
"vuepress",
"vuepress-bundler"
],
"homepage": "https://github.com/vuepress",
"bugs": {
"url": "https://github.com/vuepress/core/issues"
},
"license": "MIT",
"author": "meteorlxy",
"repository": {
"type": "git",
"url": "git+https://github.com/vuepress/core.git"
},
"files": [
"dist"
],
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"imports": {
"#vuepress-markdown-loader": "./dist/vuepress-markdown-loader.cjs",
"#vuepress-ssr-loader": "./dist/vuepress-ssr-loader.cjs"
},
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsdown",
"clean": "rimraf dist"
},
"dependencies": {
"@rspack/core": "^2.0.3",
"@rspack/dev-server": "^2.0.1",
"@vuepress/bundlerutils": "workspace:*",
"@vuepress/client": "workspace:*",
"@vuepress/core": "workspace:*",
"@vuepress/shared": "workspace:*",
"@vuepress/utils": "workspace:*",
"autoprefixer": "^10.5.0",
"css-loader": "^7.1.4",
"esbuild-loader": "~4.4.3",
"postcss": "^8.5.14",
"postcss-loader": "^8.2.1",
"rspack-chain": "^2.0.1",
"rspack-merge": "^0.1.1",
"style-loader": "^4.0.0",
"vue": "catalog:vue",
"vue-loader": "^17.4.2",
"vue-router": "catalog:vue"
},
"devDependencies": {
"connect-next": "^4.0.1"
},
"engines": {
"node": "^22.18.0 || ^24 || >=26"
}
}
114 changes: 114 additions & 0 deletions packages/bundler-rspack/src/build/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { rspack } from '@rspack/core'
import type { MultiRspackOptions } from '@rspack/core'
import { createVueServerApp, getSsrTemplate } from '@vuepress/bundlerutils'
import type { App, Bundler } from '@vuepress/core'
import { colors, debug, fs, logger, withSpinner } from '@vuepress/utils'

import { resolveRspackConfig } from '../resolveRspackConfig.js'
import type { RspackBundlerOptions } from '../types.js'
import {
CLIENT_MANIFEST_FILENAME,
createClientConfig,
} from './createClientConfig.js'
import { createServerConfig } from './createServerConfig.js'
import { renderPage } from './renderPage.js'
import { resolveClientManifestMeta } from './resolveClientManifestMeta.js'
import type { ClientManifest } from './types.js'

const log = debug('vuepress:bundler-rspack/build')

export const build = async (
options: RspackBundlerOptions,
app: App,
): ReturnType<Bundler['build']> => {
// plugin hook: extendsBundlerOptions
await app.pluginApi.hooks.extendsBundlerOptions.process(options, app)

// rspack compile
log('compiling start')
await withSpinner('Compiling with rspack')(async () => {
// create rspack config
const clientConfig = resolveRspackConfig({
config: await createClientConfig(app, options),
options,
isServer: false,
isBuild: true,
})
const serverConfig = resolveRspackConfig({
config: await createServerConfig(app, options),
options,
isServer: true,
isBuild: true,
})

await new Promise<void>((resolve, reject) => {
rspack(
[clientConfig, serverConfig] as MultiRspackOptions,
(err, stats) => {
if (err) {
reject(err)
} else {
if (stats) {
const { warnings, errors } = stats.toJson('errors-warnings')

errors?.forEach((item) => {
logger.error(item)
})
warnings?.forEach((warning) => {
logger.warn(warning)
})

if (stats.hasErrors())
reject(new Error('Failed to compile with errors'))
}

resolve()
}
},
)
})
})
log('compiling finish')

// render pages
await withSpinner(`Rendering ${app.pages.length} pages`)(async (spinner) => {
// load the client manifest file
const clientManifestPath = app.dir.temp(CLIENT_MANIFEST_FILENAME)
const clientManifest = (await fs.readJson(
clientManifestPath,
)) as ClientManifest

// resolve client files meta
const { initialFilesMeta, asyncFilesMeta, moduleFilesMetaMap } =
resolveClientManifestMeta(clientManifest)

// create vue ssr app and get ssr template
const { vueApp, vueRouter } = await createVueServerApp(
app.dir.temp('.server/app.cjs'),
)
const ssrTemplate = await getSsrTemplate(app)

// pre-render pages to html files
for (const page of app.pages) {
if (spinner) {
spinner.text = `Rendering pages ${colors.magenta(page.path)}`
}
await renderPage({
app,
page,
vueApp,
vueRouter,
ssrTemplate,
initialFilesMeta,
asyncFilesMeta,
moduleFilesMetaMap,
})
}
})

// keep the server bundle files in debug mode
if (!app.env.isDebug) {
// remove server temp directory after pages rendered
await fs.remove(app.dir.temp('.server'))
}
}
96 changes: 96 additions & 0 deletions packages/bundler-rspack/src/build/createClientConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
CopyRspackPlugin,
CssExtractRspackPlugin,
LightningCssMinimizerRspackPlugin,
} from '@rspack/core'
import type { Module } from '@rspack/core'
import type { App } from '@vuepress/core'
import { fs } from '@vuepress/utils'
import type { RspackChain } from 'rspack-chain'

import { createClientBaseConfig } from '../config/index.js'
import type { RspackBundlerOptions } from '../types.js'
import { createClientPlugin } from './createClientPlugin.js'

/**
* Filename of the client manifest file that generated by client plugin
*/
export const CLIENT_MANIFEST_FILENAME = '.server/client-manifest.json'

export const createClientConfig = async (
app: App,
options: RspackBundlerOptions,
): Promise<RspackChain> => {
const config = await createClientBaseConfig({
app,
options,
isBuild: true,
})

// vuepress client plugin, handle client assets info for ssr
config
.plugin('vuepress-client')
.use(createClientPlugin(app.dir.temp(CLIENT_MANIFEST_FILENAME)))

// copy files from public dir to dest dir
if (fs.pathExistsSync(app.dir.public())) {
config.plugin('copy').use(CopyRspackPlugin, [
{
patterns: [{ from: app.dir.public(), to: app.dir.dest() }],
},
])
}

// optimizations for production mode
// css-extract
config.plugin('css-extract').use(CssExtractRspackPlugin, [
{
filename: 'assets/css/styles.[chunkhash:8].css',
},
])

config.optimization.splitChunks({
cacheGroups: {
// ensure all css are extracted together.
// since most of the CSS will be from the theme and very little
// CSS will be from async chunks
styles: {
idHint: 'styles',
// necessary to ensure async chunks are also extracted
test: (m: Module) => m.type.includes('css/mini-extract'),
chunks: 'all',
enforce: true,
reuseExistingChunk: true,
},
// extract external library to a standalone chunk
vendor: {
idHint: 'vendor',
test: /node_modules/,
chunks: 'all',
priority: -10,
reuseExistingChunk: true,
},
},
})

// enable runtimeChunk
config.optimization.runtimeChunk(true)

// minimize
config.optimization.minimize(true)

// minimizer
config.optimization.set('minimizer', [
// keep the default minimizer
'...',
// add css minimizer
new LightningCssMinimizerRspackPlugin(),
])

// disable performance hints
if (!app.env.isDebug) {
config.performance.hints(false)
}

return config
}
Loading
Loading