@spaced-out/ui-design-system
Version:
Sense UI components library
230 lines (206 loc) • 6.62 kB
text/typescript
// .storybook/main.ts
import {fileURLToPath} from 'node:url';
import {createRequire} from 'node:module';
import type {StorybookConfig} from '@storybook/react-webpack5';
import path, {dirname} from 'node:path';
import webpack from 'webpack';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const require = createRequire(import.meta.url);
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.tsx'],
addons: [
'@storybook/addon-docs',
'@storybook/addon-a11y',
'@storybook/addon-webpack5-compiler-babel',
'@chromatic-com/storybook',
// NOTE: We handle CSS manually in webpackFinal to have full control
],
babel: async (options: any) => ({
...options,
sourceType: 'unambiguous',
presets: [
[
'@babel/preset-env',
{
targets: [
'last 2 Chrome versions',
'last 2 Safari versions',
'last 2 Firefox versions',
'last 2 Edge versions',
'last 3 iOS Major versions',
],
},
],
['@babel/preset-react', {runtime: 'automatic', development: true}],
'@babel/preset-typescript',
],
plugins: ['macros', '@regrapes/babel-plugin-add-react-memo-displayname'],
}),
webpackFinal: async (cfg) => {
const config: any = {...cfg};
// ---------- Resolve / aliases ----------
config.resolve ||= {};
config.resolve.alias = {
...(config.resolve.alias ?? {}),
src: path.resolve(__dirname, '../src'),
};
config.resolve.fallback = {
...(config.resolve.fallback ?? {}),
process: require.resolve('process/browser'),
};
config.module ||= {};
config.module.rules = config.module.rules || [];
// ---------- CRITICAL: Remove Storybook's built-in CSS rules ----------
// This prevents conflicts with our custom CSS Module configuration
config.module.rules = config.module.rules.filter((rule: any) => {
if (!rule) return true;
if (!rule.test) return true;
const testStr = rule.test.toString();
// Remove any CSS-related rules (both .css and .scss/.sass)
if (testStr.includes('css') || testStr.includes('scss') || testStr.includes('sass')) {
return false;
}
return true;
});
// ---------- Add our CSS Module rules ----------
// Rule 1: CSS Modules for *.module.css files
config.module.rules.push({
test: /\.module\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: {
// css-loader 7.x requires these options for `import css from` pattern
namedExport: false,
exportLocalsConvention: 'as-is',
localIdentName: '[name]__[local]--[hash:base64:5]',
},
},
},
{
loader: require.resolve('postcss-loader'),
options: {
implementation: require.resolve('postcss'),
},
},
],
});
// Rule 2: CSS Modules for styles/variables/*.css (design tokens with @value)
config.module.rules.push({
test: /\.css$/,
include: [path.resolve(__dirname, '../src/styles/variables')],
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: {
namedExport: false,
exportLocalsConvention: 'as-is',
localIdentName: '[name]__[local]--[hash:base64:5]',
},
},
},
{
loader: require.resolve('postcss-loader'),
options: {
implementation: require.resolve('postcss'),
},
},
],
});
// Rule 3: Regular CSS (non-modules) - must come AFTER module rules
config.module.rules.push({
test: /\.css$/,
exclude: [
/\.module\.css$/,
path.resolve(__dirname, '../src/styles/variables'),
],
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: false, // Plain CSS, not modules
},
},
{
loader: require.resolve('postcss-loader'),
options: {
implementation: require.resolve('postcss'),
},
},
],
});
// Rule 4: Sass/SCSS support
config.module.rules.push({
test: /\.s[ac]ss$/i,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 2,
modules: {
auto: (resourcePath: string) =>
resourcePath.endsWith('.module.scss') ||
resourcePath.endsWith('.module.sass'),
namedExport: false,
exportLocalsConvention: 'as-is',
localIdentName: '[name]__[local]--[hash:base64:5]',
},
},
},
{
loader: require.resolve('sass-loader'),
options: {
implementation: require.resolve('sass'),
},
},
],
});
// ---------- Font rule tweak ----------
config.module.rules = config.module.rules.map((rule: any) => {
if (rule?.test && rule.test.toString().includes('woff')) {
return {
...rule,
test: /\.(svg|ico|jpg|jpeg|png|gif|webp|cur|ani|pdf)(\?.*)?$/,
};
}
return rule;
});
// ---------- TS/TSX via babel-loader ----------
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: require.resolve('babel-loader'),
options: {
presets: [['react-app', {flow: false, typescript: true}]],
plugins: ['@regrapes/babel-plugin-add-react-memo-displayname'],
},
});
// ---------- Plugins ----------
config.plugins = [
...(config.plugins ?? []),
new webpack.ProvidePlugin({
process: 'process/browser',
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(
process.env.NODE_ENV ?? 'development',
),
}),
];
(config.resolve.extensions as string[]).push('.ts', '.tsx');
return config;
},
staticDirs: ['public', '../design-tokens'],
framework: {name: '@storybook/react-webpack5', options: {}},
typescript: {reactDocgen: 'react-docgen-typescript'},
};
export default config;