Webpack Overview & Config
Examine the architecture of Webpack's compilation pipeline and the base config files used to resolve module entry points.
Key Folder Location:./dinou/webpack/
Focus Files:webpack.config.js,postcss.config.js
💡 Overview
Dinou provides Webpack as an alternative compilation system, integrating the official loader and compiler implementation from the React team for React Server Components.
In ejected applications, Webpack coordinates custom loaders (for stripping Server Functions), splits vendors chunks to optimize cache hits, extracts CSS modules with content-hashed names, and outputs client-hydration manifests.
📊 Webpack Build Pipeline
The flowchart below shows the sequence of plugins and loaders executed during Webpack compiles:
🛠️ 1. webpack.config.js Options
This script manages JSX/TSX loaders, imports dynamic CSS paths, and registers optimization cache splits:
require("dotenv/config");
const path = require("path");
const fs = require("fs");
const ReactServerWebpackPlugin = require("react-server-dom-webpack/plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const createScopedName = require("../core/createScopedName");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const manifestGeneratorPlugin = require("./plugins/manifest-generator-plugin");
const ServerFunctionsPlugin = require("./plugins/server-functions-plugin");
const webpack = require("webpack");
const { regex } = require("../core/asset-extensions");
const getCSSEntries = require("./helpers/get-webpack-entries");
const isDevelopment = process.env.NODE_ENV !== "production";
const outputDirectory = isDevelopment ? "public" : "dist3";
function getConfigFileIfExists() {
const tsconfigPath = path.resolve(process.cwd(), "tsconfig.json");
const jsconfigPath = path.resolve(process.cwd(), "jsconfig.json");
if (fs.existsSync(tsconfigPath)) return tsconfigPath;
if (fs.existsSync(jsconfigPath)) return jsconfigPath;
return null;
}
const configFile = getConfigFileIfExists();
const localDinouPath = path.resolve(process.cwd(), "dinou");
const isEjected = fs.existsSync(localDinouPath);
const projectRoot = process.cwd();
const outputDirs = [
path.resolve(projectRoot, "public"),
path.resolve(projectRoot, "dist3"),
];
function cleanDir(dir) {
if (fs.existsSync(dir)) {
try {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
fs.rmSync(fullPath, { recursive: true, force: true });
}
} catch (e) {
try {
fs.rmSync(dir, { recursive: true, force: true });
} catch (err) {}
}
}
}
module.exports = async () => {
const outputDir = path.resolve(process.cwd(), outputDirectory);
cleanDir(outputDir);
const [cssEntries] = await getCSSEntries();
return {
performance: {
hints: isDevelopment ? false : "warning",
maxEntrypointSize: 512000,
maxAssetSize: 512000,
},
cache: false,
mode: isDevelopment ? "development" : "production",
entry: {
main: [path.resolve(__dirname, "../core/client-webpack.jsx")].filter(
Boolean,
),
error: [
path.resolve(__dirname, "../core/client-error-webpack.jsx"),
].filter(Boolean),
serverFunctionProxy: path.resolve(
__dirname,
"../core/server-function-proxy-webpack.js",
),
dinouClientRedirect: path.resolve(
__dirname,
"../core/client-redirect.jsx",
),
dinouLink: path.resolve(__dirname, "../core/link.jsx"),
...[...cssEntries].reduce(
(acc, cssEntry) => ({
...acc,
[cssEntry.outfileName]: cssEntry.absPath,
}),
{},
),
},
experiments: {
outputModule: true,
},
output: {
path: path.resolve(process.cwd(), outputDirectory),
filename: "[name]-[contenthash].js",
publicPath: "/",
clean: true,
library: {
type: "module",
},
environment: {
module: true,
},
chunkFormat: "module",
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: [/node_modules\/(?!dinou)/, ...outputDirs],
use: [
{
loader: "babel-loader",
options: {
presets: [
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
plugins: [
"babel-plugin-react-compiler",
"@babel/plugin-syntax-import-meta",
].filter(Boolean),
},
},
{
loader: path.resolve(
__dirname,
"./loaders/server-functions-loader.js"
),
},
],
},
{
test: /\.module\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
defaultExport: true,
},
},
{
loader: "css-loader",
options: {
modules: {
getLocalIdent: (context, localIdentName, localName) => {
return createScopedName(localName, context.resourcePath);
},
},
importLoaders: 1,
},
},
"postcss-loader",
],
},
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
config: path.resolve(__dirname, "postcss.config.js"),
},
},
},
],
},
{
test: regex,
type: "asset/resource",
generator: {
filename: (pathData) => {
const resourcePath =
pathData.module.resourceResolveData?.path ||
pathData.module.resource;
const base = path.basename(
resourcePath,
path.extname(resourcePath)
);
const scoped = createScopedName(base, resourcePath);
return `/assets/${scoped}[ext]`;
},
publicPath: "",
},
},
],
},
plugins: [
new ReactServerWebpackPlugin({ isServer: false }),
new CopyWebpackPlugin({
patterns: [
{
from: "favicons",
to: ".",
noErrorOnMissing: true,
},
],
}),
new MiniCssExtractPlugin({
filename: "[name].css",
}),
manifestGeneratorPlugin,
new webpack.IgnorePlugin({
checkResource(resource, context) {
if (!context) return false;
return outputDirs.some((dir) => context.startsWith(dir));
},
}),
new ServerFunctionsPlugin({
manifest: manifestGeneratorPlugin.manifestData,
}),
].filter(Boolean),
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
modules: ["src", "node_modules"],
extensionAlias: {
".js": [".js", ".ts", ".tsx"],
".jsx": [".jsx", ".tsx"],
},
alias: {
...(isEjected ? { dinou: localDinouPath } : {}),
},
plugins: configFile
? [
new TsconfigPathsPlugin({
configFile,
extensions: [".js", ".jsx", ".ts", ".tsx"],
}),
]
: [],
},
optimization: {
runtimeChunk: "single",
splitChunks: {
chunks: "all",
cacheGroups: {
reactVendor: {
test: /[\\/]node_modules[\\/](react|react-dom|react-server-dom-webpack|scheduler)[\\/]/,
name: "vendor-react",
priority: 40,
chunks: "all",
enforce: true,
},
styles: {
name: "styles",
type: "css/mini-extract",
chunks: "all",
enforce: true,
},
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
name: false,
priority: 20,
chunks: "all",
reuseExistingChunk: true,
},
},
},
},
watchOptions: {
ignored: outputDirs.map((dir) => `${dir}/**`),
},
stats: "normal",
infrastructureLogging: {
level: "info",
},
...(isDevelopment
? {
devServer: {
port: 3001,
hot: false,
devMiddleware: {
index: false,
writeToDisk: true,
},
proxy: [
{
context: () => true,
target: "http://localhost:3000",
changeOrigin: true,
},
],
client: false,
},
}
: {}),
};
};Key Details:
ReactServerWebpackPlugin: Manages React Server Components, mapping client component chunks and generating hydration paths.splitChunks.cacheGroups.reactVendor: Gathers React, ReactDOM, and schedulers into a singlevendor-reactfile to improve browser caching.experiments.outputModule: true: Configures Webpack to emit final bundle chunks as native EcmaScript modules (ESM).
🎨 2. postcss.config.js Options
Defines the PostCSS rules used by postcss-loader to compile stylesheets:
module.exports = {
plugins: [
require("@tailwindcss/postcss"),
require("autoprefixer"),
],
};