yarn: export as normal ES6 module

249 views Asked by At

Take a trivial example like this:

const myFunc = () => console.log("foo");
export const funcs = {myFunc};

I want to be able to import this with the browser's import() function and be able to access mod.funcs.

If I import that source code directly in the browser, everything works. But, how do I get yarn to build a version which is still importable?

If I try to build that version, then the compiled version is just (()=>{"use strict"})(); without the function at all.

I've also tried:

module.exports = {myFunc};

Which seems to produce some code which includes the function, but I'm still unable to access it after importing it in the browser.

Building with yarn, which is calling react-scripts build.


Full application I'm trying to build:

The application needs to be able to import an arbitrary user-defined JS module to provide customisation. Currently, this is done with:

    import(/* webpackIgnore: true */ js_module).then((mod) => {
        Object.assign(COMPONENTS, mod.components);
        Object.assign(USER_FUNCS, mod.funcs);
    });

https://github.com/aio-libs/aiohttp-admin/blob/master/admin-js/src/App.js#L77

This should work with vanilla JS modules (e.g. to do add simple functions that can be used for validators) without build tools. This currently works fine, for example this module loads without a problem, adding a new function to the application: https://github.com/aio-libs/aiohttp-admin/blob/master/examples/validators.py#L15-L21

It also needs to work with a React module so that custom react-admin components can be added to the components object. This is the part I'm struggling to build. If I build and export a component as a module, when I load it into the application I get errors from React's useContext(), which I presume is because the context in the user module is not the same context in the application.

For testing, I mostly copied the code for the CloneButton (and will modify it once something is actually working): https://github.com/marmelab/react-admin/blob/61555343ac1d325effc75f21591c422c7825e918/packages/ra-ui-materialui/src/button/CloneButton.tsx It's the useResourceContext(), useRecordContext() etc. that fail inside React's useContext().

3

There are 3 answers

0
Sam Bull On BEST ANSWER

The full solution to my problems looks like this:

First, in the main application save a reference to the library we need somewhere accessible:

import React from 'react';
window.React = React;

Now in our module where we create some custom components, create shims for each library, such as:

// shim/react/index.js
module.exports = window.React;

Then in package.json we can update the dependencies and define resolutions, to both point to the shims:

"dependencies": {
  ...
  "react": "file:./shim/react",
},
"resolutions": {
  "react": "file:./shim/react",
}

This will cause all references to React to get passed through to the application's version of React, rather than compiling another version into the module. By using this shim for react-admin, we can ensure the components will all use the same context and work correctly. It also significantly reduces the file size of the module.


Finally, the module exporting custom components also needs to be built as an ES6 module. To achieve this without ejecting the project, we can use craco.

First, add craco to the dependencies and update the scripts:

"dependencies": {
  "@craco/craco": "^7.1.0",
  ...
  "react": "file:./shim/react",
},
"scripts": {
  "start": "craco start",
  "build": "craco build",
  "test": "craco test",
  "eject": "craco eject"
},

Then add a craco.config.js file:

module.exports = {
  webpack: {
    configure: {
      output: {
        library: {
          type: 'module'
        }
      },
      experiments: {outputModule: true}
    }
  }
}

Then the project can be built normally with yarn install and yarn build.

8
Anthony Bertrand On

Library mode is probably what you are searching for. Try adding those line to your webpack.config.js :

module.exports = {
    // ...
    output: {
        // ...
        globalObject: 'this',
        library: {
          name: 'myLibrary',
          type: 'umd',
        },
    }
}

This will tell webpack to expose your exported functions. Change the library name to suit your needs.

10
Anthony Bertrand On

The environment of you project is a bit unusual at least to me, but with your updates and code samples I think what you're looking for definitely requires react-scripts eject anyways, because you will eventually need to customize your webpack and babel config.
You're probably aware, but it's worth reminding it's a one-time, non reversible action.

For the use as standalone vanilla JS module, maybe keep using the output config from my other answer that worked initially.

And for the React components, simply copying the component files in a subfolder of your dist/ folder (or whatever output folder you have) as suggested should work. You could use the CopyWebpackPlugin to achieve that.

I hope it gives you tracks to follow for an eventual solution. Don't hesitate if you have more webpack or JS-modules related questions, I'd be happy to help if I can.