Skip to content
Draft
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
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,17 @@
"tsx": "^4.21.0",
"typescript": "^5.9.3"
},
"packageManager": "pnpm@10.14.0"
"packageManager": "pnpm@10.14.0",
"pnpm": {
"overrides": {
"@storybook/addon-docs": "0.0.0-pr-34370-sha-b4eae34c",
"@storybook/addon-onboarding": "0.0.0-pr-34370-sha-b4eae34c",
"@storybook/core-webpack": "0.0.0-pr-34370-sha-b4eae34c",
"@storybook/html": "0.0.0-pr-34370-sha-b4eae34c",
"@storybook/react": "0.0.0-pr-34370-sha-b4eae34c",
"@storybook/vue3": "0.0.0-pr-34370-sha-b4eae34c",
"@storybook/web-components": "0.0.0-pr-34370-sha-b4eae34c",
"storybook": "0.0.0-pr-34370-sha-b4eae34c"
}
}
}
2 changes: 1 addition & 1 deletion packages/builder-rsbuild/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
},
"devDependencies": {
"@rsbuild/core": "^2.0.0-beta.4",
"@storybook/core-webpack": "10.3.1",
"@storybook/core-webpack": "0.0.0-pr-34370-sha-b4eae34c",
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.0.0",
"@types/pretty-hrtime": "^1.0.3",
Expand Down
96 changes: 96 additions & 0 deletions packages/builder-rsbuild/src/build-module-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { Rspack } from '@rsbuild/core'
import type { ModuleGraph, ModuleNode } from './types'

type CompilationLike = Pick<Rspack.Compilation, 'modules' | 'moduleGraph'>
type RspackModule = Rspack.Module

const getModuleFile = (module: RspackModule): string | undefined =>
module.nameForCondition()

const getModuleType = (module: RspackModule): ModuleNode['type'] => {
if (module.type.startsWith('css')) {
return 'css'
}

if (module.type.startsWith('asset')) {
return 'asset'
}

return 'js'
}

export function buildModuleGraph(compilation: CompilationLike): ModuleGraph {
const moduleGraph: ModuleGraph = new Map()
const moduleNodeMap = new WeakMap<object, ModuleNode>()

const getOrCreateModuleNode = (
module: RspackModule | null | undefined,
): ModuleNode | undefined => {
if (!module) {
return undefined
}

const file = getModuleFile(module)
if (!file) {
return undefined
}

const existingNode = moduleNodeMap.get(module)
if (existingNode) {
return existingNode
}

const moduleNode: ModuleNode = {
file,
type: getModuleType(module),
importers: new Set(),
importedModules: new Set(),
}
moduleNodeMap.set(module, moduleNode)

const moduleSet = moduleGraph.get(file) ?? new Set<ModuleNode>()
moduleSet.add(moduleNode)
moduleGraph.set(file, moduleSet)

return moduleNode
}

for (const module of compilation.modules) {
getOrCreateModuleNode(module)
}

for (const module of compilation.modules) {
const moduleNode = getOrCreateModuleNode(module)
if (!moduleNode) {
continue
}

for (const connection of compilation.moduleGraph.getIncomingConnections(
module,
)) {
const importerNode = getOrCreateModuleNode(connection.originModule)
if (importerNode) {
moduleNode.importers.add(importerNode)
importerNode.importedModules.add(moduleNode)
}
}
}

return moduleGraph
}

export function mergeModuleGraphs(graphs: ModuleGraph[]): ModuleGraph {
const mergedGraph: ModuleGraph = new Map()

for (const graph of graphs) {
for (const [file, nodes] of graph.entries()) {
const mergedNodes = mergedGraph.get(file) ?? new Set<ModuleNode>()
for (const node of nodes) {
mergedNodes.add(node)
}
mergedGraph.set(file, mergedNodes)
}
}

return mergedGraph
}
41 changes: 40 additions & 1 deletion packages/builder-rsbuild/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import type {
Preset,
StorybookConfigRaw,
} from 'storybook/internal/types'
import { buildModuleGraph, mergeModuleGraphs } from './build-module-graph'
import { withStatsJsonCompat } from './chromatic-stats'
import { overrideRsbuildLogger } from './logger'
import rsbuildConfig, {
type RsbuildBuilderOptions,
} from './preview/iframe-rsbuild.config'
import { applyReactShims } from './react-shims'
import type { RsbuildBuilder } from './types'
import type { ModuleGraph, RsbuildBuilder } from './types'

export * from './preview/virtual-module-mapping'
export * from './types'
Expand Down Expand Up @@ -127,11 +128,41 @@ export const getConfig: RsbuildBuilder['getConfig'] = async (options) => {
}

let server: RsbuildDevServer
const moduleGraphListeners = new Set<(moduleGraph: ModuleGraph) => void>()

const notifyModuleGraphListeners = (stats: StatsOrMultiStats) => {
if (!stats) {
return
}

const moduleGraph = mergeModuleGraphs(
'stats' in stats
? stats.stats.map((childStats) =>
buildModuleGraph(childStats.compilation),
)
: [buildModuleGraph(stats.compilation)],
)

for (const listener of moduleGraphListeners) {
listener(moduleGraph)
}
}

export async function bail(): Promise<void> {
moduleGraphListeners.clear()
return server?.close()
}

export const onModuleGraphChange: NonNullable<
RsbuildBuilder['onModuleGraphChange']
> = (cb) => {
moduleGraphListeners.add(cb)

return () => {
moduleGraphListeners.delete(cb)
}
}

export const start: RsbuildBuilder['start'] = async ({
startTime,
options,
Expand Down Expand Up @@ -160,8 +191,16 @@ export const start: RsbuildBuilder['start'] = async ({
const waitFirstCompileDone = new Promise<StatsOrMultiStats>((resolve) => {
rsbuildBuild.onDevCompileDone(({ stats, isFirstCompile }) => {
if (!isFirstCompile) {
if (moduleGraphListeners.size > 0) {
notifyModuleGraphListeners(stats)
}
return
}

if (moduleGraphListeners.size > 0) {
notifyModuleGraphListeners(stats)
}

resolve(stats)
})
})
Expand Down
70 changes: 69 additions & 1 deletion packages/builder-rsbuild/src/preview/iframe-rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { createRequire } from 'node:module'
import { dirname, join, resolve } from 'node:path'
import { dirname, join, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { RsbuildConfig, Rspack } from '@rsbuild/core'
import { loadConfig, mergeRsbuildConfig } from '@rsbuild/core'
import { pluginTypeCheck } from '@rsbuild/plugin-type-check'
// @ts-expect-error (I removed this on purpose, because it's incorrect)
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'
import { pluginHtmlMinifierTerser } from 'rsbuild-plugin-html-minifier-terser'
import slash from 'slash'
import {
getBuilderOptions,
isPreservingSymlinks,
Expand Down Expand Up @@ -76,6 +77,63 @@ const storybookPaths: Record<string, string> = {

export type RsbuildBuilderOptions = Options & {
typescriptOptions: TypescriptOptions
features?: Options['features'] & {
changeDetection?: boolean
}
}

const matchesStoriesByPath = (
filePath: string,
workingDir: string,
stories: Awaited<ReturnType<typeof normalizeStories>>,
) => {
const relativePath = slash(relative(workingDir, filePath))
const importPath = relativePath.startsWith('.')
? relativePath
: `./${relativePath}`

return stories.some((specifier) => {
const matcher = new RegExp(specifier.importPathMatcher)
return matcher.test(importPath)
})
}

const mergeLazyCompilationTest = (
lazyCompilation: Rspack.Configuration['lazyCompilation'],
options: {
workingDir: string
stories: Awaited<ReturnType<typeof normalizeStories>>
},
): Rspack.Configuration['lazyCompilation'] => {
if (lazyCompilation === false) {
return false
}

const existingOptions = lazyCompilation === true ? {} : lazyCompilation
const existingTest = existingOptions?.test

return {
...(existingOptions ?? {}),
test: (module: Rspack.Module) => {
const filePath = module.nameForCondition()
if (
filePath &&
matchesStoriesByPath(filePath, options.workingDir, options.stories)
) {
return false
}

if (!existingTest) {
return true
}

if (existingTest instanceof RegExp) {
return filePath ? existingTest.test(filePath) : false
}

return existingTest(module)
},
}
}

export default async (
Expand Down Expand Up @@ -164,6 +222,16 @@ export default async (
entries: false,
}
: builderOptions.lazyCompilation

if (features?.changeDetection) {
lazyCompilationConfig = mergeLazyCompilationTest(
lazyCompilationConfig,
{
workingDir,
stories,
},
)
}
}
}

Expand Down
17 changes: 16 additions & 1 deletion packages/builder-rsbuild/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,22 @@ export interface TypescriptOptions extends TypeScriptOptionsBase {
checkOptions?: PluginTypeCheckerOptions
}

export type RsbuildBuilder = Builder<RsbuildConfig, RsbuildStats>
export interface ModuleNode {
file: string
type: 'js' | 'css' | 'asset'
importers: Set<ModuleNode>
importedModules: Set<ModuleNode>
}

export type ModuleGraph = Map<ModuleNode['file'], Set<ModuleNode>>

export type OnModuleGraphChange = (
cb: (moduleGraph: ModuleGraph) => void,
) => () => void

export type RsbuildBuilder = Builder<RsbuildConfig, RsbuildStats> & {
onModuleGraphChange?: OnModuleGraphChange
}

export type RsbuildFinal = (
config: RsbuildConfig,
Expand Down
Loading
Loading