@razerspine/webpack-core 1.4.1 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,54 @@ required to ensure correct behavior in both development and production modes.
8
8
 
9
9
  ---
10
10
 
11
+ ## [1.7.0] - 2026-02-20
12
+
13
+ ### Added
14
+
15
+ - **SPA Support (Experimental Stable API)**
16
+ - Introduced new `appType` option: `'mpa' | 'spa'`
17
+ - `mpa` remains the default mode (template directory driven)
18
+ - `spa` mode supports a single Pug entry file and outputs `index.html`
19
+ - Enables SPA-style projects while preserving template-driven architecture
20
+
21
+ - **Options Normalization Layer**
22
+ - Introduced `normalizeCoreOptions()` (internal utility)
23
+ - Centralized default resolution for:
24
+ - `mode`
25
+ - `appType`
26
+ - `templates.entry`
27
+ - `resolve.alias`
28
+ - Eliminates duplicated fallback logic across config layers
29
+ - Establishes a single source of truth for configuration defaults
30
+
31
+ - **Improved Templates Validation**
32
+ - Validation logic now differentiates between:
33
+ - Directory entry (MPA)
34
+ - Single file entry (SPA)
35
+ - Prevents incorrect entry usage at build initialization
36
+
37
+ ### Changed
38
+
39
+ - **Architecture Refactor**
40
+ - Removed default resolution logic from `templatesLoader`
41
+ - Simplified `validateCoreOptions` to focus only on value validation
42
+ - Moved filesystem validation responsibility into template loader
43
+ - Improved internal separation of concerns
44
+
45
+ - **Cleaner Internal API Design**
46
+ - Base config now operates on normalized options
47
+ - Reduced configuration coupling
48
+ - Improved long-term scalability (SSR / future app types)
49
+
50
+ ### Notes
51
+
52
+ - No breaking changes were introduced.
53
+ - Default behavior remains `mpa`.
54
+ - Fully backward compatible with existing templates.
55
+ - SPA mode is stable but recommended for controlled usage.
56
+
57
+ ---
58
+
11
59
  ## [1.4.1] - 2026-02-19
12
60
 
13
61
  ### Changed
package/README.md CHANGED
@@ -11,38 +11,63 @@ template-driven builds using `pug-plugin`.
11
11
 
12
12
  ---
13
13
 
14
- ## ⚠️ Important
14
+ ## Designed for
15
15
 
16
- Versions prior to 1.1.6 were part of a stabilization phase and are not recommended for production use.
16
+ Part of the
17
+ [Webpack Starter Monorepo](https://github.com/Razerspine/webpack-starter-monorepo).
17
18
 
18
- Please use:
19
+ Can be used independently in any Pug-based project.
19
20
 
20
- ```bash
21
- npm install @razerspine/webpack-core@^1.2.1
21
+ ---
22
+
23
+ ## Key Features
24
+
25
+ - Pug template-driven builds (no implicit JS entry)
26
+ - Multi-page (MPA) and Single-page (SPA) modes
27
+ - JavaScript or TypeScript support
28
+ - SCSS or Less support
29
+ - Recursive file watching (`src/**/*`)
30
+ - SPA-friendly dev server
31
+ - Config validation layer
32
+ - Centralized options normalization (v1.7.0+)
33
+ - Fully customizable dev & prod configs
34
+
35
+ ---
36
+
37
+ ## Application Modes (v1.7.0+)
38
+
39
+ ### MPA (Default)
40
+
41
+ ```js
42
+ appType: 'mpa'
22
43
  ```
23
44
 
24
- ## Designed for
45
+ - `templates.entry` must be a directory
46
+ - Example: `src/views/pages`
47
+ - Each page generates its own HTML file
25
48
 
26
- This package is developed as part of the
27
- [Webpack Starter Monorepo](https://github.com/Razerspine/webpack-starter-monorepo).
49
+ ### SPA
28
50
 
29
- It contains shared webpack configuration and loaders used by the starter
30
- templates, but can also be used independently.
51
+ ```js
52
+ appType: 'spa'
53
+ ```
54
+
55
+ - `templates.entry` must be a single Pug file
56
+ - Example: `src/views/app.pug`
57
+ - Always outputs: `index.html`
31
58
 
32
59
  ---
33
60
 
34
- ## Design principles
61
+ ## Design Principles
35
62
 
36
63
  - **Webpack is responsible for**: module resolution, aliases (`resolve.alias`), and asset handling.
37
- - **Flexibility:** Since v1.2.1, you can override any part of the dev or prod configuration using an optional `options`
38
- argument.
39
- - **Stability:** `pug-plugin` is used to compile templates, and asset paths are resolved by webpack.
40
- - **Template-driven**: Webpack JS entry is intentionally disabled. Builds are driven by template entries in
41
- `src/views/pages`.
42
- - **Sensible Defaults**: No aggressive production optimizations (like `splitChunks`) are enabled by default to prevent
43
- asset resolution issues in templates.
44
- - **Validated configuration layer (v1.2.2+)**: Core options are validated before build initialization to prevent silent
45
- runtime failures.
64
+ - **Template-driven architecture**: Webpack JS entry is intentionally disabled. Builds are driven by Pug template entries.
65
+ - **MPA by default**: Directory-based page generation remains the primary mode.
66
+ - **Optional SPA support (v1.7.0+)**: Single-entry template mode is supported without breaking MPA workflow.
67
+ - **Stability-first production defaults**: No aggressive optimizations (e.g. `splitChunks`) are enabled by default to prevent template asset resolution issues.
68
+ - **Validated configuration layer**: Core options are validated before Webpack initialization.
69
+ - **Centralized option normalization (v1.7.0+)**: Default resolution is handled internally through a normalization layer to avoid configuration drift.
70
+ - **Flexible overrides**: Dev and Prod configs can be extended safely via optional parameters.
46
71
 
47
72
  ---
48
73
 
@@ -59,17 +84,6 @@ templates, but can also be used independently.
59
84
 
60
85
  ---
61
86
 
62
- ## What’s New in v1.2.2
63
-
64
- - Added `validateCoreOptions()` inside `createBaseConfig`
65
- - Validates `mode`, `scripts`, and `styles`
66
- - Ensures templates entry directory exists before Webpack starts
67
- - Dev server now opens the browser automatically (`open: true` by default)
68
-
69
- > No breaking changes were introduced.
70
-
71
- ---
72
-
73
87
  ## Installation
74
88
 
75
89
  ```bash
@@ -95,6 +109,7 @@ module.exports = (env = {}, argv = {}) => {
95
109
 
96
110
  const baseConfig = createBaseConfig({
97
111
  mode,
112
+ appType: 'mpa', // or 'spa'
98
113
  scripts: 'js', // or 'ts'
99
114
  styles: 'scss', // or 'less'
100
115
  templates: {
@@ -142,6 +157,22 @@ if (mode === 'production') {
142
157
 
143
158
  ---
144
159
 
160
+ ## Architecture Principles
161
+ - Webpack handles module resolution and asset processing
162
+ - PugPlugin handles template compilation
163
+ - No implicit webpack JS entry
164
+ - No aggressive production optimizations by default
165
+ - Options validated before build initialization
166
+ - Defaults resolved through a normalization layer (v1.7.0+)
167
+
168
+ ---
169
+
170
+ ## Stability
171
+
172
+ Versions prior to 1.1.6 were part of a stabilization phase and are not recommended for production use.
173
+
174
+ ---
175
+
145
176
  ## API Reference
146
177
 
147
178
  `createBaseConfig(options)`
@@ -174,6 +205,8 @@ All options are validated before initialization.
174
205
  - `options`: (Optional) Webpack configuration object for production overrides.
175
206
  - **Default behavior**: Enables source maps, minification, and disables `splitChunks` for template compatibility.
176
207
 
208
+ ---
209
+
177
210
  ## 📄 License
178
211
 
179
212
  This project is licensed under the ISC License.
@@ -1,7 +1,6 @@
1
- import { ModeType } from '../types/mode-type';
2
1
  import { ConfigOptionType } from '../types/config-option-type';
3
2
  export declare function createBaseConfig(options: ConfigOptionType): {
4
- mode: ModeType;
3
+ mode: import("..").ModeType;
5
4
  context: string;
6
5
  output: {
7
6
  path: string;
@@ -77,6 +76,6 @@ export declare function createBaseConfig(options: ConfigOptionType): {
77
76
  onlyModule?: boolean;
78
77
  }[] | {
79
78
  [index: string]: string | false | string[];
80
- };
79
+ } | undefined;
81
80
  };
82
81
  };
@@ -4,18 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createBaseConfig = createBaseConfig;
7
+ const path_1 = __importDefault(require("path"));
7
8
  const assets_1 = require("../loaders/assets");
8
9
  const scripts_1 = require("../loaders/scripts");
9
10
  const styles_1 = require("../loaders/styles");
10
11
  const templates_1 = require("../loaders/templates");
11
- const path_1 = __importDefault(require("path"));
12
12
  const validate_core_options_1 = require("../validation/validate-core-options");
13
+ const normalize_core_options_1 = require("../utils/normalize-core-options");
14
+ const webpack_1 = require("webpack");
13
15
  function createBaseConfig(options) {
14
- var _a, _b, _c, _d;
15
16
  (0, validate_core_options_1.validateCoreOptions)(options);
16
- const mode = (_a = options.mode) !== null && _a !== void 0 ? _a : 'development';
17
+ const normalized = (0, normalize_core_options_1.normalizeCoreOptions)(options);
17
18
  return {
18
- mode,
19
+ mode: normalized.mode,
19
20
  context: process.cwd(),
20
21
  output: {
21
22
  path: path_1.default.join(process.cwd(), 'dist'),
@@ -24,19 +25,27 @@ function createBaseConfig(options) {
24
25
  module: {
25
26
  rules: [
26
27
  (0, assets_1.assetsLoader)(),
27
- (0, scripts_1.scriptsLoader)(options),
28
- (0, styles_1.stylesLoader)(options),
28
+ (0, scripts_1.scriptsLoader)(normalized),
29
+ (0, styles_1.stylesLoader)(normalized),
29
30
  ],
30
31
  },
31
32
  plugins: [
33
+ new webpack_1.LoaderOptionsPlugin({
34
+ options: {
35
+ _meta: {
36
+ appType: normalized.appType,
37
+ }
38
+ }
39
+ }),
32
40
  ...(0, templates_1.templatesLoader)({
33
- entry: (_b = options.templates) === null || _b === void 0 ? void 0 : _b.entry,
34
- mode
41
+ entry: normalized.templates.entry,
42
+ mode: normalized.mode,
43
+ appType: normalized.appType,
35
44
  }),
36
45
  ],
37
46
  resolve: {
38
47
  extensions: ['.ts', '.tsx', '.js', '.json'],
39
- alias: (_d = (_c = options.resolve) === null || _c === void 0 ? void 0 : _c.alias) !== null && _d !== void 0 ? _d : {}
48
+ alias: normalized.resolve.alias,
40
49
  },
41
50
  };
42
51
  }
@@ -1,7 +1,7 @@
1
- import type { Configuration as WebpackConfiguration } from 'webpack';
2
1
  import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
3
- type DevConfig = WebpackConfiguration & {
2
+ import { BaseWebpackConfigType } from '../types/base-webpack-config-type';
3
+ type DevConfig = BaseWebpackConfigType & {
4
4
  devServer?: DevServerConfiguration;
5
5
  };
6
- export declare function createDevConfig(baseConfig: WebpackConfiguration, options?: DevServerConfiguration): DevConfig;
6
+ export declare function createDevConfig(baseConfig: BaseWebpackConfigType, options?: DevServerConfiguration): DevConfig;
7
7
  export {};
@@ -1,8 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createDevConfig = createDevConfig;
4
+ const webpack_1 = require("webpack");
4
5
  const webpack_merge_1 = require("webpack-merge");
5
6
  function createDevConfig(baseConfig, options = {}) {
7
+ var _a, _b, _c, _d, _e;
8
+ const loaderPlugin = (_a = baseConfig.plugins) === null || _a === void 0 ? void 0 : _a.find((p) => p instanceof webpack_1.LoaderOptionsPlugin);
9
+ const appType = (_e = (_d = (_c = (_b = loaderPlugin === null || loaderPlugin === void 0 ? void 0 : loaderPlugin.options) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c._meta) === null || _d === void 0 ? void 0 : _d.appType) !== null && _e !== void 0 ? _e : 'mpa';
10
+ const historyApiFallBack = {
11
+ disableDotRule: true,
12
+ rewrites: [
13
+ {
14
+ from: /./,
15
+ to: appType === 'spa' ? '/index.html' : '/404.html',
16
+ }
17
+ ]
18
+ };
6
19
  const defaultDevServer = {
7
20
  hot: false,
8
21
  open: true,
@@ -10,10 +23,7 @@ function createDevConfig(baseConfig, options = {}) {
10
23
  compress: true,
11
24
  port: 8080,
12
25
  watchFiles: ['src/**/*'],
13
- historyApiFallback: {
14
- disableDotRule: true,
15
- rewrites: [{ from: /./, to: '/404.html' }]
16
- }
26
+ historyApiFallback: historyApiFallBack,
17
27
  };
18
28
  return (0, webpack_merge_1.merge)(baseConfig, {
19
29
  devtool: 'source-map',
package/dist/index.d.ts CHANGED
@@ -4,4 +4,6 @@ export { createProdConfig } from './config/prod';
4
4
  export * from './types/mode-type';
5
5
  export * from './types/script-type';
6
6
  export * from './types/style-type';
7
+ export * from './types/app-type';
7
8
  export * from './types/config-option-type';
9
+ export * from './types/base-webpack-config-type';
package/dist/index.js CHANGED
@@ -24,4 +24,6 @@ Object.defineProperty(exports, "createProdConfig", { enumerable: true, get: func
24
24
  __exportStar(require("./types/mode-type"), exports);
25
25
  __exportStar(require("./types/script-type"), exports);
26
26
  __exportStar(require("./types/style-type"), exports);
27
+ __exportStar(require("./types/app-type"), exports);
27
28
  __exportStar(require("./types/config-option-type"), exports);
29
+ __exportStar(require("./types/base-webpack-config-type"), exports);
@@ -1,4 +1,7 @@
1
+ import { ModeType } from '../types/mode-type';
2
+ import { AppType } from '../types/app-type';
1
3
  export declare function templatesLoader(options: {
2
- entry?: string;
3
- mode: 'development' | 'production';
4
+ entry: string;
5
+ mode: ModeType;
6
+ appType: AppType;
4
7
  }): any[];
@@ -5,16 +5,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.templatesLoader = templatesLoader;
7
7
  const pug_plugin_1 = __importDefault(require("pug-plugin"));
8
- const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
9
  function templatesLoader(options) {
10
- var _a;
10
+ const { entry, appType } = options;
11
+ if (!fs_1.default.existsSync(entry)) {
12
+ throw new Error(`[webpack-core] Templates entry not found: ${entry}`);
13
+ }
14
+ const stats = fs_1.default.statSync(entry);
15
+ if (appType === 'spa' && !stats.isFile()) {
16
+ throw new Error(`[webpack-core] SPA requires a single pug file as templates.entry`);
17
+ }
18
+ if (appType === 'mpa' && !stats.isDirectory()) {
19
+ throw new Error(`[webpack-core] MPA requires templates.entry to be a directory`);
20
+ }
21
+ const pluginEntry = appType === 'spa' ? { index: entry } : entry;
11
22
  return [
12
23
  new pug_plugin_1.default({
13
- entry: path_1.default.resolve(process.cwd(), (_a = options.entry) !== null && _a !== void 0 ? _a : 'src/views/pages/'),
24
+ entry: pluginEntry,
14
25
  loaderOptions: {
15
- method: 'compile',
26
+ method: 'compile'
16
27
  },
17
28
  filename: ({ chunk }) => {
29
+ if (appType === 'spa') {
30
+ return 'index.html';
31
+ }
18
32
  let [name] = chunk.name.split('/');
19
33
  if (name === 'home')
20
34
  name = 'index';
@@ -0,0 +1 @@
1
+ export type AppType = 'mpa' | 'spa';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ import type { Configuration as WebpackConfiguration } from 'webpack';
2
+ export type BaseWebpackConfigType = WebpackConfiguration;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,11 +1,13 @@
1
1
  import { ModeType } from './mode-type';
2
2
  import { ScriptType } from './script-type';
3
3
  import { StyleType } from './style-type';
4
+ import { AppType } from './app-type';
4
5
  import { Configuration } from 'webpack';
5
6
  export type ConfigOptionType = {
6
7
  mode: ModeType;
7
8
  scripts: ScriptType;
8
9
  styles: StyleType;
10
+ appType?: AppType;
9
11
  templates?: {
10
12
  /**
11
13
  * Path to pug pages directory
@@ -0,0 +1,15 @@
1
+ import { ConfigOptionType } from '../types/config-option-type';
2
+ import { AppType } from '../types/app-type';
3
+ import { ModeType } from '../types/mode-type';
4
+ import { Configuration } from 'webpack';
5
+ export interface NormalizedCoreOptions {
6
+ mode: ModeType;
7
+ appType: AppType;
8
+ scripts: 'js' | 'ts';
9
+ styles: 'scss' | 'less';
10
+ templates: {
11
+ entry: string;
12
+ };
13
+ resolve: NonNullable<Configuration['resolve']>;
14
+ }
15
+ export declare function normalizeCoreOptions(options: ConfigOptionType): NormalizedCoreOptions;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.normalizeCoreOptions = normalizeCoreOptions;
7
+ const path_1 = __importDefault(require("path"));
8
+ function normalizeCoreOptions(options) {
9
+ var _a, _b, _c, _d, _e, _f;
10
+ const mode = (_a = options.mode) !== null && _a !== void 0 ? _a : 'development';
11
+ const appType = (_b = options.appType) !== null && _b !== void 0 ? _b : 'mpa';
12
+ const templatesEntry = (_d = (_c = options.templates) === null || _c === void 0 ? void 0 : _c.entry) !== null && _d !== void 0 ? _d : (appType === 'spa' ? 'src/views/app.pug' : 'src/views/pages');
13
+ return {
14
+ mode,
15
+ appType,
16
+ scripts: options.scripts,
17
+ styles: options.styles,
18
+ templates: {
19
+ entry: path_1.default.resolve(process.cwd(), templatesEntry),
20
+ },
21
+ resolve: {
22
+ alias: (_f = (_e = options.resolve) === null || _e === void 0 ? void 0 : _e.alias) !== null && _f !== void 0 ? _f : {},
23
+ },
24
+ };
25
+ }
@@ -1,17 +1,11 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.validateCoreOptions = validateCoreOptions;
7
- const fs_1 = __importDefault(require("fs"));
8
- const path_1 = __importDefault(require("path"));
9
4
  function validateCoreOptions(options) {
10
- var _a, _b;
11
5
  if (!options) {
12
6
  throw new Error('[webpack-core] Options are required.');
13
7
  }
14
- const { mode, scripts, styles } = options;
8
+ const { mode, scripts, styles, appType = 'mpa' } = options;
15
9
  // --- mode
16
10
  if (!['development', 'production'].includes(mode)) {
17
11
  throw new Error(`[webpack-core] Invalid mode "${mode}". Expected "development" or "production".`);
@@ -24,10 +18,8 @@ function validateCoreOptions(options) {
24
18
  if (!['scss', 'less'].includes(styles)) {
25
19
  throw new Error(`[webpack-core] Invalid styles option "${styles}". Expected "scss" or "less".`);
26
20
  }
27
- // --- templates entry
28
- const entryRelative = (_b = (_a = options.templates) === null || _a === void 0 ? void 0 : _a.entry) !== null && _b !== void 0 ? _b : 'src/views/pages';
29
- const entryAbsolute = path_1.default.resolve(process.cwd(), entryRelative);
30
- if (!fs_1.default.existsSync(entryAbsolute)) {
31
- throw new Error(`[webpack-core] Templates entry directory does not exist: ${entryAbsolute}`);
21
+ // --- appType
22
+ if (!['spa', 'mpa'].includes(appType)) {
23
+ throw new Error(`[webpack-core] Invalid appType "${appType}". Expected "spa" or "mpa".`);
32
24
  }
33
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@razerspine/webpack-core",
3
- "version": "1.4.1",
3
+ "version": "1.7.1",
4
4
  "description": "Core webpack config and loaders for starter templates",
5
5
  "keywords": [
6
6
  "webpack",
@@ -12,7 +12,7 @@
12
12
  "author": "Razerspine",
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "https://github.com/Razerspine/webpack-starter-monorepo",
15
+ "url": "git+https://github.com/Razerspine/webpack-starter-monorepo.git",
16
16
  "directory": "packages/webpack-core"
17
17
  },
18
18
  "homepage": "https://github.com/Razerspine/webpack-starter-monorepo/blob/main/packages/webpack-core#readme",