Loading SASS Modules with TypeScript in Webpack 2

10,249

Solution 1

The typings-for-css-modules-loader is a drop-in replacement for css-loader (technically it uses css-loader under the hood) and that means it takes CSS and transforms it to JavaScript. You're also using the css-loader, and that fails because it receives JavaScript, but expected CSS (as JavaScript is not valid CSS, it fails to parse).

Additionally, you are not using CSS modules, because you're not setting the modules: true option on the CSS loader (or typings-for-css-modules-loader, which passes it on to css-loader).

Your .scss rule should be:

{
    test: /\.scss$/,
    include: [
        path.resolve(__dirname, "src/raw")
    ],
    use: [
        { loader: "style-loader" },
        {
            loader: "typings-for-css-modules-loader",
            options: {
                namedexport: true,
                camelcase: true,
                modules: true
            }
        },
        { loader: "sass-loader" }
    ]
}

Solution 2

Here is a little extended version (since the above did somehow not work for me), using another package (css-modules-typescript-loader) derived from the stale typings-for-css-modules-loader.

In case anybody runs into the same problems - this is a configuration that works for me:

TypeScript + WebPack + Sass

webpack.config.js

module.exports = {
  //mode: "production", 
    mode: "development", devtool: "inline-source-map",

    entry: [ "./src/app.tsx"/*main*/ ], 
    output: {
        filename: "./bundle.js"  // in /dist
    },
    resolve: {
        // Add `.ts` and `.tsx` as a resolvable extension.
        extensions: [".ts", ".tsx", ".js", ".css", ".scss"]
    },
    module: {
        rules: [

            { test: /\.tsx?$/, loader: "ts-loader" }, 

            { test: /\.scss$/, use: [ 
                { loader: "style-loader" },  // to inject the result into the DOM as a style block
                { loader: "css-modules-typescript-loader"},  // to generate a .d.ts module next to the .scss file (also requires a declaration.d.ts with "declare modules '*.scss';" in it to tell TypeScript that "import styles from './styles.scss';" means to load the module "./styles.scss.d.td")
                { loader: "css-loader", options: { modules: true } },  // to convert the resulting CSS to Javascript to be bundled (modules:true to rename CSS classes in output to cryptic identifiers, except if wrapped in a :global(...) pseudo class)
                { loader: "sass-loader" },  // to convert SASS to CSS
                // NOTE: The first build after adding/removing/renaming CSS classes fails, since the newly generated .d.ts typescript module is picked up only later
            ] }, 

        ]
    }
}; 

Also put a declarations.d.ts in your project:

// We need to tell TypeScript that when we write "import styles from './styles.scss' we mean to load a module (to look for a './styles.scss.d.ts'). 
declare module '*.scss'; 

And you will need all these in your package.json's dev-dependencies:

  "devDependencies": {
    "@types/node-sass": "^4.11.0",
    "node-sass": "^4.12.0",
    "css-loader": "^1.0.0",
    "css-modules-typescript-loader": "^2.0.1",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "ts-loader": "^5.3.3",
    "typescript": "^3.4.4",
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.0"
  }

Then you should get a mystyle.d.ts next to your mystyle.scss containing the CSS classes you defined, which you can import as a Typescript module and use like this:

import * as styles from './mystyles.scss'; 

const foo = <div className={styles.myClass}>FOO</div>; 

The CSS will automatically be loaded (injected as a style element into the DOM) and contain cryptic identifiers instead of your CSS classes in the .scss, to isolate your styles in the page (unless you use :global(.a-global-class) { ... }).

Note that the first compile will fail whenever you add CSS classes or remove them or rename them, since the imported mystyles.d.ts is the old version and not the new version just generated during compilation. Just compile again.

Enjoy.

Share:
10,249
jmindel
Author by

jmindel

I'm a student at UC Berkeley studying Electrical Engineering and Computer Science with an emphasis on New Media, and have been developing in JavaScript, C#, and SQL for the past 8 years. I focus mainly on web development (currently using HTML/SCSS/JS/TS/React/Redux, Node.js, and NoSQL with an emphasis on front-end design), and have recently been learning more about computer graphics, AI/ML, robotics, and AR/VR. Technology has always been one of my passions and hobbies, and I'm always excited to explore. Outside of the STEM world, I'm an avid reader, and am fascinated by literary analysis, philosophy, and psychology. Lately, I've also taken an interest in design thinking and the principles of self-efficacy.

Updated on June 12, 2022

Comments

  • jmindel
    jmindel almost 2 years

    I have a simple project set up using TypeScript, ReactJS, and SASS, and would like to bundle it all using Webpack. There's plenty of documentation on how to achieve this with JavaScript and regular old CSS. However, I can't find any documentation that combines the loaders I need and uses Webpack 2 syntax (rather than the original Webpack syntax for loaders). Thus, I'm unsure of how to create the correct configuration.

    You can find my webpack.config.js file here. How would I modify the configuration so that TypeScript accepts my SCSS modules, and so that Webpack properly bundles my SCSS with my TypeScript?

    This may also be helpful: when I run Webpack at the moment, I get the following error:

    ERROR in ./node_modules/css-loader!./node_modules/typings-for-css-modules-loader/lib?{"namedExport":true,"camelCase":true}!./node_modules/sass-loader/lib/loader.js!./src/raw/components/styles.scss
    Module build failed: Unknown word (1:1)
    
    > 1 | exports = module.exports = require("../../../node_modules/css-loader/lib/css-base.js")(undefined);
        | ^
      2 | // imports
      3 |
      4 |
    
     @ ./src/raw/components/styles.scss 4:14-206
     @ ./src/raw/components/greetings/greetings.tsx
     @ ./src/raw/index.tsx
     @ multi ./src/raw/index.tsx
    
    ERROR in [at-loader] ./src/raw/components/greetings/greetings.tsx:3:25
        TS2307: Cannot find module '../styles.scss'.
    

    Note that ./src/raw/index.tsx is the entry point of my application, ./src/raw/components/greetings/greeting.tsx is my only React component, and ./src/raw/components/styles.scss is my only SCSS file.