Cra typescript css modules

Webpack + CSS Modules + TS = Love

Я считаю, что CSS Модули — это монументальный проект. С его помощью можно решить одну из худших проблем CSS — коллизию имен классов. Давайте рассмотрим простой пример, чтобы было понятно, о чем идет речь.

Представим, что мы разрабатываем компонент Button. Использовать «чистый» CSS опасно, потому что есть риск, что кто-то ещё в вашем проекте (или ещё хуже — в подключенной библиотеке) использует то же имя класса:

/* Button.css */ .button < color: #f00; padding: 10px; font-size: 18px; >/* node_modules/some_lib/styles.css */ .button
// Button.tsx import < FC >from "react"; import "./Button.module.css"; import "some_lib/styles.css"; export const Button: FC = (props) => < // Какого цвета будет кнопка остаётся только гадать return className="button" />; >;

CSS Modules решают эту проблему достаточно изящно. Модули хешируют все имена классов в файле и генерируют мап типа такого:

Разработчик импортирует этот объект к себе и обращается к его ключам:

// Button.tsx import < FC >from "react"; import styles from "./Button.module.css"; import "some_lib/styles.css"; export const Button: FC = (props) => < // Теперь мы можем не беспокоиться о конфликте селекторов, так как наш селектор // на этапе билда превратится в "_2bs4j" return className= />; >;

Если вы используете Webpack, то включить поддержку CSS Modules можно практически не трогая конфиг:

// webpack.config.js module.exports = < module: < rules: [ < test: /\.module.css$/, use: [ < loader: "css-loader", options: < modules: true, // Раз — и готово >, >, ], >, ], >, >;

Проблема

Если на проекте вы используете TypeScript (я очень на это надеюсь), при подключении CSS Modules вы скорее всего столкнетесь с проблемой, так как TS не знает, что делать с CSS файлами. Компилятору неоткуда брать типизацию для них. Но проблема достаточно легко решается написанием простенькой декларации:

// global.d.ts declare module "*.css" < export default < [index: string]: string; >>

Переводя на человеческий, мы говорим, что любые файлы с расширением .css генерируют строковый мап. TS доволен. Но теперь есть другая проблема — он вообще всем доволен 😀 Я про то, что вы можете обращаться к несуществующему имени класса и TS вас об этом не предупредит:

// Button.tsx import < FC >from "react"; import styles from "./Button.module.css"; export const Button: FC = (props) => < // По ошибке пропустили "t" в слове "button" return className= />; >;

Я думаю, нет необходимости рассуждать на тему того, почему это плохо. Давайте попробуем исправить ситуацию.

Читайте также:  Как снять блокировку html

Сообщество бежит на помощь

К счастью, сообщество уже думало над этой проблемой: typescript-plugin-css-modules. Данный плагин создаёт виртуальный .d.ts для каждого CSS файла и таким образом помогает IDE находить возможные баги:

// Button.tsx import < FC >from "react"; import styles from "./Button.module.css"; export const Button: FC = (props) => < // Теперь IDE скажет, что тут мы обращаемся к несуществующему полю "buton" return className= />; >;

Вроде бы круто, мы добились чего хотели. Но если запустить TypeScript через CLI, то внезапно никакой ошибки показано не будет. Это происходит потому что TypeScript не очень поддерживает эти плагины. Более того, Webpack нас об опечатке тоже не уведомит. С его точки зрения код корректен. То есть проблему мы решили лишь частично. Риск того, что подобный код попадет на прод всё ещё велик.

Давайте попрограммируем

Перед тем как решать вышеописанную проблему, я предлагаю вспомнить кое-что из основ JS. Поговорим о ES Modules. Этот стандарт предоставляет два типа экспорта (и импорта): именованный и дефолтный. В своих проектах я предпочитаю использовать именованный экспорт, потому считаю, что он делает код более надежным. Давайте рассмотрим два примера:

// module1.ts export const foo = "foo"; export const bar = "bar"; // module2.ts import < foo, bar >from "./module1"; console.log(foo); // "foo"
// module1.ts export default < foo: "foo", bar: "bar" >; // module2.ts import module1 from "./module1"; console.log(module1.foo); // "foo"

Примеры похожие, но между ними есть существенная разница. Если вы используете именованный экспорт, инструменты вроде Webpack кинут ошибку при попытке импортировать то, что не экспортируется:

// module1.ts export const foo = "foo"; export const bar = "bar"; // module2.ts import < foo, bar, baz >from "./module1"; // ERROR: export 'baz' (imported as 'module1') was not found in './module1.ts' (possible exports: foo, bar)

Дефолтный экспорт работает иначе. Например, если вы экспортируете объект в качестве дефолта и пытаетесь получить доступ к свойству, которого не существует, бандлер не скажет, что что-то идет не так:

// module1.ts export default < foo: "foo", bar: "bar" >; // module2.ts import module1 from "./module1"; console.log(module1.baz) // undefined

То есть о том, что вы обращаетесь к несуществующему полю вы узнаете в рантайме, а не во время билда.

Возвращаемся к нашей проблеме

Итак, как я понимаю, нам нужно экспортировать мапу с классами, используя именованные экспорты вместо дефолтных. Такой апдейт поможет нам отлавливать ошибки на этапе сборки проекта. Конфиг Webpack нужно будет обновить примерно так:

// webpack.config.js module.exports = < module: < strictExportPresence: true, // Включаем строгий режим, чтобы попытка импортировать несуществующие объекты приводила к падению билда rules: [< test: /\.module.css$/, use: ["css-loader", < options: < esModule: true, // Говорим о том, что хотим использовать ES Modules modules: < namedExport: true, // Указываем, что предпочитаем именованый экспорт дефолтному >, > >] >] > >;

Теперь подобный код упадет с ошибкой при попытке его собрать:

import styles from "./Button.module.css"; // Дефолтного экспорта больше нет

Сборка такого кода тоже рухнет, так как в импорте допущена опечатка:

import < buton >from "./Button.module.css";

С новой конфигурацией Вебпака использование стилей будет выглядеть так:

import < button >from "./Button.module.css"; console.log(button); // "_2bs4j"

Или так (так даже удобнее, чтобы не плодить кучу локальных констант):

import * as styles from "./Button.module.css"; console.log(styles.button); // "_2bs4j"

Кстати, styles.buton тоже приведет к падению билда. Напомню, что при дефолтном импорте такой код был воспринят сборщиком как корректный.

Отлично. Теперь мы можем быть уверены в нашем коде. Он не попадет на прод в случае, если в нем мы обращаемся к несуществующим классам. Мне нравится это решение ещё и тем, что оно работает даже в проектах без TS.

Последний штрих

Плагин TS всё ещё думает, что стили экспортируются по-дефолту. Это не прикольно, потому что IDE не предупредит нас о том, что мы делаем что-то не так. Да, билд упадет, но мы же не хотим всякий раз смотреть в консоль, когда что-то делаем с кодом? Будет здорово уже на этапе печатания получить информацию о том, что в коде появился баг.

Согласно доке, плагин всегда генерирует типизацию дефолтного экспорта. Грустно. Но у нас есть возможность изменить это поведение. Для этого воспользуемся опцией customTemplate . В качестве значения пропишем путь к модулю с функцией, задача которой будет принимать мап с css классами и выдавать его типизацию:

// customTemplate.js module.exports = (dts, < classes >) => < return Object.keys(classes) .map((key) =>`export const $: string`) .join("\n"); >;

Всё, в .d.ts файлах больше не будет export default .

Теперь и IDE, и бандлер будут предостерегать нас от использования несуществующих CSS классов.

Конклюжен

Webpack, TypeScript и CSS Modules позволяют писать изолированные строго типизированные стили. Я надеюсь, эта статья поможет вам улучшить надежность вашей кодовой базы. Спасибо за прочтение 🙂

Источник

Saved searches

Use saved searches to filter your results more quickly

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

Minimal repro for CSS Module/TypeScript support on CRAv3

onpaws/cra-typescript-cssmodules

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Sign In Required

Please sign in to use Codespaces.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching Xcode

If nothing happens, download Xcode and try again.

Launching Visual Studio Code

Your codespace will open once ready.

There was a problem preparing your codespace, please try again.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

TypeScript + CSS Modules + React

aka error TS2614: CSS Module has no named exported members

My goal is to use TypeScript + CSS Modules in my React app.

In JavaScript, both named and default CSS modules import work OK.

import React from 'react'; import < header >from './App.module.css'; const Component = () => 
> Hello, world.
export default Component

However with TypeScript, the compiler chokes on the named import header :

import React from 'react'; import < header >from './App.module.css'; const App: React.FC = () => 
> Hello, world.
export default App;

I guess the types are provided via webpack+babel, not sure.

Failed to compile. cra-typescript-cssmodules/src/App.tsx TypeScript error in cra-typescript-cssmodules/src/App.tsx(2,10): Module '"*.module.css"' has no exported member 'header'. Did you mean to use 'import header from "*.module.css"' instead? TS2614 1 | import React from 'react'; > 2 | import < header >from './App.module.css'; | ^ 3 | 4 | const App: React.FC = () => 5 | > 

I found this issue but it’s been closed and can’t seem to find anything newer on this.

import React from 'react'; import < header >from './App.module.css'; const App: React.FC = () => 
> Hello, world.
export default App;

About

Minimal repro for CSS Module/TypeScript support on CRAv3

Источник

Adding a CSS Modules Stylesheet

Note: this feature is available with react-scripts@2.0.0 and higher.

This project supports CSS Modules alongside regular stylesheets using the [name].module.css file naming convention. CSS Modules allows the scoping of CSS by automatically creating a unique classname of the format [filename]\_[classname]\_\_[hash] .

Tip: Should you want to preprocess a stylesheet with Sass then make sure to follow the installation instructions and then change the stylesheet file extension as follows: [name].module.scss or [name].module.sass .

CSS Modules let you use the same CSS class name in different files without worrying about naming clashes. Learn more about CSS Modules here.

Button.module.css ​

.error   background-color: red; > 

another-stylesheet.css ​

Button.js ​

import React,  Component > from 'react'; import styles from './Button.module.css'; // Import css modules stylesheet as styles import './another-stylesheet.css'; // Import regular stylesheet  class Button extends Component   render()   // reference as a js object return button className=styles.error>>Error Button/button>; > > 

Result​

No clashes from other .error class names

  button class="Button_error_ax7yz">Error Buttonbutton> 

This is an optional feature. Regular stylesheets and CSS files are fully supported. CSS Modules are turned on for files ending with the .module.css extension.

Источник

Оцените статью