Skip to content
Merged
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
122 changes: 26 additions & 96 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
const path = require('path');
const fs = require('fs');
const exportToCodesandboxAddon = require('storybook-addon-export-to-codesandbox');

const { loadWorkspaceAddon, getCodesandboxBabelOptions, registerTsPaths } = require('@fluentui/scripts-storybook');
const { loadWorkspaceAddon, registerTsPaths, registerRules, rules } = require('@fluentui/scripts-storybook');

const tsConfigPath = path.resolve(__dirname, '../tsconfig.base.json');

Expand All @@ -20,14 +19,6 @@ const tsConfigPath = path.resolve(__dirname, '../tsconfig.base.json');
* } StorybookConfig
*/

/**
* @typedef {{loader: string; options: { [index: string]: any }}} LoaderObjectDef
*/

/**
* @typedef {import('@babel/core').TransformOptions & Partial<{customize: string | null}>} BabelLoaderOptions
*/

const previewHeadTemplate = fs.readFileSync(path.resolve(__dirname, 'preview-head-template.html'), 'utf8');

module.exports = /** @type {Omit<StorybookConfig,'typescript'|'babel'>} */ ({
Expand All @@ -37,6 +28,29 @@ module.exports = /** @type {Omit<StorybookConfig,'typescript'|'babel'>} */ ({
},
stories: [],
addons: [
{
name: 'storybook-addon-swc',
options: /** @type {import('storybook-addon-swc').StoryBookAddonSwcOptions} */ ({
swcLoaderOptions: {
jsc: {
target: 'es2019',
parser: {
syntax: 'typescript',
tsx: true,
decorators: true,
dynamicImport: true,
},
transform: {
decoratorMetadata: true,
legacyDecorator: true,
},
keepClassNames: true,
externalHelpers: true,
loose: true,
},
},
}),
},
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-links',
Expand All @@ -54,33 +68,8 @@ module.exports = /** @type {Omit<StorybookConfig,'typescript'|'babel'>} */ ({
loadWorkspaceAddon('@fluentui/react-storybook-addon', { tsConfigPath }),
],
webpackFinal: config => {
registerTsPaths({ config, tsConfigPath });

if (config.module && config.module.rules) {
/**
* @type {import("webpack").RuleSetRule}
*/
const codesandboxRule = {
/**
* why the usage of 'post' ? - we need to run this loader after all storybook webpack rules/loaders have been executed.
* while we can use Array.prototype.unshift to "override" the indexes this approach is more declarative without additional hacks.
*/
enforce: 'post',
test: /\.stories\.tsx$/,
include: /stories/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: processBabelLoaderOptions({
plugins: [[exportToCodesandboxAddon.babelPlugin, getCodesandboxBabelOptions()]],
}),
},
};

config.module.rules.push(codesandboxRule);

overrideDefaultBabelLoader(/** @type {import("webpack").RuleSetRule[]} */ (config.module.rules));
}
registerTsPaths({ config, configFile: tsConfigPath });
registerRules({ config, rules: [rules.codesandboxRule] });

if ((process.env.CI || process.env.TF_BUILD || process.env.LAGE_PACKAGE_NAME) && config.plugins) {
// Disable ProgressPlugin in PR/CI builds to reduce log verbosity (warnings and errors are still logged)
Expand All @@ -100,62 +89,3 @@ module.exports = /** @type {Omit<StorybookConfig,'typescript'|'babel'>} */ ({
*/
previewHead: head => head + previewHeadTemplate,
});

/**
* Adds custom config to any `babel-loader` usage. Needs to be used on all manually added rules with babel-loader to webpack configuration.
*
* Why is this needed:
* - `options.babelrc` is ignored by `babel-loader` thus we need to use `customize` api to exclude specific babel presets/plugins
*
* @param {BabelLoaderOptions} loaderConfig
*/
function processBabelLoaderOptions(loaderConfig) {
const customLoaderPath = path.resolve(__dirname, './custom-loader.js');
const customOptions = { customize: customLoaderPath };
Object.assign(loaderConfig, customOptions);

return loaderConfig;
}

/**
* Overrides storybooks babel-loader setup
*
* We might remove this once we'll came up with robust solution (or proper behaviors will be added to babel-loader). For more context @see https://github.com/microsoft/fluentui/issues/18775
*
* Note:
* - this function mutates `rules` argument which is a reference to `modules.rules` webpack config property
* - to print used babel-loader config run: `yarn start-storybook --no-manager-cache --debug-webpack` and look for
* webpack rule set containing both:
* - `test: /\.(mjs|tsx?|jsx?)$/`
* - `node_modules/babel-loader/lib/index.js` as `loader` within module.rules
*
* @param {import("webpack").RuleSetRule[]} rules
*/
function overrideDefaultBabelLoader(rules) {
const loader = getBabelLoader();
processBabelLoaderOptions(loader.options);

function getBabelLoader() {
const ruleIdx = rules.findIndex(rule => {
return String(/** @type {import("webpack").RuleSetRule}*/ (rule).test) === '/\\.(mjs|tsx?|jsx?)$/';
});

const rule = /** @type {import("webpack").RuleSetRule}*/ (rules[ruleIdx]);

if (!Array.isArray(rule.use)) {
throw new Error('storybook webpack rules changed');
}

const loaderIdx = rule.use.findIndex(loaderConfig => {
return /** @type {LoaderObjectDef} */ (loaderConfig).loader.includes('babel-loader');
});

const loader = /** @type {LoaderObjectDef}*/ (rule.use[loaderIdx]);

if (!Object.prototype.hasOwnProperty.call(loader, 'options')) {
throw new Error('storybook webpack #module.rules changed!');
}

return loader;
}
}
17 changes: 15 additions & 2 deletions apps/public-docsite-v9/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
// @ts-check

const {
getPackageStoriesGlob,
createPathAliasesConfig,
registerTsPaths,
rules,
registerRules,
} = require('@fluentui/scripts-storybook');

const rootMain = require('../../../.storybook/main');
const { getPackageStoriesGlob } = require('@fluentui/scripts-storybook');

const { tsConfigAllPath } = createPathAliasesConfig();

module.exports = /** @type {Omit<import('../../../.storybook/main'), 'typescript'|'babel'>} */ ({
...rootMain,
Expand All @@ -14,9 +25,11 @@ module.exports = /** @type {Omit<import('../../../.storybook/main'), 'typescript
staticDirs: ['../public'],
addons: [...rootMain.addons],
webpackFinal: (config, options) => {
const localConfig = { ...rootMain.webpackFinal(config, options) };
const localConfig = /** @type config */ ({ ...rootMain.webpackFinal(config, options) });

// add your own webpack tweaks if needed
registerTsPaths({ configFile: tsConfigAllPath, config: localConfig });
registerRules({ rules: [rules.scssRule], config: localConfig });

return localConfig;
},
Expand Down
5 changes: 2 additions & 3 deletions apps/public-docsite-v9/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ This app is official documentation for Fluentui react-components published to ht

Fluent UI is a collection of projects that represent the Fluent design language in code. This website helps document the components and styles that make up Fluent UI.

## Build the website
## Start the website

1. Run `yarn lage build --to @fluentui/public-docsite-v9 --no-cache`
2. Run `yarn workspace @fluentui/public-docsite-v9 start`
Run `yarn workspace @fluentui/public-docsite-v9 start`
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@
"@storybook/manager-webpack5": "6.5.15",
"@storybook/react": "6.5.15",
"@storybook/theming": "6.5.15",
"@swc/core": "1.2.220",
"@swc/helpers": "0.4.11",
"@swc/core": "1.3.30",
"@swc/helpers": "0.4.14",
"@testing-library/dom": "8.11.3",
"@testing-library/jest-dom": "5.16.1",
"@testing-library/react": "12.1.2",
Expand Down Expand Up @@ -321,6 +321,7 @@
"source-map-loader": "4.0.0",
"storybook-addon-export-to-codesandbox": "0.8.1",
"storybook-addon-performance": "0.16.1",
"storybook-addon-swc": "1.1.9",
"storywright": "0.0.26-beta.1",
"strip-ansi": "6.0.0",
"style-loader": "2.0.0",
Expand Down Expand Up @@ -377,7 +378,8 @@
"@types/jest-axe/axe-core": "4.4.3",
"jest-axe/axe-core": "4.4.3",
"eslint": "7.25.0",
"@mdx-js/loader/loader-utils": "~2.0.4"
"@mdx-js/loader/loader-utils": "~2.0.4",
"swc-loader": "^0.2.3"
},
"syncpack": {
"prod": true,
Expand Down
11 changes: 10 additions & 1 deletion scripts/storybook/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
export { getPackageStoriesGlob, getCodesandboxBabelOptions, loadWorkspaceAddon, registerTsPaths } from './utils';
export {
getPackageStoriesGlob,
loadWorkspaceAddon,
registerTsPaths,
registerRules,
createPathAliasesConfig,
overrideDefaultBabelLoader,
} from './utils';

export * as rules from './rules';
16 changes: 15 additions & 1 deletion scripts/storybook/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
const rules = require('./rules');
const {
createPathAliasesConfig,
getPackageStoriesGlob,
loadWorkspaceAddon,
registerRules,
registerTsPaths,
} = require('./utils');

module.exports = {
...require('./utils'),
createPathAliasesConfig,
getPackageStoriesGlob,
loadWorkspaceAddon,
registerRules,
registerTsPaths,
rules,
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
const excludePresets = ['@griffel'];

/**
* Custom babel loader used with [`customize` babel-loader config](https://github.com/babel/babel-loader#customized-loader)
* The customize prop is being set by {@link ./../utils#processBabelLoaderOptions}
*
* @returns
*/
module.exports = () => {
return {
/**
Expand Down
66 changes: 66 additions & 0 deletions scripts/storybook/src/rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const { _createCodesandboxRule } = require('./utils');

/**
* v8 uses SCSS/CSS modules
* @type {import("webpack").RuleSetRule}
*/
const scssRule = {
test: /\.scss$/,
enforce: 'pre',
exclude: [/node_modules/],
use: [
{
// creates style nodes from JS strings
loader: '@microsoft/loader-load-themed-styles',
},
{
// translates CSS into CommonJS
loader: 'css-loader',
options: {
esModule: false,
modules: true,
importLoaders: 2,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['autoprefixer'],
},
},
},
{
loader: 'sass-loader',
},
],
};

/**
*
* @type {import("webpack").RuleSetRule}
*/
const griffelRule = {
test: /\.tsx?$/,
exclude: [/node_modules/],
enforce: 'post',
use: [
{
loader: '@griffel/webpack-loader',
options: {
babelOptions: {
presets: ['@babel/preset-typescript'],
},
},
},
],
};

/**
* @type {import("webpack").RuleSetRule}
*/
const codesandboxRule = _createCodesandboxRule();

exports.scssRule = scssRule;
exports.griffelRule = griffelRule;
exports.codesandboxRule = codesandboxRule;
36 changes: 36 additions & 0 deletions scripts/storybook/src/rules.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getAllPackageInfo } from '@fluentui/scripts-monorepo';
import * as semver from 'semver';

import { codesandboxRule } from './rules';

describe(`rules`, () => {
describe(`codesandbox`, () => {
it(`should generate rule definition with overridden babel loader`, () => {
const unstablePackage = Object.values(getAllPackageInfo()).find(metadata => {
return (
metadata.packagePath.includes('packages/') &&
metadata.packageJson.version.startsWith('9') &&
semver.prerelease(metadata.packageJson.version) !== null
);
});

const options = (codesandboxRule.use as { options: Record<string, unknown> }).options;
expect(options).toEqual(
expect.objectContaining({
customize: expect.stringContaining('loaders/custom-loader.js'),
plugins: [
[
expect.any(Function),
expect.objectContaining({
'@fluentui/react-utilities': { replace: '@fluentui/react-components' },
...(unstablePackage
? { [unstablePackage.packageJson.name]: { replace: '@fluentui/react-components/unstable' } }
: null),
}),
],
],
}),
);
});
});
});
Loading