When bundling my web-application, I have realised that re-exports of (some) modules do not work as expected. I have tried multiple optimization settings but so far with no success.
Setup
So basically I have the following setup
config/
webpack.config.ts
package.json
frontend/
apps/
app1/
src/
index.tsx
App.tsx
...
packages/
test/
index.ts
testFunc.ts
test1Func.ts
Test.tsx
Test1.tsx
So I run webpack from config with the entry point frontend/apps/app1/index.tsx which imports App.tsx (standard React application).
This all works fine but I have realised that when produce a production build from app1, unused exports in my App.tsx appear in the bundle. To clarify
import { testFunc } from 'packages/test' // <- `packages` is an alias
const App: React:FC = () => {
const t = testFunc();
return <>Hello World!</>;
}
will include Test.tsx, Test1.tsx and 'test1Func.ts' in the bundle. My index.ts in test/ looks like
export { testFunc } from './testFunc';
export { test1Func } from './test1Func';
export { Test } from './Test';
export { Test1 } from './Test1';
I should mention that testFunc1.ts contains a useEffect hook because I found that as soon as I have react related code, there is no tree shaking for the source anymore. So
// test1Func.ts
export const test1Func = () => {
useEffect(() => {
// do nothing
}, []);
return "Test 1";
}
However, if I import my files directly, eg. import { testFunc } from 'packages/test/testFunc', everything works as expected and only test appears in the bundle. This also applies to the other test components:
App import tests
testFunc
import via index.ts & use in App.tsx => bundles all files inside test/ ❌
import via import { testFunc } from 'packages/test/testFunc' & use in App.tsx => only testFunc.ts is included in the bundle ✅
import via import { testFunc } from 'packages/test/testFunc' & don't use in App.tsx => nothing gets included in the bundle ✅
testFunc1 | Test | Test1 <- they all behave the same
import via index.ts & use in App.tsx => bundles all but testFunc ❌
import via import { test1Func } from 'packages/test/test1Func' & use in App.tsx => only test1Func.ts is included in the bundle ✅
import via import { test1Func } from 'packages/test/testFunc' & don't use in App.tsx => nothing gets included in the bundle ✅
I guess this is just a configuration error, although I have already tried multiple different optimisation settings. The closest probably is the sideEffects option but so far, this also did not have any effect.
Any ideas?
Thank you very much!
I found the solution! 🙌
As guessed, I had Webpack's optimization.sideEffects property misconfigured. sideEffects is necessary for proper tree shaking.
However, in order to get this work properly there are 2 destinations where you have to set the sideEffects property:
your package.json (in my case in every package I use in my monorepo)
and in the webpack.config.ts
Solution
The admittedly very confusing thing is that you will have to set
sideEffects: false in the package.json
and sideEffects: true in the optimization section in your webpack.config.ts
Here's a bit more if you are interested.
Related
I hope someone can tell me where I am going wrong in trying to get usable libraries
I have created a NPM project using create-react-app --format typescript, I then created the following structure:
|->tsconfig.json
|->package.json
|->config
|->tsconfig.base.json
|->tsconfig.cjs.json
|->tsconfig.esm.json
|->src
|->index.tsx
|->components
|->index.ts
|->ExampleComponent
|->ExampleComponent.component.tsx
|->ExampleComponent.stories.tsx
|->ExampleComponent.types.ts
|->index.ts
In this example the Example Component looks something like the following:
|->tscon
import React, { FC } from 'react';
// Contact specific icons
import Container from 'react-bootstrap/Container';
// Footer Properties
import { ExampleProperties } from './ExampleComponent.types';
export const ExampleComponent: FC<ExampleProperties> = ({ text }) => {
return (<Container fluid>{text}</Container>);
};
for the tsconfig files in 'config' I've lifted the Synk recommendation directly, while tsconfig.json is fairly simple like so:
{
"extends": "./configs/tsconfig.esm.json",
}
If I start the application via 'npm start' I get a website and the component correctly appears, but the issue is trying to import into another project.
I using npm link and npm link #Example/ExampleProject to bring the project in to another website and the index.tsx of that project looks like so:
import React from 'react';
import { createRoot } from 'react-dom/client';
import { ExampleComponent } from '#Example/ExampleProject';
const container = document.getElementById('root');
if (!container) throw new Error('Failed to find the root element') const root = createRoot(container);
root.render(
<React.StrictMode>
<main role={"main"} >
<ExampleComponent/>
</main>
</React.StrictMode> );
But when it starts I am getting this error:
Module not found: Error: Can't resolve './ExampleComponent/index' in '/home/user/IdeaProjects/ExampleProject/dist/esm' Did you mean 'index.mjs'? BREAKING CHANGE: The request './Common/index' failed to resolve only because it was resolved as fully specified (probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"'). The extension in the request is mandatory for it to be fully specified. Add the extension to the request.
The only thing I can think is tsc generates src/components/index.ts as a /src/component/index.js (which I rename). But ExampleComponent has .js and .mjs files within its directory
I'm trying to implement i18n-perser with Next Js. According to the i18n-perser documentation I need to create a gulpfile.js on the root directory. My gulpfile.js is something like this.
import gulp from 'gulp';
import { gulp as i18nextParser } from 'i18next-parser';
export async function i18next() {
return gulp
.src('./**')
.pipe(
new i18nextParser({
locales: ['en', 'nl'],
output: './i18n/locales/$LOCALE/$NAMESPACE.json',
})
)
.pipe(gulp.dest('./'));
}
It's gives me SyntaxError: Cannot use import statement outside a module. I tried some solution like the dynamic import. But it's not working for me in this case. It's giving me the same SyntaxError.
import dynamic from 'next/dynamic';
const gulp = dynamic(() => import('gulp'), { ssr: false });
const { gulp: i18nextParser } = dynamic(() => import('i18next-parser"'), { ssr: false });
I triend the dynamic import but it's also not working in my case. It's working perfectly in React but not working with NextJs. Basiclly if I run npm run gulp it will create parer file for me.
Probably your project is set to use the CommonJS module system by default.
In that case, you could rename your gulpfile.js to gulpfile.mjs to keep using the import syntax.
I trying to build project using Lerna and its hoist feature. Everything worked great, before i decided to move ui components from site to packages, for reuse purposes.
I have file structure like this
root
├── node_modules
└── packages
├── admin
├── site
└── ui
where ui is react + styled-components components library, and it used as dependency in site
// site packages.json
...,
"dependencies": {
...,
"#root/ui": "^1.0.0"
}
ui have only one file index.tsx
import React, { FC } from "react";
import styled from "styled-components";
const SharedComponent: FC = () => {
return (
<Wrapper>Hello from SharedComponent</Wrapper>
);
};
const Wrapper= styled.div`
color: red;
`;
export default SharedComponent;
then i use tsc to compile tsx
trying to use it inside site
import React, { FC } from 'react';
import SharedComponentfrom '#root/ui/dist';
const App: FC = () => {
return (
<>
<SharedComponent />
<div>Hello from Site App</div>
</>
);
};
export default App;
So if i start devServer everything is OK.
But when i try to SSR, i got server side error
UnhandledPromiseRejectionWarning: Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
i tried to declare alias property in wepback settings as mentioned here https://github.com/facebook/react/issues/14257#issuecomment-508808246
// site webpack.config.js
module.exports = {
...,
resolve: {
...,
alias: {
react: path.resolve('../../node_modules/react') // path to root node_modules
}
},
}
but error is still in present
Also, if i remove styled-components from ui it works fine. It's seems like the problem is somewhere in styled-components
So, how can i pair lerna + styled-components + react + SSR ?
I managed to get it work. All i need to do is set externals field in webpack config. like this:
externals: [
nodeExternals(),
nodeExternals({ additionalModuleDirs: [path.resolve(__dirname, '../../node_modules')] }),
'express'
]
Try to use dangerouseInnerHtml to solve css probleum. otherwise if you use emtion.css then you can try #emotion/server
try to use next/dynamic or React.lazy to load dynamic
I still confused duplicate React. how to resolve this.
I have an Icons folder and it has so many Icons.
I am trying to export them automatically, but I can't achieve it.
It is my directory map
components
ㅏ ...
ㅏIcons
ㅏ index.ts
ㅏ IconA.tsx
ㅏ IconB.tsx
ㅏ ...
ㅏ IconZ.tsx
ㅏ ...
Icons/index.ts
// I wanna avoid exporting components like this.
// export { IconA } from './IconA';
//below code doesn't work in Typescript.
const req = require.context('.', true, /\.\/.\.tsx$/)
req.keys().forEach((key: any) => {
const componentName = key.replace(/\.\/.\.tsx$/, '$1')
module.exports[componentName] = req(key).default
});
Icons/IconA.tsx
import React from 'react';
export const IconA = () => (<svg>...</svg>);
I wanna use it in another component
import { IconA, IconB, IconZ } from 'components/Icons'
I think the problem here is that because you are dynamically importing and re-exporting, it is not possible for TypeScript to guess the type of the components you are exporting, because it is unknown at compile time: your imports are just files in the eyes of the compiler.
How about using a script to generate your index.ts file to export { IconA } from './IconA'; from each of your icon files, quite similarly to what you are doing here? You could then include this script in your build process and run it everytime you compile before the typescript compiler runs
I've followed various articles, particularly these...
Code Splitting in Create React App
react-loadable/README.md
Here's a example of the code splitting -- getRoutes() is called in a component's render method:
// Edit: commented out original `Loadable` abstraction to use standard `Loadable`
import React from 'react'
// import L from 'react-loadable'
import Loadable from 'react-loadable'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import { LoadingIndicator } from '../components'
// const Loadable = opts =>
// L({
// loading: LoadingIndicator,
// delay: 300,
// ...opts
// })
const AuthenticateContainer = Loadable({
loading: LoadingIndicator,
loader: () => import(/* webpackChunkName: "auth" */ '../containers/Authenticate')
})
...
export default function getRoutes (isAuthed, browserHistory) {
return (
<BrowserRouter>
<Switch>
<Route path="/auth" component={AuthenticateContainer} />
...
</Switch>
</BrowserRouter>
)
}
..but my code is not splitting:
$ npm run build
> my-app#0.1.0 build /path/to/my-app
> bash -ac '. .env.production; react-scripts build'
Creating an optimized production build...
File sizes after gzip:
854.84 KB (+4 B) build/static/js/main.1aa92927.js
17.53 KB build/static/css/main.36b767d9.css
The bundle size is significantly larger than recommended.
Consider reducing it with code splitting: https: // goo.gl/9VhYWB
You can also analyze the project dependencies: https: // goo.gl/LeUzfb
The project was built assuming it is hosted at the server root.
You can control this with the homepage field in your package.json.
For example, add this to build it for GitHub Pages:
"homepage" : "http: // myname.github.io/myapp",
The build folder is ready to be deployed.
You may serve it with a static server:
yarn global add serve
serve -s build
Find out more about deployment here:
http: // bit.ly/2vY88Kr
As you can see, the build result is not chunked, but a single JS file.
What am I doing wrong?
Current versions used:
react-scripts: 1.1.4
react-router-dom: 4.3.1
webpack: 3.8.1
npm: 6.1.0
OK, I worked this out eventually...
I had a file at /src/containers/index.js which contained the following line:
export { default as AuthenticateContainer } from './Authenticate/AuthenticateContainer'
This allowed me to import my AuthenticateContainer using the following shorthand:
import { AuthenticateContainer } from '../containers'
rather than the slightly longer
import AuthenticateContainer from '../containers/Authenticate/AuthenticateContainer'
However, leaving that line in /src/containers/index.js also prevents code splitting as the AuthenticateContainer logic has already been bundled into main.js before we even try async loading.
Remove the line and code splitting works as expected.