diff --git a/src/index.js b/src/index.js index b514565..796343c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,12 @@ +// @ts-check const { resolve } = require('node:path') -const { pathToFileURL } = require('node:url') const config = require('lilconfig') const yaml = require('yaml') const loadOptions = require('./options.js') const loadPlugins = require('./plugins.js') - -const TS_EXT_RE = /\.(c|m)?ts$/ +const req = require('./req.js') const interopRequireDefault = obj => obj && obj.__esModule ? obj : { default: obj } @@ -18,9 +17,9 @@ const interopRequireDefault = obj => * @param {Object} ctx Config Context * @param {Object} result Cosmiconfig result * - * @return {Object} PostCSS Config + * @return {Promise} PostCSS Config */ -function processResult(ctx, result) { +async function processResult(ctx, result) { let file = result.filepath || '' let projectConfig = interopRequireDefault(result.config).default || {} @@ -36,8 +35,8 @@ function processResult(ctx, result) { let res = { file, - options: loadOptions(projectConfig, file), - plugins: loadPlugins(projectConfig, file) + options: await loadOptions(projectConfig, file), + plugins: await loadPlugins(projectConfig, file) } delete projectConfig.plugins return res @@ -72,40 +71,11 @@ function createContext(ctx) { return ctx } -/** @type {import('jiti').JITI | null} */ -let jiti = null - async function loader(filepath) { - try { - let module = await import(pathToFileURL(filepath).href) - return module.default - } catch (err) { - /* c8 ignore start */ - if (!TS_EXT_RE.test(filepath)) { - throw err - } - if (!jiti) { - try { - jiti = (await import('jiti')).default(__filename, { - interopDefault: true - }) - } catch (jitiErr) { - if ( - jitiErr.code === 'ERR_MODULE_NOT_FOUND' && - jitiErr.message.includes("Cannot find package 'jiti'") - ) { - throw new Error( - `'jiti' is required for the TypeScript configuration files. Make sure it is installed\nError: ${jitiErr.message}` - ) - } - throw jitiErr - } - /* c8 ignore stop */ - } - return jiti(filepath) - } + return req(filepath) } +/** @return {import('lilconfig').Options} */ const withLoaders = (options = {}) => { let moduleName = 'postcss' @@ -119,8 +89,8 @@ const withLoaders = (options = {}) => { '.mjs': loader, '.mts': loader, '.ts': loader, - '.yaml': (filepath, content) => yaml.parse(content), - '.yml': (filepath, content) => yaml.parse(content) + '.yaml': (_, content) => yaml.parse(content), + '.yml': (_, content) => yaml.parse(content) }, searchPlaces: [ ...(options.searchPlaces || []), diff --git a/src/options.js b/src/options.js index 48c13c5..14b03ce 100644 --- a/src/options.js +++ b/src/options.js @@ -1,3 +1,4 @@ +// @ts-check const req = require('./req.js') /** @@ -8,12 +9,12 @@ const req = require('./req.js') * * @param {Object} config PostCSS Config * - * @return {Object} options PostCSS Options + * @return {Promise} options PostCSS Options */ -function options(config, file) { +async function options(config, file) { if (config.parser && typeof config.parser === 'string') { try { - config.parser = req(config.parser, file) + config.parser = await req(config.parser, file) } catch (err) { throw new Error( `Loading PostCSS Parser failed: ${err.message}\n\n(@${file})` @@ -23,7 +24,7 @@ function options(config, file) { if (config.syntax && typeof config.syntax === 'string') { try { - config.syntax = req(config.syntax, file) + config.syntax = await req(config.syntax, file) } catch (err) { throw new Error( `Loading PostCSS Syntax failed: ${err.message}\n\n(@${file})` @@ -33,7 +34,7 @@ function options(config, file) { if (config.stringifier && typeof config.stringifier === 'string') { try { - config.stringifier = req(config.stringifier, file) + config.stringifier = await req(config.stringifier, file) } catch (err) { throw new Error( `Loading PostCSS Stringifier failed: ${err.message}\n\n(@${file})` diff --git a/src/plugins.js b/src/plugins.js index 8dacee9..2ea4506 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -1,3 +1,4 @@ +// @ts-check const req = require('./req.js') /** @@ -9,18 +10,19 @@ const req = require('./req.js') * @param {String} plugin PostCSS Plugin Name * @param {Object} options PostCSS Plugin Options * - * @return {Function} PostCSS Plugin + * @return {Promise} PostCSS Plugin */ -function load(plugin, options, file) { +async function load(plugin, options, file) { try { if ( options === null || options === undefined || Object.keys(options).length === 0 ) { - return req(plugin, file) + return await req(plugin, file) } else { - return req(plugin, file)(options) + return (await req(plugin, file))(options) + /* c8 ignore next */ } } catch (err) { throw new Error( @@ -37,21 +39,22 @@ function load(plugin, options, file) { * * @param {Object} config PostCSS Config Plugins * - * @return {Array} plugins PostCSS Plugins + * @return {Promise} plugins PostCSS Plugins */ -function plugins(config, file) { +async function plugins(config, file) { let list = [] if (Array.isArray(config.plugins)) { list = config.plugins.filter(Boolean) } else { - list = Object.keys(config.plugins) - .filter(plugin => { - return config.plugins[plugin] !== false ? plugin : '' + list = Object.entries(config.plugins) + .filter(([, options]) => { + return options !== false }) - .map(plugin => { - return load(plugin, config.plugins[plugin], file) + .map(([plugin, options]) => { + return load(plugin, options, file) }) + list = await Promise.all(list) } if (list.length && list.length > 0) { diff --git a/src/req.js b/src/req.js index ddebb85..1a1cf9e 100644 --- a/src/req.js +++ b/src/req.js @@ -1,10 +1,39 @@ -// eslint-disable-next-line n/no-deprecated-api -const { createRequire, createRequireFromPath } = require('node:module') +// @ts-check +const { createRequire } = require('node:module') -function req (name, rootFile) { - let create = createRequire || createRequireFromPath - let require = create(rootFile) - return require(name) +const TS_EXT_RE = /\.(c|m)?ts$/ + +/** @type {import('jiti').default | null} */ +let jiti = null + +/** + * @param {string} name + * @param {string} rootFile + * @returns {Promise} + */ +async function req(name, rootFile = __filename) { + let __require = createRequire(rootFile) + let url = __require.resolve(name) + + try { + return (await import(url)).default + } catch (err) { + if (!TS_EXT_RE.test(url)) { + /* c8 ignore start */ + throw err + } + if (!jiti) { + try { + jiti = (await import('jiti')).default + } catch (jitiErr) { + throw new Error( + `'jiti' is required for the TypeScript configuration files. Make sure it is installed\nError: ${jitiErr.message}` + ) + } + /* c8 ignore stop */ + } + return jiti(rootFile, { interopDefault: true })(name) + } } module.exports = req diff --git a/test/ts.test.js b/test/ts.test.js index 0d8bceb..48a7d90 100644 --- a/test/ts.test.js +++ b/test/ts.test.js @@ -150,3 +150,20 @@ describe('Array', () => { equal(config.file, resolve(expectedPath)) } }) + +describe('JSON', () => { + test('Load Config - postcss.config.mts', async () => { + let { plugins } = await postcssrc(ctx, 'test/ts/json') + equal(plugins.length, 6) + plugins.forEach((plugin, index) => { + equal( + // normalize for simplicity + plugin.path + .replace(/^.*?:\/\//, '') + .replace(/\\/g, '/') + .replace(/\.[^.]+$/, ''), + resolve(process.cwd(), `test/ts/json/postcss/${index + 1}`) + ) + }) + }) +}) diff --git a/test/ts/json/.postcssrc.json b/test/ts/json/.postcssrc.json new file mode 100644 index 0000000..323a244 --- /dev/null +++ b/test/ts/json/.postcssrc.json @@ -0,0 +1,10 @@ +{ + "plugins": { + "./postcss/1.js": {}, + "./postcss/2.cjs": {}, + "./postcss/3.mjs": {}, + "./postcss/4.ts": {}, + "./postcss/5.cts": {}, + "./postcss/6.mts": {} + } +} diff --git a/test/ts/json/postcss/1.js b/test/ts/json/postcss/1.js new file mode 100644 index 0000000..0cba84b --- /dev/null +++ b/test/ts/json/postcss/1.js @@ -0,0 +1,8 @@ +module.exports = function plugin() { + return { + postcssPlugin: '1' + } +} + +module.exports.postcss = true +module.exports.path = __filename diff --git a/test/ts/json/postcss/2.cjs b/test/ts/json/postcss/2.cjs new file mode 100644 index 0000000..32d9e06 --- /dev/null +++ b/test/ts/json/postcss/2.cjs @@ -0,0 +1,8 @@ +module.exports = function plugin() { + return { + postcssPlugin: '2' + } +} + +module.exports.postcss = true +module.exports.path = __filename diff --git a/test/ts/json/postcss/3.mjs b/test/ts/json/postcss/3.mjs new file mode 100644 index 0000000..1f8f71a --- /dev/null +++ b/test/ts/json/postcss/3.mjs @@ -0,0 +1,9 @@ +let plugin = () => { + return { + postcssPlugin: '3' + } +} +plugin.postcss = true +plugin.path = import.meta.url + +export default plugin diff --git a/test/ts/json/postcss/4.ts b/test/ts/json/postcss/4.ts new file mode 100644 index 0000000..3e79935 --- /dev/null +++ b/test/ts/json/postcss/4.ts @@ -0,0 +1,9 @@ +let plugin: any = () => { + return { + postcssPlugin: '4' + } +} +plugin.postcss = true +plugin.path = __filename + +export = plugin diff --git a/test/ts/json/postcss/5.cts b/test/ts/json/postcss/5.cts new file mode 100644 index 0000000..bef70b7 --- /dev/null +++ b/test/ts/json/postcss/5.cts @@ -0,0 +1,9 @@ +let plugin: any = () => { + return { + postcssPlugin: '5' + } +} +plugin.postcss = true +plugin.path = __filename + +export = plugin diff --git a/test/ts/json/postcss/6.mts b/test/ts/json/postcss/6.mts new file mode 100644 index 0000000..e965886 --- /dev/null +++ b/test/ts/json/postcss/6.mts @@ -0,0 +1,10 @@ +let plugin: any = () => { + return { + postcssPlugin: '6' + } +} +plugin.postcss = true +// @ts-ignore +plugin.path = import.meta.url + +export default plugin