Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support array of functions and promises #1946

Merged
merged 11 commits into from Oct 14, 2020
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Expand Up @@ -74,7 +74,7 @@ jobs:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-${{ matrix.webpack-version }}-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-${{ matrix.webpack-version }}-${{ hashFiles('**/yarn.lock', './yarn.lock') }}

- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
Expand Down
122 changes: 75 additions & 47 deletions packages/webpack-cli/lib/groups/ConfigGroup.js
Expand Up @@ -63,46 +63,44 @@ const getConfigInfoFromFileName = (filename) => {
.filter((e) => existsSync(e.path));
};

// Prepare rechoir environment to load multiple file formats
const requireLoader = (extension, path) => {
rechoir.prepare(extensions, path, process.cwd());
};

// Reads a config file given the config metadata
const requireConfig = (configModule) => {
const extension = Object.keys(jsVariants).find((t) => configModule.ext.endsWith(t));

if (extension) {
requireLoader(extension, configModule.path);
rechoir.prepare(extensions, configModule.path, process.cwd());
}

let config = require(configModule.path);

if (config.default) {
config = config.default;
}

return {
content: config,
path: configModule.path,
};
return { config, path: configModule.path };
};

// Responsible for reading user configuration files
// else does a default config lookup and resolves it.
const resolveConfigFiles = async (args) => {
const { config, mode } = args;

if (config && config.length > 0) {
const resolvedOptions = [];
const finalizedConfigs = config.map(async (webpackConfig) => {
const configPath = resolve(webpackConfig);
const configFiles = getConfigInfoFromFileName(configPath);

if (!configFiles.length) {
throw new ConfigError(`The specified config file doesn't exist in ${configPath}`);
}

const foundConfig = configFiles[0];
const resolvedConfig = requireConfig(foundConfig);

return finalize(resolvedConfig, args);
});

// resolve all the configs
for await (const resolvedOption of finalizedConfigs) {
if (Array.isArray(resolvedOption.options)) {
Expand All @@ -111,10 +109,9 @@ const resolveConfigFiles = async (args) => {
resolvedOptions.push(resolvedOption.options);
}
}
// When the resolved configs are more than 1, then pass them as Array [{...}, {...}] else pass the first config object {...}
const finalOptions = resolvedOptions.length > 1 ? resolvedOptions : resolvedOptions[0] || {};

opts['options'] = finalOptions;
opts['options'] = resolvedOptions.length > 1 ? resolvedOptions : resolvedOptions[0] || {};

return;
}

Expand All @@ -127,12 +124,16 @@ const resolveConfigFiles = async (args) => {
const configFiles = tmpConfigFiles.map(requireConfig);
if (configFiles.length) {
const defaultConfig = configFiles.find((p) => p.path.includes(mode) || p.path.includes(modeAlias[mode]));

if (defaultConfig) {
opts = await finalize(defaultConfig, args);
return;
}

const foundConfig = configFiles.pop();

opts = await finalize(foundConfig, args);

return;
}
};
Expand All @@ -150,49 +151,77 @@ const finalize = async (moduleObj, args) => {
return newOptionsObject;
}

const configOptions = moduleObj.content;
const config = moduleObj.config;

if (typeof configOptions === 'function') {
// when config is a function, pass the env from args to the config function
let formattedEnv;
if (Array.isArray(env)) {
formattedEnv = env.reduce((envObject, envOption) => {
envObject[envOption] = true;
return envObject;
}, {});
}
const newOptions = configOptions(formattedEnv, args);
// When config function returns a promise, resolve it, if not it's resolved by default
newOptionsObject['options'] = await Promise.resolve(newOptions);
} else if (configName) {
if (Array.isArray(configOptions) && configOptions.length > 1) {
// In case of exporting multiple configurations, If you pass a name to --config-name flag,
// webpack will only build that specific configuration.
const namedOptions = configOptions.filter((opt) => configName.includes(opt.name));
if (namedOptions.length === 0) {
logger.error(`Configuration with name "${configName}" was not found.`);
process.exit(2);
} else {
newOptionsObject['options'] = namedOptions;
const isMultiCompilerMode = Array.isArray(config);
const rawConfigs = isMultiCompilerMode ? config : [config];

let configs = await Promise.all(
rawConfigs.map(async (rawConfig) => {
const isPromise = typeof rawConfig.then === 'function';

if (isPromise) {
rawConfig = await rawConfig;
}
} else {
logger.error('Multiple configurations not found. Please use "--config-name" with multiple configurations.');

// `Promise` may return `Function`
if (typeof rawConfig === 'function') {
// when config is a function, pass the env from args to the config function
let envs;

if (Array.isArray(env)) {
envs = env.reduce((envObject, envOption) => {
envObject[envOption] = true;

return envObject;
}, {});
}

rawConfig = await rawConfig(envs, args);
}

return rawConfig;
}),
);

if (configName) {
const foundConfigNames = [];

configs = configs.filter((options) => {
const found = configName.includes(options.name);

if (found) {
foundConfigNames.push(options.name);
}

return found;
});

if (foundConfigNames.length !== configName.length) {
// Configuration with name "test" was not found.
logger.error(
configName
.filter((name) => !foundConfigNames.includes(name))
.map((configName) => `Configuration with name "${configName}" was not found.`)
.join('\n'),
);
process.exit(2);
}
} else {
if (Array.isArray(configOptions) && !configOptions.length) {
newOptionsObject['options'] = {};
return newOptionsObject;
}
}

newOptionsObject['options'] = configOptions;
if (configs.length === 0) {
logger.error('No configurations found');
process.exit(2);
}

newOptionsObject['options'] = isMultiCompilerMode ? configs : configs[0];

return newOptionsObject;
};

const resolveConfigMerging = async (args) => {
const { merge } = args;

if (merge) {
// Get the current configuration options
const { options: configOptions } = opts;
Expand All @@ -210,10 +239,9 @@ const resolveConfigMerging = async (args) => {
}
};

const handleConfigResolution = async (args) => {
module.exports = async (args) => {
await resolveConfigFiles(args);
await resolveConfigMerging(args);

return opts;
};

module.exports = handleConfigResolution;