Create a React component lib - reactjs

I'm trying to create a library with reusable react components. I'm using React, Typescript, Sass and Webpack.
My problem is : when I'm using my components from the result of the webpack build, there is no css with them although I have an index.css file with everything I need.
It seems like the css is not used by the output.
Here is my webpack.config.json :
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
module.exports = {
mode: 'production',
entry: path.resolve(__dirname, '../src/index.ts'),
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'index.js',
library: 'whatever',
libraryTarget: 'umd',
umdNamedDefine: true
},
devtool: 'source-map',
externals: {
react: 'react',
'react-dom': 'react-dom',
},
module: {
rules: [
{
test: /\.tsx?/,
loader: 'ts-loader',
options: {
configFile: path.resolve(__dirname, '../tsconfig.build.json')
},
exclude: /node_modules/
},
{
test: /\.s(a|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]',
camelCase: true,
sourceMap: false
}
},
{
loader: 'sass-loader',
options: {
sourceMap: false
}
}
]
},
]
},
resolve: {
modules: ["node_modules", "src"],
extensions: ['.tsx', '.ts', '.scss', '.js']
},
plugins: [
new MiniCssExtractPlugin({
filename: 'index.css',
chunkFilename: '[id].[hash].css',
}),
]
};
Here is my tsconfig.json:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [ "es2015", "dom" ],
"outDir": "./dist",
"sourceMap": true,
"moduleResolution": "node",
"declaration": true,
"strict": true,
"jsx": "react"
},
"include": [
"src"
],
"exclude": [
"node_modules",
"src/**/*.stories.tsx",
"src/**/*.test.tsx"
]
}
EDIT 1:
As suggested by Muhammad Mehar I changed the loader: [MiniCssExtractPlugin.loader, ... to use: [... but it didn't work
Here is how I'm trying to use my lib :
somewhereInTargetApp.ts
import { MyComponent } from 'MyLib';
export const OtherComponent = (props) => {
return (
<MyComponent someProp={'Hello world'}/>
)
};
And in my lib I have some index.ts file which exports the component.
Everything works fine for the component except that there is no css with it

Rule.loader is a shortcut to Rule.use: [ { loader } ].
Passing a string (i.e. loader: 'style-loader' ) is a shortcut to the loader property (i.e. use: [ { loader: 'style-loader '} ])
Rule.use can be an array of UseEntry which are applied to modules. Each entry specifies a loader to be used.
Passing a string (i.e. use: [ 'style-loader' ]) is a shortcut to the loader property (i.e. use: [ { loader: 'style-loader '} ])
Loaders can be chained by passing multiple loaders, which will be applied from right to left (last to first configured)
Try this:
module: {
rules: [
{
test: /\.tsx?/,
loader: 'ts-loader',
options: {
configFile: path.resolve(__dirname, '../tsconfig.build.json')
},
exclude: /node_modules/
},
{
test: /\.s(a|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]',
camelCase: true,
sourceMap: false
}
},
{
loader: 'sass-loader',
options: {
sourceMap: false
}
}
]
},
]},
I hope it help you.

In the end I switched to CSS in JS with aphrodite
This is not what I wanted to do but it will do the work for now
If someone has an answer to my problem I would be happy to test it

Related

Problems with images in react application

When using images in react, there is either a problem with typescript, or the image breaks on the site.
To solve the problem, I tried:
Add url-loader and file-loader to the webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const BUILD_PATH = path.resolve(__dirname, './build');
const SRC_PATH = path.resolve(__dirname, './src/');
const PUBLIC_PATH = path.resolve(__dirname, './public')
module.exports = {
entry: SRC_PATH + '/index.tsx',
output: {
path: BUILD_PATH,
filename: 'bundle.js',
},
mode: process.env.NODE_ENV || 'development',
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
extensions: [ '.tsx', '.ts', '.js' ],
},
devServer: {
static: PUBLIC_PATH,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: 'ts-loader'
},
{
test: /\.(css|scss)$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.module.css$/,
use: [
{
loader: "css-loader",
options: {
modules: true,
},
},
],
},
{
test: /\.(jpg|png|svg)$/,
loader: 'url-loader',
options: {
limit: 8192,
},
},
{
test: /\.(jpg|jpeg|png|gif|mp3|svg)$/,
use: ['file-loader']
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'public', 'index.html'),
}),
],
};
Import images as components
import React from 'react';
import logo from './header-logo.svg';
import styles from './Header.module.scss';
export const Header = () => {
return <header className={styles.header}>
<img src={logo} />
</header>
};
Create the images.d.ts file in the src/types directory
declare module "*.svg" {
const content: any;
export default content;
}
And I even tried svgr..
But nothing helped. If I delete the images.d.ts file, typescript cannot detect the module when importing. When using images.d.ts, vscode does not show errors, but the picture is not displayed in the browser, and instead of the normal path, something strange data:image/svg+xml;base64,ZXhwb3J0IGRlZmF1bHQgX193ZWJwYWNrX3B1YmxpY19wYXRoX18gKyAiZWMzYzM1Nzg3YTljZTMyMzE4M2NmMzM2Y2EzMDBkOTkuc3ZnIjs=
And just in case, I attach tsconfig.json
{
"compilerOptions": {
"baseUrl": "./",
"outDir": "./build/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"plugins": [
{
"name": "typescript-plugin-css-modules"
},
],
},
}
I'm new to react so please don't judge strictly for stupid mistakes. I would appreciate any advice!
You can use svg-url-loader npm module. in webpack.config,js
{
test: /\.svg$/,
use: [
{
loader: "svg-url-loader",
options: {
limit: 10000,
},
},
],
},
in your component
// pass a correct path
import logo from './header-logo.svg';
<img src={logo} alt="" />
After some thought, I came to the conclusion that perhaps the problem occurred due to a loaders conflict. Leaving only the file-loader and deleting the url-loader, I was able to solve the problem. I hope my solution can help someone in the future.
My files now look like this:
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const SRC_PATH = path.resolve(__dirname, './src');
const BUILD_PATH = path.resolve(__dirname, './build');
const PUBLIC_PATH = path.resolve(__dirname, './public')
module.exports = {
entry: SRC_PATH + '/index.tsx',
output: {
path: BUILD_PATH,
filename: 'bundle.js',
},
mode: process.env.NODE_ENV || 'development',
resolve: {
modules: [SRC_PATH, 'node_modules'],
extensions: [ '.tsx', '.ts', '.js' ],
},
devServer: {
static: PUBLIC_PATH,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: 'ts-loader'
},
{
test: /\.(css|scss)$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.module.css$/,
use: [
{
loader: "css-loader",
options: {
modules: true,
},
},
],
},
{
test: /\.(jpg|jpeg|png|gif|mp3|svg)$/,
use: ['file-loader']
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: PUBLIC_PATH + '/index.html',
}),
],
};
A imges.d.ts file for type resolution for typescript
declare module "*.jpg";
declare module "*.png";
declare module "*.svg";

How to properly configure .png images in webpack?

Iv'e created a storybook (build without create-react-app) which deployed to npm.
When trying to use its components in a different react application the .png images that suppose to be rendered together with the component are not loading at all (for .svg images I have set configuration which works fine).
In the storybook I'm trying to import the png as the following:
import borderPurpleDashed from '../../assets/borders/border-purple-dashed.png';
border-image: url(${borderPurpleDashed}) 30 / 1.5rem round;
Then in the react-app when importing the component it tries to load the image from its own localhost
“http://localhost:3000/a87d8c2e5e8293b0372b.png”
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const svgrLoader = require.resolve('#svgr/webpack');
module.exports = {
mode: "production",
entry: './src/index.ts',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'umd',
clean: true
},
devtool: 'source-map',
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
modules: ['node_modules']
},
externals: {
react: "react"
},
module: {
rules: [
{
test: /\.(ts|tsx)?$/,
use:['ts-loader'],
exclude: /node_modules/
},
{
test: /\.(css|s[ac]ss)$/i,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
{
test: /\.svg$/i,
type: 'asset',
resourceQuery: /url/, // *.svg?url
},
{
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
resourceQuery: {not: [/url/]}, // exclude react component if *.svg?url
use: [
{
loader: svgrLoader, options: {
svgoConfig: {
plugins: [
{
name: 'removeViewBox',
active: false
}
]
}
}
},
]
},
{
test: /\.(woff(2)?|eot|ttf|otf|)$/,
type: 'asset/inline',
},
{
test: /\.(?:ico|gif|png|jpg|jpeg|)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new webpack.ProvidePlugin({
FroalaEditor: 'file_name'
})
]
};
tsconfig.json
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"target": "es5",
"module": "es2015",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"noUnusedLocals": true,
"types": ["node", "mocha", "jest","cypress", "cypress-file-upload","./cypress/support"],
"lib": ["es5","es2015","es2016", "dom","esnext"],
"outDir": "./dist/",
"sourceMap": true,
"declarationMap": true,
"declaration": true,
"resolveJsonModule": true,
},
"include": [
"src",
"node_modules/cypress/types/cypress-global-vars.d.ts"
],
"exclude": ["node_modules", "dist", "**/*.stories.tsx", "**/*.test.tsx"]
}
Did you try file-loader?
In webpack.config.js, add this in the rule array:
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
]
}
OR
You could refer to the storybook documentation for assets

'SocketClient is not a constructor' in react-refresh-webpack-plugin

I have a typescript react project where I try to add the react-refresh-webpack-client and get this error:
my webpack config:
import HtmlWebpackPlugin from 'html-webpack-plugin';
import UglifyJsPlugin from 'uglifyjs-webpack-plugin';
import ReactRefreshWebpackPlugin from '#pmmmwh/react-refresh-webpack-plugin';
import ReactRefreshTypeScript from 'react-refresh-typescript';
import { HotModuleReplacementPlugin } from 'webpack';
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"]
},
optimization: {
minimizer: [new UglifyJsPlugin()],
splitChunks: {
chunks: 'all'
}
},
mode: isDevelopment ? 'development' : 'production',
devServer: {
port: process.env.WEBPACK_PORT,
historyApiFallback: true,
hot: true,
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('ts-loader'),
options: {
getCustomTransformers: () => ({
before: isDevelopment ? [ReactRefreshTypeScript()] : [],
}),
// `ts-loader` does not work with HMR unless `transpileOnly` is used.
transpileOnly: isDevelopment,
},
},
],
},
{ enforce: "pre",
test: /\.js$/,
exclude: /node_modules/,
loader: 'source-map-loader'
},
{
test: /\.(png|jpe?g|gif)$/i,
type: 'asset/resource'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './client/index.html',
filename: './index.html',
}),
isDevelopment && new HotModuleReplacementPlugin(),
isDevelopment && new ReactRefreshWebpackPlugin(),
],
entry: {
main: ['./client/index.tsx'],
vendor: ['lodash', 'react', '#material-ui/core'],
},
devtool: 'source-map'
};
here is my tsconfig file:
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"jsx": "react",
"strict": true,
"alwaysStrict": true,
"noImplicitReturns": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": [
"node_modules"
]
}
I already removed react-hot-loader from my code.
can't find any example of react-refresh with typescript, what am I missing?
what could be my problem? thank you!
I would use export default { instead of module.exports = {
You need to use the latest refresh plugin (0.5.0), explanation is in this issue.

Webpack+ tsconfig + dynamic import throwing NoEmit error for .d.ts files

I'm having a strange issue understanding how webpack, tsconfig and .d.ts files are working together.
I've the following project structure:
The ScriptsApp contains an #types folder as follows:
My tsconfig.json is as follows:
{
"compilerOptions": {
"target": "ES2018",
"module": "esnext",
"lib": [
"es6",
"dom",
"scripthost",
"es2018",
"es2018.promise"
],
"jsx": "react",
"sourceMap": true,
"outDir": "./.out/",
"noImplicitAny": true,
"strictFunctionTypes": true,
"alwaysStrict": true,
"moduleResolution": "node",
"typeRoots": ["node_modules/#types", "ScriptsApp/#types"]
},
"include": ["./ScriptsApp/**/*.tsx", "./ScriptsApp/**/*.ts", "ScriptsApp/#types"],
"exclude": ["node_modules"],
"files": ["ScriptsApp/indexApp.tsx"]
}
And this is my webpack config:
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
module.exports = {
mode: "development",
output: {
filename: "[name].bundle.[hash].js",
path: path.join(__dirname, ".out/"),
chunkFilename: "[name].chunk.js",
publicPath: "/",
hotUpdateChunkFilename: ".hot/hot-update.js",
hotUpdateMainFilename: ".hot/hot-update.json"
},
optimization: {
runtimeChunk: {
name: "manifest"
},
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
priority: -20,
chunks: "all"
}
}
}
},
target: "web",
devServer: {
contentBase: ".out/",
hot: true
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
inject: true,
template: path.join(__dirname, "./index.html")
}),
new ForkTsCheckerWebpackPlugin({
checkSyntacticErrors: true,
tslint: "./tslint.json",
tslintAutoFix: true,
tsconfig: "./tsconfig.json",
async: false,
reportFiles: ["ScriptsApp/**/*"]
})
],
module: {
rules: [
{
test: /\.(png|jpg|ico)$/,
loader: "file-loader",
options: {
name: ".img/[name].[ext]?[hash]",
publicPath: "/"
}
},
{
test: /\.(woff(2)?|ttf|eot|svg|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "file-loader",
options: {
name: ".fonts/[name].[ext]?[hash]",
publicPath: "/"
}
},
{
test: /\.(ts|tsx)$/,
use:"ts-loader"
},
{
test: /\.js$/,
loader: "source-map-loader"
},
{
test: /\.scss$/,
use: [
{
loader: "style-loader" // creates style nodes from JS strings
},
{
loader: "css-loader",
options: {
sourceMap: true
}
},
{
loader: "resolve-url-loader"
},
{
loader: "sass-loader",
options: {
sourceMap: true
}
}
]
}
]
},
resolve: {
extensions: [".js", ".ts", ".tsx", ".scss", ".css", ".png", ".ico", ".json"]
},
devtool: "source-map"
};
Now my question:
I'm trying to use dynamic imports in one of my React components as follows:
private loadComponentFromPath(path: string) {
import(`../../ScriptsApp/${path}`).then(component =>
this.setState({
component: component.default
})
);
}
As soon as I added dynamic import, my build started showing this error for all the .d.ts files inside ScriptsApp/#types folder
WARNING in ./ScriptsApp/#types/react-adal.d.ts
Module build failed (from ./node_modules/ts-loader/index.js):
Error: TypeScript emitted no output for C:\code\AzureCXP-Eng\src\Applications\AzureCxpWebSite\WebSite\FeedbackSrc\App\ScriptsApp\#types\react-adal.d.ts.
at makeSourceMapAndFinish (C:\code\AzureCXP-Eng\src\Applications\AzureCxpWebSite\WebSite\FeedbackSrc\App\node_modules\ts-loader\dist\index.js:78:15)
at successLoader (C:\code\AzureCXP-Eng\src\Applications\AzureCxpWebSite\WebSite\FeedbackSrc\App\node_modules\ts-loader\dist\index.js:68:9)
at Object.loader (C:\code\AzureCXP-Eng\src\Applications\AzureCxpWebSite\WebSite\FeedbackSrc\App\node_modules\ts-loader\dist\index.js:22:12)
# ./ScriptsApp lazy ^\.\/.*$ namespace object ./#types/react-adal.d.ts
# ./ScriptsApp/Routes/AppRoutesList.tsx
# ./ScriptsApp/Routes/Routes.tsx
# ./ScriptsApp/Components/App.tsx
# ./ScriptsApp/indexApp.tsx
# ./ScriptsApp/index.tsx
# multi webpack-hot-middleware/client?reload=true ./ScriptsApp/index.tsx
How I can currently make the error go away?
Move #types folder outside the ScriptsApp, or
Not use dynamic imports
Rename all .d.ts files under ScriptsApp/#types to .interface.ts --> most baffling to me
I'm not able to understand why though. I'm also new to the entire technology stack so sorry if I'm missing something obvious. Please explain this behavior. Also, any suggestions on improving the configs are also much appreciated. Thanks.
From another GitHub issue you can do this in your tsconfig.json file.
{
"compilerOptions": {
"target": "ES6",
"jsx": "react",
"noEmit": true
},
"exclude": [
"node_modules",
"dev_server.js"
]
}
Could you try to add the line "noEmit": false.
The following code in tsconfig.json should ensure that the errors from within node_modules will not be reported.
{
compilerOptions: {
skipLibCheck: true
}
}

Unable to import sass files in react

I'm using the following boilerplate : https://github.com/spravo/typescript-react-express
I designed a first component : Button
import * as React from 'react';
import './button.scss';
export interface IButton {
value: string;
onClick: () => void;
}
class Button extends React.Component<IButton, {}> {
public render () {
return (
<div className='Button'>
<button onClick={this.props.onClick} className='Button__btn'>
{this.props.value}
</button>
</div>
);
}
}
export default Button;
And in the button.scss I have this simple line :
.Button {
background-color: red;
}
But I get the following error :
(function (exports, require, module, __filename, __dirname) { .Button {
SyntaxError: Unexpected token .
This is my webpack config (quite the same as the one from the repo) except the config.common.js for the webpack migration :
'use strict';
const path = require('path');
const webpack = require("webpack");
const config = require('../config')(process.env.NODE_ENV);
const vendors = require('./vendor');
const NODE_ENV = process.env.NODE_ENV || 'development';
module.exports = function getConfig(dirname) {
return {
target: 'web',
context: path.resolve(dirname),
stats: {
chunks: false,
colors: true,
},
entry: {
vendor: vendors
},
resolve: {
extensions: [ '.ts', '.tsx', '.js', '.scss', '.css' ],
modules: [
path.resolve(__dirname, '..', 'src'),
path.resolve(__dirname, '..', 'node_modules'),
]
},
output: {
path: config.PUBLIC_FOLDER,
filename: '[name].[hash].js',
chunkFilename: '[name].[chunkhash].js',
publicPath: config.PUBLIC_PATH
},
module: {
rules: []
},
optimization:{
splitChunks:{
name: 'vendor'
}
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'vendor'
// }),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(NODE_ENV)
},
__CLIENT__: true,
__SERVER__: false,
__DEV__: NODE_ENV === 'development',
__TEST__: false
})
]
};
};
The Css loaders :
'use strict';
const isDevelopment = (process.env.NODE_ENV || 'development') === 'development';
const autoprefixer = require('autoprefixer');
module.exports = {
scssLoader: [
{
loader: 'css-loader',
options: {
minimize: !isDevelopment,
sourceMap: isDevelopment
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: isDevelopment,
plugins: [
autoprefixer({
browsers:['ie >= 8', 'last 4 version']
})
]
}
},
{
loader: 'sass-loader',
options: {
sourceMap: isDevelopment
}
}
]
};
It seems that the only scss that works is in the styles/index.scss but I don't get why it doesn't take other scss files.
My tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"baseUrl" : "./src",
"target": "es5",
"jsx": "react",
"alwaysStrict": true,
"sourceMap": true,
"outDir": "dist",
"lib": [
"dom",
"es2015",
"es5",
"es6"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"typings"
],
"exclude": [
"node_modules"
]
}
It is really weird that your webpack config "module.rules" is an empty array!
That is where your loader settings should be added.
If I were you, I would have copied the content of css-loaders config file and pasted in your webpack.config.js file. Make sure you store it in a normal variable/object instead of module.exports.
This is how my webpack config looks like:
const styleLoader = {
test: /\.(scss)$/,
include: path.resolve(__dirname, 'src'),
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
sourceMap: true,
minimize: true
}
}, {
loader: 'postcss-loader',
options: {
sourceMap: true,
config: {
path: path.join(__dirname, 'postcss.config.js')
}
}
}, {
loader: 'sass-loader',
options: { sourceMap: true }
}
]
})
};
And then my webpack config looks like below. Pay attention to the modules.rules section:
module.exports = {
entry: {
app: ['babel-polyfill', 'whatwg-fetch', srcPath('index.js')],
silentrenew: [srcPath('silentrenew.js')]
},
output: {
path: path.resolve(__dirname, 'build'),
publicPath: '/',
filename: '[name].js'
},
devtool: 'source-map',
plugins: [
htmlPlugin('index.html', 'app'),
new ExtractTextPlugin('bundle.css'),
],
module: {
rules: [
babelLoader,
fontLoader,
svgLoader,
styleLoader
]
}
};

Resources