fix: support extends with env for browserslist

This commit is contained in:
alexander-akait 2025-09-30 21:50:34 +03:00
parent 3c08fd105c
commit 658853a3bc
20 changed files with 388 additions and 16 deletions

View File

@ -28,19 +28,26 @@ const inputRx = /^(?:((?:[A-Z]:)?[/\\].*?))?(?::(.+?))?$/i;
* @returns {BrowserslistHandlerConfig} config
*/
const parse = (input, context) => {
// browserslist
if (!input) {
return {};
}
// browserslist:path-to-config
// browserslist:path-to-config:env
if (path.isAbsolute(input)) {
const [, configPath, env] = inputRx.exec(input) || [];
return { configPath, env };
}
const config = browserslist.findConfig(context);
if (config && Object.keys(config).includes(input)) {
return { env: input };
// browserslist:query
// browserslist:env
// When we have a configuration file and value after `:` it can be env or query
if (config) {
return { env: input, query: input };
}
return { query: input };
@ -54,20 +61,26 @@ const parse = (input, context) => {
const load = (input, context) => {
const { configPath, env, query } = parse(input, context);
// if a query is specified, then use it, else
// if a path to a config is specified then load it, else
// find a nearest config
const config =
query ||
(configPath
? browserslist.loadConfig({
config: configPath,
env
})
: browserslist.loadConfig({ path: context, env }));
// TODO refactor me after resolve - https://github.com/browserslist/browserslist/issues/906
// Try to apply query firstly,
if (query) {
try {
return browserslist(query);
} catch (_err) {
// Nothing
}
}
// if a path to a config is specified then load it, else find a nearest config
const config = configPath
? browserslist.loadConfig({
config: configPath,
env
})
: browserslist.loadConfig({ path: context, env });
if (!config) return;
return browserslist(config);
return browserslist(config, { env, throwOnMissing: true });
};
/**

View File

@ -1326,8 +1326,13 @@ const applyOutputDefaults = (
if (tp.importScripts) return "array-push";
throw new Error(
"For the selected environment is no default script chunk format available:\n" +
"JSONP Array push can be chosen when 'document' or 'importScripts' is available.\n" +
`CommonJs exports can be chosen when 'require' or node builtins are available.\n${
`${
tp.module
? "Module ('module') can be chosen when ES modules are available (please set 'experiments.outputModule' and 'output.module' to `true`)"
: ""
}\n` +
"JSONP Array push ('array-push') can be chosen when 'document' or 'importScripts' is available.\n" +
`CommonJs exports ('commonjs') can be chosen when 'require' or node builtins are available.\n${
helpMessage
}`
);

View File

@ -0,0 +1 @@
extends browserslist-config-mycompany

View File

@ -0,0 +1 @@
it("should compile and run the test", function() {});

View File

@ -0,0 +1,37 @@
"use strict";
const fs = require("fs");
const path = require("path");
const rootPath = path.resolve(__dirname, "../../../../");
const rootNodeModules = path.resolve(rootPath, "./node_modules");
const browserslistPackage = path.resolve(
rootNodeModules,
"browserslist-config-mycompany"
);
const content = `
module.exports = {
development: [
'last 1 version'
],
production: [
'ie 9',
]
}
`;
const browserslistFile = path.resolve(browserslistPackage, "./index.js");
try {
fs.mkdirSync(browserslistPackage);
} catch (_err) {
// Nothing
}
fs.writeFileSync(browserslistFile, content);
module.exports = {
afterExecute() {
fs.rmSync(browserslistFile);
fs.rmdirSync(browserslistPackage);
}
};

View File

@ -0,0 +1,43 @@
"use strict";
const path = require("path");
/** @type {import("../../../../").Configuration} */
module.exports = {
target: `browserslist:${path.join(__dirname, ".browserslistrc")}:production`,
plugins: [
(compiler) => {
compiler.hooks.compilation.tap("Test", (compilation) => {
expect(compilation.outputOptions.environment).toMatchInlineSnapshot(`
Object {
"arrowFunction": false,
"asyncFunction": false,
"bigIntLiteral": false,
"const": false,
"destructuring": false,
"document": true,
"dynamicImport": false,
"dynamicImportInWorker": false,
"forOf": false,
"globalThis": false,
"module": false,
"nodePrefixForCoreModules": false,
"optionalChaining": false,
"templateLiteral": false,
}
`);
expect(compilation.options.externalsPresets).toMatchInlineSnapshot(`
Object {
"electron": false,
"electronMain": false,
"electronPreload": false,
"electronRenderer": false,
"node": false,
"nwjs": false,
"web": true,
}
`);
});
}
]
};

View File

@ -0,0 +1 @@
extends browserslist-config-mycompany1

View File

@ -0,0 +1 @@
it("should compile and run the test", function() {});

View File

@ -0,0 +1,38 @@
"use strict";
const fs = require("fs");
const path = require("path");
const rootPath = path.resolve(__dirname, "../../../../");
const rootNodeModules = path.resolve(rootPath, "./node_modules");
const browserslistPackage = path.resolve(
rootNodeModules,
"browserslist-config-mycompany1"
);
const content = `
module.exports = {
development: [
'last 1 version'
],
// We are in tests, so 'process.env.NODE_ENV' has the 'test' value (browserslist respects the 'process.env.NODE_ENV' value)
test: [
'ie 9',
]
}
`;
const browserslistFile = path.resolve(browserslistPackage, "./index.js");
try {
fs.mkdirSync(browserslistPackage);
} catch (_err) {
// Nothing
}
fs.writeFileSync(browserslistFile, content);
module.exports = {
afterExecute() {
fs.rmSync(browserslistFile);
fs.rmdirSync(browserslistPackage);
}
};

View File

@ -0,0 +1,43 @@
"use strict";
const path = require("path");
/** @type {import("../../../../").Configuration} */
module.exports = {
target: `browserslist:${path.join(__dirname, ".browserslistrc")}`,
plugins: [
(compiler) => {
compiler.hooks.compilation.tap("Test", (compilation) => {
expect(compilation.outputOptions.environment).toMatchInlineSnapshot(`
Object {
"arrowFunction": false,
"asyncFunction": false,
"bigIntLiteral": false,
"const": false,
"destructuring": false,
"document": true,
"dynamicImport": false,
"dynamicImportInWorker": false,
"forOf": false,
"globalThis": false,
"module": false,
"nodePrefixForCoreModules": false,
"optionalChaining": false,
"templateLiteral": false,
}
`);
expect(compilation.options.externalsPresets).toMatchInlineSnapshot(`
Object {
"electron": false,
"electronMain": false,
"electronPreload": false,
"electronRenderer": false,
"node": false,
"nwjs": false,
"web": true,
}
`);
});
}
]
};

View File

@ -0,0 +1 @@
it("should compile and run the test", function() {});

View File

@ -0,0 +1,10 @@
{
"browserslist": {
"development": [
"last 1 version"
],
"production": [
"ie 9"
]
}
}

View File

@ -0,0 +1,41 @@
"use strict";
/** @type {import("../../../../").Configuration} */
module.exports = {
target: "browserslist:production",
plugins: [
(compiler) => {
compiler.hooks.compilation.tap("Test", (compilation) => {
expect(compilation.outputOptions.environment).toMatchInlineSnapshot(`
Object {
"arrowFunction": false,
"asyncFunction": false,
"bigIntLiteral": false,
"const": false,
"destructuring": false,
"document": true,
"dynamicImport": false,
"dynamicImportInWorker": false,
"forOf": false,
"globalThis": false,
"module": false,
"nodePrefixForCoreModules": false,
"optionalChaining": false,
"templateLiteral": false,
}
`);
expect(compilation.options.externalsPresets).toMatchInlineSnapshot(`
Object {
"electron": false,
"electronMain": false,
"electronPreload": false,
"electronRenderer": false,
"node": false,
"nwjs": false,
"web": true,
}
`);
});
}
]
};

View File

@ -0,0 +1 @@
it("should compile and run the test", function() {});

View File

@ -0,0 +1,5 @@
{
"browserslist": [
"extends browserslist-config-mycompany2"
]
}

View File

@ -0,0 +1,38 @@
"use strict";
const fs = require("fs");
const path = require("path");
const rootPath = path.resolve(__dirname, "../../../../");
const rootNodeModules = path.resolve(rootPath, "./node_modules");
const browserslistPackage = path.resolve(
rootNodeModules,
"browserslist-config-mycompany2"
);
const content = `
module.exports = {
development: [
'last 1 version'
],
// We are in tests, so 'process.env.NODE_ENV' has the 'test' value (browserslist respects the 'process.env.NODE_ENV' value)
test: [
'ie 9',
]
}
`;
const browserslistFile = path.resolve(browserslistPackage, "./index.js");
try {
fs.mkdirSync(browserslistPackage);
} catch (_err) {
// Nothing
}
fs.writeFileSync(browserslistFile, content);
module.exports = {
afterExecute() {
fs.rmSync(browserslistFile);
fs.rmdirSync(browserslistPackage);
}
};

View File

@ -0,0 +1,41 @@
"use strict";
/** @type {import("../../../../").Configuration} */
module.exports = {
target: "browserslist",
plugins: [
(compiler) => {
compiler.hooks.compilation.tap("Test", (compilation) => {
expect(compilation.outputOptions.environment).toMatchInlineSnapshot(`
Object {
"arrowFunction": false,
"asyncFunction": false,
"bigIntLiteral": false,
"const": false,
"destructuring": false,
"document": true,
"dynamicImport": false,
"dynamicImportInWorker": false,
"forOf": false,
"globalThis": false,
"module": false,
"nodePrefixForCoreModules": false,
"optionalChaining": false,
"templateLiteral": false,
}
`);
expect(compilation.options.externalsPresets).toMatchInlineSnapshot(`
Object {
"electron": false,
"electronMain": false,
"electronPreload": false,
"electronRenderer": false,
"node": false,
"nwjs": false,
"web": true,
}
`);
});
}
]
};

View File

@ -0,0 +1 @@
it("should compile and run the test", function() {});

View File

@ -0,0 +1,10 @@
{
"browserslist": {
"development": [
"last 1 version"
],
"production": [
"ie 9"
]
}
}

View File

@ -0,0 +1,41 @@
"use strict";
/** @type {import("../../../../").Configuration} */
module.exports = {
target: "browserslist:maintained node versions",
plugins: [
(compiler) => {
compiler.hooks.compilation.tap("Test", (compilation) => {
expect(compilation.outputOptions.environment).toMatchInlineSnapshot(`
Object {
"arrowFunction": true,
"asyncFunction": true,
"bigIntLiteral": true,
"const": true,
"destructuring": true,
"document": false,
"dynamicImport": true,
"dynamicImportInWorker": false,
"forOf": true,
"globalThis": true,
"module": true,
"nodePrefixForCoreModules": true,
"optionalChaining": true,
"templateLiteral": true,
}
`);
expect(compilation.options.externalsPresets).toMatchInlineSnapshot(`
Object {
"electron": false,
"electronMain": false,
"electronPreload": false,
"electronRenderer": false,
"node": true,
"nwjs": false,
"web": false,
}
`);
});
}
]
};