Search code examples
javascriptnpmwebpackbabel-loadercraco

Problem with parsing javascript file marked as Shebang


I have a react app where I wanted to import a javascript file from a third-party library but file is mark with shebang #!/usr/bin/env node.

I found (e.g. here How to Configure Webpack with Shebang Loader to Ignore Hashbang Importing Cesium React Component into Typescript React Component) I can load file by overriding webpack configuration and adding a new loader shebang-loader (I also have tried shebang-loader2) but overriding webpack in react app is recommended only with @craco/craco so I added it to package.json and tried add loader to existing webpack-config.js.

I produced this lines of code. File craco.config.js:

const throwError = (message) =>
throwUnexpectedConfigError({
    packageName: 'craco',
    githubRepo: 'gsoft-inc/craco',
    message,
    githubIssueQuery: 'webpack',
});
module.exports = {
webpack: {
    configure: (webpackConfig, {paths}) => {

        const shebangLoader =   { test: /node_modules\/npm-groovy-lint\/lib\/groovy-lint.js$/, loader: "shebang-loader" }

        const {isAdded: shebangLoaderIsAdded1} = addAfterLoader(webpackConfig, loaderByName('url-loader'), shebangLoader);
        if (!shebangLoaderIsAdded1) throwError('failed to add shebang-loader');

        return webpackConfig;
    },
},

};

It resolves problem with shebang and it ignores #!/usr/bin/env node but now I still get error

Module parse failed: Unexpected token (14:16)
File was processed with these loaders:
 * ./node_modules/shebang2-loader/index.js
You may need an additional loader to handle the result of these loaders.
| const { getSourceLines, isErrorInLogLevelScope } = require("./utils");
| class NpmGroovyLint {
>     "use strict";
|     options = {}; // NpmGroovyLint options
|     args = []; // Command line arguments

It looks like it does not recognise "use strict" line. Can anyone put some suggestions what should be a problem ?


Solution

  • After few hours of investigation, I have finally come to a resolution. Firstly I have to say that there is no option to use NpmGroovyLint in react-like applications that run in browsers because after I resolved mentioned problem up here I figured that NpmGroovyLint uses node libraries as perf_hooks which are not available in a browser enviroment.

    But I can post code that resolves the problem described in my question. It was needed to add a plugin to babel-loader named 'plugin-proposal-class-properties'. Here is my snipped of craco config. You can use it as a recipe occasionally.

    const {addAfterLoader, getLoaders, loaderByName, removeLoaders, throwUnexpectedConfigError} = require('@craco/craco');
    const throwError = (message) =>
    throwUnexpectedConfigError({
        packageName: 'craco',
        githubRepo: 'gsoft-inc/craco',
        message,
        githubIssueQuery: 'webpack',
    });
    module.exports = {
    webpack: {
        configure: (webpackConfig, {paths}) => {
            const {hasFoundAny, matches} = getLoaders(webpackConfig, loaderByName('babel-loader'));
            if (!hasFoundAny) throwError('failed to find babel-loader');
    
            const {hasRemovedAny, removedCount} = removeLoaders(webpackConfig, loaderByName('babel-loader'));
            if (!hasRemovedAny) throwError('no babel-loader to remove');
            if (removedCount !== 2) throwError('had expected to remove 2 babel loader instances');
    
            //add plugin proposal class properties to existing babel loader
            const propClassOptions = {...matches[1].loader.options, ...{plugins: ["@babel/plugin-proposal-class-properties"]}};
            const propClassLoader = {...matches[1].loader, ...{options: propClassOptions}};
            const babelLoaderWithPropClassPlugin = {...matches[1], ...{loader: propClassLoader}};
    
            const shebangLoader = {
                test: /node_modules\/npm-groovy-lint\/lib\/groovy-lint.js$/,
                use: [{loader: 'shebang2-loader'}, {...{loader: require.resolve('babel-loader')}, ...{options: propClassOptions}}]
            }
    
            const {isAdded: babelLoaderWithPropClassIsAdded} = addAfterLoader(webpackConfig, loaderByName('url-loader'), matches[0].loader);
            if (!babelLoaderWithPropClassIsAdded) throwError('failed to add ts-loader');
    
            const {isAdded: babelLoaderIsAdded} = addAfterLoader(webpackConfig, loaderByName('babel-loader'), babelLoaderWithPropClassPlugin.loader);
            if (!babelLoaderIsAdded) throwError('failed to add back babel-loader for non-application JS');
    
            const {isAdded: shebangLoaderIsAdded1} = addAfterLoader(webpackConfig, loaderByName('url-loader'), shebangLoader);
            if (!shebangLoaderIsAdded1) throwError('failed to add shebang-loader');
    
            return webpackConfig;
        },
    },
    };