Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ if (!is_ios) {
}

group("storybook") {
deps = [ "//brave/.storybook:storybook" ]
deps = [ "//brave/build/storybook:storybook_deps" ]
}
}

Expand Down
1 change: 0 additions & 1 deletion PRESUBMIT.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@ def CheckLicense(input_api, output_api):

files_to_check = input_api.DEFAULT_FILES_TO_CHECK + (r'.+\.gni?$', )
files_to_skip = input_api.DEFAULT_FILES_TO_SKIP + (
r"\.storybook/",
r"ios/browser/api/brave_rewards/legacy_database/core_data_models/",
r'win_build_output/',
)
Expand Down
3 changes: 3 additions & 0 deletions build/.ci_features
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ cache_dir
env_gn_args

ai_chat_shared_conversation_lib

# Supports passing a build config to the storybook commands.
storybook_build_config
76 changes: 76 additions & 0 deletions build/commands/scripts/storybook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) 2026 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

// Check environment before doing anything.
import '../lib/checkEnvironment.js'

import { Argument, program } from 'commander'
import { createBuildConfigArgument } from '../lib/commandsUtils.ts'
import path from 'node:path'
import config from '../lib/config.ts'
import util from '../lib/util.js'
import { storybookCommand } from '../../storybook/storybook_command.ts'

program
.description(
'Build Storybook generated deps, then run Storybook (dev or static build)',
)
.addArgument(
new Argument('<mode>', 'Storybook mode').choices(['dev', 'build']),
)
.addArgument(createBuildConfigArgument())
.option('-C <build_dir>', 'build directory, relative to out/ or absolute')
.option('--target_arch <target_arch>', 'target architecture')
.option('--target_os <target_os>', 'target OS')
.allowExcessArguments(true)
.allowUnknownOption(true)
.action(async (mode, buildConfig, options) => {
const buildConfigProvided =
buildConfig !== undefined
|| options.C !== undefined
|| options.target_arch !== undefined
|| options.target_os !== undefined

if (!buildConfigProvided) {
// If no build config was provided, use the build output path from
// guessConfig.js to set the build directory. This is a fallback for CI to
// run storybook without specifying a build config, but still use the
// right build output path.
await import('../lib/guessConfig.js').then(({ outputPath }) => {
console.log(`Using build output path from guessConfig: ${outputPath}`)
options.C = outputPath
})
} else {
config.buildConfig = buildConfig || config.defaultBuildConfig
}

config.update(options)
config.buildTargets = ['brave/build/storybook:storybook_deps']

await util.buildTargets(config.buildTargets, config.defaultOptions)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we add an option to skip this, e.g. --skip-gn-deps? If I'm running the command many times in a session and I know I haven't changed any types then I would like to skip the GN time if it takes more than a couple of seconds to realize it doesn't need to compile. Restarting storybool can be necessary when adding new files or dependencies.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

disabled the build by default in dev mode and added -B flag to enable it. lmk what you think, the default can be the other way around, but since you mentioned you may restart storybook frequently, maybe it's better to have the default to be "disable gn build".


const storybookArgs = [
mode,
`--root-gen-dir=${path.join(config.outputDir, 'gen')}`,
]

if (mode === 'build') {
// If no build config was provided, use brave/.storybook-out directory.
// This is a fallback for CI to generate storybook at the CI-expected
// output path.
const outputDir = buildConfigProvided
? path.join(config.outputDir, 'storybook')
: path.join(config.braveCoreDir, '.storybook-out')
storybookArgs.push(`--output-dir=${outputDir}`)
}

if (mode === 'dev') {
// Pass through any additional arguments in dev mode.
storybookArgs.push(...program.args)
}

await storybookCommand.parseAsync(storybookArgs, { from: 'user' })
})
.parseAsync()
46 changes: 42 additions & 4 deletions .storybook/BUILD.gn → build/storybook/BUILD.gn
Original file line number Diff line number Diff line change
@@ -1,30 +1,57 @@
# Copyright (c) 2021 The Brave Authors. All rights reserved.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# you can obtain one at http://mozilla.org/MPL/2.0/.
# You can obtain one at https://mozilla.org/MPL/2.0/.

import("//brave/components/ai_chat/core/common/buildflags/buildflags.gni")
import("//brave/components/brave_news/common/buildflags/buildflags.gni")
import("//brave/components/brave_vpn/common/buildflags/buildflags.gni")
import("//brave/components/brave_wallet/common/buildflags/buildflags.gni")
import("//brave/components/email_aliases/buildflags/buildflags.gni")
import("//brave/components/playlist/core/common/buildflags/buildflags.gni")
import("//brave/components/psst/buildflags/buildflags.gni")
import("//brave/components/speedreader/common/buildflags/buildflags.gni")

group("storybook") {
group("storybook_deps") {
# Depend on any generated JS output that will
# be imported by any JS code used in any Brave
# storybook JS.
# Explicitly defined here so that even when those targets
# are disabled in a regular brave build due to build flags,
# they will be generated before storybook is compiled.
deps = [
"//brave/build/webpack:build_flags_json",
"//brave/components/brave_ads/core/mojom:mojom_js",
"//brave/components/brave_new_tab_ui:mojom_js",
"//brave/components/brave_rewards/core/mojom:mojom_js",
"//brave/components/brave_rewards/core/mojom:webui_js",
"//brave/components/brave_shields/core/common:mojom_js",
"//brave/components/brave_vpn/common/mojom:mojom_js",
"//brave/components/brave_welcome_ui:welcome_background",
"//brave/components/brave_welcome_ui:welcome_hill",
"//brave/components/brave_welcome_ui:welcome_logo",
"//brave/components/brave_welcome_ui:welcome_pyramid",
"//brave/components/brave_welcome_ui:welcome_sky",
"//brave/components/resources:strings",
"//components/omnibox/composebox:mojo_bindings_js",
"//mojo/public/js:bindings",
"//mojo/public/js:resources",
"//ui/webui/resources/js:build_ts",
]

if (!is_android) {
deps += [
"//brave/browser/ui/webui/brave_new_tab_page_refresh:mojom_js",
"//brave/browser/ui/webui/brave_welcome_page:mojom_js",
"//brave/components/ntp_background_images/browser/mojom:mojom_js",
"//ui/webui/resources/cr_components/theme_color_picker:build_ts",
]
}

if (enable_ai_chat) {
deps += [ "//brave/components/ai_chat/core/common/mojom:mojom_js" ]
}

if (enable_brave_news) {
deps += [ "//brave/components/brave_news/common:mojom_js" ]
}
Expand All @@ -33,8 +60,19 @@ group("storybook") {
deps += [ "//brave/components/brave_wallet/common:mojom_js" ]
}

if (enable_ai_chat) {
deps += [ "//brave/components/ai_chat/core/common/mojom:mojom_js" ]
if (enable_email_aliases) {
deps += [
"//brave/components/brave_account/mojom:mojom_js",
"//brave/components/email_aliases:mojom_js",
]
}

if (enable_playlist) {
deps += [ "//brave/components/playlist/core/common/mojom:mojom_js" ]
}

if (enable_psst) {
deps += [ "//brave/components/psst/core/common:mojom_js" ]
}

if (enable_speedreader) {
Expand Down
35 changes: 20 additions & 15 deletions .storybook/main.ts → build/storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

import path from 'path'
import { forkTsChecker } from './options'
import { StorybookConfig } from '@storybook/react-webpack5'
import path from 'node:path'
import { forkTsChecker } from './options.ts'
import type { StorybookConfig } from '@storybook/react-webpack5'
import type { Indexer } from '@storybook/types'
import { loadCsf } from '@storybook/csf-tools'
import { readFile } from 'fs/promises'
import { readFile } from 'node:fs/promises'

const slashStoriesIndexer: Indexer = {
test: /stories\/.*\.tsx$/,
Expand All @@ -21,33 +21,33 @@ const slashStoriesIndexer: Indexer = {

const config: StorybookConfig = {
stories: process.env.STORYBOOK_STORYPATH
? [`../${process.env.STORYBOOK_STORYPATH}`]
? [`../../${process.env.STORYBOOK_STORYPATH}`]
: [
'../components/**/stories/*.tsx',
'../components/**/*.stories.tsx',
'../browser/resources/**/stories/*.tsx',
'../browser/resources/**/*.stories.tsx',
'../../components/**/stories/*.tsx',
'../../components/**/*.stories.tsx',
'../../browser/resources/**/stories/*.tsx',
'../../browser/resources/**/*.stories.tsx',
],
typescript: {
check: false,
reactDocgen: false,
checkOptions: {
async: forkTsChecker,
typescript: {
configFile: path.resolve(__dirname, '..', 'tsconfig-storybook.json'),
configFile: path.resolve(__dirname, 'tsconfig-storybook.json'),
},
},
},
addons: ['@storybook/addon-knobs', '@storybook/addon-essentials'],
framework: '@storybook/react-webpack5',
staticDirs: [
{ from: '../node_modules/@brave/leo/icons', to: 'icons/' },
{ from: '../../node_modules/@brave/leo/icons', to: 'icons/' },
{
from: '../components/playlist/content/browser/resources/stories/assets',
from: '../../components/playlist/content/browser/resources/stories/assets',
to: 'playlist/',
},
{
from: '../browser/resources/brave_new_tab_page_refresh/stories/assets',
from: '../../browser/resources/brave_new_tab_page_refresh/stories/assets',
to: 'ntp-assets/',
},
],
Expand All @@ -58,8 +58,13 @@ const config: StorybookConfig = {
...config,
NODE_ENV: 'test',
}),
previewAnnotations: [path.resolve(__dirname, '../web/storybook/preview.tsx')],
experimental_indexers: async (existing) => [...existing, slashStoriesIndexer],
previewAnnotations: [
path.resolve(__dirname, '../../web/storybook/preview.tsx'),
],
experimental_indexers: async (existing) => [
...(existing ?? []),
slashStoriesIndexer,
],
}

export default config
2 changes: 2 additions & 0 deletions .storybook/manager.ts → build/storybook/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

// eslint-disable-next-line import/named
import { addons } from '@storybook/manager-api'
// eslint-disable-next-line import/named
import { create } from '@storybook/theming'

const braveTheme = create({
Expand Down
File renamed without changes.
86 changes: 86 additions & 0 deletions build/storybook/storybook_command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) 2026 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

import { spawnSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { Argument, Command } from 'commander'

const storybookConfigDir = import.meta.dirname
const braveCoreDir = path.resolve(storybookConfigDir, '../..')
const storybookCli = path.join(
braveCoreDir,
'node_modules',
'storybook',
'bin',
'index.cjs',
)

export const storybookCommand = new Command('storybook')
.description('Run Storybook (dev or static build)')
.addArgument(
new Argument('<mode>', 'Storybook mode').choices(['dev', 'build']),
)
.requiredOption(
'--root-gen-dir <path>',
'Path to the build gen directory (out/<config>/gen)',
)
.option(
'--output-dir <path>',
'Static Storybook output directory (required for build mode)',
)
.allowExcessArguments(true)
.allowUnknownOption(true)
.action((mode, options, program) => {
const rootGenDir = path.resolve(options.rootGenDir)
const outputDir = options.outputDir
? path.resolve(options.outputDir)
: undefined

if (mode === 'build' && !outputDir) {
program.error('--output-dir is required when --mode=build')
}

if (!fs.existsSync(rootGenDir)) {
program.error(
`Failed to find build output 'gen' folder at '${rootGenDir}'. `
+ 'Have you run a brave-core build yet with the specified '
+ '(or default) configuration?',
)
}

const nodeArgs = [
'--max-old-space-size=8192',
storybookCli,
mode,
'-c',
storybookConfigDir,
]

if (mode === 'build' && outputDir) {
nodeArgs.push('-o', outputDir)
}

// Pass through any additional arguments.
nodeArgs.push(...program.args)

const env: NodeJS.ProcessEnv = {
...process.env,
ROOT_GEN_DIR: rootGenDir,
}

console.log(`Using gen directory: ${rootGenDir}`)

const result = spawnSync(process.execPath, nodeArgs, {
env,
stdio: 'inherit',
cwd: braveCoreDir,
})

if (result.status !== 0) {
process.exit(result.status ?? 1)
}
})
15 changes: 15 additions & 0 deletions build/storybook/tsconfig-storybook.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig",
"include": [
"../../components/definitions/*",
"../../components/**/storybook",
"../../components/**/stories/*.tsx",
"../../components/**/*.stories.tsx",
],
"compilerOptions": {
"allowJs": true,
"skipLibCheck": true,
"noImplicitAny": true,
"outDir": "../../out/storybook_dist",
},
}
Loading
Loading