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().
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
.