Search code examples
javascriptnode.jsrollupjs

How to get rollup to deal with node based libraries in the browser


I've been trying to use some react layout libraries. Most of them seem to rely on making random ids using uuid or crypto.

When I try to run the result of building with them I get this error in the browser

Uncaught TypeError: Failed to resolve module specifier "crypto". Relative references must start with either "/", "./", or "../".

I've seen various possible solutions like this one and this one but they're not solving the issue for me so I must have a different problem that needs a different solution.

I've spent about 16hrs trying various things. I even just gave up for a week and made do with libraries that didn't have these dependencies. But, I finally ran into a need to use a library that has this issue and I'm back to spending hours trying to figure out what I'm doing wrong.

Here's a repo

I know it looks a little large but it is an MCVE. Further, I just wanted to make sure the question is about this specific example so that any solutions fit.

If you get/make those files and then

npm install
npm start

If it works there should be no errors in the JS console but I removed the CSS so it will likely not show anything. For me though it gets the error mentioned above.

I can't be the only person running into this issue though. I see people asking and not getting answers as well as comments seeking solutions.

package.json

{
  "name": "delme-rollup",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "module": "dist/index.js",
  "scripts": {
    "start": "rollup -c rollup.config.js -w"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^23.0.0",
    "@rollup/plugin-node-resolve": "^15.0.0",
    "@rollup/plugin-replace": "^5.0.0",
    "@rollup/plugin-typescript": "^9.0.1",
    "@types/react": "^18.0.24",
    "@types/react-dom": "^18.0.8",
    "postcss": "^8.4.18",
    "rollup": "^3.2.3",
    "rollup-plugin-dts": "^5.0.0",
    "rollup-plugin-livereload": "^2.0.5",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-serve": "^2.0.1",
    "tslib": "^2.4.0",
    "typescript": "^4.8.4"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "flexlayout-react": "^0.7.5"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

rollup.config.js

import commonjs from '@rollup/plugin-commonjs';
import replace from '@rollup/plugin-replace';
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import fs from 'fs';
import postcss from 'rollup-plugin-postcss';
import livereload from 'rollup-plugin-livereload';
import serve from 'rollup-plugin-serve';

const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));

export default [
    {
        input: 'src/index.tsx',
        output: [
            {
                file: packageJson.module,
                format: 'esm',
                sourcemap: true,
            },
        ],
        // This is a hack to workaround a warning that should be fixed
        onwarn(warning, warn) {
            if (warning.code === 'THIS_IS_UNDEFINED') {
                return;
            }
            warn(warning);
        },
        plugins: [
            resolve({
                preferBuiltins: true,
                browser: true,
            }),
            replace({
                preventAssignment: true,
                values: {
                    'process.env.NODE_ENV': JSON.stringify('development'),
                },
            }),
            commonjs({
                include: /node_modules/,
                requireReturnsDefault: 'auto', // <---- this solves default issue
            }),
            typescript({ tsconfig: './tsconfig.json', sourceRoot: '/src/ui' }),
            postcss({
                minimize: true,
                sourceMap: true,
                extract: 'styles.css',
            }),
            serve({
                open: true,
                openPage: '/',
                verbose: true,
                contentBase: [''],
                host: 'localhost',
                port: 3000,
            }),
            livereload({ watch: 'dist' }),
        ],
    },
];

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "esnext",
    "jsx": "react",
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "noErrorTruncation": true,
    "declarationDir": "types",
    "emitDeclarationOnly": true,
    "typeRoots": [
      "./node_modules/@types"
    ],
  },
  "exclude": [
    "node_modules",
    "**/*stories",
    "**/*types"
  ]
}

index.html

<!DOCTYPE html>
<html>
  <body>
    <div id="container"></div>
    <script type="module" src="dist/index.js"></script>
  </body>
</html>

src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import * as FlexLayout from 'flexlayout-react';
//import 'light.css';

const json = {
    global: {},
    layout: {
        type: 'row',
        weight: 100,
        children: [
            {
                type: 'tabset',
                weight: 50,
                selected: 0,
                children: [
                    {
                        type: 'tab',
                        name: 'One',
                        component: 'panel',
                    },
                ],
            },
        ],
    },
};

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = { model: FlexLayout.Model.fromJson(json) };
    }

    factory = node => {
        const component = node.getComponent();
        if (component === 'panel') {
            return <div className="tab_content">{node.getName()}</div>;
        }
    };

    render() {
        return (
            <div className="spector2">
                <div className="spector2-debugger">
                    <FlexLayout.Layout model={this.state.model} factory={this.factory} />;
                </div>
            </div>
        );
    }
}

const elem = document.createElement('div');
document.body.appendChild(elem);
const root = ReactDOM.createRoot(elem);
root.render(<App />);

Solution

  • You should first solve problems in your terminal. Which are like compile errors. Then ones in a browser, which are more like runtime.

    For example for me terminal displays:

    enter image description here

    Also created PR. Which solves type issues, but still has implementation errors.