nx/packages/next/src/utils/config.ts
2021-09-20 16:39:13 -04:00

208 lines
5.8 KiB
TypeScript

import { ExecutorContext, offsetFromRoot } from '@nrwl/devkit';
// ignoring while we support both Next 11.1.0 and versions before it
// @ts-ignore
import type { NextConfig } from 'next/dist/server/config-shared';
// @ts-ignore
import type {
PHASE_DEVELOPMENT_SERVER,
PHASE_EXPORT,
PHASE_PRODUCTION_BUILD,
PHASE_PRODUCTION_SERVER,
} from 'next/dist/shared/lib/constants';
import { join, resolve } from 'path';
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
import { Configuration } from 'webpack';
import {
FileReplacement,
NextBuildBuilderOptions,
WebpackConfigOptions,
} from './types';
import { normalizeAssets } from '@nrwl/web/src/utils/normalize';
import { createCopyPlugin } from '@nrwl/web/src/utils/config';
import { WithNxOptions } from '../../plugins/with-nx';
import {
createTmpTsConfig,
DependentBuildableProjectNode,
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { importConfig } from './require-shim';
const loadConfig = importConfig();
export function createWebpackConfig(
workspaceRoot: string,
projectRoot: string,
fileReplacements: FileReplacement[] = [],
assets: any = null,
nxConfigOptions: WebpackConfigOptions = {},
dependencies: DependentBuildableProjectNode[] = []
): (a, b) => Configuration {
return function webpackConfig(
config: Configuration,
{
isServer,
defaultLoaders,
}: {
buildId: string;
dev: boolean;
isServer: boolean;
defaultLoaders: any;
}
): Configuration {
// default `svgr` to `true`, as it used to be supported by default,
// before this option was introduced
if (typeof nxConfigOptions.svgr === 'undefined') {
nxConfigOptions.svgr = true;
}
const mainFields = ['es2015', 'module', 'main'];
const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx'];
let tsConfigPath = join(projectRoot, 'tsconfig.json');
if (dependencies.length > 0) {
tsConfigPath = createTmpTsConfig(
join(workspaceRoot, tsConfigPath),
workspaceRoot,
projectRoot,
dependencies
);
}
config.resolve.plugins = [
new TsconfigPathsPlugin({
configFile: tsConfigPath,
extensions,
mainFields,
}),
];
fileReplacements
.map((fileReplacement) => ({
replace: resolve(workspaceRoot, fileReplacement.replace),
with: resolve(workspaceRoot, fileReplacement.with),
}))
.reduce((alias, replacement) => {
alias[replacement.replace] = replacement.with;
return alias;
}, config.resolve.alias);
config.module.rules.push({
test: /\.([jt])sx?$/,
exclude: /node_modules/,
use: [defaultLoaders.babel],
});
if (nxConfigOptions.svgr) {
config.module.rules.push({
test: /\.svg$/,
oneOf: [
// If coming from JS/TS file, then transform into React component using SVGR.
{
issuer: /\.[jt]sx?$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
svgo: false,
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('url-loader'),
options: {
limit: 10000, // 10kB
name: '[name].[hash:7].[ext]',
},
},
],
},
// Fallback to plain URL loader if someone just imports the SVG and references it on the <img src> tag
{
loader: require.resolve('url-loader'),
options: {
limit: 10000, // 10kB
name: '[name].[hash:7].[ext]',
},
},
],
});
}
// Copy (shared) assets to `public` folder during client-side compilation
if (!isServer && Array.isArray(assets) && assets.length > 0) {
config.plugins.push(
createCopyPlugin(
normalizeAssets(assets, workspaceRoot, projectRoot).map((asset) => ({
...asset,
output: join('../public', asset.output),
}))
)
);
}
return config;
};
}
export async function prepareConfig(
phase:
| typeof PHASE_PRODUCTION_BUILD
| typeof PHASE_EXPORT
| typeof PHASE_DEVELOPMENT_SERVER
| typeof PHASE_PRODUCTION_SERVER,
options: NextBuildBuilderOptions,
context: ExecutorContext,
dependencies: DependentBuildableProjectNode[]
) {
const config = (await loadConfig(phase, options.root, null)) as NextConfig &
WithNxOptions;
const userWebpack = config.webpack;
const userNextConfig = getConfigEnhancer(options.nextConfig, context.root);
// Yes, these do have different capitalisation...
config.outdir = `${offsetFromRoot(options.root)}${options.outputPath}`;
config.distDir =
config.distDir && config.distDir !== '.next'
? config.distDir
: join(config.outdir, '.next');
config.webpack = (a, b) =>
createWebpackConfig(
context.root,
options.root,
options.fileReplacements,
options.assets,
config.nx,
dependencies
)(userWebpack ? userWebpack(a, b) : a, b);
if (typeof userNextConfig !== 'function') {
throw new Error(
`Module specified by 'nextConfig' option does not export a function. It should be of form 'module.exports = (phase, config, options) => config;'`
);
}
return userNextConfig(phase, config, { options });
}
function getConfigEnhancer(
pluginPath: undefined | string,
workspaceRoot: string
) {
if (!pluginPath) {
return (_, x) => x;
}
let fullPath: string;
try {
fullPath = require.resolve(pluginPath);
} catch {
fullPath = join(workspaceRoot, pluginPath);
}
try {
return require(fullPath);
} catch {
throw new Error(
`Could not find file specified by 'nextConfig' option: ${fullPath}`
);
}
}