reactjstypescriptwebpackreact-app-rewired

How to load a typings file as text with Webpack and React?


For setting up a Monaco editor instance I want to add a typings file for a custom lib. When mounting the editor I call:

    public componentDidMount(): void {
        languages.typescript.javascriptDefaults.addExtraLib(
            typings,
        );
    }

The variable typings is loaded by:

// eslint-disable-next-line @typescript-eslint/no-var-requires
const typings = require("../../../modules/scripting/typings/my-runtime.d.ts");

Side note: the eslint comment is necessary or it will mark the require call as failure.

I use react-app-rewired to allow editing my webpack config without ejecting the CRA based application. Now the config-overrides.js file contains:

const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');

module.exports = function override(config, env) {
    config.plugins.push(new MonacoWebpackPlugin({
        languages: ["typescript", "javascript", "mysql", "json", "markdown"]
    }));

    config.module.rules.push(
        {
            test: /\.(html|d\.ts)$/i,
            use: [
                {
                    loader: 'raw-loader',
                    options: {
                        esModule: false,
                    },
                },
            ],
        },
    );

    return config;
}

As you can see I actually handle 2 file types here: html and d.ts. The html part works nicely. A require call to load an .html file gives me the entire html file content (I need that to load an <iframe> with my custom runtime).

The require call for the typings file, however, returns an object (probably a module, hard to say as it appears empty in the debugger in vscode).

So the question is: how to change my config to make loading typings files (.d.ts) as text possible?


Solution

  • Why do you getting {}? I think because babel-loader loader rule (which handles *.ts) from cra conflicts with your raw-loader rule (which handles *.d.ts) and webpack decides to use babel-loader there.

    I found two ways to handle this problem using react-app-rewired, please take a look at this repo.

    1) Use raw-loader in a more aggressive inline way.

    // eslint-disable-next-line import/no-webpack-loader-syntax
    const dogTypings = require('!!raw-loader?esModule=false!./dog.d.ts');
    

    Explanation: !! meaning - disable all other rules in the configuration for this file. import/no-webpack-loader-syntax restricts to use inline syntax so we need to disable it there.

    2) Remove ModuleScopePlugin from cra default config and create your typings outside of src.
    By default you can't import anything from outside of src. But with react-app-rewired - of course you can. Here is config example:

    const { resolve } = require('path');
    const { removeModuleScopePlugin } = require('customize-cra')
    
    module.exports = function override(config, env) {
        const newConfig = removeModuleScopePlugin()(config, env);
    
        newConfig.module.rules.push(
            {
                test: /\.(d\.ts)$/i,
                include: resolve(__dirname, 'typings'),
                use: [
                    {
                        loader: 'raw-loader',
                        options: {
                            esModule: false,
                        },
                    },
                ],
            },
        );
    
        return newConfig;
    }
    
    

    Note: both of these ways have a downside - they disable babel loader rule (which compiles typescript in cra apps actually) for these .d.ts files and type checking can be broken for their instances, but I did't check that. The problem with your code is the typescript compiler behavior, it removes .d.ts files from the runtime so webpack didn't emit them and I didn't found any way to prevent it.