feat(angular): split get webpack config in one per supported version

This commit is contained in:
Thibaud Av 2021-11-10 18:20:33 +01:00
parent c988bb2cd6
commit ed5912e308
No known key found for this signature in database
GPG Key ID: 3F0FA53A70B49E78
6 changed files with 305 additions and 205 deletions

View File

@ -1 +1,7 @@
export declare function getWebpackConfig(baseConfig: any, options: any): any; import { JsonObject } from '@angular-devkit/core';
import { BuilderContext } from '@angular-devkit/architect';
export declare function getWebpackConfig(
baseConfig: any,
options: { builderOptions: JsonObject; builderContext: BuilderContext }
): any;

View File

@ -1,5 +1,3 @@
const { targetFromTargetString } = require('@angular-devkit/architect');
// Private angular devkit stuff // Private angular devkit stuff
const { const {
generateI18nBrowserWebpackConfigFromContext, generateI18nBrowserWebpackConfigFromContext,
@ -7,53 +5,47 @@ const {
const { const {
getCommonConfig, getCommonConfig,
getStylesConfig, getStylesConfig,
getTypescriptWorkerPlugin, getTypeScriptConfig,
} = require('@angular-devkit/build-angular/src/webpack/configs'); } = require('@angular-devkit/build-angular/src/webpack/configs');
const { filterOutStylingRules } = require('./utils/filter-out-styling-rules'); const { filterOutStylingRules } = require('./utils/filter-out-styling-rules');
/** /**
* Extract wepack config from angular-cli 12.2.x * Extract webpack config from angular-cli 12.2.x
* This file is in JavaScript to not use TypeScript. Because current storybook TypeScript version is not compatible with Angular CLI. * This file is in JavaScript to not use TypeScript. Because current storybook TypeScript version is not compatible with Angular CLI.
* FIXME: Try another way with TypeScript on future storybook version (7 maybe 🤞) * FIXME: Try another way with TypeScript on future storybook version (7 maybe 🤞)
* *
* @param {*} baseConfig Previous webpack config from storybook * @param {*} baseConfig Previous webpack config from storybook
* @param {*} options PresetOptions * @param {*} options { builderOptions, builderContext }
*/
exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext }) => {
/**
* Get angular-cli Webpack config
*/ */
exports.getWebpackConfig = async (baseConfig, options) => {
const builderContext = options.angularBuilderContext;
const target = options.angularBrowserTarget;
let targetOptions = {};
if (target) {
targetOptions = await builderContext.getTargetOptions(targetFromTargetString(target));
}
const tsConfig = options.tsConfig ?? targetOptions.tsConfig;
const { config: cliConfig } = await generateI18nBrowserWebpackConfigFromContext( const { config: cliConfig } = await generateI18nBrowserWebpackConfigFromContext(
{ {
// Default required options // Default options
index: 'noop-index', index: 'noop-index',
main: 'noop-main', main: 'noop-main',
outputPath: 'noop-out', outputPath: 'noop-out',
// Target options to override // Options provided by user
...targetOptions, ...builderOptions,
// Fixed options // Fixed options
optimization: false, optimization: false,
namedChunks: false, namedChunks: false,
progress: false, progress: false,
tsConfig,
buildOptimizer: false, buildOptimizer: false,
aot: false, aot: false,
}, },
builderContext, builderContext,
(wco) => [getCommonConfig(wco), getStylesConfig(wco), getTypescriptWorkerPlugin(wco)] (wco) => [getCommonConfig(wco), getStylesConfig(wco), getTypeScriptConfig(wco)]
); );
/**
* Merge baseConfig Webpack with angular-cli Webpack
*/
const entry = [ const entry = [
...baseConfig.entry, ...baseConfig.entry,
...(cliConfig.entry.styles ?? []), ...(cliConfig.entry.styles ?? []),
@ -63,6 +55,12 @@ exports.getWebpackConfig = async (baseConfig, options) => {
// Don't use storybooks styling rules because we have to use rules created by @angular-devkit/build-angular // Don't use storybooks styling rules because we have to use rules created by @angular-devkit/build-angular
// because @angular-devkit/build-angular created rules have include/exclude for global style files. // because @angular-devkit/build-angular created rules have include/exclude for global style files.
const rulesExcludingStyles = filterOutStylingRules(baseConfig); const rulesExcludingStyles = filterOutStylingRules(baseConfig);
const module = {
...baseConfig.module,
rules: [...cliConfig.module.rules, ...rulesExcludingStyles],
};
const plugins = [...(cliConfig.plugins ?? []), ...baseConfig.plugins];
const resolve = { const resolve = {
...baseConfig.resolve, ...baseConfig.resolve,
@ -72,11 +70,8 @@ exports.getWebpackConfig = async (baseConfig, options) => {
return { return {
...baseConfig, ...baseConfig,
entry, entry,
module: { module,
...baseConfig.module, plugins,
rules: [...cliConfig.module.rules, ...rulesExcludingStyles],
},
plugins: [...(cliConfig.plugins ?? []), ...baseConfig.plugins],
resolve, resolve,
resolveLoader: cliConfig.resolveLoader, resolveLoader: cliConfig.resolveLoader,
}; };

View File

@ -1 +1,7 @@
export declare function getWebpackConfig(baseConfig: any, options: any): any; import { JsonObject } from '@angular-devkit/core';
import { BuilderContext } from '@angular-devkit/architect';
export declare function getWebpackConfig(
baseConfig: any,
options: { builderOptions: JsonObject; builderContext: BuilderContext }
): any;

View File

@ -1,5 +1,3 @@
const { targetFromTargetString } = require('@angular-devkit/architect');
// Private angular devkit stuff // Private angular devkit stuff
const { const {
generateI18nBrowserWebpackConfigFromContext, generateI18nBrowserWebpackConfigFromContext,
@ -13,40 +11,31 @@ const {
const { filterOutStylingRules } = require('./utils/filter-out-styling-rules'); const { filterOutStylingRules } = require('./utils/filter-out-styling-rules');
/** /**
* Extract wepack config from angular-cli 12.2.x * Extract webpack config from angular-cli 13.x.x
* This file is in JavaScript to not use TypeScript. Because current storybook TypeScript version is not compatible with Angular CLI. * This file is in JavaScript to not use TypeScript. Because current storybook TypeScript version is not compatible with Angular CLI.
* FIXME: Try another way with TypeScript on future storybook version (7 maybe 🤞) * FIXME: Try another way with TypeScript on future storybook version (7 maybe 🤞)
* *
* @param {*} baseConfig Previous webpack config from storybook * @param {*} baseConfig Previous webpack config from storybook
* @param {*} options PresetOptions * @param {*} options { builderOptions, builderContext }
*/
exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext }) => {
/**
* Get angular-cli Webpack config
*/ */
exports.getWebpackConfig = async (baseConfig, options) => {
const builderContext = options.angularBuilderContext;
const target = options.angularBrowserTarget;
let targetOptions = {};
if (target) {
targetOptions = await builderContext.getTargetOptions(targetFromTargetString(target));
}
const tsConfig = options.tsConfig ?? targetOptions.tsConfig;
const { config: cliConfig } = await generateI18nBrowserWebpackConfigFromContext( const { config: cliConfig } = await generateI18nBrowserWebpackConfigFromContext(
{ {
// Default required options // Default options
index: 'noop-index', index: 'noop-index',
main: 'noop-main', main: 'noop-main',
outputPath: 'noop-out', outputPath: 'noop-out',
// Target options to override // Options provided by user
...targetOptions, ...builderOptions,
// Fixed options // Fixed options
optimization: false, optimization: false,
namedChunks: false, namedChunks: false,
progress: false, progress: false,
tsConfig,
buildOptimizer: false, buildOptimizer: false,
aot: false, aot: false,
}, },
@ -54,6 +43,9 @@ exports.getWebpackConfig = async (baseConfig, options) => {
(wco) => [getCommonConfig(wco), getStylesConfig(wco), getTypescriptWorkerPlugin(wco)] (wco) => [getCommonConfig(wco), getStylesConfig(wco), getTypescriptWorkerPlugin(wco)]
); );
/**
* Merge baseConfig Webpack with angular-cli Webpack
*/
const entry = [ const entry = [
...baseConfig.entry, ...baseConfig.entry,
...(cliConfig.entry.styles ?? []), ...(cliConfig.entry.styles ?? []),
@ -63,6 +55,12 @@ exports.getWebpackConfig = async (baseConfig, options) => {
// Don't use storybooks styling rules because we have to use rules created by @angular-devkit/build-angular // Don't use storybooks styling rules because we have to use rules created by @angular-devkit/build-angular
// because @angular-devkit/build-angular created rules have include/exclude for global style files. // because @angular-devkit/build-angular created rules have include/exclude for global style files.
const rulesExcludingStyles = filterOutStylingRules(baseConfig); const rulesExcludingStyles = filterOutStylingRules(baseConfig);
const module = {
...baseConfig.module,
rules: [...cliConfig.module.rules, ...rulesExcludingStyles],
};
const plugins = [...(cliConfig.plugins ?? []), ...baseConfig.plugins];
const resolve = { const resolve = {
...baseConfig.resolve, ...baseConfig.resolve,
@ -72,11 +70,8 @@ exports.getWebpackConfig = async (baseConfig, options) => {
return { return {
...baseConfig, ...baseConfig,
entry, entry,
module: { module,
...baseConfig.module, plugins,
rules: [...cliConfig.module.rules, ...rulesExcludingStyles],
},
plugins: [...(cliConfig.plugins ?? []), ...baseConfig.plugins],
resolve, resolve,
resolveLoader: cliConfig.resolveLoader, resolveLoader: cliConfig.resolveLoader,
}; };

View File

@ -0,0 +1,146 @@
import webpack from 'webpack';
import { logger } from '@storybook/node-logger';
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import { targetFromTargetString, Target } from '@angular-devkit/architect';
import { workspaces } from '@angular-devkit/core';
import {
findAngularProjectTarget,
getDefaultProjectName,
readAngularWorkspaceConfig,
} from './angular-read-workspace';
import {
AngularCliWebpackConfig,
extractAngularCliWebpackConfig,
} from './angular-devkit-build-webpack';
import { filterOutStylingRules } from './utils/filter-out-styling-rules';
import { PresetOptions } from './options';
/**
* Old way currently support version lower than 12.2.x
*/
export async function getWebpackConfig(baseConfig: webpack.Configuration, options: PresetOptions) {
logger.info('=> Loading angular-cli config for angular <=12.2.0');
const dirToSearch = process.cwd();
// Read angular workspace
let workspaceConfig;
try {
workspaceConfig = await readAngularWorkspaceConfig(dirToSearch);
} catch (error) {
logger.error(
`=> Could not find angular workspace config (angular.json) on this path "${dirToSearch}"`
);
logger.info(`=> Fail to load angular-cli config. Using base config`);
return baseConfig;
}
// Find angular project target
let project: workspaces.ProjectDefinition;
let target: workspaces.TargetDefinition;
let confName: string;
try {
// Default behavior when `angularBrowserTarget` are not explicitly defined to null
if (options.angularBrowserTarget !== null) {
const browserTarget = options.angularBrowserTarget
? targetFromTargetString(options.angularBrowserTarget)
: ({
configuration: undefined,
project: getDefaultProjectName(workspaceConfig),
target: 'build',
} as Target);
const fondProject = findAngularProjectTarget(
workspaceConfig,
browserTarget.project,
browserTarget.target
);
project = fondProject.project;
target = fondProject.target;
confName = browserTarget.configuration;
logger.info(
`=> Using angular project "${browserTarget.project}:${browserTarget.target}${
confName ? `:${confName}` : ''
}" for configuring Storybook`
);
}
// Start storybook when only tsConfig is provided.
if (options.angularBrowserTarget === null && options.tsConfig) {
logger.info(`=> Using default angular project with "tsConfig:${options.tsConfig}"`);
project = { root: '', extensions: {}, targets: undefined };
target = { builder: '', options: { tsConfig: options.tsConfig } };
}
} catch (error) {
logger.error(`=> Could not find angular project: ${error.message}`);
logger.info(`=> Fail to load angular-cli config. Using base config`);
return baseConfig;
}
// Use angular-cli to get some webpack config
let angularCliWebpackConfig: AngularCliWebpackConfig;
try {
angularCliWebpackConfig = await extractAngularCliWebpackConfig(
dirToSearch,
project,
target,
confName
);
logger.info(`=> Using angular-cli webpack config`);
} catch (error) {
logger.error(`=> Could not get angular cli webpack config`);
throw error;
}
return mergeAngularCliWebpackConfig(angularCliWebpackConfig, baseConfig);
}
function mergeAngularCliWebpackConfig(
{ cliCommonWebpackConfig, cliStyleWebpackConfig, tsConfigPath }: AngularCliWebpackConfig,
baseConfig: webpack.Configuration
) {
// Don't use storybooks styling rules because we have to use rules created by @angular-devkit/build-angular
// because @angular-devkit/build-angular created rules have include/exclude for global style files.
const rulesExcludingStyles = filterOutStylingRules(baseConfig);
// styleWebpackConfig.entry adds global style files to the webpack context
const entry = [
...(baseConfig.entry as string[]),
...Object.values(cliStyleWebpackConfig.entry).reduce((acc, item) => acc.concat(item), []),
];
const module = {
...baseConfig.module,
rules: [...cliStyleWebpackConfig.module.rules, ...rulesExcludingStyles],
};
// We use cliCommonConfig plugins to serve static assets files.
const plugins = [
...cliStyleWebpackConfig.plugins,
...cliCommonWebpackConfig.plugins,
...baseConfig.plugins,
];
const resolve = {
...baseConfig.resolve,
modules: Array.from(
new Set([...baseConfig.resolve.modules, ...cliCommonWebpackConfig.resolve.modules])
),
plugins: [
new TsconfigPathsPlugin({
configFile: tsConfigPath,
mainFields: ['browser', 'module', 'main'],
}),
],
};
return {
...baseConfig,
entry,
module,
plugins,
resolve,
resolveLoader: cliCommonWebpackConfig.resolveLoader,
};
}

View File

@ -1,174 +1,126 @@
import webpack from 'webpack'; import webpack from 'webpack';
import { logger } from '@storybook/node-logger'; import { logger } from '@storybook/node-logger';
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'; import { targetFromTargetString, BuilderContext } from '@angular-devkit/architect';
import { targetFromTargetString, Target } from '@angular-devkit/architect';
import { sync as findUpSync } from 'find-up'; import { sync as findUpSync } from 'find-up';
import semver from '@storybook/semver'; import semver from '@storybook/semver';
import { workspaces } from '@angular-devkit/core'; import { logging, JsonObject } from '@angular-devkit/core';
import {
findAngularProjectTarget,
getDefaultProjectName,
readAngularWorkspaceConfig,
} from './angular-read-workspace';
import {
AngularCliWebpackConfig,
extractAngularCliWebpackConfig,
} from './angular-devkit-build-webpack';
import { moduleIsAvailable } from './utils/module-is-available'; import { moduleIsAvailable } from './utils/module-is-available';
import { filterOutStylingRules } from './utils/filter-out-styling-rules';
import { getWebpackConfig as getWebpackConfig12_2_x } from './angular-cli-webpack-12.2.x'; import { getWebpackConfig as getWebpackConfig12_2_x } from './angular-cli-webpack-12.2.x';
import { getWebpackConfig as getWebpackConfig13_x_x } from './angular-cli-webpack-13.x.x'; import { getWebpackConfig as getWebpackConfig13_x_x } from './angular-cli-webpack-13.x.x';
import { getWebpackConfig as getWebpackConfigOlder } from './angular-cli-webpack-older';
import { PresetOptions } from './options'; import { PresetOptions } from './options';
export async function webpackFinal(baseConfig: webpack.Configuration, options: PresetOptions) { export async function webpackFinal(baseConfig: webpack.Configuration, options: PresetOptions) {
/**
* Find angular version and use right getWebpackConfig
*
* Only work with angular storybook builder
*/
const packageJson = await import(findUpSync('package.json', { cwd: options.configDir }));
const angularCliVersion = semver.coerce(packageJson.devDependencies['@angular/cli'])?.version;
const isNg12_2_x = semver.satisfies(angularCliVersion, '12.2.x');
if (isNg12_2_x && options.angularBuilderContext) {
return getWebpackConfig12_2_x(baseConfig, options);
}
const isNg13_x_x = semver.satisfies(angularCliVersion, '13.x.x');
if (isNg13_x_x && options.angularBuilderContext) {
return getWebpackConfig13_x_x(baseConfig, options);
}
/**
* Classic way currently support version lower than 12.2.x
*/
const dirToSearch = process.cwd();
if (!moduleIsAvailable('@angular-devkit/build-angular')) { if (!moduleIsAvailable('@angular-devkit/build-angular')) {
logger.info('=> Using base config because "@angular-devkit/build-angular" is not installed'); logger.info('=> Using base config because "@angular-devkit/build-angular" is not installed');
return baseConfig; return baseConfig;
} }
logger.info('=> Loading angular-cli config');
// Read angular workspace const packageJson = await import(findUpSync('package.json', { cwd: options.configDir }));
let workspaceConfig; const angularCliVersion = semver.coerce(packageJson.devDependencies['@angular/cli'])?.version;
try {
workspaceConfig = await readAngularWorkspaceConfig(dirToSearch); /**
} catch (error) { * Ordered array to use the specific getWebpackConfig according to some condition like angular-cli version
logger.error( */
`=> Could not find angular workspace config (angular.json) on this path "${dirToSearch}"` const webpackGetterByVersions: {
); info: string;
logger.info(`=> Fail to load angular-cli config. Using base config`); condition: boolean;
return baseConfig; getWebpackConfig(
baseConfig: webpack.Configuration,
options: PresetOptions
): Promise<webpack.Configuration> | webpack.Configuration;
}[] = [
{
info: '=> Loading angular-cli config for angular >= 13.0.0',
condition: semver.satisfies(angularCliVersion, '>=13.0.0'),
getWebpackConfig: async (_baseConfig, _options) => {
const builderContext = getBuilderContext(_options);
const builderOptions = await getBuilderOptions(_options, builderContext);
return getWebpackConfig13_x_x(_baseConfig, {
builderOptions,
builderContext,
});
},
},
{
info: '=> Loading angular-cli config for angular 12.2.x',
condition: semver.satisfies(angularCliVersion, '12.2.x') && options.angularBuilderContext,
getWebpackConfig: async (_baseConfig, _options) => {
const builderContext = getBuilderContext(_options);
const builderOptions = await getBuilderOptions(_options, builderContext);
return getWebpackConfig12_2_x(_baseConfig, {
builderOptions,
builderContext,
});
},
},
{
info: '=> Loading angular-cli config for angular lower than 12.2.0',
condition: true,
getWebpackConfig: getWebpackConfigOlder,
},
];
const webpackGetter = webpackGetterByVersions.find((wg) => wg.condition);
logger.info(webpackGetter.info);
return Promise.resolve(webpackGetter.getWebpackConfig(baseConfig, options));
} }
// Find angular project target /**
let project: workspaces.ProjectDefinition; * Get Builder Context
let target: workspaces.TargetDefinition; * If storybook is not start by angular builder create dumb BuilderContext
let confName: string; */
try { function getBuilderContext(options: PresetOptions): BuilderContext {
// Default behavior when `angularBrowserTarget` are not explicitly defined to null return (
if (options.angularBrowserTarget !== null) { options.angularBuilderContext ??
const browserTarget = options.angularBrowserTarget (({
? targetFromTargetString(options.angularBrowserTarget) target: { project: 'noop-project', builder: '', options: {} },
: ({ workspaceRoot: process.cwd(),
configuration: undefined, getProjectMetadata: () => ({}),
project: getDefaultProjectName(workspaceConfig), getTargetOptions: () => ({}),
target: 'build', logger: new logging.Logger('Storybook'),
} as Target); } as unknown) as BuilderContext)
const fondProject = findAngularProjectTarget(
workspaceConfig,
browserTarget.project,
browserTarget.target
); );
project = fondProject.project; }
target = fondProject.target;
confName = browserTarget.configuration; /**
* Get builder options
* Merge target options from browser target and from storybook options
*/
async function getBuilderOptions(
options: PresetOptions,
builderContext: BuilderContext
): Promise<JsonObject> {
/**
* Get Browser Target options
*/
let browserTargetOptions: JsonObject = {};
if (options.angularBrowserTarget) {
const browserTarget = targetFromTargetString(options.angularBrowserTarget);
logger.info( logger.info(
`=> Using angular project "${browserTarget.project}:${browserTarget.target}${ `=> Using angular browser target options from "${browserTarget.project}:${
confName ? `:${confName}` : '' browserTarget.target
}" for configuring Storybook` }${browserTarget.configuration ? `:${browserTarget.configuration}` : ''}"`
); );
} browserTargetOptions = await builderContext.getTargetOptions(browserTarget);
// Start storybook when only tsConfig is provided.
if (options.angularBrowserTarget === null && options.tsConfig) {
logger.info(`=> Using default angular project with "tsConfig:${options.tsConfig}"`);
project = { root: '', extensions: {}, targets: undefined };
target = { builder: '', options: { tsConfig: options.tsConfig } };
}
} catch (error) {
logger.error(`=> Could not find angular project: ${error.message}`);
logger.info(`=> Fail to load angular-cli config. Using base config`);
return baseConfig;
} }
// Use angular-cli to get some webpack config /**
let angularCliWebpackConfig: AngularCliWebpackConfig; * Merge target options from browser target options and from storybook options
try { */
angularCliWebpackConfig = await extractAngularCliWebpackConfig( const builderOptions = {
dirToSearch, ...browserTargetOptions,
project, tsConfig:
target, options.tsConfig ??
confName browserTargetOptions.tsConfig ??
); findUpSync('tsconfig.json', { cwd: options.configDir }),
logger.info(`=> Using angular-cli webpack config`);
} catch (error) {
logger.error(`=> Could not get angular cli webpack config`);
throw error;
}
return mergeAngularCliWebpackConfig(angularCliWebpackConfig, baseConfig);
}
function mergeAngularCliWebpackConfig(
{ cliCommonWebpackConfig, cliStyleWebpackConfig, tsConfigPath }: AngularCliWebpackConfig,
baseConfig: webpack.Configuration
) {
// Don't use storybooks styling rules because we have to use rules created by @angular-devkit/build-angular
// because @angular-devkit/build-angular created rules have include/exclude for global style files.
const rulesExcludingStyles = filterOutStylingRules(baseConfig);
// styleWebpackConfig.entry adds global style files to the webpack context
const entry = [
...(baseConfig.entry as string[]),
...Object.values(cliStyleWebpackConfig.entry).reduce((acc, item) => acc.concat(item), []),
];
const module = {
...baseConfig.module,
rules: [...cliStyleWebpackConfig.module.rules, ...rulesExcludingStyles],
}; };
logger.info(`=> Using angular project with "tsConfig:${builderOptions.tsConfig}"`);
// We use cliCommonConfig plugins to serve static assets files. return builderOptions;
const plugins = [
...cliStyleWebpackConfig.plugins,
...cliCommonWebpackConfig.plugins,
...baseConfig.plugins,
];
const resolve = {
...baseConfig.resolve,
modules: Array.from(
new Set([...baseConfig.resolve.modules, ...cliCommonWebpackConfig.resolve.modules])
),
plugins: [
new TsconfigPathsPlugin({
configFile: tsConfigPath,
mainFields: ['browser', 'module', 'main'],
}),
],
};
return {
...baseConfig,
entry,
module,
plugins,
resolve,
resolveLoader: cliCommonWebpackConfig.resolveLoader,
};
} }