We have developed a React component library for internal use. The library uses webpack to bundle everything into a bundle.js. The library is exposed as an NPM module for use by our applications, which so far has been working great.
Recently we added a Grid component that has some very large external dependencies. Even though a few of our applications won't need this component from the library, we decided to include it in the final bundle. This can make a huge difference in bundle size, so I followed Webpack's code splitting guide to break out the Grid component using a dynamic import. For our applications that have this library installed as an NPM module, the component library bundle now looks like this in node_modules:
├── node_modules/
│ ├── [our component library]/
│ │ ├── Grid.js # Asynchronously loaded Grid component
│ │ ├── bundle.js # Main bundle
│ │ └── ... other files in component library bundle
When building the application (also with Webpack), the Grid component doesn't seem to be getting included as its own file in the final bundle. In fact, it doesn't get included at all:
├── dist/
│ ├── main.js # App bundle
│ ├── vendor.js # Vendor bundle created with CommonChunksPlugin
│ └── ... other files in application bundle but no Grid.js
When trying to load a page in the browser that is using the asynchronously loaded Grid component, Webpack returns the following error:
Error: Loading chunk 0 failed.
at HTMLScriptElement.onScriptComplete (bundle.js:757)
Essentially, this says the Grid component chunk can't be found. What am I doing wrong? Is it even possible to code split a React component library like this?
I used https://webpack.js.org/guides/code-splitting/#dynamic-imports both import() and require.ensure syntax for dynamic imports. Both worked well. Check this project of mine for good example https://github.com/croraf/myboiler.
This project also is an example: https://github.com/croraf/nutrients-calculator. But I complicated dynamic import part a bit. Check https://github.com/croraf/nutrients-calculator/blob/master/frontend/src/App.js and https://github.com/croraf/nutrients-calculator/blob/master/frontend/src/routes/util/DynamicRoute.js files.
We decided to take a different route with this. Instead of including the Grid component with the rest of the components we split if off into its own npm module. I still see a use case for dynamically importing react components in a library but we felt this was a better solution to our problem. With this solution, the only real downside is that the application will now need to add 2 npm modules instead of one if it needs to use the Grid component and our standard component library. Since our application does code splitting by route with webpack + dynamic imports, the Grid code only gets bundled into the route that needs it.
one possible reason is babel transpiled your code from import("..") to require. check transpiled file to confirm this.
if this is the case and you are using preset, you can add this to disable this behavior:
"#babel/preset-env",
{
**"modules": false,**
"targets": {
"esmodules": true
}
}
],
Related
There are SSR-related problems with several pages in Next.js project that results in errors on npm run build and prevent the project from being built:
pages/
foo/
bar/
[id].jsx
index.jsx
index.jsx
...
For example, bar:
export function getStaticProps() {
return someApiCallThatCurrentlyFails()
...
}
export default function Bar() {...}
As a quick fix, it may be convenient to just not build bar/*.* pages and make routes unavailable.
Can pages be ignored on Next.js build without physically changing or removing page component files in the project?
You can configure the pageExtensions in the next.config.js.
// next.config.js
module.exports = {
pageExtensions: ["page.js"],
}
After configuring this, the only pages with *.page.js will be considered in the below given directory structure.
pages/
├── user
│ └── setting
│ ├── index.js
├── _app.page.js
├── _document.page.js
├── list.page.js
└── theme.ts
Custom file ignores patterns that are not supported yet. You can visit the PR created here, and the solution given here. This is the most satisfactory solution so far.
#Mathilda Here from Nextjs docs: it's necessary for all pages including _app, _document, etc.
https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions
Changing these values affects all Next.js pages, including the following:
- middleware.js
- pages/_document.js
- pages/_app.js
- pages/api/
For example, if you reconfigure .ts page extensions to .page.ts, you would need to rename pages like _app.page.ts.
When I use Sass files in NextJs, I am getting 'conflicting order' warnigs from mini-css-extract-plugin. The conflict always messes up my stylings on build. You can see the error described in the following link:
https://medium.com/iryl/control-css-imports-order-for-next-js-webpack-based-production-applications-3b69765444fd
This is an article with a solution to this issue but it talks about ordering Sass files for only one page. I'm not sure how to do order when there are multiple pages. How can I tackle the conflicting order issue?
if you are using nextjs 9.3 or higher you can use it like css modules. At least this is how they recommend that you do it.
This is an example.
You can also check the Sass support on the Next.js docs.
-- Update
The best way of do it it's by creating a different scss module for each component. It will look something like this.
Components
├── Header
│ ├── Header.js
│ ├── Header.module.css
├── Footer
│ ├── Footer.js
│ ├── Footer.module.css
├── Nav
│ ├── Nav.js
│ ├── Nav.module.css
The main idea of using css modules it's to prevent you from use a global sheet. this way the css will be optimized on the code splitting by components and you don't have to worry about declaring the same class twice, each css module file will generate a unique class name.
I'm working on a project using css modules, the project isn't done yet but the file structure it's almost the same working with sass, you can give it a look if you want.
https://github.com/edgarlr/portfolio/tree/main/components
There are plenty of questions and tutorials on this topic, but none of them cover all use cases for a chrome extension, because most of them assume there's only one entry point.
Here are the requisites:
Multiple "single page applications":
1) popup.html for the extension pop up page
2) options.html for the options page
3) custom.html this is a custom .html file that the extension can refer to "locally"
Each of these files are entry points for React to manipulate the DOM, but they behave independently of each other.
Non React TypeScript files
They must not be bundled together with any other scripts, and gets compiled to its own JavaScript file, for example a background.ts that compiles to background.js (which is refered to in the manifest.json).
I assume this is doable with TypeScript, React and Webpack but I'm not sure how to approach that.
There is a custom CRA template that exactly fits your needs: complex-browserext-typescript.
Usage:
npx create-react-app my-app --template complex-browserext-typescript
By default it sets up 4 entry points associated with page-like extension components:
popup (src/index.tsx) - extension's popup page, replaces the
default index entry point.
options (src/options.tsx) - extension's options page.
background (src/background.ts) - background script.
content (src/content.ts) - content script.
However there is an option to exclude any of the above components except the popup from compilation as well as add extra HTML page(s).
Also see this article for usage example.
I found a solution but it was not using create-react-app and webpack. It looks like parcel supports multiple entry points out of the box without any configuration.
Assuming a directory structure like this:
├── chrome
│ └── background.ts
├── html
│ ├── custom.html
│ ├── options.html
│ └── popup.html
├── manifest.json
├── package.json
├── react
│ ├── custom.tsx
│ ├── options.tsx
│ └── popup.tsx
With Parcel.js you simply do:
parcel build html/*.html react/*.tsx chrome/*.ts
And it will handle the multiple entry points. I created a repository that contains a template for that approach, it contains a fully runnable example.
I have a React site currently in JavaScript/ES that we are converting to TypeScript (🎉). We use the Container and Presentational breakdown, so each component will have either a Container and a Presentational file, or for simpler components just a Presentational file.
Over the course of a build, we will typically build "static" Presentational screens/components first, get them to pass design QA, and then make them dynamic by adding a Container. That means that the Presentational files are usually built first. Containers are built later, if needed.
Our directory structure looks like this:
components
├── SomeComponent
│ └── index.js
│ └── index.jsx
├── SomeOtherComponent
│ └── index.jsx
^^ Here we have SomeComponent which has Container+Presentational, while SomeOtherComponent has only Presentational.
This structure provides us with several benefits:
Imports are short and not "doubled": import SomeComponent from '../screens/SomeComponent' (not import SomeComponent from '../screens/SomeComponent/SomeComponent')
Imports do not need to change when Containers are eventually added. If only a Presentational .jsx file exists, the import above works. When/if a Container .js file is added, it then takes precedence over the .jsx, so the same import now targets the new file by virtue of it existing. (Webpack resolve.extensions = ['.js', '.jsx'])
No need to rename the presentational component file when a Container is added. This keeps git history nicely intact.
Quick & easy visual identification of which file is Container vs Presentational. Presentational is .jsx because that's where our JSX lives.
We use react/jsx-filename-extension to enforce that JSX should only be in .jsx files. This gives our devs get an eslint warning if they try to write JSX in a .js Container file. This enforces the concept that Containers should do business logic (in JS), while Presentationals should just output "dumb" markup (in JSX).
Works great!
The only caveat, which in my opinion is very minor, is: In order for this to work, in our Containers we import the Presentational like so: import SomeComponent from './index.jsx'. I say this is a caveat simply because it's the only place where we import using a complete file extension. No big deal...
In TypeScript, I'd love to just change those file extension from js/jsx to ts/tsx like so:
components
├── SomeComponent
│ └── index.ts
│ └── index.tsx
├── SomeOtherComponent
│ └── index.tsx
But we have a problem: In TypeScript we cannot use file extensions in an import (to the best of my knowledge). So this breaks our ability to execute the "caveat," and seems to force us to have different filenames for the Containers vs Presentationals. 😭
How does your team solve this dilemma?
I have searched for conventions on how other teams navigate this issue, and have come up empty-handed.
I am open to criticism of any part of our convention here, and I invite discussion. This is a higher-level issue that also affects the lower-level day-to-day of our devs. All ideas are good ideas (except the ones that suck)!
A few ideas:
A) ts-ignore
Use // #ts-ignore above the import SomeComponent from './index.tsx' line to mute the error. This feels like bad practice, but what would be the consequences?
Pros:
Allows use of index.ts and index.tsx to mimic our previous setup
No need to rename presentationals when a container is added
No need to change imports when a container is added
Cons:
Feels like bad practice (🚩)
Have to remember to add it and instruct other devs to add it
Requires we add '#typescript-eslint/ban-ts-ignore': 'off' to our eslint conf, which by extension means eslint will no longer prevent people from using #ts-ignore anywhere they want. 😬
What else might this break? Does it kill intellisense? What other havoc would this wreak?
B) PascalCase Presentationals
components
├── SomeComponent
│ └── index.ts
│ └── SomeComponent.tsx
├── SomeOtherComponent
│ └── index.tsx
Pros:
Seems kindof conventional. Some teams use PascalCase for all component names as convention.
No need to change imports when a container is added.
Cons:
Requires renaming presentational file when container is added (messes with git history).
Not as easy to instantly identify which file is container vs presentational. There's a (small) logical leap that must be taken in your mind: "That one is named after the component and when they are named after a component they are presentational").
The two Container/Presentational files will not always be "next to" eachother in the directory. If another file named index.test.ts or index.stories.tsx or styles.js exists, it might be in-between index.ts and presentational.tsx
C) presentational.tsx
components
├── SomeComponent
│ └── index.ts
│ └── presentational.tsx
├── SomeOtherComponent
│ └── index.tsx
Pros:
No need to change imports when a container is added.
Instantly obvious which file is presentational. (Solves problem with B above)
Cons:
Requires renaming presentational file when container is added (messes with git history).
The two Container/Presentational files will not always be "next to" eachother in the directory. If another file named index.test.ts or index.stories.tsx exists, it will be in-between index.ts and presentational.tsx
D) index.presentational.tsx
components
├── SomeComponent
│ └── index.ts
│ └── index.presentational.tsx
├── SomeOtherComponent
│ └── index.tsx
Pros:
No need to change imports when a container is added.
Instantly obvious which file is presentational. (Solves problem with B above)
The two Container/Presentational files will always be "next to" eachother in the directory. (Solves problem with B & C above)
Cons:
Requires renaming presentational file when container is added (messes with git history).
Long filename (not sure I really care about this)
I know that there are few questions about the react-redux project code structure but I am hoping to discuss a different approach. So the main libraries we are going to use are : webpack - react - redux -mocha - karma.
The classic folder structure for this seems to be:
js
├── actions
│ ├── action-for-componentA.js
├── components
│ ├── componentA.js
├── reducers
│ ├── reducer-for-componentA.js
└── stores ...
This seems to be what everyone else and all the generators out there are doing. But I feel like this is not the react way of structuring a project. The focus should be on the component and not on the individual constructs of react or redux. I would like to think about it in this way, when you need to change a button in PageX which is contained in a component hierarchy that starts with ComponentX -> ChildOfX - I should be able to traverse the directories in this exact same way.
Rather than having a components folder with all components thrown inside it I would rather have something like :
js
├── PageX
│ ├── action-for-pagex.js
├── componentX.js
├── containerX.js
├── reducerX.js
├── children
├── childrenC
├── childrenB
├── componentB.js
├── reducerB.js
├── ...
├── PageY
│ ├── ...
├── PAgeZ
│ ├── ...
This will be easier to traverse and it makes more sense when you think in "react". Can anyone see anything that might go wrong with this approach?
Related reading : http://engineering.kapost.com/2016/01/organizing-large-react-applications/
TL;DR
There is no standard or proper way structuring your application's code; it's mostly about your taste.
I will give you an answer due to my experience with React and Redux. Right now, I am involved in a huge project using the R&R stack. Our tech team had spent a great amount of time talking about folder structure since we should keep a linear and scalable way of coding.
Basically, we are using hybrid approach mixing the two examples your have provided. The second approach is referenced as "Folder structure by feature", the first one is called "Folder structure by type".
Since the React/Redux stack uses a standardised typeset of files it's quite easy to keep your application tree as tidy and scalable as possible.
Our technical team has agreed that:
Each Main Component stands out under the pages folder.
Each page may have some sub components as well.
Components are using a component.js entry point and may use a reducer as well
Sub components that are actually inherited and used from more than one parent components are placed under the shared folder
We also maintain some helper functions folder and a folder that holds some constants and utilities. This is mostly because our action dispatchers use common actions in our application lifecycle.
The application is bootstrap by a single container, a single store and simple entry point (app.js).
We use ES6 import statements to hold everything in place.
Here is a schematic version of our file structure:
constants
-- const.js
-- const2.js
helpers
--helperfunc1.js
shared
--Shared Element1
--- component.js
--- reducer.js
--Shared Element2
--- component.js
--- reducer.js
specs
-- spec1.js
-- spec2.js
pages
-Page1
-- Subpage1
--- component.js
--- reducer.js
-- Subpage2
--- component.js
--- reducer.js
-- component.js
-- reducer.js
-Page2
-- component.js
-- reducer.js
...
container.js
app.js
reducer.js
As we kept coding, adding features and maintaining our application, we wrote down some pros of this approach:
- Filenames are pretty short and easy to read.
- The file structure is easy to follow.
- Testing has made damn simple as importing the component and the reducer from a folder
- Naming bottlenecks have been thrown away.
- We had less naming issues and under the same folder.
- Once more, TableDataComponent.js is more verbose and hard to follow than table/component.js.
- Since React nested components are an essential part of the framework, the folder structure tracks down this logic. Inner levels of code are inherited from the upper rendered elements.
- Action reducers are also smoothly bootstrapped .
I would suggest spending some time reading this wonderful Reddit thread and this article as well, some awesome points are mentioned above.
I've came up with this structure and it works pretty well