Search code examples
reactjsmonorepoesbuildremix.run

How to use Local modules in remix-run application in yarn workspaces


i have a yarn workspace monorepo where different packages contain reusable code. like @pkg/styles , @pkg/ui-components

all of these packages are es modules (import export statements) and are used in my non ssr application built by webpack like this.

for example

import { box } from '@pkg/styles'
import {Button} from '@pkg/ui-components'

now i need to add remix-run to the same monorepo and things work fine until i start importing these local packages. i get this error

import box from './box';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)
    at Module._compile (node:internal/modules/cjs/loader:1065:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)

if i am not wrong this is happening because esbuild expects all node_modules to be pre compiled. and simply ignores them in the transpile phase.

and i need to tell my transpiler to consider my local packages in the transpilation which is super easy to do when we are using webpack. but i am not sure how to do it in remix-run and esbuild that it uses internally. there are few issues on remix-run github but nothing seem to be helpful.


Solution

  • as of 3-feb-2022 there is no official support from remix-run for building your local packages inside a yarn workspaces monorepo. i was able to patch up esbuild config and allow local modules to be build. here is the official issue raised on remix run repo.

    what i ended up doing is to patch up the remix esbuild configuration

    Create esbuild-overrides.js in the root of the project

    add following code

    const esbuild = require('esbuild');
    const Module = require('module');
    
    function manualExternalsPlugin() {
      return {
        name: 'manual-externals-overide',
        setup(build) {
          build.onResolve(
            {
              filter: /@YourNamespaceOrPackageName/,
            },
            (args) => {
              return {
                external: false,
                namespace: args.path,
              };
            },
          );
        },
      };
    }
    
    const originalRequire = Module.prototype.require;
    const originalBuild = esbuild.build;
    
    function build(options) {
      if (options.platform === 'node') {
        const { plugins } = options;
        const externalPlugin = plugins.find(
          (plugin) => plugin.name === 'manual-externals',
        );
        const localPlugins = plugins.filter(
          (plugin) => plugin.name !== 'manual-externals',
        );
        localPlugins.push(manualExternalsPlugin());
        localPlugins.push(externalPlugin);
        return originalBuild({
          ...options,
          plugins: localPlugins,
        });
      }
      return originalBuild({
        ...options,
      });
    }
    
    Module.prototype.require = function (id) {
      // when remix requires esbuild, it will get our modified build function from above
      if (id === 'esbuild') {
        return { ...esbuild, build };
      }
      return originalRequire.apply(this, arguments);
    };
    
    

    update build scripts

    
    "scrips": {
          "dev:patched": "NODE_OPTIONS='-r ./esbuild-overrides' remix dev",
         "build:patched": "NODE_OPTIONS='-r ./esbuild-overrides' remix build"
    }