@jpp-toolkit/rspack-config 0.0.6 → 0.0.8

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.
@@ -0,0 +1,4 @@
1
+ export const TS_RULE_TEST = /\.(?:ts|tsx|cts|mts)$/iu;
2
+ export const ASSET_RULE_TEST = /\.(?:jpe?g|png|gif|svg)$/iu;
3
+ export const CSS_RULE_TEST = /\.css$/iu;
4
+ export const HASHED_JS_FILENAME_PATTERN = '[name].[contenthash:8].js';
@@ -0,0 +1,136 @@
1
+ import type { RspackOptions } from '@rspack/core';
2
+
3
+ import {
4
+ basePreset,
5
+ fivemScriptPreset,
6
+ fivemUiPreset,
7
+ getFivemScriptPresetDefaultOptions,
8
+ getFivemUiPresetDefaultOptions,
9
+ getNodePresetDefaultOptions,
10
+ getReactPresetDefaultOptions,
11
+ nodePreset,
12
+ reactPreset,
13
+ } from './presets';
14
+ import type {
15
+ FivemScriptPresetOptions,
16
+ FivemUiPresetOptions,
17
+ NodePresetOptions,
18
+ ReactPresetOptions,
19
+ } from './presets';
20
+ import type { RspackEnv, RunContext } from './types';
21
+ import { findFirstExistingFile } from './utils/find-first-existing-file';
22
+ import { mergeConfig } from './utils/merge-config';
23
+
24
+ function createConfigBuilder<TOptions, TReturn extends RspackOptions | RspackOptions[]>(
25
+ func: (options: Partial<TOptions>, context: RunContext) => TReturn,
26
+ ) {
27
+ return function (options: Partial<TOptions> = {}, context: Partial<RunContext> = {}) {
28
+ return function (env: RspackEnv): TReturn {
29
+ return func(options, {
30
+ cwd: process.cwd(),
31
+ isProduction: process.env.NODE_ENV === 'production',
32
+ isBuildMode: Boolean(env.RSPACK_BUILD),
33
+ isBundleMode: Boolean(env.RSPACK_BUNDLE),
34
+ isWatchMode: Boolean(env.RSPACK_WATCH),
35
+ isServeMode: Boolean(env.RSPACK_SERVE),
36
+ ...context,
37
+ });
38
+ };
39
+ };
40
+ }
41
+
42
+ export const createNodeRspackConfig = createConfigBuilder<NodePresetOptions, RspackOptions>(
43
+ (options, context) => {
44
+ const presetOptions = getNodePresetDefaultOptions(options, context);
45
+
46
+ return mergeConfig(basePreset(presetOptions, context), nodePreset(presetOptions, context));
47
+ },
48
+ );
49
+
50
+ export const createReactRspackConfig = createConfigBuilder<ReactPresetOptions, RspackOptions>(
51
+ (options, context) => {
52
+ const presetOptions = getReactPresetDefaultOptions(options, context);
53
+
54
+ return mergeConfig(basePreset(presetOptions, context), reactPreset(presetOptions, context));
55
+ },
56
+ );
57
+
58
+ export const createFivemScriptRspackConfig = createConfigBuilder<
59
+ Omit<FivemScriptPresetOptions, 'entryFile' | 'outDir'>,
60
+ RspackOptions[]
61
+ >((options, context) => {
62
+ const createBuildPreset = (type: 'client' | 'server' | 'shared') => {
63
+ const entryFile = findFirstExistingFile(
64
+ [`src/${type}/index.ts`, `src/${type}.ts`, `src/${type}/index.js`, `src/${type}.js`],
65
+ context.cwd,
66
+ );
67
+ if (!entryFile) return null;
68
+
69
+ const presetOptions = getFivemScriptPresetDefaultOptions(
70
+ {
71
+ ...options,
72
+ entryFile,
73
+ outDir: `dist/${type}`,
74
+ },
75
+ context,
76
+ );
77
+
78
+ return mergeConfig(
79
+ basePreset(presetOptions, context),
80
+ nodePreset(presetOptions, context),
81
+ fivemScriptPreset(presetOptions, context),
82
+ { name: `fivem-${type}` },
83
+ );
84
+ };
85
+
86
+ const configs: (RspackOptions | null)[] = [];
87
+
88
+ configs.push(createBuildPreset('client'));
89
+ configs.push(createBuildPreset('server'));
90
+ configs.push(createBuildPreset('shared'));
91
+
92
+ return configs.filter(Boolean) as RspackOptions[];
93
+ });
94
+
95
+ export const createFivemUiRspackConfig = createConfigBuilder<
96
+ Omit<FivemUiPresetOptions, 'entryFile' | 'outDir' | 'htmlTemplateFile'>,
97
+ RspackOptions
98
+ >((options, context) => {
99
+ const entryFile = findFirstExistingFile(
100
+ [
101
+ 'src/ui/index.tsx',
102
+ 'src/ui.tsx',
103
+ 'src/ui/index.ts',
104
+ 'src/ui.ts',
105
+ 'src/ui/index.jsx',
106
+ 'src/ui.jsx',
107
+ 'src/ui/index.js',
108
+ 'src/ui.js',
109
+ ],
110
+ context.cwd,
111
+ );
112
+
113
+ const htmlTemplateFile = findFirstExistingFile(
114
+ ['src/ui/index.html', 'src/ui.html', 'src/index.html'],
115
+ context.cwd,
116
+ );
117
+
118
+ if (!entryFile || !htmlTemplateFile) return {};
119
+
120
+ const presetOptions = getFivemUiPresetDefaultOptions(
121
+ {
122
+ ...options,
123
+ entryFile,
124
+ htmlTemplateFile,
125
+ outDir: 'dist/ui',
126
+ },
127
+ context,
128
+ );
129
+
130
+ return mergeConfig(
131
+ basePreset(presetOptions, context),
132
+ reactPreset(presetOptions, context),
133
+ fivemUiPreset(presetOptions, context),
134
+ { name: 'fivem-ui' },
135
+ );
136
+ });
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
- export * from './create-fivem-rspack-config';
1
+ export type { RspackEnv, RunContext } from './types';
2
+ export * from './create-rspack-config';
@@ -0,0 +1,114 @@
1
+ import path from 'node:path';
2
+
3
+ import { rspack } from '@rspack/core';
4
+ import type { SwcLoaderOptions } from '@rspack/core';
5
+
6
+ import { TS_RULE_TEST } from '~/constants';
7
+ import type { GetPresetDefaultOptions, Preset } from '~/types';
8
+
9
+ export type BasePresetOptions = {
10
+ readonly entryFile: string;
11
+ readonly tsconfigFile: string;
12
+ readonly outDir: string;
13
+ readonly envVariables: Record<string, string | undefined>;
14
+ readonly useDecorators: boolean;
15
+ readonly watchIgnored: string[];
16
+ };
17
+
18
+ export const getBasePresetDefaultOptions: GetPresetDefaultOptions<BasePresetOptions> = (
19
+ options,
20
+ ) => ({
21
+ entryFile: options.entryFile ?? 'src/index.js',
22
+ tsconfigFile: options.tsconfigFile ?? 'tsconfig.json',
23
+ outDir: options.outDir ?? 'dist',
24
+ envVariables: options.envVariables ?? {},
25
+ useDecorators: options.useDecorators ?? false,
26
+ watchIgnored: options.watchIgnored ?? [
27
+ '**/.git',
28
+ '**/.turbo',
29
+ '**/coverage',
30
+ '**/dist',
31
+ '**/generated',
32
+ '**/old',
33
+ '**/tmp',
34
+ ],
35
+ });
36
+
37
+ export const basePreset: Preset<BasePresetOptions> = (options, context) => ({
38
+ name: 'base-preset',
39
+
40
+ context: context.cwd,
41
+ mode: context.isProduction ? 'production' : 'development',
42
+ cache: !context.isProduction,
43
+
44
+ entry: { main: options.entryFile },
45
+
46
+ output: {
47
+ clean: true,
48
+ path: path.resolve(context.cwd, options.outDir),
49
+ },
50
+
51
+ resolve: {
52
+ tsConfig: path.resolve(context.cwd, options.tsconfigFile),
53
+ extensions: ['.ts', '...'],
54
+ extensionAlias: {
55
+ '.js': ['.ts', '.js'],
56
+ '.cts': ['.cts', '.js'],
57
+ '.mjs': ['.mts', '.mjs'],
58
+ },
59
+ },
60
+
61
+ optimization: { minimize: context.isProduction },
62
+
63
+ module: {
64
+ rules: [
65
+ {
66
+ test: TS_RULE_TEST,
67
+ type: 'javascript/auto',
68
+ loader: 'builtin:swc-loader',
69
+ exclude: [/dist\//u, /node_modules\//u],
70
+ options: {
71
+ jsc: {
72
+ externalHelpers: false,
73
+ keepClassNames: true,
74
+ parser: {
75
+ syntax: 'typescript',
76
+ decorators: options.useDecorators,
77
+ },
78
+ transform: {
79
+ useDefineForClassFields: true,
80
+ legacyDecorator: options.useDecorators,
81
+ decoratorMetadata: options.useDecorators,
82
+ },
83
+ experimental: {
84
+ cacheRoot: path.resolve(context.cwd, 'node_modules/.cache/swc'),
85
+ },
86
+ },
87
+ } satisfies SwcLoaderOptions,
88
+ },
89
+ ],
90
+ },
91
+
92
+ experiments: {
93
+ cache: {
94
+ type: 'persistent',
95
+ storage: {
96
+ type: 'filesystem',
97
+ directory: path.resolve(context.cwd, 'node_modules/.cache/rspack'),
98
+ },
99
+ },
100
+ },
101
+
102
+ stats: {
103
+ preset: 'normal',
104
+ errorDetails: true,
105
+ colors: true,
106
+ },
107
+
108
+ watchOptions: {
109
+ aggregateTimeout: 200,
110
+ ignored: options.watchIgnored,
111
+ },
112
+
113
+ plugins: [new rspack.EnvironmentPlugin(options.envVariables)],
114
+ });
@@ -0,0 +1,27 @@
1
+ import rspack from '@rspack/core';
2
+
3
+ import type { GetPresetDefaultOptions, Preset } from '~/types';
4
+
5
+ import { getNodePresetDefaultOptions } from './node-preset';
6
+ import type { NodePresetOptions } from './node-preset';
7
+
8
+ export type FivemScriptPresetOptions = NodePresetOptions;
9
+
10
+ export const getFivemScriptPresetDefaultOptions: GetPresetDefaultOptions<
11
+ FivemScriptPresetOptions
12
+ > = (options, context) => ({
13
+ ...getNodePresetDefaultOptions(options, context),
14
+ nodeVersion: options.nodeVersion ?? 16,
15
+ });
16
+
17
+ export const fivemScriptPreset: Preset<FivemScriptPresetOptions> = (options, context) => ({
18
+ name: 'fivem-script-preset',
19
+
20
+ devtool: false,
21
+
22
+ externals: [],
23
+
24
+ optimization: { minimize: context.isProduction },
25
+
26
+ plugins: [new rspack.EnvironmentPlugin(options.envVariables)],
27
+ });
@@ -0,0 +1,41 @@
1
+ import path from 'node:path';
2
+
3
+ import type { GetPresetDefaultOptions, Preset } from '~/types';
4
+
5
+ import type { ReactPresetOptions } from './react-preset';
6
+ import { getReactPresetDefaultOptions } from './react-preset';
7
+
8
+ export type FivemUiPresetOptions = ReactPresetOptions & {
9
+ readonly resourceName: string;
10
+ };
11
+
12
+ export const getFivemUiPresetDefaultOptions: GetPresetDefaultOptions<FivemUiPresetOptions> = (
13
+ options,
14
+ context,
15
+ ) => {
16
+ const resourceName = options.resourceName ?? path.basename(context.cwd);
17
+ const reactPresetOptions = getReactPresetDefaultOptions(options, context);
18
+ return {
19
+ ...reactPresetOptions,
20
+ resourceName,
21
+ publicUrl:
22
+ options.publicUrl
23
+ ?? (context.isServeMode ? 'http://localhost:8080/' : (
24
+ `https://cfx-nui-${resourceName}/${reactPresetOptions.outDir}/`
25
+ )),
26
+ };
27
+ };
28
+
29
+ export const fivemUiPreset: Preset<FivemUiPresetOptions> = (_options, context) => ({
30
+ name: 'fivem-ui-preset',
31
+
32
+ ...(context.isProduction ? { devtool: false } : {}),
33
+
34
+ output: { filename: '[name].js' },
35
+
36
+ devServer: {
37
+ devMiddleware: {
38
+ writeToDisk: true,
39
+ },
40
+ },
41
+ });
@@ -0,0 +1,5 @@
1
+ export * from './base-preset';
2
+ export * from './fivem-script-preset';
3
+ export * from './fivem-ui-preset';
4
+ export * from './node-preset';
5
+ export * from './react-preset';
@@ -0,0 +1,65 @@
1
+ import { rspack } from '@rspack/core';
2
+ import type { ExternalItem, SwcLoaderOptions } from '@rspack/core';
3
+ import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin';
4
+ import nodeExternals from 'webpack-node-externals';
5
+
6
+ import { TS_RULE_TEST } from '~/constants';
7
+ import type { GetPresetDefaultOptions, Preset } from '~/types';
8
+
9
+ import { getBasePresetDefaultOptions } from './base-preset';
10
+ import type { BasePresetOptions } from './base-preset';
11
+
12
+ export type NodePresetOptions = BasePresetOptions & {
13
+ readonly nodeVersion: number;
14
+ };
15
+
16
+ export const getNodePresetDefaultOptions: GetPresetDefaultOptions<NodePresetOptions> = (
17
+ options,
18
+ context,
19
+ ) => ({
20
+ ...getBasePresetDefaultOptions(options, context),
21
+ nodeVersion: options.nodeVersion ?? 24,
22
+ });
23
+
24
+ export const nodePreset: Preset<NodePresetOptions> = (options, context) => ({
25
+ name: 'node-preset',
26
+
27
+ target: `node${options.nodeVersion}`,
28
+
29
+ entry: {
30
+ main: [...(context.isServeMode ? ['@rspack/core/hot/poll?100'] : []), options.entryFile],
31
+ },
32
+
33
+ externalsPresets: { node: true },
34
+ externalsType: 'commonjs',
35
+ externals: [
36
+ nodeExternals(
37
+ context.isServeMode ? { allowlist: ['@rspack/core/hot/poll?100'] } : undefined,
38
+ ) as ExternalItem,
39
+ ],
40
+
41
+ optimization: { minimize: false },
42
+
43
+ module: {
44
+ rules: [
45
+ {
46
+ test: TS_RULE_TEST,
47
+ loader: 'builtin:swc-loader',
48
+ options: {
49
+ env: { targets: `node ${options.nodeVersion}` },
50
+ } satisfies SwcLoaderOptions,
51
+ },
52
+ ],
53
+ },
54
+
55
+ devServer: {
56
+ devMiddleware: {
57
+ writeToDisk: true,
58
+ },
59
+ },
60
+
61
+ plugins: [
62
+ new rspack.EnvironmentPlugin(options.envVariables),
63
+ context.isServeMode && new RunScriptWebpackPlugin({ name: 'main.js', autoRestart: false }),
64
+ ],
65
+ });
@@ -0,0 +1,139 @@
1
+ import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
+
4
+ import { rspack } from '@rspack/core';
5
+ import type { SwcLoaderOptions } from '@rspack/core';
6
+ import ReactRefreshPlugin from '@rspack/plugin-react-refresh';
7
+
8
+ import {
9
+ ASSET_RULE_TEST,
10
+ CSS_RULE_TEST,
11
+ HASHED_JS_FILENAME_PATTERN,
12
+ TS_RULE_TEST,
13
+ } from '~/constants';
14
+ import type { GetPresetDefaultOptions, Preset } from '~/types';
15
+
16
+ import { getBasePresetDefaultOptions } from './base-preset';
17
+ import type { BasePresetOptions } from './base-preset';
18
+
19
+ const require = createRequire(import.meta.url);
20
+
21
+ export type ReactPresetOptions = BasePresetOptions & {
22
+ readonly htmlTemplateFile: string;
23
+ readonly browserlist: string;
24
+ readonly publicUrl: string;
25
+ };
26
+
27
+ export const getReactPresetDefaultOptions: GetPresetDefaultOptions<ReactPresetOptions> = (
28
+ options,
29
+ context,
30
+ ) => ({
31
+ ...getBasePresetDefaultOptions(options, context),
32
+ htmlTemplateFile: options.htmlTemplateFile ?? 'src/index.html',
33
+ browserlist: options.browserlist ?? 'defaults',
34
+ publicUrl: options.publicUrl ?? 'http://localhost:8080/',
35
+ });
36
+
37
+ export const reactPreset: Preset<ReactPresetOptions> = (options, context) => {
38
+ const publicUrl = new URL(options.publicUrl);
39
+ if (!publicUrl.pathname.endsWith('/')) publicUrl.pathname += '/';
40
+
41
+ return {
42
+ name: 'react-preset',
43
+
44
+ target: `browserslist:${options.browserlist}`,
45
+
46
+ output: {
47
+ filename: context.isProduction ? HASHED_JS_FILENAME_PATTERN : '[name].js',
48
+ publicPath: publicUrl.toString(),
49
+ },
50
+
51
+ resolve: {
52
+ extensions: ['.tsx', '.ts', '.jsx', '...'],
53
+ extensionAlias: {
54
+ '.js': ['.tsx', '.ts', '.jsx', '.js'],
55
+ '.cts': ['.ctsx', '.cts', '.cjsx', '.js'],
56
+ '.mjs': ['.mtsx', '.mts', '.mjsx', '.mjs'],
57
+ },
58
+ },
59
+
60
+ externalsPresets: { web: true },
61
+
62
+ module: {
63
+ rules: [
64
+ {
65
+ test: TS_RULE_TEST,
66
+ loader: 'builtin:swc-loader',
67
+ options: {
68
+ env: { targets: options.browserlist },
69
+ jsc: {
70
+ parser: {
71
+ syntax: 'typescript',
72
+ tsx: true,
73
+ },
74
+ transform: {
75
+ react: {
76
+ runtime: 'automatic',
77
+ throwIfNamespace: true,
78
+ useBuiltins: false,
79
+ development: !context.isProduction,
80
+ refresh: context.isServeMode,
81
+ },
82
+ },
83
+ },
84
+ } satisfies SwcLoaderOptions,
85
+ },
86
+ {
87
+ test: ASSET_RULE_TEST,
88
+ type: 'asset/resource',
89
+ },
90
+ {
91
+ test: CSS_RULE_TEST,
92
+ type: 'css',
93
+ use: [
94
+ {
95
+ loader: require.resolve('postcss-loader'),
96
+ options: {
97
+ implementation: require.resolve('postcss'),
98
+ postcssOptions: {
99
+ plugins: [
100
+ [
101
+ require.resolve('@tailwindcss/postcss'),
102
+ { optimize: context.isProduction },
103
+ ],
104
+ ],
105
+ },
106
+ },
107
+ },
108
+ ],
109
+ },
110
+ ],
111
+ },
112
+
113
+ devServer: {
114
+ host: publicUrl.hostname || undefined,
115
+ port: publicUrl.port || undefined,
116
+ historyApiFallback: true,
117
+ hot: 'only',
118
+ headers: {
119
+ 'Access-Control-Allow-Origin': '*',
120
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
121
+ 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
122
+ },
123
+ },
124
+
125
+ experiments: { css: true },
126
+
127
+ plugins: [
128
+ new rspack.EnvironmentPlugin({
129
+ ...options.envVariables,
130
+ PUBLIC_URL: publicUrl.toString(),
131
+ }),
132
+ new rspack.HtmlRspackPlugin({
133
+ template: path.resolve(context.cwd, options.htmlTemplateFile),
134
+ minify: context.isProduction,
135
+ }),
136
+ context.isServeMode && new ReactRefreshPlugin({ forceEnable: true }),
137
+ ],
138
+ };
139
+ };
@@ -0,0 +1,3 @@
1
+ export type * from './preset';
2
+ export type * from './rspack-env';
3
+ export type * from './run-context';
@@ -0,0 +1,13 @@
1
+ import type { RspackOptions } from '@rspack/core';
2
+
3
+ import type { RunContext } from './run-context';
4
+
5
+ export type GetPresetDefaultOptions<TPresetOptions> = (
6
+ options: Partial<TPresetOptions>,
7
+ context: RunContext,
8
+ ) => TPresetOptions;
9
+
10
+ export type Preset<TPresetOptions> = (
11
+ presetOptions: TPresetOptions,
12
+ runContext: RunContext,
13
+ ) => RspackOptions;
@@ -0,0 +1,6 @@
1
+ export type RspackEnv = {
2
+ readonly RSPACK_WATCH?: boolean | undefined;
3
+ readonly RSPACK_BUILD?: boolean | undefined;
4
+ readonly RSPACK_BUNDLE?: boolean | undefined;
5
+ readonly RSPACK_SERVE?: boolean | undefined;
6
+ };
@@ -0,0 +1,8 @@
1
+ export type RunContext = {
2
+ readonly cwd: string;
3
+ readonly isProduction: boolean;
4
+ readonly isBuildMode: boolean;
5
+ readonly isBundleMode: boolean;
6
+ readonly isWatchMode: boolean;
7
+ readonly isServeMode: boolean;
8
+ };
@@ -0,0 +1,7 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export function findFirstExistingFile(files: string[], cwd: string): string | null {
5
+ for (const file of files) if (fs.existsSync(path.resolve(cwd, file))) return file;
6
+ return null;
7
+ }
@@ -0,0 +1,14 @@
1
+ import type { RspackOptions } from '@rspack/core';
2
+ import { mergeWithRules } from 'webpack-merge';
3
+
4
+ export const mergeConfig = mergeWithRules({
5
+ entry: 'replace',
6
+ externals: 'replace',
7
+ module: {
8
+ rules: {
9
+ test: 'match',
10
+ options: 'merge',
11
+ },
12
+ },
13
+ plugins: 'replace',
14
+ }) as (...configs: RspackOptions[]) => RspackOptions;