Search code examples
node.jswebpackwebpack-loader

Webpack loader not being used when requiring file being processed by custom loader


I'm trying to write a custom Webpack loader that reads a JavaScript file and exports SASS variables, similar to js-to-sass-var-loader but with some added functionality.

/webpack-loaders/js-to-sass-loader.js

import _ from 'lodash';
import path from 'path';

const importRegex = /@import\s+'([^']+\.js)'\s*;/ig;

/* 
    functions for transforming...
*/

export default function (content) {
    let self = this;

    // search for "@import '*.js';" and replace that text with
    // the transformed output of the specified JavaScript file
    return content.replace(importRegex, (match, relativePath) => {
        if (match) {
            let modulePath = path.join(self.context, relativePath);
            self.addDependency(modulePath);
            let data = require(modulePath).default;
            return transform(data);
        }
    });
}

This JavaScript file loads a .json config file, does a little processing and spits out an object.

/client/sass/sassConfig.js

import config from '../../config.json';

console.log('Generating SASS variables.');
let sass = {
    // some special values here
    };

for (let key of Object.keys(config.style)) {
    sass[key] = config.style[key];
}

sass.debug = (process.env.NODE_ENV || 'production').trim() === 'development';

export default sass;

The json file includes comments, so I'm stripping them away with another trivial loader. This simply parses the json file using json5, then stringifies the output and passes it on.

/webpack-loaders/remove-json-comments-loader.js

import json5 from 'json5';

export default function (source) {
    return JSON.stringify(json5.parse(source));
}

My webpack.config.babel.js includes rules for sass and json files to use these two loaders.

/webpack.config.babel.js

// ... imports ...

const webpackConfig = {
    entry: {
        app: './client/script.js',
    },

    output: {
        filename: 'script.js',
        path: path.resolve(__dirname, 'public')
    },

    resolveLoader: {
        alias: {
            'js-to-sass-loader': path.resolve(__dirname, 'webpack-loaders/js-to-sass-loader'),
            'remove-json-comments-loader': path.resolve(__dirname, 'webpack-loaders/remove-json-comments-loader')
        }
    },

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
            },
            {
                test: /\.json$/,
                exclude: /node_modules/,
                loader: 'remove-json-comments-loader',
            },
            {
                test: /\.s[ca]ss$/,
                exclude: /node_modules/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: [
                        { loader: 'css-loader' },
                        { loader: 'sass-loader' },
                        { loader: 'js-to-sass-loader' },
                    ]
                }),
            },
            // ...
        ],
    },

    plugins: [
        // ...
        new ExtractTextPlugin({ filename: 'style.css' }),
        // ...
    ],

    // ...
};

module.exports = webpackConfig;

In a few of my JavaScript files, I can import Config from './path/to/config.json' with no problem. It correctly uses the remove-json-comments-loader. But when my js-to-sass-loader attempts to require sassConfig.js, Webpack doesn't use the loader for parsing config.json. It attempts to load it as a regular json file, which causes it to fail due to the comments. I've tried using import config from '!!remove-json-comments-loader!../../config.json'; but webpack says it cannot find the file.

I'm super new to writing webpack loaders, so I'm sure it's something simple. Help? Thanks!

Here's a link to the github repo: https://github.com/dfoverdx/PokeStreamer-Tools/tree/9b4c7315d5dc6b30c5972a0b8678489598311bf0

To reproduce the issue, open up /node/client/sass/sassConfig.js, and change the first four lines to:

// import json5 from 'json5';
// import fs from 'fs';
// const config = json5.parse(fs.readFileSync('config.json'));
import config from '../../config.json';

That is, comment the first 3 lines and uncomment the 4th.

From /node run npm run build to produce the error.


Solution

  • So as discussed earlier, below are my observations

    • You are trying to create a loader
    • Your loader requires files directly using the require native nodejs method
    • Your js-to-sass-loader requires the sassConfig.js which then requires the config.json file directly
    • The config.json is the native file which has comments and hence can't be required as such

    So the key takeout is that you are mixing webpack which is basically about bundling stuff into bundles and then requiring the bundled code. That is why the json work in rest of the code and not in the sassConfig.js.

    A loader ideally shouldn't require processed json file itself. The processed json file are suppose to land in the bundle and not as a part of your loader's internal code. So its better to just use the workaround that you are using to load the config.json using the json5 module, this make sure your loader is not looking for modified code itself