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 postcss.config.ts #218

Merged
merged 7 commits into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'

module.exports = {
testEnvironment: 'node',
transform: {
'\\.[j]sx?$': 'babel-jest',
'\\.ts': './test/ts-node-transformer.js'
}
}
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"import-cwd": "^3.0.0"
},
"devDependencies": {
"@types/cssnano": "^4.0.0",
"@types/postcss-import": "^12.0.0",
"clean-publish": "^1.1.8",
"cssnano": "^4.0.0",
"jest": "^26.4.2",
Expand All @@ -34,7 +36,17 @@
"postcss-import": "^12.0.0",
"postcss-nested": "^5.0.0",
"standard": "^14.3.4",
"sugarss": "^3.0.0"
"sugarss": "^3.0.0",
"ts-node": "^9.0.0",
"typescript": "4.3.2"
},
"peerDependencies": {
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
"ts-node": {
"optional": true
}
},
"keywords": [
"postcss",
Expand Down
72 changes: 49 additions & 23 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,71 @@
// based on @types/postcss-load-config@2.0.1
// Type definitions for postcss-load-config 2.1
import Processor from 'postcss/lib/processor'
import { Plugin, ProcessOptions, Transformer } from "postcss";
import Processor from 'postcss/lib/processor';
import { Plugin, ProcessOptions, Transformer } from 'postcss';
import { Options as CosmiconfigOptions } from 'cosmiconfig';

// In the ConfigContext, these three options can be instances of the
// appropriate class, or strings. If they are strings, postcss-load-config will
// require() them and pass the instances along.
interface ProcessOptionsPreload {
declare function postcssrc(
ctx?: postcssrc.ConfigContext,
path?: string,
options?: CosmiconfigOptions
): Promise<postcssrc.Result>;

declare namespace postcssrc {
function sync(
ctx?: ConfigContext,
path?: string,
options?: CosmiconfigOptions
): Result;

// In the ConfigContext, these three options can be instances of the
// appropriate class, or strings. If they are strings, postcss-load-config will
// require() them and pass the instances along.
export interface ProcessOptionsPreload {
parser?: string | ProcessOptions['parser'];
stringifier?: string | ProcessOptions['stringifier'];
syntax?: string | ProcessOptions['syntax'];
}
}

// The remaining ProcessOptions, sans the three above.
type RemainingProcessOptions =
Pick<ProcessOptions, Exclude<keyof ProcessOptions, keyof ProcessOptionsPreload>>;
// The remaining ProcessOptions, sans the three above.
export type RemainingProcessOptions = Pick<
ProcessOptions,
Exclude<keyof ProcessOptions, keyof ProcessOptionsPreload>
>;

// Additional context options that postcss-load-config understands.
interface Context {
// Additional context options that postcss-load-config understands.
export interface Context {
cwd?: string;
env?: string;
}
}

// The full shape of the ConfigContext.
type ConfigContext = Context & ProcessOptionsPreload & RemainingProcessOptions;
// The full shape of the ConfigContext.
export type ConfigContext = Context &
ProcessOptionsPreload &
RemainingProcessOptions;

// Result of postcssrc is a Promise containing the filename plus the options
// and plugins that are ready to pass on to postcss.
type ResultPlugin = Plugin | Transformer | Processor;
// Result of postcssrc is a Promise containing the filename plus the options
// and plugins that are ready to pass on to postcss.
export type ResultPlugin = Plugin | Transformer | Processor;

interface Result {
export interface Result {
file: string;
options: ProcessOptions;
plugins: ResultPlugin[];
}
}

declare function postcssrc(ctx?: ConfigContext, path?: string, options?: CosmiconfigOptions): Promise<Result>;
export type ConfigPlugin = Transformer | Plugin | Processor;

declare namespace postcssrc {
function sync(ctx?: ConfigContext, path?: string, options?: CosmiconfigOptions): Result;
export interface Config {
parser?: string | ProcessOptions['parser'] | false;
stringifier?: string | ProcessOptions['stringifier'] | false;
syntax?: string | ProcessOptions['syntax'] | false;
map?: string | false;
from?: string;
to?: string;
plugins?: Array<ConfigPlugin | false> | Record<string, object | false>;
}

export type ConfigFn = (ctx: ConfigContext) => Config | Promise<Config>;
}

export = postcssrc;
64 changes: 59 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const config = require('cosmiconfig')
const loadOptions = require('./options.js')
const loadPlugins = require('./plugins.js')

/* istanbul ignore next */
const interopRequireDefault = (obj) => obj && obj.__esModule ? obj : { default: obj }

/**
* Process the result from cosmiconfig
*
Expand All @@ -17,7 +20,7 @@ const loadPlugins = require('./plugins.js')
*/
const processResult = (ctx, result) => {
const file = result.filepath || ''
let config = result.config || {}
let config = interopRequireDefault(result.config).default || {}

if (typeof config === 'function') {
config = config(ctx)
Expand Down Expand Up @@ -62,6 +65,57 @@ const createContext = (ctx) => {
return ctx
}

const addTypeScriptLoader = (options = {}, loader) => {
const moduleName = 'postcss'

return {
...options,
searchPlaces: [
...(options.searchPlaces || []),
'package.json',
`.${moduleName}rc`,
`.${moduleName}rc.json`,
`.${moduleName}rc.yaml`,
`.${moduleName}rc.yml`,
`.${moduleName}rc.ts`,
`.${moduleName}rc.js`,
G-Rath marked this conversation as resolved.
Show resolved Hide resolved
`.${moduleName}rc.cjs`,
`${moduleName}.config.ts`,
`${moduleName}.config.js`,
`${moduleName}.config.cjs`
],
loaders: {
...options.loaders,
'.ts': loader
}
}
}

const withTypeScriptLoader = (rcFunc) => {
return (ctx, path, options) => {
return rcFunc(ctx, path, addTypeScriptLoader(options, (configFile) => {
let registerer = { enabled () {} }

try {
// Register TypeScript compiler instance
registerer = require('ts-node').register()

return require(configFile)
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
throw new Error(
`'ts-node' is required for the TypeScript configuration files. Make sure it is installed\nError: ${err.message}`
)
}

throw err
} finally {
registerer.enabled(false)
}
}))
}
}

/**
* Load Config
*
Expand All @@ -73,7 +127,7 @@ const createContext = (ctx) => {
*
* @return {Promise} config PostCSS Config
*/
const rc = (ctx, path, options) => {
const rc = withTypeScriptLoader((ctx, path, options) => {
/**
* @type {Object} The full Config Context
*/
Expand All @@ -93,9 +147,9 @@ const rc = (ctx, path, options) => {

return processResult(ctx, result)
})
}
})

rc.sync = (ctx, path, options) => {
rc.sync = withTypeScriptLoader((ctx, path, options) => {
/**
* @type {Object} The full Config Context
*/
Expand All @@ -113,7 +167,7 @@ rc.sync = (ctx, path, options) => {
}

return processResult(ctx, result)
}
})

/**
* Autoload Config for PostCSS
Expand Down
9 changes: 9 additions & 0 deletions test/ts-node-transformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'

const { create: createTypeScriptCompiler } = require('ts-node')

const tsCompiler = createTypeScriptCompiler()

module.exports = {
process: tsCompiler.compile.bind(tsCompiler)
}
134 changes: 134 additions & 0 deletions test/ts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
'use strict'

const path = require('path')

const postcss = require('postcss')
const postcssrc = require('../src/index.js')

const { fixture, expected } = require('./utils.js')

describe('postcss.config.ts - {Object} - Load Config', () => {
const ctx = {
parser: true,
syntax: true
}

const expected = (config) => {
expect(config.options.parser).toEqual(require('sugarss'))
expect(config.options.syntax).toEqual(require('sugarss'))
expect(config.options.map).toEqual(false)
expect(config.options.from).toEqual('./test/ts/object/fixtures/index.css')
expect(config.options.to).toEqual('./test/ts/object/expect/index.css')

expect(config.plugins.length).toEqual(2)
expect(typeof config.plugins[0]).toBe('function')
expect(typeof config.plugins[1]).toBe('function')

expect(config.file)
.toEqual(path.resolve('test/ts/object', 'postcss.config.ts'))
}

test('Async', () => {
return postcssrc(ctx, 'test/ts/object').then(expected)
})

test('Sync', () => {
const config = postcssrc.sync(ctx, 'test/ts/object')

expected(config)
})
})

test('postcss.config.ts - {Object} - Process CSS', () => {
const ctx = {
parser: false,
syntax: false
}

return postcssrc(ctx, 'test/ts/object').then((config) => {
return postcss(config.plugins)
.process(fixture('ts/object', 'index.css'), config.options)
.then((result) => {
expect(result.css).toEqual(expected('ts/object', 'index.css'))
})
})
})

test('postcss.config.ts - {Object} - Process SSS', () => {
const ctx = {
from: './test/ts/object/fixtures/index.sss',
parser: true,
syntax: false
}

return postcssrc(ctx, 'test/ts/object').then((config) => {
return postcss(config.plugins)
.process(fixture('ts/object', 'index.sss'), config.options)
.then((result) => {
expect(result.css).toEqual(expected('ts/object', 'index.sss'))
})
})
})

describe('postcss.config.ts - {Array} - Load Config', () => {
const ctx = {
parser: true,
syntax: true
}

const expected = (config) => {
expect(config.options.parser).toEqual(require('sugarss'))
expect(config.options.syntax).toEqual(require('sugarss'))
expect(config.options.map).toEqual(false)
expect(config.options.from).toEqual('./test/ts/array/fixtures/index.css')
expect(config.options.to).toEqual('./test/ts/array/expect/index.css')

expect(config.plugins.length).toEqual(2)
expect(typeof config.plugins[0]).toBe('function')
expect(typeof config.plugins[1]).toBe('object')

expect(config.file)
.toEqual(path.resolve('test/ts/array', 'postcss.config.ts'))
}

test('Async', () => {
return postcssrc(ctx, 'test/ts/array').then(expected)
})

test('Sync', () => {
const config = postcssrc.sync(ctx, 'test/ts/array')

expected(config)
})
})

test('postcss.config.ts - {Array} - Process CSS', () => {
const ctx = {
parser: false,
syntax: false
}

return postcssrc(ctx, 'test/ts/array').then((config) => {
return postcss(config.plugins)
.process(fixture('ts/array', 'index.css'), config.options)
.then((result) => {
expect(result.css).toEqual(expected('ts/array', 'index.css'))
})
})
})

test('postcss.config.ts - {Array} - Process SSS', () => {
const ctx = {
from: './test/ts/array/fixtures/index.sss',
parser: true,
syntax: false
}

return postcssrc(ctx, 'test/ts/array').then((config) => {
return postcss(config.plugins)
.process(fixture('ts/array', 'index.sss'), config.options)
.then((result) => {
expect(result.css).toEqual(expected('ts/array', 'index.sss'))
})
})
})
7 changes: 7 additions & 0 deletions test/ts/array/expect/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.import {
color: goldenrod;
}

.test {
color: cyan;
}
Loading