Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/init-no-html-css-questions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-webpack-app": minor
---

feat: stop asking about HTML and CSS in `init`, scaffold TypeScript on webpack's own support, and keep one question for the CSS tools webpack does not cover (PostCSS, Sass, Less, Stylus, and each preprocessor with PostCSS)
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"splitted",
"Statsv",
"styl",
"stylesheet",
"Tanjiro",
"tapable",
"taskkill",
Expand Down
5 changes: 5 additions & 0 deletions packages/create-webpack-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ Available templates:
- [`vue`](https://vuejs.org/)
- [`svelte`](https://svelte.dev/)

Every template scaffolds `index.html` as the entry point of the project and relies on
webpack's built-in HTML and CSS support (webpack >= 5.109), so the generated projects
carry no `html-webpack-plugin`, `css-loader`, `style-loader` or `mini-css-extract-plugin`.
Picking Sass, Less, Stylus or PostCSS adds only that tool's loader on top.

Available templates for `loader` and `plugin` generators:

- `default` (used by default when nothing specified) - generate bootstrap code
116 changes: 68 additions & 48 deletions packages/create-webpack-app/src/generators/init/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { fileURLToPath } from "node:url";
import { type DynamicActionsFunction, type NodePlopAPI } from "node-plop";
import { type ActionType, type Answers, type FileRecord } from "../../types.js";

const STYLE_EXTENSIONS: Record<string, string> = {
SASS: "scss",
LESS: "less",
Stylus: "styl",
};

export default async function defaultInitGenerator(plop: NodePlopAPI) {
const __dirname = dirname(fileURLToPath(import.meta.url));

Expand All @@ -28,15 +34,17 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
default: "none",
},
{
type: "confirm",
name: "devServer",
message: "Would you like to use Webpack Dev server?",
default: true,
type: "list",
name: "tsCompiler",
message: "How should TypeScript be compiled?",
choices: ["built-in", "ts-loader"],
default: "built-in",
when: (answers: Answers) => answers.langType === "Typescript",
},
{
type: "confirm",
name: "html",
message: "Do you want to simplify the creation of HTML files for your bundle?",
name: "devServer",
message: "Would you like to use Webpack Dev server?",
default: true,
},
{
Expand All @@ -47,33 +55,19 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
},
{
type: "list",
name: "cssType",
message: "Which of the following CSS solution do you want to use?",
choices: ["none", "CSS only", "SASS", "LESS", "Stylus"],
default: "CSS only",
filter: (input: string, answers: Answers) => {
if (input === "none") {
answers.isCSS = false;
answers.isPostCSS = false;
} else if (input === "CSS only") {
answers.isCSS = true;
}
return input;
},
},
{
type: "confirm",
name: "isCSS",
message: (answers: Answers) =>
`Will you be using CSS styles along with ${answers.cssType} in your project?`,
when: (answers: Answers) => answers.cssType !== "CSS only",
default: true,
},
{
type: "confirm",
name: "isPostCSS",
message: "Do you want to use PostCSS in your project?",
default: (answers: Answers) => answers.cssType === "CSS only",
name: "cssTool",
message: "Which CSS tool do you want to use?",
choices: [
"none",
"PostCSS",
"SASS",
"SASS with PostCSS",
"LESS",
"LESS with PostCSS",
"Stylus",
"Stylus with PostCSS",
],
default: "none",
},
{
type: "list",
Expand All @@ -92,12 +86,24 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
actions: function actions(answers: Answers) {
const actions: ActionType[] = [];

// the config template reads this, so it has to be set whatever the language is
answers.useTsLoader = false;

switch (answers.langType) {
case "ES6":
devDependencies.push("babel-loader", "@babel/core", "@babel/preset-env");
break;
case "Typescript":
devDependencies.push("typescript", "ts-loader");
// Type stripping covers erasable syntax only, so ts-loader stays on
// offer; either way `typescript` backs the editor and `check:types`
answers.useTsLoader = answers.tsCompiler === "ts-loader";

if (answers.useTsLoader) {
// ts-loader 9 (its latest) throws on TypeScript 7, so pin what it supports
devDependencies.push("typescript@5", "ts-loader");
} else {
devDependencies.push("typescript");
}
break;
}

Expand All @@ -109,22 +115,31 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
devDependencies.push("workbox-webpack-plugin");
}

if (answers.isPostCSS) {
const cssTool = (answers.cssTool as string | undefined) ?? "none";
// PostCSS runs on its own or over a preprocessor, so both are read off the answer
const preprocessor = Object.keys(STYLE_EXTENSIONS).find((name) => cssTool.startsWith(name));

answers.usePostCSS = cssTool.includes("PostCSS");
answers.useSASS = preprocessor === "SASS";
answers.useLESS = preprocessor === "LESS";
answers.useStylus = preprocessor === "Stylus";
// The starter stylesheet is written in the language that was picked
answers.styleExtension = preprocessor ? STYLE_EXTENSIONS[preprocessor] : "css";

if (answers.usePostCSS) {
devDependencies.push("postcss-loader", "postcss", "autoprefixer");
}

if (answers.cssType !== "none") {
switch (answers.cssType) {
case "SASS":
devDependencies.push("sass-loader", "sass");
break;
case "LESS":
devDependencies.push("less-loader", "less");
break;
case "Stylus":
devDependencies.push("stylus-loader", "stylus");
break;
}
if (answers.useSASS) {
devDependencies.push("sass-loader", "sass");
}

if (answers.useLESS) {
devDependencies.push("less-loader", "less");
}

if (answers.useStylus) {
devDependencies.push("stylus-loader", "stylus");
}

const files: FileRecord[] = [
Expand Down Expand Up @@ -158,10 +173,15 @@ export default async function defaultInitGenerator(plop: NodePlopAPI) {
break;
}

if (answers.isPostCSS) {
if (answers.usePostCSS) {
files.push({ filePath: "postcss.config.js", fileType: "text" });
}

files.push({
filePath: `./src/styles.${answers.styleExtension}`,
fileType: "text",
});

for (const file of files) {
actions.push({
type: "generate-files",
Expand Down
117 changes: 54 additions & 63 deletions packages/create-webpack-app/src/generators/init/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { fileURLToPath } from "node:url";
import { type DynamicActionsFunction, type NodePlopAPI } from "node-plop";
import { type ActionType, type Answers, type FileRecord } from "../../types.js";

const STYLE_EXTENSIONS: Record<string, string> = {
SASS: "scss",
LESS: "less",
Stylus: "styl",
};

export default async function reactInitGenerator(plop: NodePlopAPI) {
const __dirname = dirname(fileURLToPath(import.meta.url));

Expand Down Expand Up @@ -49,33 +55,19 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
},
{
type: "list",
name: "cssType",
message: "Which of the following CSS solution do you want to use?",
choices: ["none", "CSS only", "SASS", "LESS", "Stylus"],
default: "CSS only",
filter: (input: string, answers: Answers) => {
if (input === "none") {
answers.isCSS = false;
answers.isPostCSS = false;
} else if (input === "CSS only") {
answers.isCSS = true;
}
return input;
},
},
{
type: "confirm",
name: "isCSS",
message: (answers: Answers) =>
`Will you be using CSS styles along with ${answers.cssType} in your project?`,
when: (answers: Answers) => answers.cssType !== "CSS only",
default: true,
},
{
type: "confirm",
name: "isPostCSS",
message: "Do you want to use PostCSS in your project?",
default: (answers: Answers) => answers.cssType === "CSS only",
name: "cssTool",
message: "Which CSS tool do you want to use?",
choices: [
"none",
"PostCSS",
"SASS",
"SASS with PostCSS",
"LESS",
"LESS with PostCSS",
"Stylus",
"Stylus with PostCSS",
],
default: "none",
},
{
type: "list",
Expand All @@ -94,7 +86,6 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
actions: function actions(answers: Answers) {
// setting some default values based on the answers
const actions: ActionType[] = [];
answers.html = true;
answers.devServer = true;
switch (answers.langType) {
case "ES6":
Expand All @@ -106,33 +97,39 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
);
break;
case "Typescript":
devDependencies.push("typescript", "ts-loader", "@types/react", "@types/react-dom");
// ts-loader 9 (its latest) throws on TypeScript 7, so pin what it supports
devDependencies.push("typescript@5", "ts-loader", "@types/react", "@types/react-dom");
break;
}
if (answers.isPostCSS) {
if (answers.workboxWebpackPlugin) {
devDependencies.push("workbox-webpack-plugin");
}

const cssTool = (answers.cssTool as string | undefined) ?? "none";
// PostCSS runs on its own or over a preprocessor, so both are read off the answer
const preprocessor = Object.keys(STYLE_EXTENSIONS).find((name) => cssTool.startsWith(name));

answers.usePostCSS = cssTool.includes("PostCSS");
answers.useSASS = preprocessor === "SASS";
answers.useLESS = preprocessor === "LESS";
answers.useStylus = preprocessor === "Stylus";
// The starter stylesheet is written in the language that was picked
answers.styleExtension = preprocessor ? STYLE_EXTENSIONS[preprocessor] : "css";

if (answers.usePostCSS) {
devDependencies.push("postcss-loader", "postcss", "autoprefixer");
}
if (answers.cssType === "none") {
answers.isCSS = false;
answers.isPostCSS = false;
} else {
switch (answers.cssType) {
case "CSS only":
answers.isCSS = true;
break;
case "SASS":
devDependencies.push("sass-loader", "sass");
break;
case "LESS":
devDependencies.push("less-loader", "less");
break;
case "Stylus":
devDependencies.push("stylus-loader", "stylus");
break;
}

if (answers.useSASS) {
devDependencies.push("sass-loader", "sass");
}
if (answers.workboxWebpackPlugin) {
devDependencies.push("workbox-webpack-plugin");

if (answers.useLESS) {
devDependencies.push("less-loader", "less");
}

if (answers.useStylus) {
devDependencies.push("stylus-loader", "stylus");
}

const files: FileRecord[] = [
Expand Down Expand Up @@ -173,21 +170,15 @@ export default async function reactInitGenerator(plop: NodePlopAPI) {
break;
}

switch (answers.cssType) {
case "CSS only":
files.push({ filePath: "./src/styles/global.css", fileType: "text" });
break;
case "SASS":
files.push({ filePath: "./src/styles/global.scss", fileType: "text" });
break;
case "LESS":
files.push({ filePath: "./src/styles/global.less", fileType: "text" });
break;
case "Stylus":
files.push({ filePath: "./src/styles/global.styl", fileType: "text" });
break;
if (answers.usePostCSS) {
files.push({ filePath: "postcss.config.js", fileType: "text" });
}

files.push({
filePath: `./src/styles/global.${answers.styleExtension}`,
fileType: "text",
});

for (const file of files) {
actions.push({
type: "generate-files",
Expand Down
Loading
Loading