Can't use Material UI in external library with webpack/rollup/react - javascript

I'm working on a component library that wraps some of the MaterialUI components and implements others to be used in a larger project. While setting up the library I had to setup webpack in order to be able to import and bundle images and css files in my library.
This library is located nested in the folder of the main project, where I add it as a dependency with npm i ./shared/path_to_library. this seems to work fine, since the typescript is working correctly and the npm start gives me no error. Then when I open it in a browser I get the following error page:
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
But this error only occurs if I try to use any component from #mui/material inside my library. Exporting handmade components and using them in the main project works fine, but using any material component wrapped by my own component and then using it in the project brings me to this error. I also tried to move from webpack to rollup but I ended up getting the same problem.
Here are my config files for webpack and rollup:
rollup.config.js
import resolve from "#rollup/plugin-node-resolve";
import commonjs from "#rollup/plugin-commonjs";
import typescript from "#rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
const pkg = require("./package.json");
const config = [
{
input: "src/index.ts",
output: [
{ file: pkg.main, format: "cjs", sourcemap: true },
{ file: pkg.module, format: "esm", sourcemap: true },
],
plugins: [
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
],
},
{
input: "lib/esm/types/index.d.ts",
output: [{ file: "lib/index.d.ts", format: "esm" }],
plugins: [dts()],
},
];
export default config;
webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.ts",
mode: "development",
output: {
path: path.resolve(__dirname, "lib"),
filename: "[name].js",
libraryTarget: "umd",
library: "my-core-library",
umdNamedDefine: true,
},
devtool: "source-map",
module: {
rules: [
{
test: /\.css?$/,
use: ["style-loader", "css-loader"],
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
use: ["babel-loader", "ts-loader"],
exclude: /node_modules/,
},
{
test: /\.(png|jpe?g|gif|svg)$/,
use: ["file-loader"],
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
externals: [
{
react: "react",
"react-dom": "react-dom",
"#mui/material": "#mui/material",
"#emotion/react": "#emotion/react",
"#emotion/styled": "#emotion/styled",
"#mui/lab": "#mui/lab",
},
],
};
I'm running out of ideas :(

This is probably happening because multiple instances of React are loaded. You can check with npm ls react, all react packages should be deduped
A short term solution is to link the react from the library, ie
npm link ../shared/path_to_library/node_modules/react
However you would have to re-link that everytime you install an npm package.

I think you need to configure your .babelrc as following:
"presets": ["#babel/preset-env", "#babel/preset-react", "#babel/preset-typescript", "jest"],
"plugins": [
[
"babel-plugin-transform-imports",
{
"#material-ui/core": {
"transform": "#material-ui/core/${member}",
"preventFullImport": true
},
"#material-ui/icons": {
"transform": "#material-ui/icons/${member}",
"preventFullImport": true
}
}
]
]

Related

Webpack configuration to allow import

I am creating a basic structure where the controller.js would need some Views to be imported from view.js and hence this is what I am using in the controller.js
import View from './View'
However there was an issue with the bundler with webpack and I ended up visiting this page
https://webpack.js.org/loaders/imports-loader/
and below is webpack.config.js.. I've added the lines at the end to the RULES as metioned in the documentation
I ran npm install npm install imports-loader --save-dev
// SOURCE OF TUTORIAL
// https://www.youtube.com/watch?v=MpGLUVbqoYQ&t=1205s
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
mode: "development",
devtool: false,
entry: {
main: "./src/js/controller.js",
model: "./src/js/model.js",
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/main.html",
}),
],
module: {
rules: [
{
test: /\.scss$/,
use: [
"style-loader", // injects STYLES into DOM
"css-loader", // turns CSS into commonjs
"sass-loader", // turns SASS into CSS
],
},
{
test: /\.html$/,
use: ["html-loader"],
},
{
test: /\.(png|gif|svg|jpe?g)$/,
type: "asset/resource",
},
{
test: require.resolve("./path/to/example.js"),
loader: "imports-loader",
options: {
type: "module",
imports: "default lib myName",
},
},
],
},
};
The last section of the rule is what I have added.. it's a basic copy paste without really understanding what it does and it didn't work.. I am obviously doing somethibg wrong but I could not figure it out even after reading the section at the link above..
Could someone explain how the last section should be configured so that I am able to use import? I've recently created this webpack configuration based on a youtube tutorial so my knowledge on webpack is very basic..
Any help would be appreciated.

How to propperly build react modular library

I'm trying to create a react components library which is based on Typescript and SASS. The components library will be used in multiple other typescript projects, so type exports are needed as well. Ideally I want to mimic something like "Material-UI"/"React-Bootrap" libraries dist output solutions.
Example project structure:
|Tabs
+--Tabs.tsx
+--Tabs.scss
+--index.tsx
index.tsx
index.tsx
export { Tabs } from './Tabs/Tabs';
Tabs/index.tsx
import React from 'react';
import './Tabs.scss';
interface TabsProps {
...
}
export const Tabs: React.FC<TabsProps> = (props) => <div>...</div>
Tabs/index.tsx
export { Tabs } from './Tabs';
Expected built dist structure should mimic the src structure:
|Tabs
+--Tabs.js
+--Tabs.d.ts
+--index.js
+--index.d.ts
index.js
index.tsx
I tried analyzing open source projects and see how they are building the libraries, however I could not find libraries using the same approaches that I could reuse.
The solutions I've tried:
Webpack: While I could compile typescript and sass files the webpack would always emit only one file specified in the output section, which usually would be bundled and I would loose the ability to import single component from a specific component's module. I know I can specify multiple entry points, but the project will be having a lot of exports and manually specifying them is not an option...
Example config I tried:
const path = require('path');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
entry: './src/index.tsx',
module: {
rules: [
// sass-loader is not used here yet, but should be once desired structure can be reached
{
test: /\.tsx?$/,
loader: 'babel-loader',
},
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: "source-map-loader" }
]
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
extensions: [".tsx", ".ts", ".js"],
plugins: [
new TsconfigPathsPlugin({ configFile: "./tsconfig.build.json" })
]
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
};
Rollup: Similar situation as webpack
Example config that I tried:
// rollup.config.js
import babel from 'rollup-plugin-babel';
import sass from 'rollup-plugin-sass';
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import react from 'react';
import reactDom from 'react-dom';
const babelOptions = {
exclude: /node_modules/,
// We are using #babel/plugin-transform-runtime
runtimeHelpers: true,
extensions: ['.js', '.ts', '.tsx'],
configFile: './babel.config.js',
};
const nodeOptions = {
extensions: ['.js', '.tsx', '.ts'],
};
const commonjsOptions = {
ignoreGlobal: true,
include: /node_modules/,
namedExports: {
react: Object.keys(react),
'react-dom': Object.keys(reactDom)
},
};
export default {
input: 'src/index.tsx',
output: {
name: '[name].js',
dir: 'dist',
format: 'umd',
sourcemap: true,
},
plugins: [
nodeResolve(nodeOptions),
sass(),
commonjs(commonjsOptions),
babel(babelOptions)
],
};
Babel: I managed to compile the typescript code however once I came close to transpiling SASS files I would end up with suggestions to use webpack for that...
TSC: I successfully could run the typescript compiler and it would compile all the files without problems and would maintain the same structure. However TSC does not support other transpiling options so after a lot of searches I would end up with suggestions to use webpack and "ts-loader" or "babel-loader"..
tsconfig:
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"lib": [ "es2015", "dom" ],
"outDir": "dist",
"baseUrl": ".",
"declaration": true,
"composite": true,
"module": "commonjs",
"target": "es5"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
Desirced solution:
I should be able after compiling the library and installing it in another project be able to run the following:
import { Tabs } from 'my-lib/Tabs';
import { Tabs } from 'my-lib';
After a lot of playing around I managed to produce the wanted result with rollup. The only downside of the current configuration is that it does not support newly added files in the --watch mode. The magic setting is under the output.preserveModules
Config:
// rollup.config.js
import commonjs from '#rollup/plugin-commonjs';
import typescript from '#rollup/plugin-typescript';
import postcss from 'rollup-plugin-postcss';
import postcssUrl from 'postcss-url';
import resolve from "#rollup/plugin-node-resolve";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
export default {
input: 'src/index.tsx',
output: {
dir: 'dist',
format: 'es',
preserveModules: true,
sourcemap: true,
},
plugins: [
resolve(),
peerDepsExternal(),
commonjs(),
typescript({
tsconfig: 'tsconfig.build.json'
}),
postcss({
minimize: true,
modules: {
generateScopedName: "[hash:base64:5]"
},
plugins: [
postcssUrl({
url: "inline"
})
]
}),
],
};
I hope this config can help others as well
You can checkout this repo. I made some changes for building a lib.
https://github.com/21paradox/react-webpack-typescript-starter
To use a library like below:
import Input from 'my-custom-ui/entry/Input';
import { Input } from 'my-custom-ui';
After doing a lot of searching, I ended up writing a plugin to manually generate the webpack entry code that webpack needed (for building a ui library).
The multiple entry + manualy generated entry file seems to be working for component seperate & no redudant code. This is also very helpful if you want to build a vue based libray.

How to use the kind of TypeScript in a Single File Component with Vue?

I'm trying to use the TypeScript benefits in a Vue SFC,
Install the ts-loader, typescript dependencies
I added the tsconfig.json configuration
// tsconfig.json
{
"compilerOptions": {
// this aligns with Vue's browser support
"target": "es5",
// this enables stricter inference for data properties on `this`
"strict": true,
// if using webpack 2+ or rollup, to leverage tree shaking:
"module": "es2015",
"moduleResolution": "node"
}
}
But when trying to compile the next component it shows me error.
<script lang="ts">
import Vue from "vue";
import { mapState,mapGetters } from 'vuex'
export default Vue.extend({
data() {
return {
number : 0
}
},
methods:{
// error void
upCount() : void {
this.$store.commit('increment');
},
downCount() : void{
this.$store.commit('decrement');
},
upCountBy() : void {
this.$store.commit('incrementBy',{count : this.number});
}
},
....
the error
Module parse failed: Unexpected token (45:12) You may need an
appropriate loader to handle this file type.
I am using VueJs together with WebPack from a Laravel and Laravel Mix base installation. How do I solve this?
When I started using Vue and TypeScript, I ran into similiar problems at first and it took me quite a while to get it to work. Try adding this to your webpack.config.js
...
module: {
rules: [
{
test: /\.tsx?$/,
use: [{
loader: 'ts-loader',
options: {
appendTsSuffixTo: [ /\.vue$/ ]
}
}],
exclude: /node_modules/
},
// make sure vue-loader comes after ts-loader
{
test: /\.vue$/,
loader: 'vue-loader'
},
...
Try and flip the order of Connum's suggested answer.
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
},
{
test: /\.ts$/,
exclude: /node_modules/,
use: [{ loader: 'ts-loader', options: { appendTsSuffixTo: [/\.vue$/] } }],
},
]
}
Worked for me. You have installed the vue-loader?
npm i -D ts-loader typescript vue-loader

ReactJS test files issue 'cannot find module' because of webpack module resolver

i'm developing an application with react.js. My application is growing up. So i had little trouble with imports. For example i have a component named foo i'm using it in many places.
import foo from '../../components/foo';
import foo from '../components/foo';
import foo from '../../../components/foo';
As you can see its dirty, not good. So i searched to fix it and i found a solution with webpack. Also i read that title (Configure Webpack’s modules resolution to avoid nested imports) in this article
I added this code into my webpack.config.js file
modules: [
'node_modules',
path.resolve(__dirname, 'src')
]
So my resolve object looks like this
export default {
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, 'src')
],
extensions: ['*', '.js', '.jsx', '.json']
},
...
After that i am able to use import my foo component in anywhere like this.
import foo from 'components/foo';
Everything is okay so far. But problem shows up in test files.
When i try to test foo component it says
Cannot find module 'components/foo' from 'foo.js'
Example test file.
foo.spec.js
import React from 'react';
import foo from 'components/foo';
describe('(Component) foo', () => {
it('should render foo', () => {
expect(true).toBe(true);
});
});
Here is the first problem. I can not import foo like this.
Note: My test file is not in src folder it is in the test folder.
So i changed the path like this then it worked.
import foo from '../../../src/components/foo';
Tes passed everything is looks fine. But we still have the path problem in test files.
Lets try to import another component in foo component.
foo.js
import bar from 'components/admin/bar';
Here is the second problem. Test file FAILED error message is
Cannot find module 'components/admin/bar' from 'foo.js'
I moved my test file in to my foo.js file. But didn't worked.
Here is my whole webpack.config.js
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import autoprefixer from 'autoprefixer';
import path from 'path';
export default {
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, 'src')
],
extensions: ['*', '.js', '.jsx', '.json']
},
devtool: 'inline-source-map', // more info:https://webpack.github.io/docs/build-performance.html#sourcemaps and https://webpack.github.io/docs/configuration.html#devtool
entry: [
// must be first entry to properly set public path
'./src/webpack-public-path',
'webpack-hot-middleware/client?reload=true',
path.resolve(__dirname, 'src/index.js') // Defining path seems necessary for this to work consistently on Windows machines.
],
target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test
output: {
path: path.resolve(__dirname, 'dist'), // Note: Physical files are only output by the production build task `npm run build`.
publicPath: '/',
filename: 'bundle.js'
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'), // Tells React to build in either dev or prod modes. https://facebook.github.io/react/downloads.html (See bottom)
__DEV__: true,
//'API_URL': API_URL.dev
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new HtmlWebpackPlugin({ // Create HTML file that includes references to bundled CSS and JS.
template: 'src/index.ejs',
minify: {
removeComments: true,
collapseWhitespace: true
},
inject: true
}),
new webpack.LoaderOptionsPlugin({
minimize: false,
debug: true,
noInfo: true, // set to false to see a list of every file being bundled.
options: {
sassLoader: {
includePaths: [path.resolve(__dirname, 'src', 'scss')]
},
context: '/',
postcss: () => [autoprefixer],
}
})
],
module: {
rules: [
{test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel-loader']},
{test: /\.eot(\?v=\d+.\d+.\d+)?$/, loader: 'file-loader'},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000&mimetype=application/font-woff'
},
{test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'},
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'},
{test: /\.(jpe?g|png|gif)$/i, loader: 'file-loader?name=[name].[ext]'},
{test: /\.ico$/, loader: 'file-loader?name=[name].[ext]'},
{
test: /(\.css|\.scss|\.sass)$/,
loaders: ['style-loader', 'css-loader?sourceMap', 'postcss-loader', 'sass-loader?sourceMap']
}
]
}
};
How can i solve?
Thanks for your help.
Webpack is not used during test execution. Since you are using babel the babel-plugin-module-resolver https://github.com/tleunen/babel-plugin-module-resolver should solve the issue:
in your .babelrc file
{
"plugins": [
["module-resolver", {
"root": ["./src"]
}]
]
}
A more cleaner approach would be to create an alias in your .babelrc file and then import from that alias, for instance:
{
"plugins": [
["module-resolver", {
"alias": {
"#app": "./src"
}
}]
]
}
And in your file:
import foo from '#app/components/foo'
That way you have no naming conflicts and your paths are nice and short.
Problem solved after i changed this jest object in package.json file.
"jest": {
"moduleDirectories": [
"node_modules",
"src"
]
...

Wow.js with webpack and react

I try to implement wow.js using react and webpack.
I install it via npm.
npm install --save wow.js
It install perfectly. Now the problem is how to import it properly. I can't make it work keep getting undefined.
I've tried few way:
First:
import wow from 'wow.js';
But webpack can't compile it. It says the module cannot be found. Even I using complete url import wow from /node_modules/wow.js
Second:
I'm using this solution from here:
require('imports?this=>window!js/wow.js');
But I still get cannot find modules (i change the path to my node_modules).
Third:
From here:
I'm using the expose module and try to new WOW().init(); it says Wow is undefined.
I'm not using his first solution because I want my html to look simple only has bundle.js script.
I can't found any other solution. What should I do to make it work?.
Thanks!
my webpack.config.js:
module.exports = {
entry: [
'webpack-dev-server/client?http://localhost:8080',
'bootstrap-loader',
'webpack/hot/only-dev-server',
'./src/js/index.js'
],
output: {
path: __dirname + "/build",
publicPath: "/build/",
filename: 'bundle.js'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery"
})
],
module: {
loaders: [
{
exclude: /node_modules/,
loader: 'react-hot!babel'
},
{
test: /\.css$/,
loader: 'style!css!postcss'
},
{
test: /\.scss$/,
loader: 'style!css!postcss!sass'
},
{ test: /\.(woff2?|ttf|eot|svg|otf)$/, loader: 'url?limit=10000' },
{
test: 'path/to/your/module/wow.min.js',
loader: "expose?WOW"
}
]
},
resolve: {
extensions: ['', '.js', '.jsx']
},
devServer: {
historyApiFallback: true,
contentBase: './'
},
postcss: [autoprefixer]
};
Alternative option without updating your wepack config.
Install WOW.js: npm install wowjs
Import in your component: import WOW from 'wowjs';
Under componentDidMount() of your component: new WOW.WOW().init();
import React, {Component} from 'react';
import WOW from 'wowjs';
class Cmp extends Component {
componentDidMount() {
new WOW.WOW().init();
}
render() {
/* code */
}
}
export default Cmp;
Do the following steps
Install exports-loader
npm i exports-loader --save-dev
Add to webpack.config.js this loader
{
test: require.resolve('wow.js/dist/wow.js'),
loader: 'exports?this.WOW'
}
add import to your app
import WOW from 'wow.js/dist/wow.js';
You can also change .call(this) to .call(window) (wow.js last line). Found this solution here https://foundation.zurb.com/forum/posts/46574-how-to-add-js-library-from-bower

Categories

Resources