Skip to content

Commit a2418aa

Browse files
authored
feat(create-webpack-app): use webpack's native CSS and HTML support in init templates (#4833)
Replace css-loader, style-loader, vue-style-loader and mini-css-extract-plugin with `experiments.css`, and html-webpack-plugin and html-loader with `experiments.html`, where `index.html` becomes the entry point. Both experiments default to "auto" since webpack 5.109, so the templates only declare them when the auto detection would turn them off: a PostCSS rule for `.css` files, and the Vue template, where VueLoaderPlugin clones every non-vue rule with a catch-all `resource: () => true` matcher before webpack resolves the defaults, which makes the auto detection see a loader for `.css` and `.html`. The Vue template also needs `experimentalInlineMatchResource` in vue-loader, required with `experiments.css`. The "Do you want to extract CSS into separate files?" prompt is gone, native CSS always emits separate files, and the `htmlWebpackPlugin` answer is now `html`.
1 parent 3664b9d commit a2418aa

16 files changed

Lines changed: 158 additions & 377 deletions

File tree

‎.changeset/native-css-templates.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-webpack-app": minor
3+
---
4+
5+
feat: use webpack's native CSS and HTML support in the `init` templates

‎packages/create-webpack-app/src/generators/init/default.ts‎

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
3535
},
3636
{
3737
type: "confirm",
38-
name: "htmlWebpackPlugin",
38+
name: "html",
3939
message: "Do you want to simplify the creation of HTML files for your bundle?",
4040
default: true,
4141
},
@@ -55,7 +55,6 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
5555
if (input === "none") {
5656
answers.isCSS = false;
5757
answers.isPostCSS = false;
58-
answers.extractPlugin = "No";
5958
} else if (input === "CSS only") {
6059
answers.isCSS = true;
6160
}
@@ -76,13 +75,6 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
7675
message: "Do you want to use PostCSS in your project?",
7776
default: (answers: Answers) => answers.cssType === "CSS only",
7877
},
79-
{
80-
type: "list",
81-
name: "extractPlugin",
82-
message: "Do you want to extract CSS into separate files?",
83-
choices: ["No", "Only for Production", "Yes"],
84-
default: "Only for Production",
85-
},
8678
{
8779
type: "list",
8880
name: "packageManager",
@@ -113,10 +105,6 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
113105
devDependencies.push("webpack-dev-server");
114106
}
115107

116-
if (answers.htmlWebpackPlugin) {
117-
devDependencies.push("html-webpack-plugin", "html-loader");
118-
}
119-
120108
if (answers.workboxWebpackPlugin) {
121109
devDependencies.push("workbox-webpack-plugin");
122110
}
@@ -125,12 +113,7 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
125113
devDependencies.push("postcss-loader", "postcss", "autoprefixer");
126114
}
127115

128-
if (answers.extractPlugin !== "No") {
129-
devDependencies.push("mini-css-extract-plugin");
130-
}
131-
132116
if (answers.cssType !== "none") {
133-
devDependencies.push("style-loader", "css-loader");
134117
switch (answers.cssType) {
135118
case "SASS":
136119
devDependencies.push("sass-loader", "sass");
@@ -143,9 +126,6 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
143126
break;
144127
}
145128
}
146-
if (answers.extractPlugin !== "No") {
147-
devDependencies.push("mini-css-extract-plugin");
148-
}
149129

150130
const files: FileRecord[] = [
151131
{ filePath: "./index.html", fileType: "text" },

‎packages/create-webpack-app/src/generators/init/react.ts‎

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
1313
"react@18",
1414
"react-dom@18",
1515
"webpack-dev-server",
16-
"html-webpack-plugin",
1716
"react-router-dom",
1817
"@types/react-router-dom",
1918
];
@@ -58,7 +57,6 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
5857
if (input === "none") {
5958
answers.isCSS = false;
6059
answers.isPostCSS = false;
61-
answers.extractPlugin = "No";
6260
} else if (input === "CSS only") {
6361
answers.isCSS = true;
6462
}
@@ -79,13 +77,6 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
7977
message: "Do you want to use PostCSS in your project?",
8078
default: (answers: Answers) => answers.cssType === "CSS only",
8179
},
82-
{
83-
type: "list",
84-
name: "extractPlugin",
85-
message: "Do you want to extract CSS into separate files?",
86-
choices: ["No", "Only for Production", "Yes"],
87-
default: "Only for Production",
88-
},
8980
{
9081
type: "list",
9182
name: "packageManager",
@@ -103,7 +94,7 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
10394
actions: function actions(answers: Answers) {
10495
// setting some default values based on the answers
10596
const actions: ActionType[] = [];
106-
answers.htmlWebpackPlugin = true;
97+
answers.html = true;
10798
answers.devServer = true;
10899
switch (answers.langType) {
109100
case "ES6":
@@ -124,9 +115,7 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
124115
if (answers.cssType === "none") {
125116
answers.isCSS = false;
126117
answers.isPostCSS = false;
127-
answers.extractPlugin = "No";
128118
} else {
129-
devDependencies.push("style-loader", "css-loader");
130119
switch (answers.cssType) {
131120
case "CSS only":
132121
answers.isCSS = true;
@@ -142,9 +131,6 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
142131
break;
143132
}
144133
}
145-
if (answers.extractPlugin !== "No") {
146-
devDependencies.push("mini-css-extract-plugin");
147-
}
148134
if (answers.workboxWebpackPlugin) {
149135
devDependencies.push("workbox-webpack-plugin");
150136
}

‎packages/create-webpack-app/src/generators/init/svelte.ts‎

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export default async function svelteInitGenerator(plop: NodePlopAPI) {
1313
"svelte",
1414
"svelte-loader",
1515
"webpack-dev-server",
16-
"html-webpack-plugin",
1716
];
1817

1918
await plop.load("../../utils/install-dependencies.js", {}, true);
@@ -49,7 +48,6 @@ export default async function svelteInitGenerator(plop: NodePlopAPI) {
4948
if (input === "none") {
5049
answers.isCSS = false;
5150
answers.isPostCSS = false;
52-
answers.extractPlugin = "No";
5351
} else if (input === "CSS only") {
5452
answers.isCSS = true;
5553
}
@@ -70,13 +68,6 @@ export default async function svelteInitGenerator(plop: NodePlopAPI) {
7068
message: "Do you want to use PostCSS in your project?",
7169
default: (answers: Answers) => answers.cssType === "CSS only",
7270
},
73-
{
74-
type: "list",
75-
name: "extractPlugin",
76-
message: "Do you want to extract CSS into separate files?",
77-
choices: ["No", "Only for Production", "Yes"],
78-
default: "Only for Production",
79-
},
8071
{
8172
type: "list",
8273
name: "packageManager",
@@ -94,7 +85,7 @@ export default async function svelteInitGenerator(plop: NodePlopAPI) {
9485
actions: function actions(answers: Answers) {
9586
// setting some default values based on the answers
9687
const actions: ActionType[] = [];
97-
answers.htmlWebpackPlugin = true;
88+
answers.html = true;
9889
answers.devServer = true;
9990

10091
switch (answers.langType) {
@@ -117,9 +108,7 @@ export default async function svelteInitGenerator(plop: NodePlopAPI) {
117108
if (answers.cssType === "none") {
118109
answers.isCSS = false;
119110
answers.isPostCSS = false;
120-
answers.extractPlugin = "No";
121111
} else {
122-
devDependencies.push("style-loader", "css-loader");
123112
switch (answers.cssType) {
124113
case "CSS only":
125114
answers.isCSS = true;
@@ -136,10 +125,6 @@ export default async function svelteInitGenerator(plop: NodePlopAPI) {
136125
}
137126
}
138127

139-
if (answers.extractPlugin !== "No") {
140-
devDependencies.push("mini-css-extract-plugin");
141-
}
142-
143128
const files: FileRecord[] = [
144129
{ filePath: "./index.html", fileType: "text" },
145130
{ filePath: "./src/assets/webpack.png", fileType: "binary" },

‎packages/create-webpack-app/src/generators/init/vue.ts‎

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export default async function vueInitGenerator(plop: NodePlopAPI) {
1212
"webpack-cli",
1313
"vue@3",
1414
"webpack-dev-server",
15-
"html-webpack-plugin",
1615
"vue-loader@next",
1716
"@vue/compiler-sfc",
1817
"vue-router@4",
@@ -57,7 +56,6 @@ export default async function vueInitGenerator(plop: NodePlopAPI) {
5756
if (input === "none") {
5857
answers.isCSS = false;
5958
answers.isPostCSS = false;
60-
answers.extractPlugin = "No";
6159
} else if (input === "CSS only") {
6260
answers.isCSS = true;
6361
}
@@ -78,13 +76,6 @@ export default async function vueInitGenerator(plop: NodePlopAPI) {
7876
message: "Do you want to use PostCSS in your project?",
7977
default: (answers: Answers) => answers.cssType === "CSS only",
8078
},
81-
{
82-
type: "list",
83-
name: "extractPlugin",
84-
message: "Do you want to extract CSS into separate files?",
85-
choices: ["No", "Only for Production", "Yes"],
86-
default: "Only for Production",
87-
},
8879
{
8980
type: "list",
9081
name: "packageManager",
@@ -102,7 +93,7 @@ export default async function vueInitGenerator(plop: NodePlopAPI) {
10293
actions: function actions(answers: Answers) {
10394
// setting some default values based on the answers
10495
const actions: ActionType[] = [];
105-
answers.htmlWebpackPlugin = true;
96+
answers.html = true;
10697
answers.devServer = true;
10798

10899
switch (answers.langType) {
@@ -129,9 +120,7 @@ export default async function vueInitGenerator(plop: NodePlopAPI) {
129120
if (answers.cssType === "none") {
130121
answers.isCSS = false;
131122
answers.isPostCSS = false;
132-
answers.extractPlugin = "No";
133123
} else {
134-
devDependencies.push("vue-style-loader", "style-loader", "css-loader");
135124
switch (answers.cssType) {
136125
case "CSS only":
137126
answers.isCSS = true;
@@ -148,10 +137,6 @@ export default async function vueInitGenerator(plop: NodePlopAPI) {
148137
}
149138
}
150139

151-
if (answers.extractPlugin !== "No") {
152-
devDependencies.push("mini-css-extract-plugin");
153-
}
154-
155140
const files: FileRecord[] = [
156141
{ filePath: "./index.html", fileType: "text" },
157142
{ filePath: "./src/assets/webpack.png", fileType: "binary" },

‎packages/create-webpack-app/src/index.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ const baseAnswers: Answers = {
1717
projectPath: process.cwd(),
1818
langType: "none",
1919
devServer: true,
20-
htmlWebpackPlugin: true,
20+
html: true,
2121
workboxWebpackPlugin: true,
2222
cssType: "none",
2323
isCSS: false,
2424
isPostCSS: false,
25-
extractPlugin: "No",
2625
packageManager: "npm",
2726
};
2827
const initValues: Record<string, Answers> = {

‎packages/create-webpack-app/templates/init/default/index.html.tpl‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
</head>
77
<body>
88
<h1>Hello world!</h1>
9-
<h2>Tip: Check your console</h2>
9+
<h2>Tip: Check your console</h2><% if (html) { %>
10+
<script src="<%= entryPoint %>"></script><% } %>
1011
</body>
1112
<% if (workboxWebpackPlugin) { %>
1213
<script>

‎packages/create-webpack-app/templates/init/default/webpack.config.tpl‎

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,26 @@
33
import path from "node:path";
44
import { fileURLToPath } from "node:url";<% if (langType === "Typescript") { %>
55
import { type Configuration } from "webpack";<% if (devServer) { %>
6-
import "webpack-dev-server";<% } %><% } %><% if (htmlWebpackPlugin) { %>
7-
import HtmlWebpackPlugin from "html-webpack-plugin";<% } %><% if (extractPlugin !== "No") { %>
8-
import MiniCssExtractPlugin from "mini-css-extract-plugin";<% } %><% if (workboxWebpackPlugin) { %>
6+
import "webpack-dev-server";<% } %><% } %><% if (workboxWebpackPlugin) { %>
97
import WorkboxWebpackPlugin from "workbox-webpack-plugin";<% } %>
108

119
const __filename = fileURLToPath(import.meta.url);
1210
const __dirname = path.dirname(__filename);
13-
const isProduction = process.env.NODE_ENV === "production";<% if (cssType !== "none") { %><% if (extractPlugin === "Yes") { %>
14-
const stylesHandler = MiniCssExtractPlugin.loader;<% } else if (extractPlugin === "Only for Production") { %>
15-
const stylesHandler = isProduction ? MiniCssExtractPlugin.loader : "style-loader";<% } else { %>
16-
const stylesHandler = "style-loader";<% } %><% } %>
11+
const isProduction = process.env.NODE_ENV === "production";
1712

1813
/** @type {import("webpack").Configuration} */
1914
const config <% if (langType === "Typescript") { %>: Configuration <% } %>= {
20-
entry: "<%= entryPoint %>",
15+
entry: <% if (html) { %>"./index.html"<% } else { %>"<%= entryPoint %>"<% } %>,
2116
output: {
2217
path: path.resolve(__dirname, "dist"),
2318
},<% if (devServer) { %>
2419
devServer: {
2520
open: true,
21+
},<% } %><% if (isCSS && isPostCSS) { %>
22+
experiments: {
23+
css: true,
2624
},<% } %>
27-
plugins: [<% if (htmlWebpackPlugin) { %>
28-
new HtmlWebpackPlugin({
29-
template: "index.html",
30-
}),<% } %><% if (extractPlugin === "Yes") { %>
31-
new MiniCssExtractPlugin(),<% } %>
25+
plugins: [
3226
// Add your plugins here
3327
// Learn more about plugins from https://webpack.js.org/configuration/plugins/
3428
],
@@ -42,36 +36,31 @@ const config <% if (langType === "Typescript") { %>: Configuration <% } %>= {
4236
test: /\.(ts|tsx)$/i,
4337
loader: "ts-loader",
4438
exclude: ["/node_modules/"],
45-
},<% } %><% if (isCSS && !isPostCSS) { %>
39+
},<% } %><% if (isCSS && isPostCSS) { %>
4640
{
4741
test: /\.css$/i,
48-
use: [stylesHandler,"css-loader"],
42+
type: "css/auto",
43+
use: ["postcss-loader"],
4944
},<% } %><% if (cssType == "SASS") { %>
5045
{
5146
test: /\.s[ac]ss$/i,
52-
use: [stylesHandler, "css-loader", <% if (isPostCSS) { %>"postcss-loader", <% } %>"sass-loader"],
47+
type: "css/auto",
48+
use: [<% if (isPostCSS) { %>"postcss-loader", <% } %>"sass-loader"],
5349
},<% } %><% if (cssType == "LESS") { %>
5450
{
5551
test: /\.less$/i,
56-
use: [stylesHandler, "css-loader", <% if (isPostCSS) { %>"postcss-loader", <% } %>"less-loader"],
52+
type: "css/auto",
53+
use: [<% if (isPostCSS) { %>"postcss-loader", <% } %>"less-loader"],
5754
},<% } %><% if (cssType == "Stylus") { %>
5855
{
5956
test: /\.styl$/i,
60-
use: [stylesHandler, "css-loader", <% if (isPostCSS) { %>"postcss-loader", <% } %>"stylus-loader"],
61-
},<% } %><% if (isPostCSS && isCSS) { %>
62-
{
63-
test: /\.css$/i,
64-
use: [stylesHandler, "css-loader", "postcss-loader"],
57+
type: "css/auto",
58+
use: [<% if (isPostCSS) { %>"postcss-loader", <% } %>"stylus-loader"],
6559
},<% } %>
6660
{
6761
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
6862
type: "asset",
6963
},
70-
%><% if (htmlWebpackPlugin) { %>
71-
{
72-
test: /\.html$/i,
73-
use: ["html-loader"],
74-
},<% } %>
7564

7665
// Add your rules for custom modules here
7766
// Learn more about loaders from https://webpack.js.org/loaders/
@@ -84,8 +73,7 @@ const config <% if (langType === "Typescript") { %>: Configuration <% } %>= {
8473

8574
export default () => {
8675
if (isProduction) {
87-
config.mode = "production";<% if (extractPlugin === "Only for Production") { %>
88-
config.plugins?.push(new MiniCssExtractPlugin());<% } %><% if (workboxWebpackPlugin) { %>
76+
config.mode = "production";<% if (workboxWebpackPlugin) { %>
8977
config.plugins?.push(new WorkboxWebpackPlugin.GenerateSW());<% } %>
9078
} else {
9179
config.mode = "development";

‎packages/create-webpack-app/templates/init/react/index.html.tpl‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
</head>
99
<body>
1010
<div id="root"></div>
11+
<script src="<%= entry %>"></script>
1112
</body>
1213
</html>

0 commit comments

Comments
 (0)