symfony / webpack-encore

A simple but powerful API for processing & compiling assets built around Webpack
https://symfony.com/doc/current/frontend.html
MIT License
2.23k stars 197 forks source link

css module issue #626

Open lowki opened 5 years ago

lowki commented 5 years ago

Hello. I can't get css module fully working in my React app.

The problem is that it only works if I use the :local(.myClassName) notation in my .scss files

Here's a js sample

import React from 'react'
import styles from './Card.scss'

class Card extends React.Component {
  render () {
    return (
      <a className={styles.card} id={'card'}/>
   )
  }
}

export default Card

Here's a scss sample

.card {
  border: 1px solid #dadada;
  display: block;
  text-decoration: none;
  color: #000;
}

And the generated dom (no class tag)

<a id="card"></a>

Here is my webpack.config.js

var Encore = require('@symfony/webpack-encore')
var CopyWebpackPlugin = require('copy-webpack-plugin')
Encore
  .setOutputPath('web/build/')
  .setPublicPath('/build')
  .enableSingleRuntimeChunk()
  .cleanupOutputBeforeBuild()
  .enableSourceMaps(!Encore.isProduction())
  .enableVersioning(Encore.isProduction())
  .addEntry('app', './assets/js/index.js')
  .copyFiles({
    from: './assets/img',
    to: 'img/[path][name].[ext]'
  })
  .enableReactPreset()
  .enableSassLoader()
  .addPlugin(new CopyWebpackPlugin([
    { from: './assets/pwa', to: './' }
  ]))
module.exports = Encore.getWebpackConfig()
Kocal commented 5 years ago

What about using import styles from './Card.scss?module'?

lowki commented 5 years ago

What about using import styles from './Card.scss?module'?

I wasn't aware of this notation, but it dos not change anything.

nicolalamacchia commented 4 years ago

I'm trying to use CSS modules with Encore, but I got stuck here. I have a component like this:

import React from 'react'
import style from '@style/Component.module.css?module'

const Component = () => <section className={style.section}><Stuff /></section>
export default Component

While in my webpack.config.js I have:

// ...
Encore
  // ...
  .enableSassLoader(() => ({ resolveUrlLoader: false }))
  // ...
// alias to @style which is working correctly
// ...

Am I missing something?

nicolalamacchia commented 4 years ago

Eventually I opted for something like this:

import style from '!!style-loader!css-loader?modules!@style/Component.module.css'

But I would prefer to update my webpack config in order to avoid the verbose import in every file.

Lyrkan commented 4 years ago

@nicolalamacchia If you want to enable modules globally you can probably do it using:

Encore.configureCssLoader(options => {
  options.modules = true;
});

Not sure why it doesn't work when importing something like @style/Component.module.css?module though... it should be matched by the first block (for which that option is already set to true by default) here:

https://github.com/symfony/webpack-encore/blob/a4ae7cecfff098bbb48f2eec331f6d8ee047897d/lib/config-generator.js#L263-L277

nicolalamacchia commented 4 years ago

I cannot use options.modules = true since it messes with my global CSS (namely Bootstrap).

Lyrkan commented 4 years ago

@nicolalamacchia Are you using a recent version of Encore? Modules seem to work properly with the following setup:

// webpack.config.js
const Encore = require('@symfony/webpack-encore');
const path = require('path');

Encore
  .cleanupOutputBeforeBuild()
  .setOutputPath('build/')
  .setPublicPath('/build')
  .addEntry('app', './assets/js/app.js')
  .enableSingleRuntimeChunk()
  .addAliases({
    '@style': path.resolve('./assets/css')
  })
;

module.exports = Encore.getWebpackConfig();
// assets/js/app.js
import style from '@style/app.css?module';

console.log(style);
/* assets/css/app.css */
.foo {
  background: #ff0000;
}

.bar {
  color: white;
}

Generated files:

/* build/app.css */
.foo_25gLR{background:red}.bar_3FvIL{color:#fff}
// build/app.js
(window.webpackJsonp=window.webpackJsonp||[]).push([["app"],{PhA5:function(o,n,s){o.exports={foo:"foo_25gLR",bar:"bar_3FvIL"}},ng4s:function(o,n,s){"use strict";s.r(n);var p=s("PhA5"),a=s.n(p);console.log(a.a)}},[["ng4s","runtime"]]])
rmaury commented 2 years ago

Hi, I had some similar issue (?module not working with jest ) and fixed it with something like that (with Webpack Encore) :

.configureCssLoader(options => { options.modules = { auto: /.module.\w+$/i }}) And now you can create a file with '.module.scss' suffix, and you can import it in a ts file. It works with Jest.

derekqq commented 2 years ago

@rmaury I have a similar problem with jest, unfortunately adding your code does not help, can you please add the whole config wepback encore + jest here?

rmaury commented 2 years ago

@rmaury I have a similar problem with jest, unfortunately adding your code does not help, can you please add the whole config wepback encore + jest here?

Sorry for the delay

Yes of course, In webpack file I'll have something like that (simplified version) :

// webpack.config.js
var Encore = require("@symfony/webpack-encore");
var path = require("path");

if (!Encore.isRuntimeEnvironmentConfigured()) {
  Encore.configureRuntimeEnvironment(process.env.NODE_ENV || "dev");
}

Encore.setOutputPath("public/build/")
  .setPublicPath("/build")
  .addEntry("app", "./assets/js/index.tsx")
  .splitEntryChunks()
  .enableSingleRuntimeChunk()
  .cleanupOutputBeforeBuild()
  .enableBuildNotifications()
  .enableSourceMaps(!Encore.isProduction())
  .enableVersioning(Encore.isProduction())
  .configureCssLoader(options => {
    options.modules = {
      auto: /\.module\.\w+$/i
    }
  })
  .enableSassLoader()
  .enableTypeScriptLoader()
  .enableReactPreset()
  .addEntry("polyfill", "babel-polyfill");

const config = Encore.getWebpackConfig();

module.exports = config;

A custom.module.sass in a sass folder

@import 'variables'

$theme-colors: ("primary": $primary, "danger": $danger, "success": $success, "light": $gray-100, "secondary": $secondary, "info": $info)

:export
  @each $key, $value in $theme-colors
    #{unquote($key)}: $value

A custom.module.sass.ts in the same folder

export interface I_globalSass {
  primary: string;
  danger: string;
  success: string;
  light: string;
  secondary: string;
  info: string;
}

export const variables: I_globalSass;

export default variables;

And in you component you can now import like that :

import variables from "../../../../sass/custom.module.sass";

Et voilà.

Now, You should be able to import your component in Jest file without error.

Just in case, for my project this is my jest.config.js

module.exports = {
  clearMocks: true,
  globals: {
    fetch: require('node-fetch'),
  },
  moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'],
  moduleNameMapper: {
    '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js',
    '\\.(scss|sass|css|less)$': '<rootDir>/__mocks__/fileMock.js',
  },
  preset: 'ts-jest',
  setupFiles: ['<rootDir>/.jest/register-context.js'],
  setupFilesAfterEnv: ['<rootDir>/.jest/setupTests.js'],
  snapshotSerializers: ['enzyme-to-json/serializer'],
  testEnvironment: "jsdom",
  testMatch: ["<rootDir>/assets/**/*.test.(ts|tsx)"],
  moduleDirectories: [
    'node_modules'
  ],
  transform: {
    "\\.tsx?$": "ts-jest",
    "\\.jsx?$": "babel-jest", // if you have jsx tests too
  },
  globals: {
    "ts-jest": {
      "tsConfig": '<rootDir>/tsconfig.test.json'
    }
  },
  transformIgnorePatterns: [
    "[/\\\\]node_modules[/\\\\](?!lodash-es/).+\\.js$"
  ],

const { defaults } = require('jest-config');

And my tsconfig.json

{
  "compilerOptions": {
    "noImplicitAny": false,
    "target": "ES5",
    "module": "commonjs",
    "jsx": "react-jsx",
    "declaration": true,

    "outDir": "./public/build/",

    "strict": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    /* Module Resolution Options */
    "moduleResolution": "node",

    "typeRoots": [
      "./node_modules/@types"
    ],
    "types": [
      "jest",
      "node"
    ],
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true, 
    "resolveJsonModule": true
  },
  "include": ["./assets/js/**/*", "./custom.d.ts", "./assets/__tests__/**/*"],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}