Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading SASS Modules with TypeScript in Webpack 2

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.

like image 835
jmindel Avatar asked Jul 31 '17 09:07

jmindel


2 Answers

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" }
    ]
}
like image 99
Michael Jungo Avatar answered Sep 28 '22 09:09

Michael Jungo


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.

like image 44
frevd Avatar answered Sep 28 '22 10:09

frevd