Jack Hsu 2e621f324c
feat(misc): v19 cleanup for Nx plugins (#23104)
This PR removes deprecated code that's been slated for removal in Nx 19
- mentioned as `TODO(v19)` comments.

## Breaking Changes

- **CNW:** `create-nx-workspace` no longer support `--preset=empty` and
`--preset=core`, use `--preset=apps` and `--preset=npm` respectively.
Deprecated in Nx 15.9.
- **Next.js:** `NX_` environment variables are no longer bundled into
Next.js apps, use `NEXT_PUBLIC` instead. Deprecated in Nx 16.8.
- **Webpack, Storybook, Esbuild:** `NX_` environment variables are no
longer bundled into browser bundles, use `NX_PUBLIC` instead. This
removes the possibility of intentional bundling of `NX_` variables.
Deprecated in Nx 18.
- **Cypress:** `cypressComponentConfiguration` generator removed from
`@nx/cypress`, use `configurationGenerator`instead. Deprecated in Nx
16.8.
- **Cypress:** `cypressProjectGenerator` generator removed from
`@nx/cypress`, use `configurationGenerator` instead. Deprecated in Nx
15.9.
- **Expo:** `withNxWebpack` removed from `@nx/expo`, use [metro
bundler](https://docs.expo.dev/guides/customizing-metro/)
(https://docs.expo.dev/guides/customizing-metro/) in app.json instead.
There is a migration to handle this in Nx 19. Deprecated in Nx 15.8.

## Deferred to v20

- **JS:** `classProperties.loose` option removed from `@nx/js/babel`
preset, use `loose` instead. Deprecated in Nx 17.0.
- **ESLint:** Low priority task to "deviations from
@typescript-eslint/recommended" for our lint rules. @JamesHenry will
look at this later before Nx 20, but it is unimportant.
- **React:** component testing does not work with Project Crystal, and
we need the executor + built-in webpack configs to run CT. Will do a
follow-up on this after Nx 19 release. Related issue:
https://github.com/nrwl/nx/issues/21546
- **Next.js:** `withStylus` removal from `@nx/next`, use SASS instead.
It hasn't worked, but we kept the file to throw an error when used.
Deprecated in Nx 17.0.
- **Next.js**: `@nx/next:component` and `@nx/next:page` generators to
not derive the `components` and `app`/`pages` directory. Use `nx g
@nx/next:component apps/myapp/components/button` instead. Deprecated in
Nx 17.0.
- **Webpack:** `isolatedConfig` option removal from
`@nx/webpack:webpack` executor. There is a migration to handle this in
Nx 19. Deprecated in in Nx 17.2.
- **Angular:** `executeWebpackDevServerBuilder` removal from
`@nx/angular/executors`, use `executeDevServerBuilder` instead.
Deprecated in Nx 17.0.
2024-05-02 13:37:12 -04:00

242 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
createProjectGraphAsync,
ExecutorContext,
logger,
ProjectGraphProjectNode,
workspaceRoot,
} from '@nx/devkit';
import { composePluginsSync } from '@nx/webpack/src/utils/config';
import { NormalizedWebpackExecutorOptions } from '@nx/webpack/src/executors/webpack/schema';
import { join } from 'path';
import {
Configuration,
DefinePlugin,
ResolvePluginInstance,
WebpackPluginInstance,
} from 'webpack';
import { mergePlugins } from './merge-plugins';
import { withReact } from '../with-react';
import { existsSync } from 'fs';
// 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_PUBLIC_/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 were 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 core = (prev, options) => ({
...prev,
disableWebpackDefaults: true,
});
interface NxProjectData {
workspaceRoot: string;
projectRoot: string;
sourceRoot: string;
projectNode?: ProjectGraphProjectNode;
}
const getProjectData = async (
storybookOptions: any
): Promise<NxProjectData> => {
const fallbackData = {
workspaceRoot: storybookOptions.configDir,
projectRoot: '',
sourceRoot: '',
};
// Edge-case: not running from Nx
if (!process.env.NX_WORKSPACE_ROOT) return fallbackData;
const projectGraph = await createProjectGraphAsync();
const projectNode = projectGraph.nodes[process.env.NX_TASK_TARGET_PROJECT];
return projectNode
? {
workspaceRoot: process.env.NX_WORKSPACE_ROOT,
projectRoot: projectNode.data.root,
sourceRoot: projectNode.data.sourceRoot,
projectNode,
}
: // Edge-case: missing project node
fallbackData;
};
const fixBabelConfigurationIfNeeded = (
webpackConfig: Configuration,
projectData: NxProjectData
): void => {
if (!projectData.projectNode) return;
const isUsingBabelUpwardRootMode = Object.keys(
projectData.projectNode.data.targets
).find((k) => {
const targetConfig = projectData.projectNode.data.targets[k];
return (
(targetConfig.executor === '@nx/webpack:webpack' ||
targetConfig.executor === '@nrwl/webpack:webpack') &&
targetConfig.options?.babelUpwardRootMode
);
});
if (isUsingBabelUpwardRootMode) return;
let babelrcPath: string;
for (const ext of ['', '.json', '.js', '.cjs', '.mjs', '.cts']) {
const candidate = join(
projectData.workspaceRoot,
projectData.projectRoot,
`.babelrc${ext}`
);
if (existsSync(candidate)) {
babelrcPath = candidate;
break;
}
}
// Unexpected setup, skip.
if (!babelrcPath) return;
let babelRuleItem;
for (const rule of webpackConfig.module.rules) {
if (typeof rule === 'string') continue;
if (!rule || !Array.isArray(rule.use)) continue;
for (const item of rule.use) {
if (typeof item !== 'string' && item['loader'].includes('babel-loader')) {
babelRuleItem = item;
break;
}
}
}
if (babelRuleItem) {
babelRuleItem.options.configFile = babelrcPath;
}
};
export const webpack = async (
storybookWebpackConfig: Configuration = {},
options: any
): Promise<Configuration> => {
logger.info(
'=> Loading Nx React Storybook Addon from "@nx/react/plugins/storybook"'
);
// In case anyone is missing dep and did not run migrations.
// See: https://github.com/nrwl/nx/issues/14455
try {
require.resolve('@nx/webpack');
} catch {
throw new Error(
`'@nx/webpack' package is not installed. Install it and try again.`
);
}
const { withNx, withWeb } = require('@nx/webpack');
const projectData = await getProjectData(options);
const tsconfigPath = existsSync(
join(projectData.projectRoot, 'tsconfig.storybook.json')
)
? join(projectData.projectRoot, 'tsconfig.storybook.json')
: join(projectData.projectRoot, 'tsconfig.json');
// The 'tsconfig.json' is mainly for the cypress test to be able to run
// because it will look into the cypress project dir and it will not find tsconfig.storybook.json
fixBabelConfigurationIfNeeded(storybookWebpackConfig, projectData);
const builderOptions: NormalizedWebpackExecutorOptions = {
...options,
root: projectData.workspaceRoot,
projectRoot: projectData.projectRoot,
sourceRoot: projectData.sourceRoot,
fileReplacements: [],
sourceMap: true,
styles: options.styles ?? [],
optimization: {},
tsConfig: tsconfigPath,
extractCss: storybookWebpackConfig.mode === 'production',
target: 'web',
};
// ESM build for modern browsers.
let baseWebpackConfig: Configuration = {};
const configure = composePluginsSync(
withNx({ target: 'web', skipTypeChecking: true }),
withReact()
);
const finalConfig = configure(baseWebpackConfig, {
options: builderOptions,
context: { root: workspaceRoot } as ExecutorContext, // The context is not used here.
});
return {
...storybookWebpackConfig,
module: {
...storybookWebpackConfig.module,
rules: [
...storybookWebpackConfig.module.rules,
...finalConfig.module.rules,
],
},
resolve: {
...storybookWebpackConfig.resolve,
fallback: {
...storybookWebpackConfig.resolve?.fallback,
// Next.js and other React frameworks may have server-code that uses these modules.
// They are not meant for client-side components so skip the fallbacks.
assert: false,
path: false,
util: false,
},
plugins: mergePlugins(
...((storybookWebpackConfig.resolve.plugins ??
[]) as ResolvePluginInstance[]),
...((finalConfig.resolve
.plugins as unknown as ResolvePluginInstance[]) ?? [])
) as ResolvePluginInstance[],
},
plugins: mergePlugins(
new DefinePlugin(
getClientEnvironment(storybookWebpackConfig.mode).stringified
),
...((storybookWebpackConfig.plugins as WebpackPluginInstance[]) ?? []),
...((finalConfig.plugins as WebpackPluginInstance[]) ?? [])
) as WebpackPluginInstance[],
};
};