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

feat: support loading esm and ts files from json config #254

Merged
merged 4 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 7 additions & 38 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -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 }
Expand All @@ -18,9 +17,9 @@ const interopRequireDefault = obj =>
* @param {Object} ctx Config Context
* @param {Object} result Cosmiconfig result
*
* @return {Object} PostCSS Config
* @return {Promise<Object>} PostCSS Config
*/
function processResult(ctx, result) {
async function processResult(ctx, result) {
let file = result.filepath || ''
let projectConfig = interopRequireDefault(result.config).default || {}

Expand All @@ -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
Expand Down Expand Up @@ -72,38 +71,8 @@ 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)
}

const withLoaders = (options = {}) => {
Expand Down
11 changes: 6 additions & 5 deletions src/options.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
const req = require('./req.js')

/**
Expand All @@ -8,12 +9,12 @@ const req = require('./req.js')
*
* @param {Object} config PostCSS Config
*
* @return {Object} options PostCSS Options
* @return {Promise<Object>} 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})`
Expand All @@ -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})`
Expand All @@ -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})`
Expand Down
25 changes: 14 additions & 11 deletions src/plugins.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
const req = require('./req.js')

/**
Expand All @@ -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<Function>} 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(
Expand All @@ -37,21 +39,22 @@ function load(plugin, options, file) {
*
* @param {Object} config PostCSS Config Plugins
*
* @return {Array} plugins PostCSS Plugins
* @return {Promise<Array>} 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(([plugin, options]) => {
return options !== false ? plugin : false
brc-dd marked this conversation as resolved.
Show resolved Hide resolved
})
.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) {
Expand Down
41 changes: 35 additions & 6 deletions src/req.js
Original file line number Diff line number Diff line change
@@ -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<any>}
*/
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
17 changes: 17 additions & 0 deletions test/ts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
)
})
})
})
10 changes: 10 additions & 0 deletions test/ts/json/.postcssrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"plugins": {
"./postcss/1.js": {},
"./postcss/2.cjs": {},
"./postcss/3.mjs": {},
"./postcss/4.ts": {},
"./postcss/5.cts": {},
"./postcss/6.mts": {}
}
}
8 changes: 8 additions & 0 deletions test/ts/json/postcss/1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = function plugin() {
return {
postcssPlugin: '1'
}
}

module.exports.postcss = true
module.exports.path = __filename
8 changes: 8 additions & 0 deletions test/ts/json/postcss/2.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = function plugin() {
return {
postcssPlugin: '2'
}
}

module.exports.postcss = true
module.exports.path = __filename
9 changes: 9 additions & 0 deletions test/ts/json/postcss/3.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let plugin = () => {
return {
postcssPlugin: '3'
}
}
plugin.postcss = true
plugin.path = import.meta.url

export default plugin
9 changes: 9 additions & 0 deletions test/ts/json/postcss/4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let plugin: any = () => {
return {
postcssPlugin: '4'
}
}
plugin.postcss = true
plugin.path = __filename

export = plugin
9 changes: 9 additions & 0 deletions test/ts/json/postcss/5.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let plugin: any = () => {
return {
postcssPlugin: '5'
}
}
plugin.postcss = true
plugin.path = __filename

export = plugin
10 changes: 10 additions & 0 deletions test/ts/json/postcss/6.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
let plugin: any = () => {
return {
postcssPlugin: '6'
}
}
plugin.postcss = true
// @ts-ignore
plugin.path = import.meta.url

export default plugin