feat: add create webpack configuration function
This commit is contained in:
parent
d14e151681
commit
cda2c48613
419
README.md
Normal file
419
README.md
Normal file
@ -0,0 +1,419 @@
|
||||
## ksv741-react-scripts
|
||||
***
|
||||
|
||||
### Быстрый способ запуска React приложений для сборки и разработки, с расширяемой настройкой webpack
|
||||
|
||||
### Содержание
|
||||
|
||||
- [`Установка`](#установка)
|
||||
- [`Настройка`](#настройка)
|
||||
- [`Использование`](#использование)
|
||||
- [`Поддерживаемые файлы`](#поддерживаемые-файлы)
|
||||
- [`Кастомизация`](#кастомизация)
|
||||
- [`Режим запуска`](#режим-запуска)
|
||||
- [`Конфигурация путей`](#конфигурация-путей)
|
||||
- [`Порт`](#порт)
|
||||
- [`Загрузчик`](#загрузчик)
|
||||
- [`Анализ`](#анализ)
|
||||
- [`Плагины`](#плагины)
|
||||
- [`Лоадеры`](#лоадеры)
|
||||
- [`Расширенная конфигурация`](#расширенная-конфигурация)
|
||||
|
||||
***
|
||||
|
||||
### Установка
|
||||
|
||||
#### npm
|
||||
|
||||
```js
|
||||
npm install ksv741-react-scripts
|
||||
```
|
||||
|
||||
#### yarn
|
||||
|
||||
```js
|
||||
yarn add ksv741-react-scripts
|
||||
```
|
||||
|
||||
#### pnpm
|
||||
|
||||
```js
|
||||
pnpm install ksv741-react-scripts
|
||||
```
|
||||
|
||||
### Настройка
|
||||
Библиотека предоставляет функцию `createConfig` для быстрого создания конфигурации `webpack` c набором предустановленных плагинов и лоадеров,
|
||||
|
||||
1. Создание файла конфигурации webpack
|
||||
|
||||
```js
|
||||
// webpack.config.js
|
||||
|
||||
const { createConfig } = require('ksv741-react-scripts');
|
||||
|
||||
module.exports = createConfig();
|
||||
```
|
||||
2. Добавление файла декларации для использования различных файлов совместно с TypeScript
|
||||
```js
|
||||
// global.d.ts
|
||||
|
||||
/// <reference types="ksv741-react-scripts/global" />
|
||||
```
|
||||
3. Создание файлов инициализации
|
||||
|
||||
```html
|
||||
<!-- public/index.html -->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```js
|
||||
// src/index.js - также доступно использование src/index.tsx и src/index.jsx
|
||||
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
const domNode = document.getElementById('root')!;
|
||||
const root = createRoot(domNode);
|
||||
|
||||
root.render(
|
||||
<h1>
|
||||
Hello ksv741
|
||||
</h1>,
|
||||
);
|
||||
```
|
||||
|
||||
### Использование
|
||||
|
||||
- `npx ksv741-react-scripts start` - для разработки
|
||||
- `npx ksv741-react-scripts build` - для сборки
|
||||
|
||||
### Поддерживаемые файлы
|
||||
|
||||
- JavaScript (`.js`, `.jsx`)
|
||||
- TypeScript (`.ts`, `.tsx`)
|
||||
- Изображения (`.png`, `.jpg`, `.jpeg`, `.gif`, `.avif`, `.webp`)
|
||||
- Шрифты (`.woff`, `.woff2`, `.eot`, `.ttf`, `.otf`)
|
||||
- Стилизация (`.css`, `.scss`, `.sass`)
|
||||
- Иконки (`.svg`)
|
||||
|
||||
### Кастомизация
|
||||
|
||||
Если необходимо подключить уже существующий проект со своей структурой папок,
|
||||
или хотим держать единый файл конфигурации для разработки и сборки, но исходя из каких-то условий настроить пути.
|
||||
Функция `createConfig` принимает объект с конфигурацией
|
||||
```js
|
||||
{
|
||||
mode?: 'development' | 'production';
|
||||
paths?: {
|
||||
assets?: string;
|
||||
build?: string;
|
||||
entry?: string[] | string;
|
||||
html?: string;
|
||||
public?: string;
|
||||
root?: string;
|
||||
src?: string;
|
||||
};
|
||||
port?: number;
|
||||
devtool?: string | false;
|
||||
mainLoader?: 'esbuild' | 'swc';
|
||||
analyze?: boolean;
|
||||
plugins?: {
|
||||
htmlWebpackPlugin?: object | 'off';
|
||||
progressPlugin?: object | 'off';
|
||||
definePlugin?: object | 'off';
|
||||
forkTsCheckerPlugin?: object | 'off';
|
||||
eslintPlugin?: object | 'off';
|
||||
reactRefreshPlugin?: object | 'off';
|
||||
miniCssPlugin?: object | 'off';
|
||||
copyPlugin?: object | 'off';
|
||||
ignorePlugin?: object | 'off';
|
||||
analyzerPlugin?: object | 'off';
|
||||
}
|
||||
loaders?: {
|
||||
esbuildLoader?: object | 'off';
|
||||
swcLoader?: object | 'off';
|
||||
svgrLoader?: object | 'off';
|
||||
styleLoader?: object | 'off';
|
||||
cssLoader?: object | 'off';
|
||||
postCssLoader?: object | 'off';
|
||||
miniCssLoader?: object | 'off';
|
||||
sassLoader?: object | 'off';
|
||||
imageLoader?: object | 'off';
|
||||
fontLoader?: object | 'off';
|
||||
};
|
||||
}
|
||||
```
|
||||
#### Режим запуска
|
||||
|
||||
- `mode` - режим запуска.
|
||||
|
||||
В режиме `production` включена минификация, отключены некоторые плагины.
|
||||
Не путать с параметром запуска приложения `start`, `build`,
|
||||
возможно запустить проект для разработки в режиме `production` или сбилдить проект в режиме `development`, иногда это позволяет получить нужную информацию.
|
||||
По умолчанию - `development` для `ksv741-react-scripts start` и `production` для `ksv741-react-scripts build`
|
||||
|
||||
#### Конфигурация путей
|
||||
`paths` - объект с настройкой путей
|
||||
- `assets` - путь до директории, в которой расположены файлы которые используются непосредственно в коде.
|
||||
По умолчанию: `public/assets`
|
||||
- `build` - путь до директории, в которую будут собраны файлы
|
||||
По умолчанию: `build`
|
||||
- `entry` - файл(ы) точки входа
|
||||
По умолчанию: `src/index` с расширением `.tsx`, `.jsx`, `.js`, приоритет расширений в указанном порядке.
|
||||
- `html` - файла шаблона index.html
|
||||
По умолчанию `public/index.html`
|
||||
- `public` - путь до директории, в которой будут храниться файлы, которые попадут в сборку,
|
||||
относительно этого пути будут искаться файл `html`
|
||||
По умолчанию - `public`
|
||||
- `root` - путь, относительно которого будут резолвиться все остальные пути
|
||||
По умолчанию: текущая директория, откуда выполнена команда запуска (`cwd`)
|
||||
- `src` - путь до директории с исходными файлами, относительно которого будет находиться файл `entry`
|
||||
По умолчанию - `src`
|
||||
|
||||
#### Порт
|
||||
- `port` - порт, в котором будет запускаться dev server, для `mode = 'production'` настройка игнорируется
|
||||
По умолчанию: `3000`
|
||||
|
||||
#### Загрузчик
|
||||
- `mainLoader` - главный загрузчик javascript/typescript файлов, доступны `esbuild` и `swc`
|
||||
По умолчанию: `esbuild`
|
||||
|
||||
#### Анализ
|
||||
- `analyze` - запуск сервера на порту `8888`, для анализа сборки. Рекомендуется запускать совместно с `mode = 'production'`. Но допустим запуск и в режиме `development`
|
||||
По умолчанию: `false`
|
||||
|
||||
#### Devtool
|
||||
- `devtool` - [формат source-map](https://webpack.js.org/configuration/devtool/)
|
||||
По умолчанию: `eval`
|
||||
|
||||
#### Плагины
|
||||
- `plugins` - объект с настройками плагинов
|
||||
|
||||
Список используемых плагинов:
|
||||
- [HTMLWebpackPlugin](https://github.com/jantimon/html-webpack-plugin)
|
||||
- [ProgressPlugin](https://webpack.js.org/plugins/progress-plugin), доступен только в режиме `development`
|
||||
- [DefinePlugin](https://webpack.js.org/plugins/define-plugin)
|
||||
- [ForkTsCheckerWebpackPlugin](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin)
|
||||
- [ESLintPlugin](https://github.com/webpack-contrib/eslint-webpack-plugin), доступен только в режиме `development`, а также при наличии установленного пакета `eslint`
|
||||
- [ReactRefreshWebpackPlugin](https://github.com/pmmmwh/react-refresh-webpack-plugin), доступен только в режиме `development`
|
||||
- [MiniCssExtractPlugin](https://webpack.js.org/plugins/mini-css-extract-plugin), доступен только в режиме `production`
|
||||
- [CopyPlugin](https://webpack.js.org/plugins/copy-webpack-plugin), доступен только в режиме `production`
|
||||
- [IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin), доступен только в режиме `production`
|
||||
- [BundleAnalyzerPlugin](https://github.com/webpack-contrib/webpack-bundle-analyzer)
|
||||
|
||||
Для настройки плагина необходимо передать объект с конфигурацией или строку `off` для того чтобы отключить плагин:
|
||||
- `htmlWebpackPlugin` - настройка плагина [HTMLWebpackPlugin](https://github.com/jantimon/html-webpack-plugin#options)
|
||||
По умолчанию:
|
||||
```
|
||||
{
|
||||
template: paths.html,
|
||||
favicon: path.resolve(paths.assets, 'favicon.ico'),
|
||||
title: 'My App',
|
||||
}
|
||||
```
|
||||
- `progressPlugin` - настройка плагина [ProgressPlugin](https://webpack.js.org/plugins/progress-plugin/#providing-object)
|
||||
- `definePlugin` - настройка плагина [DefinePlugin](https://webpack.js.org/plugins/define-plugin/#usage)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
'process.env.IS_DEV': JSON.stringify(isDev),
|
||||
'process.env.APP_VERSION': JSON.stringify(process.env.npm_package_version),
|
||||
}
|
||||
```
|
||||
- `forkTsCheckerPlugin` - настройка плагина [ForkTsCheckerWebpackPlugin](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin?tab=readme-ov-file#options)
|
||||
- `eslintPlugin` - настройка плагина [ESLintPlugin](https://github.com/webpack-contrib/eslint-webpack-plugin?tab=readme-ov-file#options)
|
||||
По умолчанию:
|
||||
```
|
||||
{
|
||||
extensions: ['.js', '.jsx', '.tsx', '.ts'],
|
||||
failOnError: false,
|
||||
lintDirtyModulesOnly: true,
|
||||
exclude: [
|
||||
path.resolve(paths.root, 'node_modules'),
|
||||
paths.build,
|
||||
]
|
||||
}
|
||||
```
|
||||
- `reactRefreshPlugin` - настройка плагина [ReactRefreshWebpackPlugin](https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/API.md#options)
|
||||
- `miniCssPlugin` - настройка плагина [MiniCssExtractPlugin](https://webpack.js.org/plugins/mini-css-extract-plugin/#options)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
filename: 'static/css/[name].[contenthash:8].css',
|
||||
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
|
||||
}
|
||||
```
|
||||
- `copyPlugin` - настройка плагина [CopyPlugin](https://webpack.js.org/plugins/copy-webpack-plugin/#options)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
patterns: [{
|
||||
from: path.resolve(paths.root, paths.public),
|
||||
globOptions: {
|
||||
ignore: [
|
||||
path.resolve(paths.root, paths.public, paths.html),
|
||||
path.resolve(paths.root, paths.public, paths.assets),
|
||||
],
|
||||
},
|
||||
to: path.resolve(paths.root, paths.build),
|
||||
}]
|
||||
}
|
||||
```
|
||||
- `ignorePlugin` - настройка плагина [IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin/#root)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
resourceRegExp: /^\.\/locale$/,
|
||||
contextRegExp: /moment$/,
|
||||
}
|
||||
```
|
||||
- `analyzerPlugin` - настройка плагина [BundleAnalyzerPlugin](https://github.com/webpack-contrib/webpack-bundle-analyzer?tab=readme-ov-file#options-for-plugin)
|
||||
|
||||
#### Лоадеры
|
||||
- `loaders` - объект с настройками лоадеров
|
||||
|
||||
Список используемых лоадеров:
|
||||
- [esbuild-loader](https://github.com/privatenumber/esbuild-loader) - лоадер для обработки `.tsx`, `.jsx`, `.ts`, `.js` файлов, при указании настройки [`mainLoader: 'esbuild'`](#загрузчик)
|
||||
- [swc-loader](https://github.com/swc-project/pkgs/tree/main/packages/swc-loader) - лоадер для обработки `.tsx`, `.jsx`, `.ts`, `.js` файлов, при указании настройки [`mainLoader: 'swc'`](#загрузчик)
|
||||
- [@svgr/webpack](https://github.com/gregberge/svgr/tree/main/packages/webpack)- лоадер для обработки `.svg` файлов, для работы с файлами, как с React компонентами
|
||||
- [style-loader](https://webpack.js.org/loaders/style-loader/) - лоадер для вставки стилей inline элементами в html, доступен в режиме `development`
|
||||
- [mini-css-loader](https://webpack.js.org/plugins/mini-css-extract-plugin/#loader-options), доступен в режиме `production` - лоадер для формирование стилей `.css` файлами
|
||||
- [css-loader](https://webpack.js.org/loaders/css-loader/) - лоадер для обработки `.css` файлов
|
||||
- [postcss-loader](https://webpack.js.org/loaders/postcss-loader/) - лоадер для расширение функционала CSS
|
||||
- [sass-loader](https://webpack.js.org/loaders/sass-loader/) - лоадер для обработки `.sass`, `.scss` файлов
|
||||
- [image-loader](https://webpack.js.org/guides/asset-modules/) - лоадер для обработки изображений в формате `.png`, `.jpg`, `.jpeg`, `.gif`, `.avif`, `.webp`
|
||||
- [font-loader](https://webpack.js.org/guides/asset-modules/) - лоадер для обработки шрифтов в формате `.woff`, `.woff2`, `.eot`, `.ttf`, `.otf`
|
||||
|
||||
Для настройки лоадера необходимо передать объект с конфигурацией или строку `off` для того чтобы отключить лоадер:
|
||||
- `esbuilLoader` - настройки для [esbuild-loader](https://github.com/privatenumber/esbuild-loader?tab=readme-ov-file#%EF%B8%8F-options)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
loader: 'tsx',
|
||||
}
|
||||
```
|
||||
- `swcLoader` - настройкаи для [swc-loader](https://swc.rs/docs/configuration/swcrc)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
sync: true,
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
tsx: true,
|
||||
dynamicImport: true,
|
||||
privateMethod: true,
|
||||
functionBind: true,
|
||||
exportDefaultFrom: true,
|
||||
exportNamespaceFrom: true,
|
||||
decorators: true,
|
||||
decoratorsBeforeExport: true,
|
||||
topLevelAwait: true,
|
||||
importMeta: true,
|
||||
},
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
development: isDev,
|
||||
refresh: isDev,
|
||||
},
|
||||
},
|
||||
target: 'es2015',
|
||||
loose: false,
|
||||
externalHelpers: false,
|
||||
keepClassNames: false,
|
||||
},
|
||||
},
|
||||
```
|
||||
- `svgrLoader` - настройка для [@svgr/webpack](https://react-svgr.com/docs/webpack/#options)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
icon: true,
|
||||
}
|
||||
```
|
||||
- `styleLoader` - настройка для [style-loader](https://webpack.js.org/loaders/style-loader/#options)
|
||||
- `miniCssLoader` - настойка для [MiniCssExtractPlugin.loader](https://webpack.js.org/plugins/mini-css-extract-plugin/#loader-options)
|
||||
- `cssLoader` - настройка для [css-loader](https://webpack.js.org/loaders/css-loader/#options)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
sourceMap: mode === 'development',
|
||||
}
|
||||
```
|
||||
- `postCssLoader` - настройка для [postcss-loader](https://webpack.js.org/loaders/postcss-loader/#options)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
postcssOptions: {
|
||||
plugins: [
|
||||
[
|
||||
'postcss-preset-env',
|
||||
{
|
||||
browsers: 'last 2 versions',
|
||||
autoprefixer: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
- `sassLoader` - настройка для [sass-loader](https://webpack.js.org/loaders/sass-loader/#options)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
sourceMap: mode === 'development',
|
||||
}
|
||||
```
|
||||
- `imageloader` - настройка для опции `generator` [Assets Modules](https://webpack.js.org/guides/asset-modules/)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
filename: 'static/images/[name].[contenthash:8][ext][query]',
|
||||
}
|
||||
```
|
||||
- `fontLoader` - настройка для опции `generator` [Assets Modules](https://webpack.js.org/guides/asset-modules/)
|
||||
По умолчанию:
|
||||
```js
|
||||
{
|
||||
filename: 'static/fonts/[hash][ext][query]'
|
||||
}
|
||||
```
|
||||
|
||||
### Расширенная конфигурация
|
||||
Если текущей кастомизации не достаточно или Вы хотите добавить/изменить какой-то плагин или загрузчик,
|
||||
то это можно легко сделать, т.к. функция `createConfig` возвращает обычный объект с конфигурацией webpack, который можно изменить.
|
||||
|
||||
Например, давайте добавим плагин [ `compression-webpack-plugin`](https://www.npmjs.com/package/compression-webpack-plugin)
|
||||
|
||||
```js
|
||||
// webpack.config.js
|
||||
const { createConfig } = require('ksv741-react-scripts');
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
|
||||
module.exports = () => {
|
||||
const baseConfig = createConfig({
|
||||
mode: 'production',
|
||||
});
|
||||
|
||||
baseConfig.plugins.push(
|
||||
new CompressionPlugin({
|
||||
algorithm: 'gzip',
|
||||
}),
|
||||
);
|
||||
|
||||
return baseConfig;
|
||||
};
|
||||
```
|
||||
@ -1,14 +1,28 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
const path = require('path');
|
||||
|
||||
process.on('unhandledRejection', (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
const spawn = require('cross-spawn');
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
const command = argv[0];
|
||||
|
||||
if (!['start', 'build'].includes(command)) {
|
||||
console.log(`Unknown command "${command}"`);
|
||||
|
||||
console.log('***************************');
|
||||
console.log('argv', argv);
|
||||
console.log('***************************');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const webpackArgv = ['webpack'];
|
||||
if (command === 'start') {
|
||||
webpackArgv.push('serve');
|
||||
}
|
||||
webpackArgv.push(...argv.slice(1));
|
||||
|
||||
const result = spawn.sync('npx', webpackArgv, { stdio: 'inherit' });
|
||||
|
||||
if (result.signal) {
|
||||
if (result.signal === 'SIGKILL') {
|
||||
|
||||
30
global.d.ts
vendored
Normal file
30
global.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
declare module '*.jpg' {
|
||||
export default '' as string;
|
||||
}
|
||||
declare module '*.png' {
|
||||
export default '' as string;
|
||||
}
|
||||
declare module '*.jpeg' {
|
||||
export default '' as string;
|
||||
}
|
||||
declare module '*.gif' {
|
||||
export default '' as string;
|
||||
}
|
||||
declare module '*.webp' {
|
||||
export default '' as string;
|
||||
}
|
||||
declare module '*.avif' {
|
||||
export default '' as string;
|
||||
}
|
||||
|
||||
declare module '*.scss' {
|
||||
const content: Record<string, string>;
|
||||
export = content;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
import type React from 'react';
|
||||
|
||||
const SVG: React.VFC<React.SVGProps<SVGSVGElement>>;
|
||||
export default SVG;
|
||||
}
|
||||
66
package.json
66
package.json
@ -2,17 +2,73 @@
|
||||
"name": "ksv741-react-scripts",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"source": "./src/webpack/index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"build": "rm -rf ./dist && tsc",
|
||||
"lint": "eslint --ignore-pattern node_modules --color src"
|
||||
},
|
||||
"bin": {
|
||||
"ksv741-react-scripts": "./bin/scripts.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"engines": {
|
||||
"node": ">16"
|
||||
},
|
||||
"files": [
|
||||
"./dist",
|
||||
"./bin",
|
||||
"global.d.ts"
|
||||
],
|
||||
"keywords": [
|
||||
"webpack",
|
||||
"esbuild",
|
||||
"swc",
|
||||
"bundle",
|
||||
"development",
|
||||
"typescript",
|
||||
"react"
|
||||
],
|
||||
"author": "Sergey <ksv741@gmail.com> Krylov",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3"
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@swc/core": "1.4.0",
|
||||
"@types/node": "20.11.17",
|
||||
"@types/react": "18.2.55",
|
||||
"@types/react-dom": "18.2.19",
|
||||
"@types/react-router-dom": "5.3.3",
|
||||
"@types/sass-loader": "8.0.8",
|
||||
"@types/webpack-bundle-analyzer": "4.7.0",
|
||||
"@types/webpack-dev-server": "4.7.2",
|
||||
"copy-webpack-plugin": "12.0.2",
|
||||
"cross-spawn": "7.0.3",
|
||||
"css-loader": "6.10.0",
|
||||
"css-minimizer-webpack-plugin": "6.0.0",
|
||||
"esbuild-loader": "4.0.3",
|
||||
"eslint-webpack-plugin": "4.0.1",
|
||||
"fork-ts-checker-webpack-plugin": "9.0.2",
|
||||
"html-webpack-plugin": "5.6.0",
|
||||
"mini-css-extract-plugin": "2.8.0",
|
||||
"postcss": "8.4.35",
|
||||
"postcss-loader": "8.1.0",
|
||||
"postcss-preset-env": "9.3.0",
|
||||
"react-refresh": "0.14.0",
|
||||
"sass": "1.70.0",
|
||||
"sass-loader": "14.1.0",
|
||||
"style-loader": "3.3.4",
|
||||
"swc-loader": "0.2.6",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.3.3",
|
||||
"webpack": "5.90.1",
|
||||
"webpack-bundle-analyzer": "4.10.1",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-ksv741": "git+ssh://git@github.com:ksv741/eslint-config-ksv741.git"
|
||||
}
|
||||
}
|
||||
|
||||
117
src/config/index.ts
Normal file
117
src/config/index.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import process from 'process';
|
||||
import type webpack from 'webpack';
|
||||
import { createDevServer } from '../dev-server';
|
||||
import { createLoaders } from '../loaders';
|
||||
import { getOptimization } from '../optimization';
|
||||
import { createOutput } from '../output';
|
||||
import { createPlugins } from '../plugins';
|
||||
import { createResolvers } from '../resolvers';
|
||||
import type { WebpackMode, WebpackOptions, WebpackPaths } from '../types';
|
||||
|
||||
const getDefaultEntryPath = (rootDir: string) => {
|
||||
const indexTsx = path.resolve(rootDir, 'index.tsx');
|
||||
const indexJsx = path.resolve(rootDir, 'index.jsx');
|
||||
const indexJs = path.resolve(rootDir, 'index.js');
|
||||
|
||||
switch (true) {
|
||||
case fs.existsSync(indexTsx):
|
||||
return indexTsx;
|
||||
|
||||
case fs.existsSync(indexJsx):
|
||||
return indexJsx;
|
||||
|
||||
case fs.existsSync(indexJs):
|
||||
return indexJs;
|
||||
|
||||
default:
|
||||
throw new Error(`Has no index.tsx/index.jsx/index.js file in ${rootDir} directory`);
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultPaths = (initialPaths?: WebpackPaths): WebpackPaths => {
|
||||
const root = initialPaths?.root ?? path.resolve(process.cwd());
|
||||
const publicPath = initialPaths?.public ?? path.resolve(root, 'public');
|
||||
const src = initialPaths?.src ?? path.resolve(root, 'src');
|
||||
const assets = initialPaths?.assets ?? path.resolve(publicPath, 'assets');
|
||||
const build = initialPaths?.build ?? path.resolve(root, 'build');
|
||||
const entry = getDefaultEntryPath(src);
|
||||
const html = initialPaths?.html ?? path.resolve(publicPath, 'index.html');
|
||||
|
||||
return {
|
||||
root,
|
||||
public: publicPath,
|
||||
assets,
|
||||
src,
|
||||
entry,
|
||||
build,
|
||||
html,
|
||||
};
|
||||
};
|
||||
|
||||
const getDefaultMode = (): WebpackMode => {
|
||||
const isServe = process.argv.slice(2)[0] === 'serve';
|
||||
|
||||
return isServe ? 'development' : 'production';
|
||||
};
|
||||
|
||||
const createConfig = (params?: WebpackOptions): webpack.Configuration => {
|
||||
const {
|
||||
mode = getDefaultMode(),
|
||||
analyze = false,
|
||||
mainLoader = 'esbuild',
|
||||
port = 3000,
|
||||
devtool = 'inline-source-map',
|
||||
plugins,
|
||||
} = params ?? {};
|
||||
const {
|
||||
assets,
|
||||
html,
|
||||
build,
|
||||
root,
|
||||
entry,
|
||||
public: publicPath,
|
||||
src,
|
||||
} = getDefaultPaths(params?.paths);
|
||||
|
||||
if (!fs.existsSync(html)) {
|
||||
throw new Error(`index.html file not found. Define path to index.html file or create one in "${publicPath}" directory`);
|
||||
}
|
||||
|
||||
const options: WebpackOptions = {
|
||||
mode,
|
||||
analyze,
|
||||
mainLoader,
|
||||
port,
|
||||
paths: {
|
||||
assets,
|
||||
html,
|
||||
build,
|
||||
root,
|
||||
entry,
|
||||
src,
|
||||
public: publicPath,
|
||||
},
|
||||
plugins,
|
||||
};
|
||||
|
||||
const isDev = mode === 'development';
|
||||
|
||||
return {
|
||||
mode,
|
||||
entry,
|
||||
output: createOutput(options),
|
||||
plugins: createPlugins(options),
|
||||
module: {
|
||||
rules: createLoaders(options),
|
||||
},
|
||||
resolve: createResolvers(options),
|
||||
devtool: isDev ? devtool : false,
|
||||
devServer: createDevServer(options),
|
||||
// eslint-disable-next-line no-undefined
|
||||
optimization: !isDev ? getOptimization(options) : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export { createConfig };
|
||||
22
src/dev-server/index.ts
Normal file
22
src/dev-server/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
|
||||
import type { WebpackOptions } from '../types';
|
||||
|
||||
export const createDevServer = (options: WebpackOptions): DevServerConfiguration => {
|
||||
const {
|
||||
port,
|
||||
} = options;
|
||||
|
||||
return {
|
||||
port,
|
||||
open: false,
|
||||
historyApiFallback: true,
|
||||
hot: true,
|
||||
client: {
|
||||
overlay: {
|
||||
errors: true,
|
||||
warnings: false,
|
||||
runtimeErrors: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
8
src/index.ts
Normal file
8
src/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export type {
|
||||
WebpackEnv,
|
||||
WebpackMode,
|
||||
WebpackOptions,
|
||||
WebpackPaths,
|
||||
WebpackMainLoaders,
|
||||
} from './types';
|
||||
export { createConfig } from './config';
|
||||
23
src/loaders/asset-loaders/font-loader/index.ts
Normal file
23
src/loaders/asset-loaders/font-loader/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../../types';
|
||||
|
||||
export const getFontLoader = (options: WebpackOptions): webpack.RuleSetRule | null => {
|
||||
const {
|
||||
loaders: {
|
||||
fontLoader,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (fontLoader === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'static/fonts/[hash][ext][query]',
|
||||
...fontLoader,
|
||||
},
|
||||
};
|
||||
};
|
||||
23
src/loaders/asset-loaders/image-loader/index.ts
Normal file
23
src/loaders/asset-loaders/image-loader/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../../types';
|
||||
|
||||
export const getImageLoader = (options: WebpackOptions): webpack.RuleSetRule | null => {
|
||||
const {
|
||||
loaders: {
|
||||
imageLoader,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (imageLoader === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
test: /\.(png|jpg|jpeg|gif|avif|webp)$/i,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'static/images/[name].[contenthash:8][ext][query]',
|
||||
...imageLoader,
|
||||
},
|
||||
};
|
||||
};
|
||||
9
src/loaders/asset-loaders/index.ts
Normal file
9
src/loaders/asset-loaders/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
import { getFontLoader } from './font-loader';
|
||||
import { getImageLoader } from './image-loader';
|
||||
|
||||
export const getAssetsLoaders = (options: WebpackOptions): (webpack.RuleSetRule | null)[] => [
|
||||
getImageLoader(options),
|
||||
getFontLoader(options),
|
||||
];
|
||||
26
src/loaders/esbuild-loader/index.ts
Normal file
26
src/loaders/esbuild-loader/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import path from 'path';
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getEsbuildLoader = (options: WebpackOptions): webpack.RuleSetRule | null => {
|
||||
const {
|
||||
paths,
|
||||
loaders: {
|
||||
esbuildLoader,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (esbuildLoader === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
test: /\.[j|t]sx?$/,
|
||||
loader: 'esbuild-loader',
|
||||
options: {
|
||||
loader: 'tsx',
|
||||
...esbuildLoader,
|
||||
},
|
||||
exclude: path.resolve(paths.root, 'node_modules'),
|
||||
};
|
||||
};
|
||||
30
src/loaders/index.ts
Normal file
30
src/loaders/index.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../types';
|
||||
import { getAssetsLoaders } from './asset-loaders';
|
||||
import { getStyleLoaders } from './style-loaders';
|
||||
import { getEsbuildLoader } from './esbuild-loader';
|
||||
import { getSvgLoader } from './svgr-loader';
|
||||
import { getSwcLoader } from './swc-loader';
|
||||
|
||||
export const createLoaders = (options: WebpackOptions): (webpack.RuleSetRule | null)[] => {
|
||||
const {
|
||||
mainLoader,
|
||||
} = options;
|
||||
|
||||
const getMainLoader = () => {
|
||||
switch (mainLoader) {
|
||||
case 'swc':
|
||||
return getSwcLoader(options);
|
||||
case 'esbuild':
|
||||
default:
|
||||
return getEsbuildLoader(options);
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
getMainLoader(),
|
||||
getSvgLoader(options),
|
||||
...getStyleLoaders(options),
|
||||
...getAssetsLoaders(options),
|
||||
].filter(Boolean);
|
||||
};
|
||||
67
src/loaders/style-loaders/css-loader/index.ts
Normal file
67
src/loaders/style-loaders/css-loader/index.ts
Normal file
@ -0,0 +1,67 @@
|
||||
// todo fixme
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment, no-unsafe-optional-chaining, @typescript-eslint/no-unsafe-member-access, no-nested-ternary */
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../../types';
|
||||
|
||||
export const getCssLoader = (options: WebpackOptions): webpack.RuleSetRule => {
|
||||
const {
|
||||
mode,
|
||||
loaders: {
|
||||
styleLoader,
|
||||
miniCssLoader,
|
||||
cssLoader,
|
||||
postCssLoader,
|
||||
} = {},
|
||||
} = options;
|
||||
const isDev = mode === 'development';
|
||||
|
||||
return {
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
isDev
|
||||
? styleLoader === 'off'
|
||||
? null
|
||||
: {
|
||||
loader: 'style-loader',
|
||||
options: styleLoader,
|
||||
}
|
||||
: miniCssLoader === 'off'
|
||||
? null
|
||||
: {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: miniCssLoader,
|
||||
},
|
||||
cssLoader === 'off'
|
||||
? null
|
||||
: {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: isDev,
|
||||
...cssLoader,
|
||||
},
|
||||
},
|
||||
postCssLoader === 'off'
|
||||
? null
|
||||
: {
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: [
|
||||
[
|
||||
'postcss-preset-env',
|
||||
{
|
||||
browsers: 'last 2 versions',
|
||||
autoprefixer: true,
|
||||
},
|
||||
],
|
||||
...postCssLoader?.postcssOptions?.plugins ?? [],
|
||||
].filter(Boolean),
|
||||
...postCssLoader?.postcssOptions,
|
||||
},
|
||||
...postCssLoader,
|
||||
},
|
||||
},
|
||||
].filter(Boolean),
|
||||
};
|
||||
};
|
||||
9
src/loaders/style-loaders/index.ts
Normal file
9
src/loaders/style-loaders/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
import { getCssLoader } from './css-loader';
|
||||
import { getSassLoader } from './sass-loader';
|
||||
|
||||
export const getStyleLoaders = (options: WebpackOptions): (webpack.RuleSetRule | null)[] => [
|
||||
getCssLoader(options),
|
||||
getSassLoader(options),
|
||||
];
|
||||
32
src/loaders/style-loaders/sass-loader/index.ts
Normal file
32
src/loaders/style-loaders/sass-loader/index.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../../types';
|
||||
import { getCssLoader } from '../css-loader';
|
||||
|
||||
export const getSassLoader = (options: WebpackOptions): webpack.RuleSetRule | null => {
|
||||
const {
|
||||
mode,
|
||||
loaders: {
|
||||
sassLoader,
|
||||
} = {},
|
||||
} = options;
|
||||
const isDev = mode === 'development';
|
||||
|
||||
if (sassLoader === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
// @ts-ignore
|
||||
...getCssLoader(options).use,
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: isDev,
|
||||
...sassLoader,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
26
src/loaders/svgr-loader/index.ts
Normal file
26
src/loaders/svgr-loader/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getSvgLoader = (options: WebpackOptions): webpack.RuleSetRule | null => {
|
||||
const {
|
||||
loaders: {
|
||||
svgrLoader,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (svgrLoader === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
test: /\.svg$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: [{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
icon: true,
|
||||
...svgrLoader,
|
||||
},
|
||||
}],
|
||||
};
|
||||
};
|
||||
58
src/loaders/swc-loader/index.ts
Normal file
58
src/loaders/swc-loader/index.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getSwcLoader = (options: WebpackOptions): webpack.RuleSetRule | null => {
|
||||
const {
|
||||
mode,
|
||||
loaders: {
|
||||
swcLoader,
|
||||
} = {},
|
||||
} = options;
|
||||
const isDev = mode === 'development';
|
||||
|
||||
if (swcLoader === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
test: /\.[j|t]sx?$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
use: {
|
||||
loader: 'swc-loader',
|
||||
options: {
|
||||
sync: true,
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
tsx: true,
|
||||
dynamicImport: true,
|
||||
privateMethod: true,
|
||||
functionBind: true,
|
||||
exportDefaultFrom: true,
|
||||
exportNamespaceFrom: true,
|
||||
decorators: true,
|
||||
decoratorsBeforeExport: true,
|
||||
topLevelAwait: true,
|
||||
importMeta: true,
|
||||
...swcLoader?.jsc?.parser,
|
||||
},
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
development: isDev,
|
||||
refresh: isDev,
|
||||
...swcLoader?.jsc?.transform?.react,
|
||||
},
|
||||
...swcLoader?.jsc?.transform,
|
||||
},
|
||||
target: 'es2015',
|
||||
loose: false,
|
||||
externalHelpers: false,
|
||||
keepClassNames: false,
|
||||
...swcLoader?.jsc,
|
||||
},
|
||||
...swcLoader,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
26
src/optimization/index.ts
Normal file
26
src/optimization/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../types';
|
||||
|
||||
export const getOptimization = (options: WebpackOptions): webpack.Configuration['optimization'] => {
|
||||
const {
|
||||
mode,
|
||||
} = options;
|
||||
const isDev = mode === 'development';
|
||||
|
||||
return {
|
||||
minimize: !isDev,
|
||||
minimizer: [
|
||||
new TerserPlugin(),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
name: false,
|
||||
},
|
||||
runtimeChunk: {
|
||||
name: (entrypoint: { name: string }) => `runtime-${entrypoint.name}`,
|
||||
},
|
||||
};
|
||||
};
|
||||
29
src/output/index.ts
Normal file
29
src/output/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import path from 'path';
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../types';
|
||||
|
||||
export const createOutput = (options: WebpackOptions): webpack.Configuration['output'] => {
|
||||
const {
|
||||
paths,
|
||||
mode,
|
||||
} = options;
|
||||
|
||||
const isDev = mode === 'development';
|
||||
|
||||
const devOutput: webpack.Configuration['output'] = {
|
||||
filename: '[name][fullhash].js',
|
||||
path: path.resolve(paths.build),
|
||||
publicPath: '/',
|
||||
};
|
||||
|
||||
const productionOutput: webpack.Configuration['output'] = {
|
||||
filename: 'static/js/[name].[contenthash:8].js',
|
||||
path: path.resolve(paths.build),
|
||||
publicPath: '/',
|
||||
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
|
||||
clean: true,
|
||||
assetModuleFilename: 'static/assets/[hash][ext][query]',
|
||||
};
|
||||
|
||||
return isDev ? devOutput : productionOutput;
|
||||
};
|
||||
17
src/plugins/bundle-analyzer-plugin/index.ts
Normal file
17
src/plugins/bundle-analyzer-plugin/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getBundleAnalyzerPlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
analyze,
|
||||
plugins: {
|
||||
analyzerPlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (!analyze || analyzerPlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BundleAnalyzerPlugin(analyzerPlugin);
|
||||
};
|
||||
34
src/plugins/copy-plugin/index.ts
Normal file
34
src/plugins/copy-plugin/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import CopyPlugin from 'copy-webpack-plugin';
|
||||
import path from 'path';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getCopyPlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
paths,
|
||||
plugins: {
|
||||
copyPlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (copyPlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(paths.root, paths.public),
|
||||
globOptions: {
|
||||
ignore: [
|
||||
path.resolve(paths.root, paths.public, paths.html),
|
||||
path.resolve(paths.root, paths.public, paths.assets),
|
||||
],
|
||||
},
|
||||
to: path.resolve(paths.root, paths.build),
|
||||
},
|
||||
|
||||
...copyPlugin?.patterns ?? [],
|
||||
].filter(Boolean),
|
||||
options: copyPlugin?.options,
|
||||
});
|
||||
};
|
||||
22
src/plugins/define-plugin/index.ts
Normal file
22
src/plugins/define-plugin/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getDefinePlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
mode,
|
||||
plugins: {
|
||||
definePlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
const isDev = mode === 'development';
|
||||
|
||||
if (definePlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new webpack.DefinePlugin({
|
||||
'process.env.IS_DEV': JSON.stringify(isDev),
|
||||
'process.env.APP_VERSION': JSON.stringify(process.env.npm_package_version),
|
||||
...definePlugin,
|
||||
});
|
||||
};
|
||||
45
src/plugins/eslint-plugin/index.ts
Normal file
45
src/plugins/eslint-plugin/index.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import ESLintPlugin from 'eslint-webpack-plugin';
|
||||
import path from 'path';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getEslintPlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
paths,
|
||||
plugins: {
|
||||
eslintPlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
const hasEslintPackage = Boolean(process.env.npm_package_dependencies_eslint
|
||||
?? process.env.npm_package_devDependencies_eslint);
|
||||
|
||||
if (!hasEslintPackage || eslintPlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getExcludeArray = () => {
|
||||
// todo fixme
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (!eslintPlugin?.exclude) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (Array.isArray(eslintPlugin.exclude)) {
|
||||
return eslintPlugin.exclude;
|
||||
}
|
||||
|
||||
return [eslintPlugin.exclude];
|
||||
};
|
||||
|
||||
return new ESLintPlugin({
|
||||
extensions: ['.js', '.jsx', '.tsx', '.ts'],
|
||||
failOnError: false,
|
||||
lintDirtyModulesOnly: true,
|
||||
exclude: [
|
||||
path.resolve(paths.root, 'node_modules'),
|
||||
paths.build,
|
||||
...getExcludeArray(),
|
||||
].filter(Boolean),
|
||||
...eslintPlugin,
|
||||
});
|
||||
};
|
||||
16
src/plugins/fork-ts-checker-plugin/index.ts
Normal file
16
src/plugins/fork-ts-checker-plugin/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getForkTsCheckerPlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
plugins: {
|
||||
forkTsCheckerPlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (forkTsCheckerPlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ForkTsCheckerWebpackPlugin(forkTsCheckerPlugin);
|
||||
};
|
||||
23
src/plugins/html-plugin/index.ts
Normal file
23
src/plugins/html-plugin/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import HTMLWebpackPlugin from 'html-webpack-plugin';
|
||||
import path from 'path';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getHtmlPlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
paths,
|
||||
plugins: {
|
||||
htmlWebpackPlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (htmlWebpackPlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new HTMLWebpackPlugin({
|
||||
template: paths.html,
|
||||
favicon: path.resolve(paths.assets, 'favicon.ico'),
|
||||
title: 'My App',
|
||||
...htmlWebpackPlugin,
|
||||
});
|
||||
};
|
||||
20
src/plugins/ignore-plugin/index.ts
Normal file
20
src/plugins/ignore-plugin/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getIgnorePlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
plugins: {
|
||||
ignorePlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (ignorePlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new webpack.IgnorePlugin({
|
||||
resourceRegExp: /^\.\/locale$/,
|
||||
contextRegExp: /moment$/,
|
||||
...ignorePlugin,
|
||||
});
|
||||
};
|
||||
43
src/plugins/index.ts
Normal file
43
src/plugins/index.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../types';
|
||||
import { getBundleAnalyzerPlugin } from './bundle-analyzer-plugin';
|
||||
import { getCopyPlugin } from './copy-plugin';
|
||||
import { getDefinePlugin } from './define-plugin';
|
||||
import { getEslintPlugin } from './eslint-plugin';
|
||||
import { getForkTsCheckerPlugin } from './fork-ts-checker-plugin';
|
||||
import { getHtmlPlugin } from './html-plugin';
|
||||
import { getIgnorePlugin } from './ignore-plugin';
|
||||
import { getMiniCssPlugin } from './mini-css-plugin';
|
||||
import { getProgressPlugin } from './progress';
|
||||
import { getReactRefreshPlugin } from './react-refresh-plugin';
|
||||
|
||||
export const createPlugins = (options: WebpackOptions): webpack.Configuration['plugins'] => {
|
||||
const {
|
||||
mode,
|
||||
} = options;
|
||||
const isDev = mode === 'development';
|
||||
|
||||
const developmentPlugins = [
|
||||
getHtmlPlugin(options),
|
||||
getProgressPlugin(options),
|
||||
getDefinePlugin(options),
|
||||
getForkTsCheckerPlugin(options),
|
||||
getEslintPlugin(options),
|
||||
getReactRefreshPlugin(options),
|
||||
getBundleAnalyzerPlugin(options),
|
||||
];
|
||||
|
||||
const productionPlugins = [
|
||||
getHtmlPlugin(options),
|
||||
getDefinePlugin(options),
|
||||
getMiniCssPlugin(options),
|
||||
getForkTsCheckerPlugin(options),
|
||||
getCopyPlugin(options),
|
||||
getIgnorePlugin(options),
|
||||
getBundleAnalyzerPlugin(options),
|
||||
];
|
||||
|
||||
const plugins = isDev ? developmentPlugins : productionPlugins;
|
||||
|
||||
return plugins.filter(Boolean);
|
||||
};
|
||||
20
src/plugins/mini-css-plugin/index.ts
Normal file
20
src/plugins/mini-css-plugin/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getMiniCssPlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
plugins: {
|
||||
miniCssPlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (miniCssPlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MiniCssExtractPlugin({
|
||||
filename: 'static/css/[name].[contenthash:8].css',
|
||||
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
|
||||
...miniCssPlugin,
|
||||
});
|
||||
};
|
||||
16
src/plugins/progress/index.ts
Normal file
16
src/plugins/progress/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getProgressPlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
plugins: {
|
||||
progressPlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (progressPlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new webpack.ProgressPlugin(progressPlugin);
|
||||
};
|
||||
19
src/plugins/react-refresh-plugin/index.ts
Normal file
19
src/plugins/react-refresh-plugin/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
|
||||
import type { WebpackOptions } from '../../types';
|
||||
|
||||
export const getReactRefreshPlugin = (options: WebpackOptions) => {
|
||||
const {
|
||||
plugins: {
|
||||
reactRefreshPlugin,
|
||||
} = {},
|
||||
} = options;
|
||||
|
||||
if (reactRefreshPlugin === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ReactRefreshWebpackPlugin({
|
||||
overlay: false,
|
||||
...reactRefreshPlugin,
|
||||
});
|
||||
};
|
||||
20
src/resolvers/index.ts
Normal file
20
src/resolvers/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import path from 'path';
|
||||
|
||||
import type webpack from 'webpack';
|
||||
import type { WebpackOptions } from '../types';
|
||||
|
||||
export const createResolvers = (options: WebpackOptions): webpack.ResolveOptions => {
|
||||
const {
|
||||
paths,
|
||||
} = options;
|
||||
|
||||
return {
|
||||
extensions: ['.tsx', '.ts', '.js', '.jsx'],
|
||||
mainFiles: ['index'],
|
||||
modules: [
|
||||
paths.src,
|
||||
path.resolve(paths.root),
|
||||
path.resolve(paths.root, 'node_modules'),
|
||||
],
|
||||
};
|
||||
};
|
||||
180
src/types/index.ts
Normal file
180
src/types/index.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import type { ReactRefreshPluginOptions } from '@pmmmwh/react-refresh-webpack-plugin/types/lib/types';
|
||||
import type { Config as SvgrLoaderOptions } from '@svgr/core';
|
||||
import type { Config as SwcLoaderOptions } from '@swc/core';
|
||||
import type CopyPlugin from 'copy-webpack-plugin';
|
||||
import type { LoaderOptions as EsbuildLoaderOptions } from 'esbuild-loader';
|
||||
import type ESLintWebpackPlugin from 'eslint-webpack-plugin';
|
||||
import type { ForkTsCheckerWebpackPluginOptions } from 'fork-ts-checker-webpack-plugin/lib/plugin-options';
|
||||
import type HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import type MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import type { LoaderOptions as MiniCssLoaderOptions } from 'mini-css-extract-plugin';
|
||||
import type { PostCSSLoaderOptions } from 'postcss-loader/dist/config';
|
||||
import type webpack from 'webpack';
|
||||
import type { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||
import type SassLoader from 'sass-loader';
|
||||
|
||||
type WebpackMode = 'development' | 'production';
|
||||
|
||||
type WebpackMainLoaders = 'esbuild' | 'swc';
|
||||
|
||||
type WebpackPaths = {
|
||||
entry: string[] | string;
|
||||
build: string;
|
||||
root: string;
|
||||
html: string;
|
||||
src: string;
|
||||
public: string;
|
||||
assets: string;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace AssetsLoader {
|
||||
type Options = {
|
||||
filename?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace StyleLoader {
|
||||
type InjectType =
|
||||
'autoStyleTag' | 'lazyAutoStyleTag' | 'lazySingletonStyleTag' | 'lazyStyleTag' | 'linkTag' | 'singletonStyleTag' | 'styleTag';
|
||||
|
||||
type Insert =
|
||||
| string
|
||||
| ((htmlElement: HTMLElement, options: Record<string, unknown>) => void);
|
||||
|
||||
type Attributes = Record<string, unknown>;
|
||||
|
||||
type StyleTagTransform =
|
||||
| string
|
||||
| ((
|
||||
css: string,
|
||||
styleElement: HTMLStyleElement,
|
||||
options: Record<string, unknown>
|
||||
) => void);
|
||||
|
||||
type Base = number;
|
||||
|
||||
type EsModule = boolean;
|
||||
|
||||
type Options = {
|
||||
injectType?: InjectType;
|
||||
attributes?: Attributes;
|
||||
insert?: Insert;
|
||||
styleTagTransform?: StyleTagTransform;
|
||||
base?: Base;
|
||||
esModule?: EsModule;
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace CssLoader {
|
||||
type Url =
|
||||
| boolean
|
||||
| {
|
||||
filter: (url: string, resourcePath: string) => boolean;
|
||||
};
|
||||
|
||||
type ImportFn =
|
||||
| boolean
|
||||
| {
|
||||
filter: (
|
||||
url: string,
|
||||
media: string,
|
||||
resourcePath: string,
|
||||
supports?: string,
|
||||
layer?: string
|
||||
) => boolean;
|
||||
};
|
||||
type Modules =
|
||||
boolean | 'global' | 'icss' | 'local' | 'pure' | {
|
||||
auto: RegExp | boolean | ((resourcePath: string) => boolean);
|
||||
mode:
|
||||
'global' | 'icss' | 'local' | 'pure' | ((resourcePath: string) => 'global' | 'icss' | 'local' | 'pure');
|
||||
localIdentName: string;
|
||||
localIdentContext: string;
|
||||
localIdentHashSalt: string;
|
||||
localIdentHashFunction: string;
|
||||
localIdentHashDigest: string;
|
||||
localIdentRegExp: RegExp | string;
|
||||
getLocalIdent: (
|
||||
// @ts-ignore
|
||||
context: webpack.loader.LoaderContext,
|
||||
localIdentName: string,
|
||||
localName: string
|
||||
) => string;
|
||||
namedExport: boolean;
|
||||
exportGlobals: boolean;
|
||||
exportLocalsConvention:
|
||||
| 'asIs'
|
||||
| 'camelCase'
|
||||
| 'camelCaseOnly'
|
||||
| 'dashes'
|
||||
| 'dashesOnly'
|
||||
| ((name: string) => string);
|
||||
exportOnlyLocals: boolean;
|
||||
};
|
||||
type SourceMap = boolean;
|
||||
type ImportLoaders = number;
|
||||
type EsModule = boolean;
|
||||
type ExportType = 'array' | 'css-style-sheet' | 'string';
|
||||
|
||||
type Options = {
|
||||
url?: Url;
|
||||
import?: ImportFn;
|
||||
modules?: Modules;
|
||||
sourceMap?: SourceMap;
|
||||
importLoaders?: ImportLoaders;
|
||||
esModule?: EsModule;
|
||||
exportType?: ExportType;
|
||||
};
|
||||
}
|
||||
|
||||
type WebpackOptions = {
|
||||
mode: WebpackMode;
|
||||
paths: WebpackPaths;
|
||||
port: number;
|
||||
mainLoader: WebpackMainLoaders;
|
||||
analyze: boolean;
|
||||
devtool?: webpack.Configuration['devtool'];
|
||||
plugins?: {
|
||||
htmlWebpackPlugin?: HtmlWebpackPlugin.Options | 'off';
|
||||
progressPlugin?: ConstructorParameters<typeof webpack.ProgressPlugin>[0] | 'off';
|
||||
definePlugin?: ConstructorParameters<typeof webpack.DefinePlugin>[0] | 'off';
|
||||
forkTsCheckerPlugin?: ForkTsCheckerWebpackPluginOptions | 'off';
|
||||
eslintPlugin?: ESLintWebpackPlugin.Options | 'off';
|
||||
reactRefreshPlugin?: ReactRefreshPluginOptions | 'off';
|
||||
miniCssPlugin?: MiniCssExtractPlugin.PluginOptions | 'off';
|
||||
copyPlugin?: CopyPlugin.PluginOptions | 'off';
|
||||
ignorePlugin?: ConstructorParameters<typeof webpack.IgnorePlugin>[0] | 'off';
|
||||
analyzerPlugin?: BundleAnalyzerPlugin.Options | 'off';
|
||||
};
|
||||
loaders?: {
|
||||
esbuildLoader?: EsbuildLoaderOptions | 'off';
|
||||
swcLoader?: SwcLoaderOptions | 'off';
|
||||
svgrLoader?: SvgrLoaderOptions | 'off';
|
||||
styleLoader?: StyleLoader.Options | 'off';
|
||||
cssLoader?: CssLoader.Options | 'off';
|
||||
miniCssLoader?: MiniCssLoaderOptions | 'off';
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
postCssLoader?: PostCSSLoaderOptions | 'off';
|
||||
sassLoader?: SassLoader.Options | 'off';
|
||||
imageLoader?: AssetsLoader.Options | 'off';
|
||||
fontLoader?: AssetsLoader.Options | 'off';
|
||||
};
|
||||
};
|
||||
|
||||
type WebpackEnv = {
|
||||
mode?: WebpackMode;
|
||||
port?: number;
|
||||
mainLoader?: WebpackMainLoaders;
|
||||
analyze?: boolean;
|
||||
};
|
||||
|
||||
export type {
|
||||
WebpackMode,
|
||||
WebpackPaths,
|
||||
WebpackOptions,
|
||||
WebpackEnv,
|
||||
WebpackMainLoaders,
|
||||
};
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"outDir": "./dist",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"DOM"
|
||||
]
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user