Related
I'm upgrading an existing web site from Webpack 3 to Webpack 5.
The site uses server side rendering for the first load, the client side routing for any in-site navigation. All this worked fine with Webpack 3, but after migrating to Webpack 5 it looks like some of the styles are being applied via javascript and that's creating a FOUC during the first load (after that, in-site navigation works fine and looks correct). As a test, I turned off javascript in my browser; the old site loads fine and looks correct, but the upgraded site does not. It feels like I need style-loader in the server config somewhere, but when that's added, I get a "Cannot GET /" when trying to load the site. Any help is appreciated.
Server-side config
require('dotenv').config({ silent: true });
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const includePaths = [path.resolve(__dirname, 'stylesheets')];
module.exports = {
bail: true,
entry: {
main: './src/entry-server',
},
output: {
path: path.join(__dirname, 'build', 'prerender'),
filename: '[name].js',
publicPath: '/bundle/',
libraryTarget: 'commonjs2',
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production'),
PRERENDER: true,
ASSETS_CDN_PREFIX: JSON.stringify(process.env.ASSETS_CDN_PREFIX || ''),
},
}),
// only load moment english locale: https://github.com/moment/moment/issues/2517
new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /^\.\/(en)$/),
new webpack.optimize.ModuleConcatenationPlugin(),
new MiniCssExtractPlugin({
ignoreOrder: true,
}),
],
module: {
rules: [
{
test: /\.jsx?$/,
include: path.join(__dirname, 'src'),
use: 'babel-loader',
},
{
test: /\.s?css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
},
},
'postcss-loader',
{
loader: 'sass-loader',
options: {
sassOptions: {
includePaths,
data: `$assetprefix: "${process.env.ASSETS_CDN_PREFIX || ''}";`,
},
},
},
],
},
{
test: /\.svg$/,
use: `svg-inline-loader?removeTags&removingTags[]=${['defs', 'style', 'title'].join(',removingTags[]=')}`,
},
],
},
resolve: {
extensions: ['.js', '.jsx', '.css', '.scss', '.json'],
},
target: 'node',
};
Server entry point
export default function (req, res, environmentConstants, callback) {
// ...setup
match({ routes, location: targetUrl }, (error, redirectLocation, renderProps) => {
// ...setup
fetchSomeData().then(() => renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>,
))
.then((content) => {
callback(null, {
helmet: Helmet.renderStatic(),
content,
initialState: serialize(store.getState(), { isJSON: true }),
env: serialize(someEnvConstants),
});
})
Client-side config
require('dotenv').config({ silent: true });
const AssetsPlugin = require('assets-webpack-plugin');
const CleanPlugin = require('clean-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const webpack = require('webpack');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const includePaths = [path.resolve(__dirname, 'stylesheets')];
// Match all routes that we want to lazy load
const lazyRouteRegex = /route\/([^/]+\/?[^/]+)Route.jsx$/;
module.exports = {
bail: true,
entry: {
main: './src/entry-client',
vendor: [
'react',
'react-dom',
'react-router',
'redux',
'react-redux',
'xmldom',
],
},
output: {
path: path.join(__dirname, 'build', 'public', '[fullhash]'),
filename: '[name].js',
chunkFilename: '[id].chunk.js',
publicPath: `${process.env.ASSETS_CDN_PREFIX || ''}/build/public/[fullhash]/`,
},
plugins: [
// only load moment english locale: https://github.com/moment/moment/issues/2517
new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /^\.\/(en)$/),
new MiniCssExtractPlugin({
ignoreOrder: true,
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production'),
PRERENDER: false,
ASSETS_CDN_PREFIX: JSON.stringify(process.env.ASSETS_CDN_PREFIX || ''),
},
}),
new AssetsPlugin(),
new CleanPlugin([path.join(__dirname, 'build', 'public')]),
new CompressionPlugin(),
// new BundleAnalyzerPlugin(),
],
module: {
rules: [
{
test: /\.jsx?$/,
include: path.join(__dirname, 'src'),
exclude: lazyRouteRegex,
use: [
{
loader: 'babel-loader',
},
],
},
{
test: lazyRouteRegex,
include: path.resolve(__dirname, 'src'),
use: [
{
loader: 'bundle-loader',
options: {
lazy: true,
},
},
{
loader: 'babel-loader',
},
],
},
{
test: /swiper.*\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
},
{
loader: 'sass-loader',
options: {
sassOptions: {
includePaths,
data: `$assetprefix: "${process.env.ASSETS_CDN_PREFIX || ''}";`,
},
},
},
],
},
{
test: /\.s?css$/,
exclude: /swiper.*\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
},
},
{
loader: 'postcss-loader',
},
{
loader: 'sass-loader',
options: {
sassOptions: {
includePaths,
data: `$assetprefix: "${process.env.ASSETS_CDN_PREFIX || ''}";`,
},
},
},
],
},
{
test: /\.svg$/,
use: `svg-inline-loader?removeTags&removingTags[]=${['defs', 'style', 'title'].join(',removingTags[]=')}`,
},
],
},
resolve: {
extensions: ['.js', '.jsx', '.css', '.scss', '.json'],
},
target: 'web',
};
Before I had the following webpack.js:
const path = require('path');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const config = {
entry: {
index: path.resolve(__dirname, 'src') + '/index.jsx
},
plugins: [
new CleanWebpackPlugin({
cleanAfterEveryBuildPatterns: ['static/dist']
})
],
resolve: {
extensions: ['.js','.jsx','.css'],
alias: {
'#src': path.resolve(__dirname, 'src')
}
},
module: {
rules: [
{
test: /\.jsx?/,
loader: 'babel-loader',
exclude: /node_modules/,
query:{
presets: ['#babel/react', '#babel/preset-env'],
plugins: [
['#babel/plugin-proposal-decorators', { 'legacy': true }],
['#babel/plugin-proposal-class-properties', {'loose': true}],
'#babel/transform-runtime'
]
}
},
{
test: /\.html$/,
use: 'html-loader'
},
{
test: /\.md$/,
use: [
{
loader: "html-loader"
},
{
loader: "markdown-loader",
}
]
}
]}
};
module.exports = config;
Everything worked. After I decided to add environment variables into it I had to rewrite the content to:
module.exports = (env)=>{
const envPath = env.NODE_ENV ? `.env.${env.NODE_ENV}` : '.env';
const config = {
entry: {
index: path.resolve(__dirname, 'src') + '/index.jsx
},
plugins: [
new CleanWebpackPlugin({
cleanAfterEveryBuildPatterns: ['static/dist']
})
],
resolve: {
extensions: ['.js','.jsx','.css'],
alias: {
'#src': path.resolve(__dirname, 'src')
}
},
module: {
rules: [
{
test: /\.jsx?/,
loader: 'babel-loader',
exclude: /node_modules/,
query:{
presets: ['#babel/react', '#babel/preset-env'],
plugins: [
['#babel/plugin-proposal-decorators', { 'legacy': true }],
['#babel/plugin-proposal-class-properties', {'loose': true}],
'#babel/transform-runtime'
]
}
},
{
test: /\.html$/,
use: 'html-loader'
},
{
test: /\.md$/,
use: [
{
loader: "html-loader"
},
{
loader: "markdown-loader",
}
]
}
]}
};
return config;
}
Now I have the following error:
Insufficient number of arguments or no entry found.
Alternatively, run 'webpack(-cli) --help' for usage info.
ERROR in Entry module not found: Error: Can't resolve './src' in 'D:\path'
resolve './src' in 'D:\path'
Why now is it not working and how to fix it?
The index.jsx is in path/src folder.
Found the solution. I forgot I used merging configs so I have a webpack.dev.js which has the following line:
const common = require('./webpack.common.js');
And after the refactoring, as the webpack.common.js returns the function instead of the object the line should be changed to:
const common = require('./webpack.common.js')();
I'm using React component as an NPM Package. in the component, I have SCSS file
with url(../iamges/img..) path, but actually, the images folder located in the Dist folder, how can I point Webpack to take the relative path from node_modules and serve it from images folder located in the Dist?
located in node_modules =>
background: url('../images/some-icon.svg') no-repeat center center;
Webpack config:
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
devtool: 'inline-module-source-map',
output: {
path: path.resolve(__dirname, '/dist'),
filename: 'bundle.js',
publicPath: '/',
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.scss$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{
loader: 'resolve-url-loader',
// options: {
// debug: true,
// root: path.join(__dirname, './dist/images'),
// includeRoot: true,
// absolute: true,
// },
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
sourceMapContents: false,
},
},
],
},
{
test: /\.css$/,
loaders: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader?name=/images/[name].[ext]',
options: {
limit: 10000,
},
},
},
],
},
resolve: {
extensions: ['.js', '.jsx'],
// modules: [path.resolve(__dirname, '/images'), 'node_modules'],
alias: {
'react-redux': path.resolve('./node_modules/react-redux'),
},
},
plugins: [new webpack.HotModuleReplacementPlugin()],
devServer: {
hot: true,
publicPath: '/dist/',
},
};
babel.config.js
module.exports = {
// presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: [
'#babel/plugin-proposal-class-properties',
'#babel/plugin-proposal-export-default-from',
'#babel/transform-runtime',
],
sourceType: 'unambiguous',
presets: [
[
'#babel/preset-env',
{
targets: {
node: 'current',
},
},
],
'#babel/preset-react',
],
};
dist
-- images
-- index.html
ERROR:
ERROR in ./node_modules/comp/src/styles/details.scss (./node_modules/css-loader!./node_modules/resolve-url-loader!./node_modules/sass-loader/lib/loader.js??ref--5-3!./node_modules/compdetails/src/styles/details.scss)
Module not found: Error: Can't resolve '../images/icon.svg'
Anything referred through url('...') in css will be computed with reference to the path of deployed application (scss will not compute the path unless variable or function is not being used):
For example:
If your referred component SCSS module is having background: url('../images/some-icon.svg') no-repeat center center;
The final CSS compilation will be same (it is also because the component is not using any SCSS variables or functions to compute the final path).
So your application will always try to find that image as:
Example: http://localhost:3000/../images/some-icon.svg which is a problem.
(.. is referred as parent directory)
If you try to run your app with some sub-url (also known as sub context) as http://localhost:3000/sub-url/ and you keep your images parallel to sub-url folder it will automatically work.
-- /
|
|-- sub-url
|
|-- index.html
|-- images
|
|-- some-icon.svg
Another option can be override the component SCSS with yours.
You already found the solution to use resolve-url-loader, but in this case you need to import the component's scss file into you scss.
so your webpack config should look like:
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
devtool: 'inline-module-source-map',
output: {
path: path.resolve(__dirname, '/dist'),
filename: 'bundle.js',
publicPath: '/',
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.scss$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
// CHANGE HERE
{
loader: 'resolve-url-loader',
options: {
root: '/images', // considering all your images are placed in specified folder. Note: this is just a string that will get as prefix to image path
includeRoot: true,
absolute: true,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
sourceMapContents: false,
},
},
],
},
{
test: /\.css$/,
loaders: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader?name=/images/[name].[ext]',
options: {
limit: 10000,
},
},
},
],
},
resolve: {
extensions: ['.js', '.jsx'],
// modules: [path.resolve(__dirname, '/images'), 'node_modules'],
alias: {
'react-redux': path.resolve('./node_modules/react-redux'),
},
},
plugins: [new webpack.HotModuleReplacementPlugin()],
devServer: {
hot: true,
publicPath: '/dist/',
},
};
I hope it helps.
I'm integrating typescript in my React app which has significant amount of code. I've some app level HOCs which I apply on React Components as :
import React from 'react';
import HOC1 from 'app/hocs/hoc1';
import compose from 'recompose/compose;
class MyComponent from React.Component {
...component stuff...
}
export default compose(HOC1())(MyComponent);
But now I've added typescript in my application, whenever I'm importing
import HOC1 from 'app/hocs/hoc1';
it says
TS2307: Cannot find module 'app/hocs/hoc1'.
I don't want to add type definitions to all my HOCs. What is the solution and why I'm getting this error?
[EDIT] I'm using baseUrl in tsconfig as well. My folder structure is
/Project/configs/tsconfig
/Project/src/app/hocs
In tsconfig, I've given baseUrl as ../src through this documentation.
Another Edit
And my webpack config looks like :
{
test: /\.(t|j)sx?$/,
loader: 'happypack/loader?id=jsx-without-proptypes',
include: [
path.resolve(__dirname),
path.resolve(__dirname, '../src'),
],
},
The whole webpack config looks something like
const config = {
context: path.resolve(__dirname, '../src'),
mode: NODE_ENV,
optimization: {
splitChunks: false,
nodeEnv: NODE_ENV,
minimize: false,
},
node: {
net: 'empty',
},
output: {
path: path.resolve(__dirname, '../build/public/assets'),
publicPath: '/assets/',
sourcePrefix: ' ',
pathinfo: DEBUG, //https://webpack.js.org/configuration/output/#output-pathinfo
},
module: {
noParse: [/html2canvas/],
rules: [
{
test: /\.tsx?$/,
enforce: 'pre',
use: { loader: 'awesome-typescript-loader' },
},
...shimLoaders,
...selectiveModulesLoader,
{
test: /\.(t|j)sx?$/,
loader: 'happypack/loader?id=jsx-without-proptypes',
include: [
path.resolve(__dirname),
path.resolve(__dirname, '../src'),
],
},
{
test: /\.jsx?$/,
loader: 'happypack/loader?id=jsx-without-lodash-plugin',
include: [
path.resolve(__dirname, '../src/app/modules/insights/'),
],
exclude: /node_modules/,
},
{
test: /\.jsx?$/,
loader: 'happypack/loader?id=jsx-with-proptypes',
},
{
test: /\.jsx?$/,
loader: 'babel-loader',
query: {
presets: [['env', { include: ['babel-plugin-transform-es2015-template-literals'] }]],
},
},
{
test: /\.css$/,
use: getCssLoaders({
pwd: PWD,
debug: DEBUG,
}),
},
...getScssRules(DEBUG, PWD),
{
test: /\.less$/,
use: [DEBUG ? 'css-loader' : 'css-loader?minimize', 'less-loader'],
},
{
test: /\.txt$/,
loader: 'raw-loader',
},
{
test: /\.svg$/,
loader: 'spr-svg-loader',
},
{
test: /\.(png|jpg|jpeg|gif)$/,
loader: 'url-loader',
query: {
name: DEBUG ? '[path][name].[ext]' : '[hash].[ext]', // ?[hash]
limit: 10000,
},
},
{
test: /\.(woff|woff2)$/,
loader: 'url-loader?name=fonts/[name].[ext]&limit=65000&mimetype=application/font-woff',
},
{
test: /\.(otf|ttf)$/,
loader: 'url-loader?name=fonts/[name].[ext]&limit=65000&mimetype=application/octet-stream',
},
{
test: /\.eot$/,
loader: 'url-loader?name=fonts/[name].[ext]&limit=65000&mimetype=application/vnd.ms-fontobject',
},
{
test: /\.(wav|mp3)$/,
loader: 'file-loader',
query: {
name: DEBUG ? '[path][name].[ext]' : '[hash].[ext]', // ?[hash]
},
},
{
test: /\.pug/,
loader: 'pug-loader',
},
{
test: /\.html$/,
include: /src\/app/,
loader: StringReplacePlugin.replace({
replacements: [
{
//Replaces ES6 strings from languagePack to simple string
pattern: /__\(\s*`([^`]*)`\s*\)/gi,
replacement: (match, p1) => {
let replacedStr = p1;
replacedStr = replacedStr.replace(new RegExp('\\$\\{([\\w\\.\\:\\-]+)\\}', 'g'), '\' + $1 + \'');
return `'${replacedStr}'`;
},
},
{
//Following methods - look out carefully for the *quotes* (single/double)
//doing what i18nPlugin would do for html files - with the *single* quotes
pattern: /__\(\s*'(.+?)'\s*\)/g,
replacement: (match, p1) => {
const replacedStr = p1;
return `'${replacedStr}'`;
},
},
{
//doing what i18nPlugin would do for html files - with the *double* quotes
pattern: /__\(\s*"(.+?)"\s*\)/g,
replacement: (match, p1) => {
const replacedStr = p1;
return `"${replacedStr}"`;
},
},
],
}),
},
],
},
resolve: {
modules: [
path.resolve(PWD),
path.resolve(PWD, '..'),
'node_modules',
'web_modules',
'src',
],
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.webpack.js', '.web.js'],
alias: ALIAS,
// symlinks: false, //https://webpack.js.org/configuration/resolve/#resolve-symlinks, https://github.com/webpack/webpack/issues/1643
},
plugins: [getProvidePlugin(), getLoaderOptionPlugin({ debug: DEBUG }), ...getHappypackPlugin({ debug: DEBUG })],
resolveLoader: {
modules: ['node_modules', path.resolve(PWD, '../../node_modules'), path.resolve(PWD, './config/loaders/')],
alias: {
text: 'raw-loader', // treat text plugin as raw-loader
jst: 'ejs-loader',
style: 'style-loader',
imports: 'imports-loader',
},
},
bail: !DEBUG,
watch: DEBUG,
cache: DEBUG,
stats: DEBUG ?
{
colors: true,
reasons: false,
hash: VERBOSE,
version: VERBOSE,
timings: true,
chunks: false,
chunkModules: VERBOSE,
cached: VERBOSE,
cachedAssets: VERBOSE,
performance: true,
} :
{ all: false, assets: true, warnings: true, errors: true, errorDetails: false },
};
Another Edit
Alias as defined on webpack config also didn't do the trick.
https://webpack.js.org/configuration/resolve/#resolve-alias
Using Alias
To map the imports to the right files you'll need to use the config.resolve.alias field in the webpack configuration.
Where in your scenario will look like:
const config = {
resolve: {
alias: {
'app': path.resolve(__dirname, 'src/app/'),
},
},
};
I am not 100% sure I am doing it right, but I also specify paths key in tsconfig, like this:
"baseUrl": "./src",
"paths": {
"#common/*": ["./common/*"],
"#moduleA/*": ["./moduleA/*"],
"#moduleB/*": ["./moduleB/*"],
}
and it seems to work all right. # prefix is just something i do to discern between my modules and npm dependencies
I have the following jsx component:
import React, { Component } from 'react';
class App extends Component {
render () {
return (
<div className="app">
<h2>Welcome to React</h2>
</div>
)
}
}
export default App;
and my webpack file:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
context: path.resolve(__dirname, './src'),
entry: {
app: './index.jsx'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, './dist/assets'),
publicPath: '/assets',
},
devServer: {
contentBase: path.resolve(__dirname, './src')
},
module: {
rules: [
{
test: /\.jsx$/,
exclude: [/node-modules/],
use: [
{
loader: "babel-loader",
options: { presets: ["es2015"] }
}
]
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: [{
loader: 'css-loader',
options: { importLoaders: 1 },
}],
}),
},
{
test: /\.(sass|scss)$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
]
},
resolve: {
modules: [
path.resolve(__dirname, './src'),
'node_modules'
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common'
}),
new ExtractTextPlugin({
filename: '[name].bundle.css',
allChunks: true
})
]
}
however when I run:
node_modules/.bin/webpack -d
I get the following error:
Add "babel-preset-react"
npm install babel-preset-react
and add "presets" option to babel-loader in your webpack.config.js
Here is an example webpack.config.js:
module: {
loaders: [{
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['react', 'es2015', 'stage-1']
}
}]
}