Search code examples
reactjsangularwebpackloader

React component inside Angular app: import scss files


I'm trying to understand how to customize the Angular CLI build process to be able to have React components properly built when they import scss files. I'm currently able to build an Angular project where some React components are used. There's a lot of material out there that explains how to achieve it (it's pretty simple actually). I still haven't found an article that points out how to have scss file imports properly resolved in .tsx files though.

For example, if I have this simple React component:

import * as styles from "./styles.scss";
const ReactComponentWithBrokenStyles = () => (
    <div className={styles.root}>This div won't have a class</div>
);
export default ReactComponentWithBrokenStyles;

how can I edit the Angular CLI build process to properly add the loaders that would transform the .scss file?

I've found some resources pointing me to using a custom builder instead of the standard builder, which would allow me to provide an extra webpack config file. What I don't understand is how to write the loaders in that file to solve my problem. The default Angular CLI webpack config already has a loader for .scss files, which need to be transformed and referenced by Angular components, and I don't want to override that... so how can I add a loader just for .scss files that are imported in .tsx files? Is there a way to tell webpack to match files based on siblings in addition to the usual regex?

I guess this is exquisitely a webpack question after all.

Note: using Angular CLI v7 and webpack 4.


Solution

  • The real problem was: I need to distinguish Angular .scss files from React .scss files.

    The solution I came up with is centered around using oneOf in Webpack's rule configuration for .scss files, which allows discriminating between files that pass the same test (/\.scss|\.sass$/) in a more fine grained way.

    So I've used @angular-builders/custom-webpack as suggested by the link I posted in the question, and I've created this customizer for the standard Angular CLI Webpack configuration:

    const reactScssFilesLoaders = [
        "style-loader",
        {
            loader: "css-loader",
            options: {
                discardDuplicates: true,
                importLoaders: 1,
                modules: true,
                localIdentName: "[name]__[local]___[hash:base64:5]",
            },
        },
        "postcss-loader",
        "sass-loader"
    ];
    
    /**
     * Modifies the default Webpack config so that our React components are able to
     * import styles from scss files as JS objects
     * @param {*} defaultAngularConfig The default Webpack config that comes with the NG Cli
     * @param {*} buildOptions The build options (not needed here)
     * @returns The adjusted Webpack config
     */
    function customizeWebpackConfig(defaultAngularConfig, buildOptions) {
        const builtInNgScssFilesRule = defaultAngularConfig.module.rules.find(r => r.test.source.includes("scss"));
        if (!builtInNgScssFilesRule) {
            throw new Error("WTF?");
        }
    
        const angularScssFilesLoaders = builtInNgScssFilesRule.use;
        // We only want one top level rule for .scss files, so we need to further test
        // We want to leave normal Angular style files to the default loaders, and
        // we just want to turn styles into JS code when imported by tsx components
        builtInNgScssFilesRule.oneOf = [{
            issuer: /\.(tsx)$/,
            use: reactScssFilesLoaders
        }, {
            test: /\.(component)\.(scss)$/,
            use: angularScssFilesLoaders
        }]
        delete builtInNgScssFilesRule.exclude;
        delete builtInNgScssFilesRule.use;
        return defaultAngularConfig;
    }
    
    module.exports = customizeWebpackConfig;
    

    Works like a charm.