I'm using create-react-app to create a component library using Storybook JS. The aim is to publish a NPM package where these components can be used in other projects. SASS is being used for this library, with global variables defined and imported into the src/index.js file.
I'm at the point of testing my NPM package, and trying to bundle it with Webpack V4. This has been semi-successful using npm link on another local project. However, I'm facing an issue with MiniCssExtractPlugin where the styles are not being inserted at all into the HEAD of this project.
The SASS stylesheets are converted into CSS and added to the dist/ folder of my component library project with no issues. But these don't get applied anywhere when importing components via the NPM package to my other project, e.g. import { Button } from '#my-account/components';
There are no issues on my dev environment when using style-loader, and the styles are inserted straight into the DOM with a <style> tag. I'm sure I must be missing something, but I feel like I've exhausted everything to try diagnose this. Is create-react-app not compatible with this plugin? Or are styles not automatically injected to the HEAD of projects via NPM packages with this plugin?
The styling does work if I import the file from the NPM package into my local project, e.g. import '#my-account/components/dist/main.cd2be00655e79c5077cb.css'; - but this doesn't seem maintainable if styles are regularly updated and the file uses a hash in its name?
I attempted to add HtmlWebpackPlugin to create an index.html file, but this didn't resolve the issue.
Any help would be greatly appreciated!
webpack.config.prod.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: path.resolve('dist'),
filename: 'index.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /(node_modules)/,
use: 'babel-loader',
}
],
rules: [
{
test: /\.scss$/,
sideEffects: true,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-resources-loader',
options: {
resources: require(path.join(process.cwd(), 'src/assets/sass/WebpackStyles.js')),
hoistUseStatements: true
}
}
],
},
]
},
resolve: {
extensions: ['.js'],
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: '[name].[hash].css'
})
]
};
Component libraries regularly require css to be imported alongside the js in order to work properly. If you update your plugins as:
new MiniCssExtractPlugin({
filename: '[name].css'
})
Then you can instruct consumers of your library to add import '#my-account/components/dist/main.css' which is a little easier to swallow. I'm not sure there's a magical way to have the styles just show up without extensive custom webpack loaders in your consumers.
Related
I've been using Webpack and have found it great for bundling, polyfilling and all sorts of other functions for Javascript, however I wondered if there was a known loader / NPM script that automatically compiles designated .scss files into one.
I have previously used the sass-loader, extract-css plugin to take SCSS out of components and bundle them into the final dist file which is fine for web apps. However, a lot of my day to day work is spent working on traditional websites, so it's often not generated via components / modules.
What I'm looking for, is anytime I save an .scss file, if this is included within my entry points / rules, it automatically compiles all .scss files into a dist/styles.css sheet.
I've had a look online and Gulp seems to offer this, but it looks like Webpack already does 90% of what Gulp offers so ideally would want to keep this all in Webpack.
Thanks in advance
import the scss file on your entry point like this import './style.scss';.
Install mini-css-extract-plugin.
Then add this to your webpack config
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
rules: [
{
test: /\.(sc|c)ss$/,
use: [
// { loader: 'style-loader' },
{
loader: MiniCssExtractPlugin.loader, // extract css to a single file
options: {
publicPath: '../',
hmr: false
}
},
{
loader: 'css-loader', options: {
sourceMap: false
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: false,
plugins: [
require('autoprefixer')
]
}
},
{
loader: 'sass-loader', options: { sourceMap: false }
}
],
exclude: /node_modules/
}
],
},
I am still in the process of setting up my project configuration so I don't have any errors to work with right now, but if I am understanding the Typescript docs correctly...
It seems like Project references TypeScript Docs - Project references are not that necessary if transpiling with babel-loader in webpack. (I'm working in VSCode)
I am trying to convert an Electron app to TypeScript and currently reorganizing the folder structure so I have minimal issues.
I am trying to understand if I am on the right track and if I can avoid including "references" and instead just use "extends" to get the functionality I want.🤔
Here is my project structure ignoring all files that are not tsconfig files:
./tsconfig.json
./tsconfig-base.json
./main/tsconfig.json
./src/client/tsconfig.json
./__tests__
./__tests__/__client__/tsconfig.json
./__tests__/__main__/tsconfig.json
In this structure ./tsconfig.json would really just be for references like the example on Microsoft's Github
Electron Main Process and related files are in ./main. The tsconfig here will set "module":"commonjs" for working in node. I think it will also extend from the ./tsconfig-base.json
Electron Renderer Process and my React-Redux app files are in ./src/client. The tsconfig here sets "module":"es2015" or "module":"ESNEXT" for working with es modules. I think it will also extend from the ./tsconfig-base.json
The ./__tests__/__client__/tsconfig.json and ./__tests__/__main__/tsconfig.json would just be duplicates of the non tests folder versions similarly extending from the base config in ./
Webpack config is already set up to handle creating separate bundles for main and renderer processes so that the entire app can be in TypeScript. Is there any reason I should be using "references" in my files in the main or client folders?
Sample snippets of the Webpack config before I switch the tnry files to be .ts files (dev):
const rendererInclude = path.resolve(__dirname, "src");
const mainInclude = path.resolve(__dirname, "main");
Main Process:
module.exports = [
{
mode: "development",
entry: path.join(__dirname, "main", "swell.js"),
output: {
path: path.join(__dirname, "dist"),
filename: "main-bundle.js",
},
target: "electron-main",
node: {
__dirname: false,
__filename: false,
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
loader: "babel-loader",
include: mainInclude,
exclude: /node_modules/,
}
] } ... },
continued to Renderer Process:
{
mode: "development",
entry: path.join(__dirname, "src", "index.js"),
output: {
path: path.join(__dirname, "dist"),
filename: "renderer-bundle.js",
},
target: "electron-renderer",
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
loader: "babel-loader",
include: rendererInclude,
exclude: /node_modules/,
]} ...} ]
Project Reference will help you solve some problems in case where your test projects import src/main modules.
This is what Project Reference solves:
There’s no built-in up-to-date checking, so you end up always running tsc twice
Invoking tsc twice incurs more startup time overhead
tsc -w can’t run on multiple config files at once
read more about Project References
I've inherited a web app that uses webpack. In my app, I have a directory called "pub", which looks like this:
./pub
/styles
app.css
/images
brand.png
I have been trying unsuccessfully all morning to use these via webpack. In my webpack.config.js file, I have the following:
const path = require('path');
const projectRoot = path.resolve(__dirname, '../');
module.exports = {
entry: {
app: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
}
]
}
};
Then, in my index.js file, I have the following:
import logoImage from './public/images/brand.png';
require("css!./public/css/app.css");
When I run webpack, I receive an error that says:
BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.
You need to specify 'css-loader' instead of 'css',
see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed
I don't really understand this error. When I look at it, and then I look at my webpack.config.js file, it looks to me like I'm using css-loader. Beyond that though, how do I use a style in my webpage once the require statement is working. I'm just trying to use webpack with a web app and want to import my brand and CSS and I can't figure it out.
You don't need the css! in your require statement
require("css!./public/css/app.css");
You can just use
require("./public/css/app.css");
Because you are testing files with:
{
test: /\.css$/, // <-- here
loader: "style-loader!css-loader"
},
Or without the rule in your webpack config
// No test in rules matched but you tell webpack
// explicitly to use the css loader
require("style-loader!css-loader!./public/css/app.css");
Your hierarchy is pub/styles/app.css but the location you use in your require is public/css/app.css. It looks like you're trying to call your css from the wrong location.
If this doesn't solve your issue, check out this link https://webpack.github.io/docs/stylesheets.html
The first step on that page is to install css-loader and configure it, this might be a good place to start.
I'm trying to migrate a requireJS based app to webpack.
This app doesn't have many dependencies - actually it only needs a promise polyfill - and I've already figured out how to make webpack using the minified one.
The bundle size with requireJS used to be 43KB, when using webpack it's 121KB.
While 121KB isn't really huge it is a notable size increase.
From running webpack --display-reasons --display-modules I have learned that there seems to be some node_module dependencies included in my bundle. Way more than I expected.
I see things like buffer, readable-stream, stream-http, stream-browserify, core-util-is, buffer-shims, ...
Is this expected / part of the webpack wrapper code?
Is there anything I can do to exclude these dependencies?
This is my webpack.config.js:
var webpack = require('webpack');
module.exports = {
entry: {
"mynexuz": "./js/mynexuz-api.js",
"kws": "./js/kws-api.js",
"main": "./js/main.js",
"quest": "./js/quest.js"
},
output: {
filename: "./dist/[name]-bundle.js",
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production'),
}
})
],
node: {
//stream: false,
//process: false,
//global: false
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
modules: ['js', 'js/lib', 'node_modules'],
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
loaders: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{
test: /\.js$/,
loader: "source-map-loader",
exclude: /node_modules/
},
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
{
test: /\.tsx?$/,
loader: "awesome-typescript-loader",
exclude: /node_modules/
}
]
},
};
This doesn't work for all libraries you are using, but when possible you can save on file size by only importing the actual function/component you need to use.
Here is an example with lodash
import has from 'lodash/has';
That way above will ONLY import the has method.
However if you do either of the following:
import { has } from 'lodash';
Or
import _ from 'lodash';
Then you will import ALL of the lodash library which will bump up your file size.
However with other libraries (i.e. current version of moment.js) it's not so simple to import just the PART of the library you need.
There are a few other ways to try to solve this problem (i.e. tweaking your webpack settings) but I would start with this method.
After looking deeper into the issue I've found the reason for the large size of the bundle. In true requireJS style I had:
define(['http', 'config'], function (Http, Config) { ... });
This 'http' thing was supposed to refer to my own library, but webpack resolved this to some NPM module, bringing in all the aforementioned dependencies.
I've now changed the code to:
define(['./http', 'config'], function (Http, Config) { ... });
And the bundle sizes are back to around 44KB.
I've been banging my head over this for a while, and am wondering if it's even possible to begin with. Thanks for any help with this!
The npm package
I've got an npm package which is basically a library of React components. This library has embedded stylesheets, which references assets like fonts and images from the CSS. These are then all bundled using webpack into my-package.js.
The config for this looks like:
var path = require('path');
module.exports = {
module: {
loaders: [
{
test: /\.js$/,
loaders: ['babel-loader'],
exclude: /node_modules/
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
loader: 'file-loader'
},
{
test: /\.styl$/,
loader: 'style-loader!css-loader!stylus-loader'
}
]
},
entry: [
'./lib/components/index.js',
'./lib/index.styl'
],
output: {
path: path.join(__dirname, 'build/'),
filename: 'my-package.js'
}
}
With ./lib/components/index.js looking like:
import '../index.styl';
import MyComponent from './my-component.js';
export {
MyComponent
}
So far, so good.
The application
Now in another code base I've got the main application, which install this npm package.
My application root requires this package...
import MyPackage from 'my-package';
And is then itself webpack bundled and loaded onto the browser. All the scripts and style blocks are bundled correctly, however the styles which reference the assets are using the relative url from the npm package itself, therefore giving me 404s from the application.
console errs
Is there any way to tell webpack to resolve these images from node_modules/my-package/build/[webpack-generated-name].jpg ?
My application's webpack config looks like this:
var path = require('path'),
webpack = require('webpack');
module.exports = {
devtool: '#eval-source-map',
entry: [
'my-package',
'webpack/hot/only-dev-server',
'./app/index.js',
],
output: {
path: path.join(__dirname, 'build/static'),
filename: 'bundled.js',
publicPath: '/',
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
resolve: {
extensions: ['', '.js']
},
resolveLoader: {
'fallback': path.join(__dirname, 'node_modules')
},
module: {
loaders: [
{
test: /\.js$/,
loaders: ['react-hot', 'babel'],
exclude: /node_modules/,
include: __dirname
},
{
test: /\.css?$/,
loader: "style-loader!css-loader",
include: __dirname
},
{
test: /\.(jpg|jpeg|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader'
},
{
test: /\.styl$/,
loader: 'style-loader!css-loader!stylus-loader'
}
]
}
};
Figured out a way around this.
In my application's webpack config I added a plugin (recommended by #Interrobang) which copies the static assets from the node_module/my-package into the app server's public path:
var TransferWebpackPlugin = require('transfer-webpack-plugin');
...
plugins: [
new TransferWebpackPlugin([
{ from: 'node_modules/my-package/assets', to: path.join(__dirname, 'my/public') }
])
]
...
These will then be made accessible by calling the asset name: localhost:XXXX/my-image.jpg. The server here is basically looking at /my/public/my-image.jpg if you've set it up correctly.
I'm using Express, so I just had to define app.use(express.static('my/public')) in my app server.
When bundling your NPM package, you could inline all the static assets into your final bundle. That way, your index.js that Webpack bundles for you would have all the static assets it needs, and simply importing that wherever you need your React components would do the trick.
The best way to inline static assets as of Webpack 5 is by using the "asset/inline" module from Asset Modules, which you can read about here: https://webpack.js.org/guides/asset-modules/
Simply put, your webpack.config.js should have a section as such:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
type: 'asset/inline'
}
]
},
};
What really relates to your question here is the test for png, jpg, and gif files which uses the asset/inline module.
The post here https://stackoverflow.com/a/73417058/14358290 explains it with slightly more detail.
Other plugins that copy such files from your /node_modules to /build directory are hacky and create packages that are not really distributable - everyone else that uses the said package would have to set up their Webpack to do the same copying operation. That approach can be avoided now that Webpack 5 solves this problem for us.