0

I want to use not a dumb mock for CSS modules but real types so I use webpack plugin typings-for-css-modules-loader

for simple css

.foo { color: red; }
.bar { color: green; }

it can generate example.css.d.ts declaration

export interface ILocals {
  'foo': string;
  'bar': string;
}
export interface IExampleCss {
  locals: ILocals;
  'foo': string;
  'bar': string;
}

declare const styles: IExampleCss

export default styles;

so in my code I able to use it like so

import css from './example.css';

const { locals } = css;

<div className={locals.foo}>Red</div>
<div className={css.bar}>Green</div>

and VSCode knows that locals.foo and css.bar members

I try to write a function which is able to get any css definitions in typescript as an input parameter

interface IStringHash {
    [key: string]: string;
}
interface ICSSLocals {
    locals: IStringHash;
    [key: string]: string | IStringHash;
}

interface ISomeInterface {
    renderComponent: (css: ICSSLocals): React.ReactNode
}

const myFunction = (css: IExampleCss) => {
    return (
        <div key="1" className={css.locals.foo}>Red</div>
        <div key="2" className={css.bar}>Green</div>
    )
}

const MyRenderer: ISomeInterface = {
    renderComponent: myFunction
                     ^^^^^^^^^^
};

I get the error:

[ts] Type 'IExampleCss' is not assignable to type 'ICSSLocals'. Type 'IExampleCss' is not assignable to type 'ICSSLocals'. Types of property 'locals' are incompatible. Type 'ILocals' is not assignable to type 'IStringHash'. Index signature is missing in type 'ILocals'.

What is wrong?

I've wrote a relevant sample in typescript playground

// Declaration with generics
interface IStringHash {
    [key: string]: string;
}
interface ICSSModule {
    locals: IStringHash;
    [key: string]: string | IStringHash;
}

interface returnObject {
    a: string,
    b: string
}

interface ISomeInterface {
    renderComponent: (css: ICSSModule) => returnObject
}

// usage with certain realisation
export interface ILocals {
  'foo': string;
  'bar': string;
}
export interface IExampleCss {
  locals: ILocals;
  'foo': string;
  'bar': string;
}

const myFunction = (css: IExampleCss): returnObject => {
    return {
        a: css.locals.foo,
        b: css.bar
    }
}

const MyRenderer: ISomeInterface = {
    renderComponent: myFunction
      ^^^^^^^
};

here is the error:

[ts] Type '(css: IExampleCss) => returnObject' is not assignable to type '(css: ICSSModule) => returnObject'. Types of parameters 'css' and 'css' are incompatible. Type 'ICSSModule' is not assignable to type 'IExampleCss'. Property ''foo'' is missing in type 'ICSSModule'. (property) ISomeInterface.renderComponent: (css: ICSSModule) => returnObject

Vadim
  • 3,474
  • 3
  • 14
  • 21
  • Can you give more info what are you doing with `myFunction` ? Taking 'any css declaration' seems not much better than just using `any` type. – Ski Sep 29 '18 at 06:15
  • @Ski I've fixed function above – Vadim Sep 29 '18 at 11:44
  • @Ski I need type suggestions an show error if there are – Vadim Sep 29 '18 at 11:56
  • I'm confused: if `myFunction` accesses `css.locals.foo` and `css.bar`, then it will only work for CSS modules that have classes named `foo` and `bar`. So why are you trying to declare `myFunction` as taking any CSS module? – Matt McCutchen Sep 30 '18 at 02:39
  • @MattMcCutchen I use some loaders for servrer & browser usage styles - so there it's not so important – Vadim Oct 01 '18 at 12:19
  • Sorry, I don't understand your comment at all. To try to clarify my question: The type `(css: ICSSModule) => returnObject` is not accurate for `myFunction` because it would allow the CSS file `.baz { color: blue; }` to be passed to `myFunction`, but `myFunction` would not work as intended: it would access class names `foo` and `bar` that do not exist. So why do you want to use this inaccurate type? It might help if you add more information to the question about the role that `myFunction` plays within your application. – Matt McCutchen Oct 02 '18 at 03:10

2 Answers2

1

Good practice is to only require what you need of the interface and not more. So looks like bar should not be in ILocals and foo should not be in IExampleCss

export interface ILocals {
  'foo': string;   
//  'bar': string; <--
}
export interface IExampleCss {
  locals: ILocals;
  // 'foo': string; <--
  'bar': string;
}

const myFunction = (css: IExampleCss): returnObject => {
    return {
        a: css.locals.foo,
        b: css.bar
    }
}

Sidenote: for simple cases you don't have to declare return type - it will be inferred. And if you need the type of function return value, you can use ReturnType<myFunction>.

Ski
  • 14,197
  • 3
  • 54
  • 64
0

try out this webpack loader: React-css-components loader

configuration example with nextjs

const nextConfig = {
  webpack: (config, { dev, isServer }) => {
    const rccLoaderRule = {
      test: /\.module\.scss$/,
      use: [
        {
          loader: 'rcc-loader',
          options: {
            enabled: !!dev && isServer,
            exportStyleOnly: true
          }
        }
      ]
    }

    config.module.rules = config.module.rules ?? []
    config.module.rules.push(rccLoaderRule)

    return config
  }
}
user3550446
  • 405
  • 3
  • 10