Problem
When building and starting a production build of our application, no css is loaded. CHecking the devtools, I can see a myriad of errors and warnings:
Possible Culprits
I do not experience any of these problems, when starting the app in dev mode. Also, other assets like images or fonts are loaded correctly. We use scss and import the global stylesheet in _app.tsx like this:
import "../styles/globals.scss";
In order to solve a problem with another library, we had to setup a custom webpack config:
module.exports = phase => ({
webpack: (config, { isServer }) => {
config.module.rules.push({
test: /\.node$/,
use: "node-loader"
});
config.module.rules.push({
test: /\.(ts|js)x?$/,
use: [
{
loader: "ts-loader",
options: {
transpileOnly: true,
experimentalWatchApi: true,
onlyCompileBundledFiles: true
}
}
],
include: [path.resolve(__dirname, "node_modules/#private/")],
exclude: [path.resolve(__dirname, "node_modules/#private/src/styleguide")]
});
if (!isServer) {
config.module.rules.push({
test: /\.s?[ac]ss$/i,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
{
loader: "resolve-url-loader",
options: { removeCR: true, debug: true }
},
{ loader: "sass-loader" }
]
});
}
config.module.rules.push({
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: "file-loader",
options: {
name: "[name].[ext]",
exclude: /node_modules/
}
}
],
exclude: [path.resolve(__dirname, "node_modules/#private/src")]
});
return config;
}
});
Also, we this is the file of the custom server we use to start the application in production mode:
const PORT = parseInt(process.env.PORT, 10) || 3364;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
server.get("*", (req, res) => handle(req, res));
server.listen(PORT, err => {
if (err) throw err;
console.log(`🐎 => Ready on http://localhost:${PORT}`);
});
});
Assumptions
As the custom server is the only difference between production and development, I can only assume that the error is maybe somewhere there. But it looks fine to me. So if anybody has a hint or an idea, I would be very grateful.
Ok, I just deleted the .next folder prior to building the production version via npm run build and after that, everything worked. Seems like there is some problems with the chunk generation when the .next folder is there.
Related
In my current project we are migrating an application to nextjs. Now I would like to inject a react component into a legacy page through including a script, which is using code from the new nextjs application. Her a simplified version of the script:
function injectCode() {
const container = document.getElementById("container")
const reactRoot = createRoot(container)
reactRoot.render(<ExistingComponent />)
}
document.addEventListener('load', injectCode)
How can I generate a js file, that can be included into a webpage not rendered by nextjs? Best would be to generate the file with next build, but directly running webpack would be ok as well.
Until now I tried to write my own webpack config and just run webpack directly. It would be ok if I run webpack once and check the generated file into git. Therefore the generated file is put into the public folder.
This is not the only webpack config I tried, but after too many iterations solving one problem and getting the next I gave up thinking there is probably an easier solution.
module.exports = {
entry: './script.tsx',
module: {
rules: [
{
test: /\.(ts|tsx)?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.(ts|tsx)?$/,
include: path.resolve(__dirname, 'lib'),
use: [
{
loader: 'ts-loader',
},
],
},
{
test: /\.(ts|tsx)?$/,
include: path.resolve(__dirname, 'components'),
use: [
{
loader: 'ts-loader',
},
],
},
{
test: /\.(ts|tsx)?$/,
include: path.resolve(__dirname, 'single-header-footer'),
use: [
{
loader: 'ts-loader',
},
],
},
{
test: /\.(js)?$/,
include: path.resolve(__dirname, 'node-modules'),
use: [
{
loader: 'ts-loader',
},
],
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
path: path.resolve(__dirname, 'public'),
filename: 'next-js-component.js',
},
}
I am probably missing a lot of settings which are required.
The second thing I tried was extending the webpack configuration within the next.config.js file:
...
webpack: (config, { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }) => {
const originalEntry = config.entry
config.entry = async () => {
const entries = await originalEntry()
entries['next-js-component'] = ['script.tsx']
return entries
}
return config
},
...
This creates a chunk file .next/static/chunks/next-js-component-dc0ae8d2d7a87ec9.js. But I don't know how to use this file. Probably I need to add some other code into the page which loads the created chunk and additional chunks.
The header and footer of the legacy pages are already rendered by the next.js app and are included into the page via server side include. So I might be able to use something like the HtmlWebpackPlugin to inject the correct script tag into the header, which is then included into the legacy page.
I'm using Next.JS with a few other modules. One of them, Megadraft, comes with its own CSS. I don't know if this is relevant, but I also use PurgeCSS.
Everything works fine on development mode, but the CSS seems to break in production mode. To be a little more explicit, all of the classes of Megadraft, seem to have no definition in production mode.
The HTML nodes in the inspector still show that the classes are here, but they have just no definition.
Here's how I import the said CSS files in my pages/_app.js file:
// pages/_app.js
import "css/tailwind.css";
import "megadraft/dist/css/megadraft.css";
And this is my postcss.config.js:
// postcss.config.js
const purgecss = [
"#fullhuman/postcss-purgecss",
{
content: [
"./components/**/*.js",
"./Layout/**/*.js",
"./pages/**/*.js",
"./node_modules/next/dist/pages/**/*.js",
"./node_modules/next/dist/Layout/**/*.js",
"./node_modules/next/dist/components/**/*.js"
],
defaultExtractor: (content) => content.match(/[A-Za-z0-9-_:/]+/g) || [],
},
];
module.exports = {
plugins: [
"postcss-import",
"tailwindcss",
"autoprefixer",
...(process.env.NODE_ENV === "production" ? [purgecss] : []),
],
};
I'm using next ^9.4.4. It may be worth noticing that TailwindCSS seems to work just fine (both in dev and prod), but I think it may be because it is used as a plugin in postcss...
Just in case also, I integrated webpack to my project to solve an error I had where the code was telling that I needed a loader:
// next.config.js
module.exports = {
cssModules: true,
webpack: (config, options) => {
config.node = {
fs: "empty",
};
config.module.rules.push({
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
use: [
options.defaultLoaders.babel,
{
loader: "url-loader?limit=100000",
},
{
loader: "file-loader",
},
],
});
return config;
},
};
Anyway, if someone has an idea of why this works in development mode and not in production, it could be of great help.
Option 1: use Tailwind CSS built-in PurgeCSS
// tailwind.config.css
module.exports = {
purge: ["./components/**/*.js", "./pages/**/*.js"],
theme: {
extend: {}
},
variants: {},
plugins: []
};
// postcss.config.js
module.exports = {
plugins: ["tailwindcss", "postcss-preset-env"]
};
Be sure to add postcss-preset-env to the package's dev dependencies with npm i --save-dev postcss-preset-env or yarn add -D postcss-preset-env.
Option 2: Manually setup purge and add "./node_modules/megadraft/dist/**/*.css" to purgecss whitelisting content array:
// tailwind.config.css
module.exports = {
theme: {
extend: {}
},
variants: {},
plugins: []
};
// postcss.config.js
const purgecss = ['#fullhuman/postcss-purgecss',{
content: ["./node_modules/megadraft/dist/**/*.css", "./components/**/*.js", "./pages/**/*.js"],
defaultExtractor: content => {
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []
const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || []
return broadMatches.concat(innerMatches)
}
}]
module.exports = {
plugins: [
'tailwindcss',
'autoprefixer',
...process.env.NODE_ENV === 'production'
? [purgecss]
: []
]
}
There may be better solutions but these two are what I can think of.
Thank you guys for reading my question. Really hoping for a solution to this, I've been trying to find a fix for days to no avail.
Here's the rundown: My goal is to render a React application on the server-side using .NET Core. I haven't even started with the react part yet, right now I'm simply trying to render an h1 tag with the ASP.NET Javascript Services Prerendering functionality.
On my first iteration, I wrote my boot-server.js file with es5 and it worked perfectly. However, I quickly realized I was going to need to compile the file through webpack in order for it to understand my React code.
As soon as I piped that file through webpack though, I got a "window is not defined" error which I haven't been able to fix. I understand that part of my application should not be aware of the window object since it lives in the server but setting the webpack config's target field to node does not seem to fix the issue. Below are all the files involved.
Here is my boot-server.js file:
import { createServerRenderer } from 'aspnet-prerendering';
module.exports = createServerRenderer((params) => {
return new Promise((resolve, reject) => {
var html = '<h1>Hello world!</h1>';
resolve({
html: html
});
});
});
Here is my cshtml view:
#addTagHelper "*, Microsoft.AspNetCore.SpaServices"
<app id="root" asp-prerender-module="wwwroot/client/boot-server.bundle">Loading...</app>
Here is my webpack.config.js file:
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const path = require("path");
const clientConfig = {
entry: './FrontEnd/index.js',
output: {
path: path.resolve(__dirname, "wwwroot/client"),
filename: "client.min.js",
publicPath: "/client/"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["es2015", "react", "stage-0"],
plugins: ["transform-class-properties", "transform-decorators-legacy", "react-html-attrs"]
}
}
},
{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader"
}]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader?name=[name]_[hash:8].[ext]'
]
}
]
},
mode: 'development',
devServer: {
contentBase: './wwwroot/client',
hot: true
}
};
const bootServerConfig = {
stats: { modules: false },
resolve: { extensions: ['.js'] },
output: {
filename: '[name].js',
publicPath: '/wwwroot/client/', // Webpack dev middleware, if enabled, handles requests for this URL prefix
libraryTarget: 'commonjs'
},
entry: {
'main-server': './FrontEnd/boot-server.js'
},
module: {
rules: [
{
test: /\.js$/,
include: /FrontEnd/,
use: [
{
loader: "babel-loader",
options: {
presets: ["es2015", "react", "stage-0"],
plugins: ["transform-class-properties", "transform-decorators-legacy", "react-html-attrs"]
}
}
]
},
{
test: /\.svg$/,
use: {
loader: 'url-loader',
options: { limit: 25000 } //?limit=100000'
}
}
]
},
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './wwwroot/client')
},
target: 'node'
}
module.exports = [clientConfig, bootServerConfig];
Here is a screenshot of the error page:
been having a tremendously difficult time determining why my stylesheets seem to be ignored in a package I am trying to modify for my own use. Not sure if this is a problem with Material-UI or Webpack itself, but any require or import statements I add to the head of any .js doc throw errors when running build script. The same imports for 'import style from './style.css' works in documents from original repository.
Best I am able to analyze the Webpack configs I am using seem to disregard any stylesheets except those a handful that were included with the original package AND any modifications within stylesheets that do render to the DOM are also disregarded. Everything I have researched indicates that this config works, and throws no errors when I run the corresponding build script.
Please help! Thank you!
webpack.production.configjs
/* eslint-env node */
/* eslint no-console: 0, no-var: 0 */
// Webpack config for PRODUCTION and DEVELOPMENT modes.
// Changes needed if used for devserver mode.
const webpack = require('webpack');
const rucksack = require('rucksack-css');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const envalid = require('envalid');
const path = require('path');
// Validate environment variables
validateEnvironmentVariables();
const config = require('config');
if (config.NODE_ENV === 'devserver') {
throw new Error('This webpack config does not work as is with the web-dev-server.')
}
const isProduction = config.isProduction;
const outputPublicPaths = {
production: '/dist/',
development: '/dist/',
devserver: 'http://localhost:8080/', // we don't use this config for webpack-dev-server
};
console.log(`----- ${config.NODE_ENV.toUpperCase()} build.`); // eslint-disable-line no-console
// Base Webpack configuration
const webpackConfig = {
context: path.join(__dirname, 'client'),
// re devtool: http://cheng.logdown.com/posts/2016/03/25/679045
devtool: isProduction ? 'cheap-module-source-map' : 'source-map',
entry: {
main: ['./index.js'],
// Webpack cannot produce chunks with a stable chunk hash as of June 2016,
// stable meaning the hash value changes only when the the code itself changes.
// See doc/FAQ.md#webpackChunks.
// This vendor chunk will not reduce network requests, it will likely force a second request
// each time the main chunk changes. So why separate them?
// Chunks for code which is dynamically optionally loaded makes sense.
// The first page will render faster as the parsing of such chunks can be delayed
// till they are needed.
// Of course the React routing must be changed to load such chunks as needed.
// Maybe we'll make the routing do that at some time.
/*
user: [
// './screens/Users/UserSignIn', // sign in occurs often enough to retain in main chunk
'./screens/Users/UserEmailChange',
'./screens/Users/UserForgotPwdReset',
'./screens/Users/UserForgotPwdSendEmail',
'./screens/Users/UserPasswordChange',
'./screens/Users/UserProfile',
'./screens/Users/UserProfileChange',
'./screens/Users/UserRolesChange',
'./screens/Users/UserSignIn',
'./screens/Users/UserSignInPending',
'./screens/Users/UserSignUp',
'./screens/Users/UserSignUpSendEmail',
'./screens/Users/UserSignUpValidateEmail',
],
*/
},
output: {
filename: '[name].bundle.[chunkhash].js',
// Tell Webpack where it should store the resulting code.
path: path.join(__dirname, 'public', 'dist'),
// Give Webpack the URL that points the server to output.path
publicPath: outputPublicPaths[config.NODE_ENV],
},
/* This is needed for joi to work on the browser, if the client has that dependency
node: {
net: 'empty',
tls: 'empty',
dns: 'empty',
},
*/
module: {
loaders: [
{
// File index.html is created by html-webpack-plugin. It should be a file webpack processes.
test: /\.html$/,
loader: 'file?name=[name].[ext]',
},
{
// When require'd, these /client/../*.inject.css files are injected into the DOM as is.
test: /\.inject\.css$/,
include: /client/,
loader: 'style!css',
},
{
// When required, the class names in these /client/../*.css are returned as an object.
// after being made unique. The css with the modified class names is injected into the DOM.
test: /^(?!.*\.inject\.css).*\.css$/,
include: /client/,
loaders: [
'style-loader',
'css-loader'
],
},
{
// Standard processing for .css outside /client
test: /\.css$/,
exclude: /client/,
loader: 'style!css',
},
{
test: /\.(js|jsx)$/, // does anyone still use .jsx?
exclude: /(node_modules|bower_components)/,
loaders: [
/*
'react-hot',
*/
'babel-loader',
],
},
{
test: /\.(svg|woff|woff2|ttf|eot)$/,
loader: 'file?name=assets/fonts/[name].[hash].[ext]'
},
],
},
resolve: {
extensions: ['', '.js', '.jsx'],
// Reroute import/require to specific files. 'react$' reroutes 'react' but not 'react/foo'.
alias: {
},
},
postcss: [
rucksack({
autoprefixer: true,
}),
],
plugins: [
// Webpack's default file watcher does not work with NFS file systems on VMs,
// definitely not with Oracle VM, and likely not with other VMs.
// OldWatchingPlugin is a slower alternative that works everywhere.
new webpack.OldWatchingPlugin(), // can use "webpack-dev-server --watch-poll" instead
/*
Build our HTML file.
*/
// repeat new HtmlWebpackPlugin() for additional HTML files
new HtmlWebpackPlugin({
// Template based on https://github.com/jaketrent/html-webpack-template/blob/master/index.ejs
template: path.join(process.cwd(), 'server', 'utils', 'index.ejs'),
filename: 'index.html',
inject: false, // important
minify: {
collapseWhitespace: true,
conservativeCollapse: true,
minifyCSS: true,
minifyJS: true,
preserveLineBreaks: true, // leave HTML readable
},
cache: false,
/* We'd need this if we had a dynamically loaded user chunk
excludeChunks: ['user'],
*/
// Substitution values
supportOldIE: false,
meta: { description: config.client.appName },
title: config.client.appName,
faviconFile: '/favicon.ico',
mobile: false,
links: [],
baseHref: null,
unsupportedBrowserSupport: false,
appMountId: 'root',
appMountIds: {},
addRobotoFont: true, // See //www.google.com/fonts#UsePlace:use/Collection:Roboto:400,300,500
copyWindowVars: {},
scripts: ['/socket.io/socket.io.js'],
devServer: false,
googleAnalytics: false,
}),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify(config.NODE_ENV) }, // used by React, etc
__processEnvNODE_ENV__: JSON.stringify(config.NODE_ENV), // used by us
}),
],
};
// Production customization
if (isProduction) {
webpackConfig.plugins.push(
/*
Besides the normal benefits, this is needed to minify React, Redux and React-Router
for production if you choose not to use their run-time versions.
*/
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
comments: false,
sourceMap: false,
mangle: true,
minimize: true,
verbose: false,
})
);
}
module.exports = webpackConfig;
// Validate environment variables
function validateEnvironmentVariables() {
const strPropType = envalid.str;
// valid NODE_ENV values.
const nodeEnv = {
production: 'production',
prod: 'production',
development: 'development',
dev: 'development',
devserver: 'devserver',
testing: 'devserver',
test: 'devserver',
};
const cleanEnv = envalid.cleanEnv(process.env,
{
NODE_ENV: strPropType({
choices: Object.keys(nodeEnv),
default: 'developmwent',
desc: 'processing environment',
}),
}
);
process.env.NODE_ENV = nodeEnv[cleanEnv.NODE_ENV];
}
Just at a first glance, you need to add -loader to each loader. You've done it for one, but not the other two:
{
// When require'd, these /client/../*.inject.css files are injected into the DOM as is.
test: /\.inject\.css$/,
include: /client/,
loaders: [
'style-loader',
'css-loader'
]
},
{
// When required, the class names in these /client/../*.css are returned as an object.
// after being made unique. The css with the modified class names is injected into the DOM.
test: /^(?!.*\.inject\.css).*\.css$/,
include: /client/,
loaders: [
'style-loader',
'css-loader'
],
},
{
// Standard processing for .css outside /client
test: /\.css$/,
exclude: /client/,
loaders: [
'style-loader',
'css-loader'
]
},
I have a package and i want export my SASS variables to other packages use it. Currently my all .scss files are compiles and put in /dist/main.css file. My webpack config:
var webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: ['./src/index.js'],
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel'
},
{
test: /\.(scss|sass|css)$/,
loader: ExtractTextPlugin.extract("style", "css!sass")
},
{
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=10000&name=fonts/[hash].[ext]'
},
{
test: /\.scss$/, loader: 'style!css!sass!sass-resources'
}
]
},
resolve: {
extensions: ['', '.js', '.jsx']
},
output: {
path: __dirname + '/build',
publicPath: '/',
filename: 'index.js',
library: 'Supernova',
libraryTarget: 'umd'
},
externals: {
'react': 'react',
'react-dom': 'react-dom'
},
plugins: [
new ExtractTextPlugin("[name].css")
]
};
My objective is create a package like bootstrap-sass.
If you want to make the variables you use within your sass files available to consumers of your published package, then you'll need to look at some special configuration for node-sass.
Currently (and as of the time you posted this) node-sass supports writing your own custom sass functions in javascript: https://github.com/sass/node-sass#functions--v300---experimental
This is untested, but we did this a while ago at a company i worked for...to do what you want, you'd need something like:
src/
your-package.js
your-styles.scss
tools/
constants/
colours.js
webpack/
...
base.sass.js
base.js
development.js
production.js
sass/
functions/
colours.js
# tools/webpack/base.sass.js
const Config = require('webpack-config').default
import {
signature as ColourSignature,
handler as ColourHandler
} from '#tools/sass/functions/colours
module.exports = new Config()
.merge({
module: {
rules: [
{
test: /\.scss$/,
use: [
...
{ loader: 'sass-loader',
options: {
sourceMap: true,
functions: {
[ColourSignature]: ColourHandler
}
}
},
]
}
]
}
})
# src/your-package.js
import Colours from '#tools/constants/colours'
import "./your-styles.scss"
export default YourAwesomeComponent {
static Colours = Colours
}
export const colours = Colours
# src/your-styles.scss
.your-awesome-component {
background-color: ColourGet(veganvomit, sobrightithurts);
}
# tools/sass/functions/colour.js
import Colours from '#tools/constants/colours'
export signature = 'ColourGet($name, $shade: default)'
export handler = function(name, shade) {
const colour = Colours[name]
if (!colour) return
if (typeof colour === 'string') return colour
return colour[shade]
}
# tools/sass/constants/colours.js
export default {
veganvomit: {
sobrightithurts: "darkkhaki",
light: "#D2691E",
default: "#8B4513",
somethingsomethingsomethingdarkside: "#000"
}
}
So now when you publish your package, they can access sass variables from your default export YourAwesomeClass.Colours or they can import it directly `import { Colours } from 'your-awesome-package'
I highly recommend using webpack-merge to separate out your Sass config to make it easy for other packages to use it. For your current config, I would do three things:
Add webpack-merge to your project (npm i --save-dev webpack-merge).
Put your Sass config into a separate file, named something like webpack.sass-config.js. Have it include the following:
var ExtractTextPlugin = require('extract-text-webpack-plugin');
exports.config = function(options) {
return {
module: {
loaders: [
{
test: /\.(scss|sass|css)$/,
loader: ExtractTextPlugin.extract("style", "css!sass")
},
{
test: /\.scss$/, loader: 'style!css!sass!sass-resources'
}
]
},
plugins: [
new ExtractTextPlugin("[name].css")
]
}
}
// Side note: returning a function instead of a plain object lets
// you pass optional parameters from your main config file. This
// is useful if you want to make something like your compiled css
// file name different for another Webpack project without having
// to edit your Sass configuration file.
Update your webpack.config.js to the following:
var merge = require('webpack-merge');
// import your separated Sass configuration
var sassConfig = require('webpack.sass-config');
// Define your common config for entry, output, JSX, fonts, etc.
var common = {
entry: ['./src/index.js'],
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel'
},
{
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=10000&name=fonts/[hash].[ext]'
}
]
},
resolve: {
extensions: ['', '.js', '.jsx']
},
output: {
path: __dirname + '/build',
publicPath: '/',
filename: 'index.js',
library: 'Supernova',
libraryTarget: 'umd'
},
externals: {
'react': 'react',
'react-dom': 'react-dom'
}
};
// Merge your common config and Sass config
var config = merge(
common,
sassConfig.config()
);
// Export the merged configuration
modules.exports = config;
Obviously, this can go far beyond just your Sass config. I use webpack-merge to separate my development config from my production config. This article on Survive JS is a great resource for how to make the most of your Webpack setup.