diff --git a/packages/feature-flags-webpack-plugin/src/index.ts b/packages/feature-flags-webpack-plugin/src/index.ts index bd56ebd9..85ed249d 100644 --- a/packages/feature-flags-webpack-plugin/src/index.ts +++ b/packages/feature-flags-webpack-plugin/src/index.ts @@ -1,72 +1,73 @@ -import { Plugin, Compiler } from 'webpack' +import { Expression, CallExpression, Identifier, SpreadElement } from 'estree' +import { Compilation, Compiler, javascript } from 'webpack' -const featureFlagsFunctionName = 'isFeatureEnabled' +type PluginOptions = { + /** + * Function name for match + * + * @default 'isFeatureEnabled' + */ + isFeatureEnabledFnName: string + /** + * Object with flags + */ + flags: Record +} -function isFeatureFlagFunction(expression: any) { - if (expression.callee.property) { - return expression.callee.property.name === featureFlagsFunctionName +class FeatureFlagsWebpackPlugin { + constructor(public options: PluginOptions) { + Object.assign(this.options, { isFeatureEnabledFnName: 'isFeatureEnabled' }) } - return expression.callee.name === featureFlagsFunctionName -} -function getFeatureProperties(expression: any) { - if (!expression || !expression.arguments.length) return [] - return expression.arguments[0].properties -} + apply(compiler: Compiler) { + const { isFeatureEnabledFnName, flags } = this.options -function getFeatureFlag(properties: any) { - return properties.reduce((result: any, property: any) => { - const key = property.key.name - const value = property.value.value - result[key] = value - return result - }, {}) -} + function onCallExpression(expression: Expression, parser: javascript.JavascriptParser) { + if (isFeatureFlagFunction(expression, isFeatureEnabledFnName)) { + const argument = expression.arguments[0] + if (isIdentifier(argument)) { + const isEnabled = flags[argument.name] !== undefined + const result = isEnabled ? parser.evaluate(true) : parser.evaluate(false) + if (result !== undefined) { + result.setRange(expression.range) + } + return result + } + } + return undefined + } -function isEnabledFlag(flag: any, options: any) { - return options.some( - (option: any) => option.value === flag.value && option.component === flag.component, - ) -} + function onParseJavascript(parser: javascript.JavascriptParser) { + parser.hooks.evaluate + .for('CallExpression') + .tap('FeatureFlagsWebpackPlugin', (expression) => onCallExpression(expression, parser)) + } -class FeatureFlagsWebpackPlugin extends Plugin { - options: any[] + function onThisCompilation(_compilation: Compilation, compilationParams: any) { + compilationParams.normalModuleFactory.hooks.parser + .for('javascript/auto') + .tap('FeatureFlagsWebpackPlugin', onParseJavascript) + } - constructor(options: any[]) { - super() - this.options = options || [] + compiler.hooks.thisCompilation.tap('FeatureFlagsWebpackPlugin', onThisCompilation) } +} - apply(compiler: Compiler) { - const options = this.options - - compiler.hooks.thisCompilation.tap( - 'FeatureFlagsWebpackPlugin', - (_, { normalModuleFactory }) => { - normalModuleFactory.hooks.parser - .for('javascript/auto') - .tap('FeatureFlagsWebpackPlugin', (parser) => { - // @ts-expect-error - const getTrue = () => parser.evaluate(true) - // @ts-expect-error - const getFalse = () => parser.evaluate(false) - - parser.hooks.evaluate - .for('CallExpression') - .tap('FeatureFlagsWebpackPlugin', (expression) => { - if (!isFeatureFlagFunction(expression)) return +function isFeatureFlagFunction( + expression: Expression, + isFeatureEnabledFnName: string, +): expression is CallExpression { + // prettier-ignore + return ( + expression.type === 'CallExpression' + && expression.arguments.length > 0 + && expression.callee.type === 'Identifier' + && expression.callee.name === isFeatureEnabledFnName + ) +} - const properties = getFeatureProperties(expression) - const flag = getFeatureFlag(properties) - const isEnabled = isEnabledFlag(flag, options) - const result = isEnabled ? getTrue() : getFalse() - result.setRange(expression.range) - return result - }) - }) - }, - ) - } +function isIdentifier(expression: Expression | SpreadElement): expression is Identifier { + return expression.type === 'Identifier' } module.exports = FeatureFlagsWebpackPlugin