219 lines
6.8 KiB
TypeScript
219 lines
6.8 KiB
TypeScript
import {
|
||
ExecutorContext,
|
||
joinPathFragments,
|
||
logger,
|
||
ProjectGraph,
|
||
readJsonFile,
|
||
readNxJson,
|
||
TargetConfiguration,
|
||
workspaceRoot,
|
||
} from '@nrwl/devkit';
|
||
import { getBaseWebpackPartial } from '@nrwl/webpack/src/utils/config';
|
||
import { getStylesPartial } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config';
|
||
import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utilities/version-utils';
|
||
import { join } from 'path';
|
||
import { gte } from 'semver';
|
||
import { Configuration, DefinePlugin, WebpackPluginInstance } from 'webpack';
|
||
import * as mergeWebpack from 'webpack-merge';
|
||
import { mergePlugins } from './merge-plugins';
|
||
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
||
|
||
const reactWebpackConfig = require('../webpack');
|
||
|
||
// This is shamelessly taken from CRA and modified for NX use
|
||
// https://github.com/facebook/create-react-app/blob/4784997f0682e75eb32a897b4ffe34d735912e6c/packages/react-scripts/config/env.js#L71
|
||
function getClientEnvironment(mode) {
|
||
// Grab NODE_ENV and NX_* and STORYBOOK_* environment variables and prepare them to be
|
||
// injected into the application via DefinePlugin in webpack configuration.
|
||
const NX_PREFIX = /^NX_/i;
|
||
const STORYBOOK_PREFIX = /^STORYBOOK_/i;
|
||
|
||
const raw = Object.keys(process.env)
|
||
.filter((key) => NX_PREFIX.test(key) || STORYBOOK_PREFIX.test(key))
|
||
.reduce(
|
||
(env, key) => {
|
||
env[key] = process.env[key];
|
||
return env;
|
||
},
|
||
{
|
||
// Useful for determining whether we’re running in production mode.
|
||
NODE_ENV: process.env.NODE_ENV || mode,
|
||
|
||
// Environment variables for Storybook
|
||
// https://github.com/storybookjs/storybook/blob/bdf9e5ed854b8d34e737eee1a4a05add88265e92/lib/core-common/src/utils/envs.ts#L12-L21
|
||
NODE_PATH: process.env.NODE_PATH || '',
|
||
STORYBOOK: process.env.STORYBOOK || 'true',
|
||
// This is to support CRA's public folder feature.
|
||
// In production we set this to dot(.) to allow the browser to access these assets
|
||
// even when deployed inside a subpath. (like in GitHub pages)
|
||
// In development this is just empty as we always serves from the root.
|
||
PUBLIC_URL: mode === 'production' ? '.' : '',
|
||
}
|
||
);
|
||
|
||
// Stringify all values so we can feed into webpack DefinePlugin
|
||
const stringified = {
|
||
'process.env': Object.keys(raw).reduce((env, key) => {
|
||
env[key] = JSON.stringify(raw[key]);
|
||
return env;
|
||
}, {}),
|
||
};
|
||
|
||
return { stringified };
|
||
}
|
||
|
||
export const babelDefault = (): Record<
|
||
string,
|
||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||
(string | [string, object])[]
|
||
> => {
|
||
// Add babel plugin for styled-components or emotion.
|
||
// We don't have a good way to know when a project uses one or the other, so
|
||
// add the plugin only if the other style package isn't used.
|
||
const packageJson = readJsonFile(join(workspaceRoot, 'package.json'));
|
||
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
||
const hasStyledComponents = !!deps['styled-components'];
|
||
|
||
const plugins = [];
|
||
if (hasStyledComponents) {
|
||
plugins.push(['styled-components', { ssr: false }]);
|
||
}
|
||
|
||
return {
|
||
presets: [],
|
||
plugins: [...plugins],
|
||
};
|
||
};
|
||
|
||
export const core = (prev, options) => ({
|
||
...prev,
|
||
disableWebpackDefaults: true,
|
||
});
|
||
|
||
export const webpack = async (
|
||
storybookWebpackConfig: Configuration = {},
|
||
options: any
|
||
): Promise<Configuration> => {
|
||
logger.info(
|
||
'=> Loading Nrwl React Storybook preset from "@nrwl/react/plugins/storybook"'
|
||
);
|
||
|
||
const tsconfigPath = join(options.configDir, 'tsconfig.json');
|
||
|
||
const builderOptions: any = {
|
||
...options,
|
||
root: options.configDir,
|
||
sourceRoot: '',
|
||
fileReplacements: [],
|
||
sourceMap: {
|
||
hidden: false,
|
||
scripts: true,
|
||
styles: true,
|
||
vendors: false,
|
||
},
|
||
styles: [],
|
||
optimization: {},
|
||
tsConfig: tsconfigPath,
|
||
extractCss: storybookWebpackConfig.mode === 'production',
|
||
};
|
||
|
||
const esm = true;
|
||
const isScriptOptimizeOn = storybookWebpackConfig.mode !== 'development';
|
||
const extractCss = storybookWebpackConfig.mode === 'production';
|
||
|
||
// ESM build for modern browsers.
|
||
const baseWebpackConfig = mergeWebpack.merge([
|
||
getBaseWebpackPartial(builderOptions, {
|
||
esm,
|
||
isScriptOptimizeOn,
|
||
skipTypeCheck: true,
|
||
}),
|
||
getStylesPartial(
|
||
options.workspaceRoot,
|
||
options.configDir,
|
||
builderOptions,
|
||
extractCss
|
||
),
|
||
]);
|
||
|
||
// run it through the React customizations
|
||
const finalConfig = reactWebpackConfig(baseWebpackConfig);
|
||
|
||
// Check whether the project .babelrc uses @emotion/babel-plugin. There's currently
|
||
// a Storybook issue (https://github.com/storybookjs/storybook/issues/13277) which apparently
|
||
// doesn't work with `@emotion/*` >= v11
|
||
// this is a workaround to fix that
|
||
let resolvedEmotionAliases = {};
|
||
const packageJson = readJsonFile(join(workspaceRoot, 'package.json'));
|
||
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
||
|
||
const emotionReactVersion = deps['@emotion/react'];
|
||
if (
|
||
emotionReactVersion &&
|
||
gte(
|
||
checkAndCleanWithSemver('@emotion/react', emotionReactVersion),
|
||
'11.0.0'
|
||
)
|
||
) {
|
||
try {
|
||
const babelrc = readJsonFile(
|
||
options.babelrcPath ||
|
||
joinPathFragments(options.configDir, '../', '.babelrc')
|
||
);
|
||
if (babelrc?.plugins?.includes('@emotion/babel-plugin')) {
|
||
resolvedEmotionAliases = {
|
||
resolve: {
|
||
alias: {
|
||
'@emotion/core': joinPathFragments(
|
||
workspaceRoot,
|
||
'node_modules',
|
||
'@emotion/react'
|
||
),
|
||
'@emotion/styled': joinPathFragments(
|
||
workspaceRoot,
|
||
'node_modules',
|
||
'@emotion/styled'
|
||
),
|
||
'emotion-theming': joinPathFragments(
|
||
workspaceRoot,
|
||
'node_modules',
|
||
'@emotion/react'
|
||
),
|
||
},
|
||
},
|
||
};
|
||
}
|
||
} catch (error) {
|
||
// silently ignore if the .babelrc doesn't exist
|
||
}
|
||
}
|
||
return mergeWebpack.merge(
|
||
{
|
||
...storybookWebpackConfig,
|
||
module: {
|
||
...storybookWebpackConfig.module,
|
||
rules: [
|
||
...storybookWebpackConfig.module.rules,
|
||
...finalConfig.module.rules,
|
||
],
|
||
},
|
||
resolve: {
|
||
...storybookWebpackConfig.resolve,
|
||
plugins: mergePlugins(
|
||
...((storybookWebpackConfig.resolve.plugins ??
|
||
[]) as unknown as WebpackPluginInstance[]),
|
||
...(finalConfig.resolve.plugins ?? [])
|
||
),
|
||
},
|
||
plugins: mergePlugins(
|
||
new DefinePlugin(
|
||
getClientEnvironment(storybookWebpackConfig.mode).stringified
|
||
),
|
||
...(storybookWebpackConfig.plugins ?? []),
|
||
...(finalConfig.plugins ?? [])
|
||
),
|
||
},
|
||
resolvedEmotionAliases
|
||
);
|
||
};
|