ptech-preset 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Precio Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,283 @@
1
+ # @precio/rsbuild-preset
2
+
3
+ A comprehensive Rsbuild preset with Module Federation, auto-expose, and auto-remotes functionality.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Auto-Expose**: Automatically expose components using JSDoc `@expose` tags or `exposeComponent()` wrapper
8
+ - 🔗 **Auto-Remotes**: Automatically map remotes from environment variables or manifest JSON
9
+ - âš¡ **Zero Configuration**: Minimal setup with sensible defaults
10
+ - 🎯 **TypeScript Support**: Full TypeScript support with type definitions
11
+ - 📦 **Module Federation**: Built-in Module Federation support with optimized shared dependencies
12
+
13
+ ## Installation
14
+
15
+ ### In your preset package (for development):
16
+
17
+ ```bash
18
+ npm i -D tsup typescript
19
+ npm i -S fast-glob
20
+ ```
21
+
22
+ ### In your application:
23
+
24
+ ```bash
25
+ npm i -D @rsbuild/core @rsbuild/plugin-react @module-federation/rsbuild-plugin
26
+ npm i -D @precio/rsbuild-preset
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### Basic Usage
32
+
33
+ ```typescript
34
+ // rsbuild.config.ts
35
+ import { defineConfig } from '@rsbuild/core';
36
+ import { pluginPrecioPreset } from '@precio/rsbuild-preset';
37
+
38
+ export default defineConfig({
39
+ plugins: [
40
+ pluginPrecioPreset({
41
+ mf: {
42
+ name: 'my-app',
43
+ },
44
+ }),
45
+ ],
46
+ });
47
+ ```
48
+
49
+ ### Advanced Configuration
50
+
51
+ ```typescript
52
+ // rsbuild.config.ts
53
+ import { defineConfig } from '@rsbuild/core';
54
+ import { pluginPrecioPreset } from '@precio/rsbuild-preset';
55
+
56
+ export default defineConfig({
57
+ plugins: [
58
+ pluginPrecioPreset({
59
+ cdn: 'https://cdn.example.com',
60
+ html: {
61
+ title: 'My Micro-Frontend App',
62
+ description: 'A modern micro-frontend application',
63
+ },
64
+ output: {
65
+ assetPrefixInDev: '/',
66
+ sourceMapProd: false,
67
+ hashFilenames: true,
68
+ filename: {
69
+ js: 'static/js/[name].[contenthash:8].js',
70
+ css: 'static/css/[name].[contenthash:8].css',
71
+ },
72
+ },
73
+ mf: {
74
+ name: 'my-app',
75
+ filename: 'static/js/remoteEntry.js',
76
+ manifest: false,
77
+ shared: {
78
+ lodash: { singleton: true },
79
+ },
80
+ // Manual overrides (will override auto-detected)
81
+ exposes: {
82
+ './CustomComponent': './src/components/Custom.tsx',
83
+ },
84
+ remotes: {
85
+ 'legacy-app': 'legacy-app@https://legacy.example.com/remoteEntry.js',
86
+ },
87
+ },
88
+ autoExpose: {
89
+ enabled: true,
90
+ globs: ['src/components/**/*.{ts,tsx}', 'src/pages/**/*.{ts,tsx}'],
91
+ baseDir: 'src',
92
+ tag: 'expose',
93
+ },
94
+ autoRemotes: {
95
+ envPrefix: 'REMOTE_',
96
+ manifestPathOrUrl: './remotes.json',
97
+ },
98
+ }),
99
+ ],
100
+ });
101
+ ```
102
+
103
+ ## Auto-Expose
104
+
105
+ ### Using JSDoc Comments
106
+
107
+ ```typescript
108
+ // src/components/Button.tsx
109
+ /**
110
+ * A reusable button component
111
+ * @expose Button
112
+ */
113
+ export const Button = ({ children, ...props }) => {
114
+ return <button {...props}>{children}</button>;
115
+ };
116
+ ```
117
+
118
+ ### Using exposeComponent Wrapper
119
+
120
+ ```typescript
121
+ // src/components/Modal.tsx
122
+ import { Modal } from './Modal';
123
+ import { exposeComponent } from '@precio/rsbuild-preset';
124
+
125
+ export default exposeComponent(Modal, 'Modal');
126
+ ```
127
+
128
+ ### Configuration Options
129
+
130
+ ```typescript
131
+ autoExpose: {
132
+ enabled: true, // Enable/disable auto-expose
133
+ globs: ['src/components/**/*.{ts,tsx}'], // File patterns to scan
134
+ baseDir: 'src', // Base directory for relative paths
135
+ tag: 'expose', // JSDoc tag to look for
136
+ }
137
+ ```
138
+
139
+ ## Auto-Remotes
140
+
141
+ ### Environment Variables
142
+
143
+ ```bash
144
+ # .env
145
+ REMOTE_USER_SERVICE=https://user-service.example.com/remoteEntry.js
146
+ REMOTE_PAYMENT_SERVICE=https://payment.example.com/remoteEntry.js
147
+ ```
148
+
149
+ ### Manifest JSON
150
+
151
+ ```json
152
+ // remotes.json
153
+ {
154
+ "user-service": "https://user-service.example.com/remoteEntry.js",
155
+ "payment-service": "https://payment.example.com/remoteEntry.js",
156
+ "analytics": "https://analytics.example.com/remoteEntry.js"
157
+ }
158
+ ```
159
+
160
+ ### HTTP Manifest
161
+
162
+ ```typescript
163
+ autoRemotes: {
164
+ manifestPathOrUrl: 'https://api.example.com/remotes.json',
165
+ }
166
+ ```
167
+
168
+ ### Configuration Options
169
+
170
+ ```typescript
171
+ autoRemotes: {
172
+ envPrefix: 'REMOTE_', // Environment variable prefix
173
+ manifestPathOrUrl: './remotes.json', // Local file or HTTP URL
174
+ }
175
+ ```
176
+
177
+ ## API Reference
178
+
179
+ ### PrecioPresetOptions
180
+
181
+ ```typescript
182
+ interface PrecioPresetOptions {
183
+ cdn?: string;
184
+ html?: {
185
+ title?: string;
186
+ description?: string;
187
+ };
188
+ output?: {
189
+ assetPrefixInDev?: string;
190
+ sourceMapProd?: boolean;
191
+ hashFilenames?: boolean;
192
+ filename?: {
193
+ js?: string;
194
+ css?: string;
195
+ };
196
+ distPath?: {
197
+ js?: string;
198
+ css?: string;
199
+ };
200
+ };
201
+ mf: {
202
+ name: string; // Required: Module Federation name
203
+ filename?: string; // Default: 'static/js/remoteEntry.js'
204
+ manifest?: boolean; // Default: false
205
+ shared?: Record<string, SharedEntry>;
206
+ exposes?: Record<string, string>; // Manual overrides
207
+ remotes?: Record<string, string>; // Manual overrides
208
+ };
209
+ autoExpose?: AutoExposeOptions;
210
+ autoRemotes?: AutoRemotesOptions;
211
+ }
212
+ ```
213
+
214
+ ### SharedEntry
215
+
216
+ ```typescript
217
+ type SharedEntry =
218
+ | string
219
+ | {
220
+ singleton?: boolean;
221
+ eager?: boolean;
222
+ requiredVersion?: string | false;
223
+ };
224
+ ```
225
+
226
+ ## Examples
227
+
228
+ ### Host Application
229
+
230
+ ```typescript
231
+ // rsbuild.config.ts
232
+ import { defineConfig } from '@rsbuild/core';
233
+ import { pluginPrecioPreset } from '@precio/rsbuild-preset';
234
+
235
+ export default defineConfig({
236
+ plugins: [
237
+ pluginPrecioPreset({
238
+ mf: {
239
+ name: 'host-app',
240
+ },
241
+ autoRemotes: {
242
+ envPrefix: 'REMOTE_',
243
+ manifestPathOrUrl: './remotes.json',
244
+ },
245
+ }),
246
+ ],
247
+ });
248
+ ```
249
+
250
+ ### Remote Application
251
+
252
+ ```typescript
253
+ // rsbuild.config.ts
254
+ import { defineConfig } from '@rsbuild/core';
255
+ import { pluginPrecioPreset } from '@precio/rsbuild-preset';
256
+
257
+ export default defineConfig({
258
+ plugins: [
259
+ pluginPrecioPreset({
260
+ mf: {
261
+ name: 'remote-app',
262
+ },
263
+ autoExpose: {
264
+ globs: ['src/components/**/*.{ts,tsx}'],
265
+ },
266
+ }),
267
+ ],
268
+ });
269
+ ```
270
+
271
+ ## Development
272
+
273
+ ```bash
274
+ # Build the preset
275
+ npm run build
276
+
277
+ # Watch mode for development
278
+ npm run dev
279
+ ```
280
+
281
+ ## License
282
+
283
+ MIT
@@ -0,0 +1,51 @@
1
+ import { RsbuildPlugin } from '@rsbuild/core';
2
+
3
+ type SharedEntry = string | {
4
+ singleton?: boolean;
5
+ eager?: boolean;
6
+ requiredVersion?: string | false;
7
+ };
8
+ type AutoExposeOptions = {
9
+ enabled?: boolean;
10
+ globs?: string[];
11
+ baseDir?: string;
12
+ tag?: string;
13
+ };
14
+ type AutoRemotesOptions = {
15
+ envPrefix?: string;
16
+ manifestPathOrUrl?: string;
17
+ };
18
+ type PrecioPresetOptions = {
19
+ cdn?: string;
20
+ html?: {
21
+ title?: string;
22
+ description?: string;
23
+ };
24
+ output?: {
25
+ assetPrefixInDev?: string;
26
+ sourceMapProd?: boolean;
27
+ hashFilenames?: boolean;
28
+ filename?: {
29
+ js?: string;
30
+ css?: string;
31
+ };
32
+ distPath?: {
33
+ js?: string;
34
+ css?: string;
35
+ };
36
+ };
37
+ mf: {
38
+ name: string;
39
+ filename?: string;
40
+ manifest?: boolean;
41
+ shared?: Record<string, SharedEntry>;
42
+ exposes?: Record<string, string>;
43
+ remotes?: Record<string, string>;
44
+ };
45
+ autoExpose?: AutoExposeOptions;
46
+ autoRemotes?: AutoRemotesOptions;
47
+ };
48
+
49
+ declare function pluginPrecioPreset(opts: PrecioPresetOptions): RsbuildPlugin;
50
+
51
+ export { type AutoExposeOptions, type AutoRemotesOptions, type PrecioPresetOptions, type SharedEntry, pluginPrecioPreset };
package/dist/index.js ADDED
@@ -0,0 +1,162 @@
1
+ import { pluginReact } from '@rsbuild/plugin-react';
2
+ import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
3
+ import path from 'path';
4
+ import fs2 from 'fs';
5
+ import fg from 'fast-glob';
6
+
7
+ // src/index.ts
8
+ async function collectAutoExposesWithWrapper(opts = {}) {
9
+ if (opts.enabled === false) return {};
10
+ const globs = opts.globs ?? ["src/components/**/*.{ts,tsx}"];
11
+ const baseDir = path.resolve(process.cwd(), opts.baseDir ?? "src");
12
+ const tag = opts.tag ?? "expose";
13
+ const files = await fg(globs, {
14
+ cwd: process.cwd(),
15
+ absolute: true,
16
+ ignore: ["**/*.d.ts"]
17
+ });
18
+ const exposes = {};
19
+ for (const abs of files) {
20
+ const text = fs2.readFileSync(abs, "utf8");
21
+ const jsdocMatch = text.match(new RegExp(`/\\*\\*[^*]*\\*+[^/]*@${tag}\\s*([^*\\n\\r]*)`, "m"));
22
+ if (jsdocMatch) {
23
+ const raw = (jsdocMatch[1] || "").trim();
24
+ const key = raw || path.basename(abs).replace(/\.(tsx?|jsx?)$/, "");
25
+ const rel = path.relative(baseDir, abs).replace(/\\/g, "/");
26
+ exposes[`./${key}`] = `./${rel}`;
27
+ continue;
28
+ }
29
+ const wrapperMatch = text.match(/exposeComponent\s*\(\s*[^,]+,\s*['"`]([^'"`]+)['"`]\s*\)/);
30
+ if (wrapperMatch) {
31
+ const key = wrapperMatch[1];
32
+ const rel = path.relative(baseDir, abs).replace(/\\/g, "/");
33
+ exposes[`./${key}`] = `./${rel}`;
34
+ }
35
+ }
36
+ return exposes;
37
+ }
38
+ async function collectAutoRemotes(opts = {}) {
39
+ const remotes = {};
40
+ const prefix = opts.envPrefix ?? "REMOTE_";
41
+ for (const [k, v] of Object.entries(process.env)) {
42
+ if (!v) continue;
43
+ if (k.startsWith(prefix)) {
44
+ const scope = k.slice(prefix.length).toLowerCase();
45
+ remotes[scope] = `${scope}@${v}`;
46
+ }
47
+ }
48
+ const manifestPathOrUrl = opts.manifestPathOrUrl;
49
+ if (manifestPathOrUrl) {
50
+ let obj = {};
51
+ if (/^https?:\/\//i.test(manifestPathOrUrl)) {
52
+ try {
53
+ const res = await fetch(manifestPathOrUrl);
54
+ if (!res.ok) {
55
+ console.warn(`Failed to fetch manifest from ${manifestPathOrUrl}: ${res.status} ${res.statusText}`);
56
+ } else {
57
+ obj = await res.json();
58
+ }
59
+ } catch (error) {
60
+ console.warn(`Error fetching manifest from ${manifestPathOrUrl}:`, error);
61
+ }
62
+ } else {
63
+ try {
64
+ const manifestPath = path.resolve(manifestPathOrUrl);
65
+ if (fs2.existsSync(manifestPath)) {
66
+ obj = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
67
+ } else {
68
+ console.warn(`Manifest file not found: ${manifestPath}`);
69
+ }
70
+ } catch (error) {
71
+ console.warn(`Error reading manifest file ${manifestPathOrUrl}:`, error);
72
+ }
73
+ }
74
+ for (const [scope, url] of Object.entries(obj)) {
75
+ if (typeof url === "string" && url.trim()) {
76
+ remotes[scope] = `${scope}@${url}`;
77
+ }
78
+ }
79
+ }
80
+ return remotes;
81
+ }
82
+
83
+ // src/index.ts
84
+ function pluginPrecioPreset(opts) {
85
+ return {
86
+ name: "precio-rsbuild-preset",
87
+ async setup(api) {
88
+ const isProd = process.env.NODE_ENV === "production";
89
+ const CDN = opts.cdn ?? process.env.CDN_URL;
90
+ const autoExposes = await collectAutoExposesWithWrapper(opts.autoExpose);
91
+ const autoRemotes = await collectAutoRemotes(opts.autoRemotes);
92
+ api.modifyRsbuildConfig((config) => {
93
+ config.html = {
94
+ ...config.html ?? {},
95
+ title: opts.html?.title ?? config.html?.title ?? "Precio App",
96
+ meta: {
97
+ viewport: "width=device-width, initial-scale=1",
98
+ description: opts.html?.description ?? "Precio App",
99
+ ...config.html?.meta ?? {}
100
+ }
101
+ };
102
+ const out = opts.output ?? {};
103
+ config.output = {
104
+ ...config.output ?? {},
105
+ assetPrefix: isProd ? CDN ?? "/" : out.assetPrefixInDev ?? "/",
106
+ sourceMap: isProd ? out.sourceMapProd ?? false : true,
107
+ filenameHash: out.hashFilenames ?? true,
108
+ filename: {
109
+ js: out.filename?.js ?? "static/js/[name].[contenthash:8].js",
110
+ css: out.filename?.css ?? "static/css/[name].[contenthash:8].css",
111
+ ...config.output?.filename ?? {}
112
+ },
113
+ distPath: {
114
+ js: out.distPath?.js ?? "static/js",
115
+ css: out.distPath?.css ?? "static/css",
116
+ ...config.output?.distPath ?? {}
117
+ }
118
+ };
119
+ const shared = {
120
+ react: {
121
+ singleton: true,
122
+ eager: true,
123
+ requiredVersion: false
124
+ },
125
+ "react-dom": {
126
+ singleton: true,
127
+ eager: true,
128
+ requiredVersion: false
129
+ },
130
+ ...opts.mf?.shared ?? {}
131
+ };
132
+ const exposes = { ...autoExposes, ...opts.mf.exposes ?? {} };
133
+ const remotes = { ...autoRemotes, ...opts.mf.remotes ?? {} };
134
+ config.plugins = [
135
+ ...config.plugins ?? [],
136
+ pluginReact(),
137
+ pluginModuleFederation({
138
+ name: opts.mf.name,
139
+ filename: opts.mf.filename ?? "static/js/remoteEntry.js",
140
+ manifest: opts.mf.manifest ?? false,
141
+ exposes,
142
+ remotes,
143
+ shared
144
+ })
145
+ ];
146
+ config.performance = {
147
+ ...config.performance ?? {},
148
+ chunkSplit: {
149
+ strategy: "split-by-module",
150
+ forceSplitting: [/node_modules\/react/, /node_modules\/react-dom/],
151
+ ...config.performance?.chunkSplit ?? {}
152
+ }
153
+ };
154
+ return config;
155
+ });
156
+ }
157
+ };
158
+ }
159
+
160
+ export { pluginPrecioPreset };
161
+ //# sourceMappingURL=index.js.map
162
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/autoExpose.ts","../src/autoRemotes.ts","../src/index.ts"],"names":["fs","path"],"mappings":"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","file":"index.js","sourcesContent":["import path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport fg from 'fast-glob';\r\nimport type { AutoExposeOptions } from './types';\r\n\r\nexport async function collectAutoExposes(opts: AutoExposeOptions = {}) {\r\n if (opts.enabled === false) return {};\r\n \r\n const globs = opts.globs ?? ['src/components/**/*.{ts,tsx}'];\r\n const baseDir = path.resolve(process.cwd(), opts.baseDir ?? 'src');\r\n const tag = opts.tag ?? 'expose';\r\n\r\n const files = await fg(globs, { \r\n cwd: process.cwd(), \r\n absolute: true, \r\n ignore: ['**/*.d.ts'] \r\n });\r\n \r\n const exposes: Record<string, string> = {};\r\n\r\n for (const abs of files) {\r\n const text = fs.readFileSync(abs, 'utf8');\r\n \r\n // Tìm /** @expose Name */\r\n const m = text.match(new RegExp(`/\\\\*\\\\*[^*]*\\\\*+[^/]*@${tag}\\\\s*([^*\\\\n\\\\r]*)`, 'm'));\r\n if (!m) continue;\r\n\r\n // lấy tên sau @expose, nếu trống => dùng baseName file\r\n const raw = (m[1] || '').trim();\r\n const key = raw || path.basename(abs).replace(/\\.(tsx?|jsx?)$/, '');\r\n const rel = path.relative(baseDir, abs).replace(/\\\\/g, '/');\r\n exposes[`./${key}`] = `./${rel}`;\r\n }\r\n \r\n return exposes;\r\n}\r\n\r\n// Extended version that also supports exposeComponent wrapper\r\nexport async function collectAutoExposesWithWrapper(opts: AutoExposeOptions = {}) {\r\n if (opts.enabled === false) return {};\r\n \r\n const globs = opts.globs ?? ['src/components/**/*.{ts,tsx}'];\r\n const baseDir = path.resolve(process.cwd(), opts.baseDir ?? 'src');\r\n const tag = opts.tag ?? 'expose';\r\n\r\n const files = await fg(globs, { \r\n cwd: process.cwd(), \r\n absolute: true, \r\n ignore: ['**/*.d.ts'] \r\n });\r\n \r\n const exposes: Record<string, string> = {};\r\n\r\n for (const abs of files) {\r\n const text = fs.readFileSync(abs, 'utf8');\r\n \r\n // 1. Tìm /** @expose Name */\r\n const jsdocMatch = text.match(new RegExp(`/\\\\*\\\\*[^*]*\\\\*+[^/]*@${tag}\\\\s*([^*\\\\n\\\\r]*)`, 'm'));\r\n if (jsdocMatch) {\r\n const raw = (jsdocMatch[1] || '').trim();\r\n const key = raw || path.basename(abs).replace(/\\.(tsx?|jsx?)$/, '');\r\n const rel = path.relative(baseDir, abs).replace(/\\\\/g, '/');\r\n exposes[`./${key}`] = `./${rel}`;\r\n continue;\r\n }\r\n\r\n // 2. Tìm exposeComponent(comp, 'Key')\r\n const wrapperMatch = text.match(/exposeComponent\\s*\\(\\s*[^,]+,\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/);\r\n if (wrapperMatch) {\r\n const key = wrapperMatch[1];\r\n const rel = path.relative(baseDir, abs).replace(/\\\\/g, '/');\r\n exposes[`./${key}`] = `./${rel}`;\r\n }\r\n }\r\n \r\n return exposes;\r\n}\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport type { AutoRemotesOptions } from './types';\r\n\r\nexport async function collectAutoRemotes(opts: AutoRemotesOptions = {}) {\r\n const remotes: Record<string, string> = {};\r\n const prefix = opts.envPrefix ?? 'REMOTE_';\r\n\r\n // 1) ENV variables\r\n for (const [k, v] of Object.entries(process.env)) {\r\n if (!v) continue;\r\n if (k.startsWith(prefix)) {\r\n const scope = k.slice(prefix.length).toLowerCase();\r\n remotes[scope] = `${scope}@${v}`;\r\n }\r\n }\r\n\r\n // 2) Manifest JSON (local hoặc http)\r\n const manifestPathOrUrl = opts.manifestPathOrUrl;\r\n if (manifestPathOrUrl) {\r\n let obj: Record<string, string> = {};\r\n \r\n if (/^https?:\\/\\//i.test(manifestPathOrUrl)) {\r\n // HTTP/HTTPS URL\r\n try {\r\n const res = await fetch(manifestPathOrUrl);\r\n if (!res.ok) {\r\n console.warn(`Failed to fetch manifest from ${manifestPathOrUrl}: ${res.status} ${res.statusText}`);\r\n } else {\r\n obj = await res.json();\r\n }\r\n } catch (error) {\r\n console.warn(`Error fetching manifest from ${manifestPathOrUrl}:`, error);\r\n }\r\n } else {\r\n // Local file path\r\n try {\r\n const manifestPath = path.resolve(manifestPathOrUrl);\r\n if (fs.existsSync(manifestPath)) {\r\n obj = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\r\n } else {\r\n console.warn(`Manifest file not found: ${manifestPath}`);\r\n }\r\n } catch (error) {\r\n console.warn(`Error reading manifest file ${manifestPathOrUrl}:`, error);\r\n }\r\n }\r\n \r\n // Process manifest entries\r\n for (const [scope, url] of Object.entries(obj)) {\r\n if (typeof url === 'string' && url.trim()) {\r\n remotes[scope] = `${scope}@${url}`;\r\n }\r\n }\r\n }\r\n \r\n return remotes;\r\n}\r\n","import type { RsbuildConfig, RsbuildPlugin } from \"@rsbuild/core\";\r\nimport { pluginReact } from \"@rsbuild/plugin-react\";\r\nimport { pluginModuleFederation } from \"@module-federation/rsbuild-plugin\";\r\nimport type { PrecioPresetOptions, SharedEntry } from \"./types\";\r\nimport { collectAutoExposesWithWrapper } from \"./autoExpose\";\r\nimport { collectAutoRemotes } from \"./autoRemotes\";\r\n\r\nexport function pluginPrecioPreset(opts: PrecioPresetOptions): RsbuildPlugin {\r\n return {\r\n name: \"precio-rsbuild-preset\",\r\n async setup(api) {\r\n const isProd = process.env.NODE_ENV === \"production\";\r\n const CDN = opts.cdn ?? process.env.CDN_URL;\r\n\r\n // Chuẩn bị dữ liệu \"thông minh\"\r\n const autoExposes = await collectAutoExposesWithWrapper(opts.autoExpose);\r\n const autoRemotes = await collectAutoRemotes(opts.autoRemotes);\r\n\r\n api.modifyRsbuildConfig((config: RsbuildConfig) => {\r\n // HTML\r\n config.html = {\r\n ...(config.html ?? {}),\r\n title: opts.html?.title ?? config.html?.title ?? \"Precio App\",\r\n meta: {\r\n viewport: \"width=device-width, initial-scale=1\",\r\n description: opts.html?.description ?? \"Precio App\",\r\n ...(config.html?.meta ?? {}),\r\n },\r\n };\r\n\r\n // OUTPUT\r\n const out = opts.output ?? {};\r\n config.output = {\r\n ...(config.output ?? {}),\r\n assetPrefix: isProd ? CDN ?? \"/\" : out.assetPrefixInDev ?? \"/\",\r\n sourceMap: isProd ? out.sourceMapProd ?? false : true,\r\n filenameHash: out.hashFilenames ?? true,\r\n filename: {\r\n js: out.filename?.js ?? \"static/js/[name].[contenthash:8].js\",\r\n css: out.filename?.css ?? \"static/css/[name].[contenthash:8].css\",\r\n ...(config.output?.filename ?? {}),\r\n },\r\n distPath: {\r\n js: out.distPath?.js ?? \"static/js\",\r\n css: out.distPath?.css ?? \"static/css\",\r\n ...(config.output?.distPath ?? {}),\r\n },\r\n };\r\n\r\n // SHARED (giữ literal type cho false)\r\n const shared = {\r\n react: {\r\n singleton: true,\r\n eager: true,\r\n requiredVersion: false as const,\r\n },\r\n \"react-dom\": {\r\n singleton: true,\r\n eager: true,\r\n requiredVersion: false as const,\r\n },\r\n ...(opts.mf?.shared ?? {}),\r\n } satisfies Record<string, SharedEntry>;\r\n\r\n // Exposes/Remotes: auto + cho phép override thủ công\r\n const exposes = { ...autoExposes, ...(opts.mf.exposes ?? {}) };\r\n const remotes = { ...autoRemotes, ...(opts.mf.remotes ?? {}) };\r\n\r\n config.plugins = [\r\n ...(config.plugins ?? []),\r\n pluginReact(),\r\n pluginModuleFederation({\r\n name: opts.mf.name,\r\n filename: opts.mf.filename ?? \"static/js/remoteEntry.js\",\r\n manifest: opts.mf.manifest ?? false,\r\n exposes,\r\n remotes,\r\n shared,\r\n } as any),\r\n ];\r\n\r\n config.performance = {\r\n ...(config.performance ?? {}),\r\n chunkSplit: {\r\n strategy: \"split-by-module\",\r\n forceSplitting: [/node_modules\\/react/, /node_modules\\/react-dom/],\r\n ...(config.performance?.chunkSplit ?? {}),\r\n },\r\n };\r\n\r\n return config;\r\n });\r\n },\r\n };\r\n}\r\n\r\n// Export types for consumers\r\nexport type {\r\n PrecioPresetOptions,\r\n AutoExposeOptions,\r\n AutoRemotesOptions,\r\n SharedEntry,\r\n} from \"./types\";\r\n"]}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "ptech-preset",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": ["dist", "README.md", "LICENSE"],
8
+ "scripts": {
9
+ "build": "tsup src/index.ts --format esm --dts --out-dir dist --clean",
10
+ "dev": "tsup src/index.ts --format esm --dts --out-dir dist --watch"
11
+ },
12
+ "peerDependencies": {
13
+ "@rsbuild/core": ">=1.4.16",
14
+ "@rsbuild/plugin-react": ">=1.4.1",
15
+ "@module-federation/rsbuild-plugin": ">=0.19.1"
16
+ },
17
+ "devDependencies": {
18
+ "tsup": "^8.5.0",
19
+ "typescript": "^5.9.3"
20
+ },
21
+ "dependencies": {
22
+ "fast-glob": "^3.3.3"
23
+ },
24
+ "keywords": [
25
+ "rsbuild",
26
+ "module-federation",
27
+ "preset",
28
+ "micro-frontend",
29
+ "auto-expose",
30
+ "auto-remotes"
31
+ ],
32
+ "description": "A comprehensive Rsbuild preset",
33
+ "author": "PTech Team",
34
+ "license": "MIT"
35
+ }