I am using Next.js for React. I like how Arc (other react boilerplate) dynamically imports components without requiring developer to specify path.
import { Input, Label, Field, HomePage, PageTemplate } from 'components'
Folder structure may look somewhat like this:
components
|_ index.js
|_ atoms
|_ Input
|__ index.js
|_ molecules
|_ organisms
|_ templates
and I'd like to import it like:
import { Input } from 'components'
Code that is used for dynamic import: components/index.js
const req = require.context('.', true, /\.\/[^/]+\/[^/]+\/index\.js$/)
req.keys().forEach((key) => {
const componentName = key.replace(/^.+\/([^/]+)\/index\.js/, '$1')
module.exports[componentName] = req(key).default
})
However it doesn't work. The error I get:
Module not found: Error: Cannot resolve module 'components'...
The issue is that require.context is not available on serverside.
I suppose I need to specify this path to be imported like this in loader config. Can anybody share a hint on how is this done properly?
I don't think there is a simple and reliable way to do what you want with Next.js
The standard/"correct" answer is to use a relative path to import your local components. It is a bit more boilerplate, but I think you'll ultimately find it to be less work than fighting with Next.js to make it do things the Arc way.
To followup on #Robsonsjre's suggestion:
Why dont you make your /components/index.js export all components in the folder?
I think there is an implied "you write that index.js file". For your example, it would look something like this:
export {default as Input } from './atoms/Input';
export {default as Foo } from './molecules/Foo';
// etc
The catch is that you'd have to remember to update this file every time you add or remove a component. It's probably possible to automate that, but I'm not aware of any system to do it off the top of my head.
This way is not exactly what you want, but works similar, well for performance because its only object pointers to imported components
//Input.js
class Input extends Component {
render() {
return <div>'input'</div>
}
}
export default Input
//index.js
export { default as Input } from './Input/Input'
//otherComponent.js
import { Input } from './components' //path to folder components
Related
For example, the recommended way of importing in React Bootstrap is to go this way:
import Button from 'react-bootstrap/Button' instead of import { Button } from 'react-bootstrap';
The reason is "Doing so pulls in only the specific components that you use, which can significantly reduce the amount of code you end up sending to the client."
source: https://react-bootstrap.github.io/getting-started/introduction/
Same for React MUI components:
import Button from '#mui/material/Button';
source: https://mui.com/material-ui/getting-started/usage/
I want to implement something similar in my React components library, to limit the usage of code in the bundle, but I don't know how they implement this specific pattern. I have looked at their code base, but I don't quite understand.
Basically it is all about modules and module files and their organization. You can have a lot of.. lets call them folders, "compoments/*" for example. "components/button", "components/alert", "component/badge", and other things. All of them will have some index.js or .ts file that will export or declare and export all the functionality that needed in order to make this component work, 'react-bootstrap/Button' for example. Ideally all those subfolders or submodules are independend from each other, no references between them but probably each one will have 1 reference to 1 common/shared submodule like "components/common" which will contain some constants, for example, and no references to other files. At the top level of them you will have another index.js or .ts file that is referencing all of those components, so "components/index.js" will import and reexport all the nested components index files. So in order to import a Button, for example, you can either import "components/index.js" file with all the other imports this file is using, either only 1 single "components/button/index.js" file which is obviously much more easy to fetch. Just imagine a tree data structure, you import root of the tree (root index.js) - you get all the tree nodes. You import one specific Node (components/button/index.js) of the tree - just load all the childs (imports) of that node.
Sorry for a long read but asuming you mentioned webpack - there is a technique called tree-shaking which will cut off all the unused things.
Info about modules: https://www.w3schools.com/js/js_modules.asp
Info about Tree-Shaking: https://webpack.js.org/guides/tree-shaking/
It might not be as complicated as you think. Let's say you write the following library:
// your-library.js
const A = 22
const B = 33
export function getA () { return A }
export function getB () { return B }
export function APlusB () { return A + B }
// a lot of other stuff here
If some consumer of your library wants to make use of the APlusB function, they must do the following:
// their-website.js
import { APlusB } from 'your-library'
const C = APlusB()
However, depending on how the code is bundled, they may or may not wind up with the entire your-library file in their web bundle. Modern bundling tools like Webpack may provide tree shaking to eliminate dead code, but this should be considered an additional optimization that the API consumer can opt into rather than a core behavior of the import spec.
To make your library more flexible, you can split up independent functions or chunks of functionality into their own files while still providing a full bundle for users who prefer that option. For example:
// your-library/constants.js
export const A = 22
export const B = 33
// your-library/aplusb.js
import { A, B } from 'constants'
export default function APlusB () { return A + B }
// your-library/index.js
// instead of declaring everything in one file, export it from each module
export * from 'constants'
export { default as APlusB } from 'aplusb'
// more exports here
For distribution purposes you can package your library like so:
your-library
|__aplusb.js
|__constants.js
|__index.js
You mentioned react-bootstrap and you can see this exact pattern in their file structure:
https://github.com/react-bootstrap/react-bootstrap/tree/master/src
and you can see they aggregate and re-export modules in their index file here:
https://github.com/react-bootstrap/react-bootstrap/blob/master/src/index.tsx
Essentially, what you are asking is:
"How to export react components"
OR
"How are react components exported to be able to use it in a different react project ?"
Now coming to your actual question:
import Button from 'react-bootstrap/Button' instead of import { Button } from 'react-bootstrap';
The reason is 'Button' component is the default export of that file react-bootstrap/Button.tsx. So there is no need for destructuring a specific component.
If you export multiple components/ functions out of a file, only 1 of them can be a default export.
If you have only 1 export in a file you can make it the default export.
Consider the file project/elements.js
export default function Button(){
// Implementation of custom button component
}
export function Link(){
// Implementation of custom Link component
}
function Image(){
// Implementation of custom Image component
}
Notice that the Button component has 'default' as a keyword and the Link component doesn't.
The Image component can't even be imported and can only be used by other functions/components in the same file.
Now in project/index.js
import 'Button', {Link} from './elements.js'
As Button component is the default export its possible to import without destructuring and as Link component is a regular export, I have to destructure it for importing.
It's a common thing to create a index.js file in an React application with the only purpose to export several modules, in order to avoid having too many import statements on other components. This can be done by:
index.js
export { Credits } from './Credits.js';
export { SocialMedia } from './SocialMedia.js';
any module that might use those exports:
import * as var_name from index.js
And this is very nice. It wraps exports into a single file. However, when I changed my project to React with typescript, I found that .tsx files cannot be exported like that. The image below is the error I got after changing the project to typescript and the extensions became .tsx
Is there a way of 'bundle' export React .tsx files with the structure shown above? If not, what is the simplest way of centralizing .tsx files export?
My webpack.config.js:
module.exports = {
module: {
rules: [{
test: /\.scss$/,
use: ["sass-loader"]
}]
}
};
You can definitely use the same style of having an index file to group up exports for a whole folder. The simplest way around your problem would be to omit the file extension (assuming you only have one "index" file in the folder).
For example, let's say you have a component in 'common/Example.tsx':
import React from 'react'
export const Example = () => (<div>I'm an example component</div>)
You can then export it in an index file 'common/index.tsx':
export { Example } from './Example'
And import it from somewhere else, e.g. 'App.tsx':
import { Example } from './common'
Structure of the project generally looks like this:
components
- my-component
- - my-component.ts
- - index.ts
Where index.(ts/js) always consists of
import MyComponent from './my-component';
export default MyComponent;
I want to remove index.(js/ts) and still import/require my components using the path
import MyComponent from './components/my-component' // not having the index file!
// Please, do not suggest importing them like this
import MyComponent from './components/my-component/my-component'
I use Webpack 5. I know there was a module for Webpack 4, but it does not work with 5.
I want NodeJS/Webpack to look for a custom filename rather than index.
Some other solutions:
You could move components/my-component/my-component.ts to components/my-component.ts. I assume you have a reason not to do this (presumably, other files in that component directory).
You could use resolve.alias and list all mappings you want:
module.exports = {
//...
resolve: {
alias: {
"./components/my-component": path.resolve(__dirname, 'components/my-component/my-component.js'),
//...
},
},
};
You could set resolve.mainFiles to list all of your .js filenames. This seems pretty ugly though, as it's a global setting.
Are you sure that Webpack 5 doesn't support DirectoryNamedWebpackPlugin? It's explicitly mentioned in the documentation.
I m wondering if there is a performance cost if we make multiple imports, like so:
import { wrapper } from './components/wrapper';
import { error } from './components/error';
import { products } from './components/products';
In each component folder i have an index.js and export it as named, like so:
export { default as wrapper } from '.wrapper';
Compared to:
Import all the files as named imports from the same source, like so:
import {
wrapper,
error,
products,
} from './components';
In components folder i have an index where i gather and export all the files, like so:
export { wrapper } from '...';
export { error } from '...';
export { products } from '...';
According to the ES262 specification, import and export statements just provide information about dependencies between modules to the engine. How the modules are actually loaded in the end is up to the engine (there are a few constraints though). So whether there is actually a difference between importing from the source vs. importing a reexport depends on the environment.
Whatsoever the differences are probably irrelevant. Choose what works best for you.
I'm a fan of that approach. I like to split some components into folder and only expose what I want to the rest of my application. I really don't think that impact the perf on dev. (Obviously, there is absolutely no difference on prod as the whole project is pack in one file)
I've followed meteor tutorial, and when I finished I've decided to install eslint.
Now I see
Prefer default export import/prefer-default-export
for this line: export const Tasks = new Mongo.Collection('tasks'); in imports/api/tasks.js file. It contains also some Meteor methods. Here it is full source code: tasks.js.
I was trying to fix this eg. with
const Tasks = new Mongo.Collection('tasks');
export { Tasks as default };
But then browser stopped rendering the view.
Here is the server/main.js content, which imports tasks.js:
import '../imports/api/tasks.js';
How can I fix lint error without breaking applications functionality?
You could add an .eslintrc file to your project root and adapt the rule:
{"rules": {"import/prefer-default-export": ["off"]}}
UPDATE:
If you want to keep the rule, then you need to export Tasks as default like so:
const Tasks = new Mongo.Collection('tasks');
export default Tasks;
Now you have to change all the imports in the rest of your codebase from a named import to a default import. The named import looks like this
import { Tasks } from '/imports/api/tasks';
see e.g. here, whereas the new default import has to look like this
import Tasks from '/imports/api/tasks';
This should do it. Let me know if it works for you.