mirror of
https://github.com/svg/svgo.git
synced 2025-07-29 20:21:14 +03:00
Support es modules (#1583)
Ref https://github.com/svg/svgo/issues/1579 In config of course. Projects with type:module can now use modules to export config ```js export default { plugins: [] } ``` Also added support for resolving svgo.config.mjs and svgo.config.cjs. Moved loadConfig tests to svgo-node tests. mjs test is skipped for now in node 10, just don't use modules there
This commit is contained in:
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const { pathToFileURL } = require('url');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const {
|
const {
|
||||||
extendDefaultPlugins,
|
extendDefaultPlugins,
|
||||||
@ -13,7 +14,25 @@ exports.extendDefaultPlugins = extendDefaultPlugins;
|
|||||||
exports.createContentItem = createContentItem;
|
exports.createContentItem = createContentItem;
|
||||||
|
|
||||||
const importConfig = async (configFile) => {
|
const importConfig = async (configFile) => {
|
||||||
const config = require(configFile);
|
let config;
|
||||||
|
try {
|
||||||
|
// dynamic import expects file url instead of path and may fail
|
||||||
|
// when windows path is provided
|
||||||
|
const { default: imported } = await import(pathToFileURL(configFile));
|
||||||
|
config = imported;
|
||||||
|
} catch (importError) {
|
||||||
|
// TODO remove require in v3
|
||||||
|
try {
|
||||||
|
config = require(configFile);
|
||||||
|
} catch (requireError) {
|
||||||
|
// throw original error if es module is detected
|
||||||
|
if (requireError.code === 'ERR_REQUIRE_ESM') {
|
||||||
|
throw importError;
|
||||||
|
} else {
|
||||||
|
throw requireError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (config == null || typeof config !== 'object' || Array.isArray(config)) {
|
if (config == null || typeof config !== 'object' || Array.isArray(config)) {
|
||||||
throw Error(`Invalid config file "${configFile}"`);
|
throw Error(`Invalid config file "${configFile}"`);
|
||||||
}
|
}
|
||||||
@ -40,9 +59,17 @@ const loadConfig = async (configFile, cwd = process.cwd()) => {
|
|||||||
let dir = cwd;
|
let dir = cwd;
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
while (true) {
|
while (true) {
|
||||||
const file = path.join(dir, 'svgo.config.js');
|
const js = path.join(dir, 'svgo.config.js');
|
||||||
if (await isFile(file)) {
|
if (await isFile(js)) {
|
||||||
return await importConfig(file);
|
return await importConfig(js);
|
||||||
|
}
|
||||||
|
const mjs = path.join(dir, 'svgo.config.mjs');
|
||||||
|
if (await isFile(mjs)) {
|
||||||
|
return await importConfig(mjs);
|
||||||
|
}
|
||||||
|
const cjs = path.join(dir, 'svgo.config.cjs');
|
||||||
|
if (await isFile(cjs)) {
|
||||||
|
return await importConfig(cjs);
|
||||||
}
|
}
|
||||||
const parent = path.dirname(dir);
|
const parent = path.dirname(dir);
|
||||||
if (dir === parent) {
|
if (dir === parent) {
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const { optimize } = require('./svgo-node.js');
|
const path = require('path');
|
||||||
|
const { optimize, loadConfig } = require('./svgo-node.js');
|
||||||
|
|
||||||
const describeLF = os.EOL === '\r\n' ? describe.skip : describe;
|
const describeLF = os.EOL === '\r\n' ? describe.skip : describe;
|
||||||
const describeCRLF = os.EOL === '\r\n' ? describe : describe.skip;
|
const describeCRLF = os.EOL === '\r\n' ? describe : describe.skip;
|
||||||
@ -123,3 +124,94 @@ describeCRLF('with CRLF line-endings', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('loadConfig', () => {
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const fixtures = path.join(cwd, './test/fixtures/config-loader');
|
||||||
|
|
||||||
|
test('loads by absolute path', async () => {
|
||||||
|
expect(await loadConfig(path.join(fixtures, 'one/two/config.js'))).toEqual({
|
||||||
|
plugins: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loads by relative path to cwd', async () => {
|
||||||
|
const config = await loadConfig('one/two/config.js', fixtures);
|
||||||
|
expect(config).toEqual({ plugins: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('searches in cwd and up', async () => {
|
||||||
|
expect(await loadConfig(null, path.join(fixtures, 'one/two'))).toEqual({
|
||||||
|
plugins: [],
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
await loadConfig(null, path.join(cwd, './test/fixtures/missing'))
|
||||||
|
).toEqual(null);
|
||||||
|
// TODO remove check in v3
|
||||||
|
if (process.version.startsWith('v10.') === false) {
|
||||||
|
expect(await loadConfig(null, path.join(fixtures, 'mjs'))).toEqual({
|
||||||
|
plugins: ['mjs'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
expect(await loadConfig(null, path.join(fixtures, 'cjs'))).toEqual({
|
||||||
|
plugins: ['cjs'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails when specified config does not exist', async () => {
|
||||||
|
try {
|
||||||
|
await loadConfig('{}');
|
||||||
|
expect.fail('Config is loaded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatch(/Cannot find module/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails when exported config not an object', async () => {
|
||||||
|
try {
|
||||||
|
await loadConfig(path.join(fixtures, 'invalid-null.js'));
|
||||||
|
expect.fail('Config is loaded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatch(/Invalid config file/);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await loadConfig(path.join(fixtures, 'invalid-array.js'));
|
||||||
|
expect.fail('Config is loaded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatch(/Invalid config file/);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await loadConfig(path.join(fixtures, 'invalid-string.js'));
|
||||||
|
expect.fail('Config is loaded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatch(/Invalid config file/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles runtime errors properly', async () => {
|
||||||
|
try {
|
||||||
|
await loadConfig(path.join(fixtures, 'invalid-runtime.js'));
|
||||||
|
expect.fail('Config is loaded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatch(/plugins is not defined/);
|
||||||
|
}
|
||||||
|
// TODO remove check in v3
|
||||||
|
if (process.version.startsWith('v10.') === false) {
|
||||||
|
try {
|
||||||
|
await loadConfig(path.join(fixtures, 'invalid-runtime.mjs'));
|
||||||
|
expect.fail('Config is loaded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatch(/plugins is not defined/);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles MODULE_NOT_FOUND properly', async () => {
|
||||||
|
try {
|
||||||
|
await loadConfig(path.join(fixtures, 'module-not-found.js'));
|
||||||
|
expect.fail('Config is loaded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatch(/Cannot find module 'unknown-module'/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
"!**/*.test.js"
|
"!**/*.test.js"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest --coverage",
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest --maxWorkers=3 --coverage",
|
||||||
"lint": "eslint --ignore-path .gitignore . && prettier --check \"**/*.js\" --ignore-path .gitignore",
|
"lint": "eslint --ignore-path .gitignore . && prettier --check \"**/*.js\" --ignore-path .gitignore",
|
||||||
"fix": "eslint --ignore-path .gitignore --fix . && prettier --write \"**/*.js\" --ignore-path .gitignore",
|
"fix": "eslint --ignore-path .gitignore --fix . && prettier --write \"**/*.js\" --ignore-path .gitignore",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const { loadConfig } = require('../../lib/svgo-node.js');
|
|
||||||
const {
|
const {
|
||||||
resolvePluginConfig,
|
resolvePluginConfig,
|
||||||
extendDefaultPlugins,
|
extendDefaultPlugins,
|
||||||
@ -173,91 +171,6 @@ describe('config', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('config', () => {
|
|
||||||
it('is loaded by absolute path', async () => {
|
|
||||||
const config = await loadConfig(
|
|
||||||
path.join(process.cwd(), './test/config/fixtures/one/two/config.js')
|
|
||||||
);
|
|
||||||
expect(config).toEqual({ plugins: [] });
|
|
||||||
});
|
|
||||||
it('is loaded by relative path to cwd', async () => {
|
|
||||||
const config = await loadConfig(
|
|
||||||
'one/two/config.js',
|
|
||||||
path.join(process.cwd(), './test/config/fixtures')
|
|
||||||
);
|
|
||||||
expect(config).toEqual({ plugins: [] });
|
|
||||||
});
|
|
||||||
it('is searched in cwd and up', async () => {
|
|
||||||
const config = await loadConfig(
|
|
||||||
null,
|
|
||||||
path.join(process.cwd(), './test/config/fixtures/one/two')
|
|
||||||
);
|
|
||||||
expect(config).toEqual({ plugins: [] });
|
|
||||||
});
|
|
||||||
it('gives null when config is not found', async () => {
|
|
||||||
const config = await loadConfig(
|
|
||||||
null,
|
|
||||||
path.join(process.cwd(), './test/config')
|
|
||||||
);
|
|
||||||
expect(config).toEqual(null);
|
|
||||||
});
|
|
||||||
it('is failed when specified config does not exist', async () => {
|
|
||||||
try {
|
|
||||||
await loadConfig('{}');
|
|
||||||
expect.fail('Config is loaded successfully');
|
|
||||||
} catch (error) {
|
|
||||||
expect(error.message).toMatch(/Cannot find module/);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it('is failed to load when module exports not an object', async () => {
|
|
||||||
try {
|
|
||||||
await loadConfig(
|
|
||||||
path.join(process.cwd(), './test/config/fixtures/invalid-null.js')
|
|
||||||
);
|
|
||||||
expect.fail('Config is loaded successfully');
|
|
||||||
} catch (error) {
|
|
||||||
expect(error.message).toMatch(/Invalid config file/);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await loadConfig(
|
|
||||||
path.join(process.cwd(), './test/config/fixtures/invalid-array.js')
|
|
||||||
);
|
|
||||||
expect.fail('Config is loaded successfully');
|
|
||||||
} catch (error) {
|
|
||||||
expect(error.message).toMatch(/Invalid config file/);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await loadConfig(
|
|
||||||
path.join(process.cwd(), './test/config/fixtures/invalid-string.js')
|
|
||||||
);
|
|
||||||
expect.fail('Config is loaded successfully');
|
|
||||||
} catch (error) {
|
|
||||||
expect(error.message).toMatch(/Invalid config file/);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it('handles config errors properly', async () => {
|
|
||||||
try {
|
|
||||||
await loadConfig(
|
|
||||||
null,
|
|
||||||
path.join(process.cwd(), './test/config/fixtures/invalid/config')
|
|
||||||
);
|
|
||||||
expect.fail('Config is loaded successfully');
|
|
||||||
} catch (error) {
|
|
||||||
expect(error.message).toMatch(/plugins is not defined/);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it('handles MODULE_NOT_FOUND properly', async () => {
|
|
||||||
try {
|
|
||||||
await loadConfig(
|
|
||||||
path.join(process.cwd(), './test/config/fixtures/module-not-found.js')
|
|
||||||
);
|
|
||||||
expect.fail('Config is loaded successfully');
|
|
||||||
} catch (error) {
|
|
||||||
expect(error.message).toMatch(/Cannot find module 'unknown-module'/);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function getPlugin(name, plugins) {
|
function getPlugin(name, plugins) {
|
||||||
|
3
test/fixtures/config-loader/cjs/svgo.config.cjs
vendored
Normal file
3
test/fixtures/config-loader/cjs/svgo.config.cjs
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: ['cjs'],
|
||||||
|
};
|
1
test/fixtures/config-loader/invalid-runtime.mjs
vendored
Normal file
1
test/fixtures/config-loader/invalid-runtime.mjs
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default { plugins }; // eslint-disable-line no-undef
|
3
test/fixtures/config-loader/mjs/svgo.config.mjs
vendored
Normal file
3
test/fixtures/config-loader/mjs/svgo.config.mjs
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
plugins: ['mjs'],
|
||||||
|
};
|
Reference in New Issue
Block a user