@imtf/profile-scripts 2.0.0-beta.0 → 2.0.0-beta.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/README.md ADDED
@@ -0,0 +1,275 @@
1
+ # @imtf/profile-scripts (Vite + Module Federation)
2
+
3
+ ## Overview
4
+
5
+ `profile-scripts` is a standardized build and development utility designed for Profile applications within the ICOS.
6
+
7
+ It provides a **vite-based build** with optional **Module Federation integration**.
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install --save-dev @imtf/profile-scripts
15
+ ```
16
+
17
+ or
18
+
19
+ ```bash
20
+ yarn add -D @imtf/profile-scripts
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Usage
26
+
27
+ ### Start (development/watch mode)
28
+
29
+ ```bash
30
+ profile-scripts start
31
+ ```
32
+
33
+ Starts the build in watch mode and serves output at:
34
+
35
+ ```
36
+ http://localhost:3010
37
+ ```
38
+
39
+ ---
40
+
41
+ ### Build (Production)
42
+
43
+ ```bash
44
+ profile-scripts build
45
+ ```
46
+
47
+ Generates the production bundle in the `build/` directory.
48
+
49
+ ---
50
+
51
+ ## CLI Options
52
+
53
+ ### `--entryPoint`
54
+
55
+ Specify a custom entry file.
56
+
57
+ ```bash
58
+ profile-scripts start --entryPoint src/index.ts
59
+ ```
60
+
61
+ If not provided, the following files are checked (in order):
62
+
63
+ ```
64
+ index.ts
65
+ index.tsx
66
+ index.js
67
+ index.jsx
68
+ src/index.ts
69
+ src/index.tsx
70
+ src/index.js
71
+ src/index.jsx
72
+ ```
73
+
74
+ ---
75
+
76
+ ### `--federation`
77
+
78
+ Enable Module Federation support.
79
+
80
+ ```bash
81
+ profile-scripts start --federation
82
+ ```
83
+
84
+ When enabled:
85
+ - Loads configuration from `federation.config.js` or `federation.config.mjs` in project root
86
+ - Integrates with `vite-plugin-federation`
87
+
88
+ ---
89
+
90
+ ### `--externalGlobal`
91
+
92
+ Map external dependencies to global variables (optional).
93
+
94
+ ```bash
95
+ profile-scripts build --externalGlobal=react/jsx-runtime=window.jsxRuntime
96
+ ```
97
+
98
+ Use this when a dependency is provided globally by the host application and should not be bundled.
99
+
100
+ ---
101
+
102
+ ## Project Requirements
103
+
104
+ Minimal structure:
105
+
106
+ ```
107
+ project-root/
108
+ src/
109
+ index.tsx (or equivalent entry)
110
+ package.json
111
+ ```
112
+
113
+ For Module Federation:
114
+
115
+ ```
116
+ federation.config.js
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Remote Application Requirements
122
+
123
+ To enable Module Federation integration, the **remote application** must be configured as follows.
124
+
125
+ ### Module Federation Configuration
126
+
127
+ ```js
128
+ module.exports = {
129
+ name: "remote-app",
130
+ exposes: {
131
+ "./App": "./src/App",
132
+ },
133
+ filename: "remoteEntry.js",
134
+ library: { type: "module" }, // **required**
135
+ };
136
+ ```
137
+
138
+ ---
139
+
140
+ ### Build Configuration
141
+
142
+ ```js
143
+ experiments: { outputModule: true },
144
+
145
+ externalsType: "global",
146
+
147
+ externals: {
148
+ react: "React",
149
+ "react-dom": "ReactDOM",
150
+ };
151
+ ```
152
+
153
+ ### Notes
154
+
155
+ * The `library.type = "module"` configuration is **mandatory** for compatibility with vite-based host applications
156
+ * `outputModule: true` ensures the remote is emitted as an ES module
157
+ * React dependencies must be externalised and provided by the host application
158
+
159
+ ---
160
+
161
+ ## SVG Handling
162
+
163
+ Built-in SVG support:
164
+
165
+ - Files inside `src/` → transformed into React components
166
+ - Files inside `src/assets/` or outside `src/` → converted to base64 data URLs
167
+
168
+ ---
169
+
170
+ ## CSS Handling
171
+
172
+ CSS is automatically injected into the JavaScript bundle.
173
+ No separate CSS files are generated.
174
+
175
+ ---
176
+
177
+ ## Output
178
+
179
+ Build output:
180
+
181
+ ```
182
+ build/
183
+ index.js
184
+ ```
185
+
186
+ - Format: IIFE
187
+ - Designed for integration into host applications
188
+
189
+ ---
190
+
191
+ ## Environment Variable
192
+
193
+ ### `IMTF_MINIFY_WEBAPP`
194
+
195
+ Controls minification:
196
+
197
+ ```bash
198
+ IMTF_MINIFY_WEBAPP=false profile-scripts build
199
+ ```
200
+
201
+ - Default: minification enabled
202
+ - Uses Vite default minifier
203
+
204
+ ---
205
+
206
+ ## Notes
207
+
208
+ - vite is the default build system
209
+ - Module Federation is enabled only via `--federation`
210
+ - Entry point is optional but must resolve to a valid file
211
+ - External globals should be used only when dependencies are provided by the host
212
+
213
+ ---
214
+ ## Profile Configuration
215
+ Two supported approaches are available based on how the remote is resolved.
216
+
217
+ ---
218
+
219
+ ### Option 1: URL-based Remote (Static)
220
+
221
+ ```js
222
+ export default {
223
+ name: "host-app",
224
+ remotes: {
225
+ remoteModule: {
226
+ externalType: "url",
227
+ external: "http://localhost:3021/remoteEntry.js",
228
+ from: "webpack",
229
+ format: "esm",
230
+ },
231
+ },
232
+ };
233
+ ```
234
+
235
+ ---
236
+
237
+ ### Option 2: Promise-based Remote (Dynamic)
238
+
239
+ Use this when the remote URL needs to be resolved dynamically at runtime.
240
+
241
+ ```js
242
+ export default {
243
+ name: "host-app",
244
+ remotes: {
245
+ remoteapp: {
246
+ externalType: "promise",
247
+ external: "<<resolver function as string>>",
248
+ from: "webpack",
249
+ format: "esm",
250
+ },
251
+ },
252
+ };
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Troubleshooting
258
+
259
+ ### Entry point not found
260
+
261
+ Ensure one of the supported entry files exists or pass `--entryPoint`.
262
+
263
+ ---
264
+
265
+ ### Federation configuration missing
266
+
267
+ Ensure `federation.config.js` or `.mjs` exists when using `--federation`.
268
+
269
+ ---
270
+
271
+ ### Runtime dependency issues
272
+
273
+ If a dependency is expected from the host, configure it using `--externalGlobal`.
274
+
275
+ ---
@@ -7,26 +7,13 @@ const args = process.argv.slice(2);
7
7
  const scriptIndex = args.findIndex((x) => x === "build" || x === "start");
8
8
  const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
9
9
 
10
- const useVite = args.includes("--vite");
11
- const useViteFederation = args.includes("--federation");
12
-
13
- // federation automatically implies vite
14
- const useViteBuild = useVite || useViteFederation;
15
-
16
- // Remove internal routing flags before forwarding
17
- const cleanedArgs = args.filter(
18
- (arg) => arg !== "--vite"
19
- );
20
-
21
- const scriptPath = useViteBuild
22
- ? require.resolve("../scripts/viteBuild.mjs")
23
- : require.resolve("../scripts/esbuild.mjs");
10
+ const scriptPath = require.resolve("../scripts/viteBuild.mjs");
24
11
 
25
12
  switch (script) {
26
13
  case "start":
27
14
  case "build": {
28
- const nodeArgs = scriptIndex > 0 ? cleanedArgs.slice(0, scriptIndex) : [];
29
- const scriptArgs = cleanedArgs.slice(scriptIndex + 1);
15
+ const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
16
+ const scriptArgs = args.slice(scriptIndex + 1);
30
17
 
31
18
  if (script === "start") {
32
19
  scriptArgs.push("--watch");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imtf/profile-scripts",
3
- "version": "2.0.0-beta.0",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "Default scripts to bundle & transpile imtf front-end plugins",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -12,28 +12,21 @@
12
12
  "changelog.md"
13
13
  ],
14
14
  "scripts": {
15
- "build": "NODE_ENV=production node scripts/esbuild.mjs --entryPoint=src/index.tsx",
16
- "start": "node scripts/esbuild.mjs --entryPoint=src/index.tsx --watch"
15
+ "build": "NODE_ENV=production node scripts/viteBuild.mjs",
16
+ "start": "node scripts/viteBuild.mjs --watch"
17
17
  },
18
18
  "prettier": "@imtf/prettier-config",
19
19
  "license": "UNLICENSED",
20
20
  "private": false,
21
21
  "dependencies": {
22
22
  "@originjs/vite-plugin-federation": "^1.4.1",
23
- "@svgr/core": "^8.1.0",
24
- "@svgr/plugin-jsx": "^8.1.0",
25
- "@svgr/plugin-svgo": "^8.1.0",
26
23
  "@vitejs/plugin-react": "^5.1.4",
27
24
  "esbuild": "^0.27.0",
28
- "esbuild-css-modules-plugin": "^3.1.4",
29
- "esbuild-plugin-external-global": "^1.0.1",
30
- "esbuild-plugin-inline-css": "^0.0.1",
31
- "http-server": "^14.1.1",
32
25
  "minimist": "^1.2.8",
33
26
  "serve-handler": "^6.1.6",
34
27
  "vite": "^7.3.1",
35
28
  "vite-plugin-css-injected-by-js": "^4.0.1",
36
- "vite-tsconfig-paths": "^6.1.1"
29
+ "vite-tsconfig-paths": "^6.0.1"
37
30
  },
38
31
  "devDependencies": {
39
32
  "@types/minimist": "^1.2.5"
@@ -0,0 +1,50 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const DEFAULT_ENTRYPOINTS = [
5
+ "src/index.ts",
6
+ "src/index.tsx",
7
+ "src/index.js",
8
+ "src/index.jsx",
9
+ "index.ts",
10
+ "index.tsx",
11
+ "index.js",
12
+ "index.jsx",
13
+ ];
14
+
15
+ /**
16
+ * Resolves the application entry point.
17
+ * Uses the provided entryPoint, otherwise falls back to defaults.
18
+ * Throws a simple error if no valid entry file is found.
19
+ *
20
+ * @param {string} [entryPoint] - Entry point passed via CLI
21
+ * @returns {string} Absolute path to resolved entry file
22
+ */
23
+ const resolveEntryPoint = (entryPoint) => {
24
+ const consumerRoot = process.cwd();
25
+
26
+ // Use CLI entryPoint if provided
27
+ if (entryPoint) {
28
+ const resolvedPath = path.isAbsolute(entryPoint)
29
+ ? entryPoint
30
+ : path.resolve(consumerRoot, entryPoint);
31
+
32
+ if (!fs.existsSync(resolvedPath)) {
33
+ throw new Error(`[profile-scripts] entryPoint not found: ${entryPoint}`);
34
+ }
35
+
36
+ return resolvedPath;
37
+ }
38
+
39
+ // Resolve from defaults
40
+ for (const candidate of DEFAULT_ENTRYPOINTS) {
41
+ const fullPath = path.resolve(consumerRoot, candidate);
42
+ if (fs.existsSync(fullPath)) {
43
+ return fullPath;
44
+ }
45
+ }
46
+
47
+ throw new Error(`[profile-scripts] entryPoint not found`);
48
+ };
49
+
50
+ export default resolveEntryPoint;
@@ -1,9 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-return */
2
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
3
- /* eslint-disable @typescript-eslint/no-unsafe-argument */
4
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
5
- /* eslint-disable @typescript-eslint/no-unsafe-call */
6
-
7
1
  import http from "http";
8
2
  import fs from "node:fs";
9
3
  import path from "path";
@@ -18,6 +12,7 @@ import { build as viteBuild } from "vite";
18
12
  import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";
19
13
  import tsconfigPaths from "vite-tsconfig-paths";
20
14
 
15
+ import resolveEntryPoint from "./utils/resolveEntryPoint.mjs";
21
16
  import notifierPlugin from "./vitePlugins/viteNotifierPlugin.mjs";
22
17
  import svgPlugin from "./vitePlugins/viteSvgPlugin.mjs";
23
18
 
@@ -31,9 +26,7 @@ const {
31
26
  const consumerRoot = process.cwd();
32
27
  const outDir = "build";
33
28
 
34
- const entryAbs = entryPoint
35
- ? path.resolve(consumerRoot, entryPoint)
36
- : path.resolve(consumerRoot, "src/index.js");
29
+ const entryAbs = resolveEntryPoint(entryPoint);
37
30
 
38
31
  // Force JSX transform for .js files in src
39
32
  const jsxPreTransformPlugin = {
@@ -143,7 +136,6 @@ const vitePlugins = [
143
136
  // Inject Module Federation support when --federation flag is provided
144
137
  if (enableFederation) {
145
138
  const federationConfig = await resolveFederationConfig();
146
-
147
139
  vitePlugins.push(federation(federationConfig));
148
140
  }
149
141
 
@@ -154,12 +146,13 @@ if (enableFederation) {
154
146
  const viteConfig = {
155
147
  root: consumerRoot,
156
148
  plugins: vitePlugins,
157
- logLevel: watch ? "error" : "info", // !!! Need to discuss
149
+ logLevel: watch ? "error" : "info",
158
150
  build: {
159
151
  outDir,
160
152
  emptyOutDir: true,
161
153
  target: "esnext",
162
154
  sourcemap: watch,
155
+ chunkSizeWarningLimit: 3000,
163
156
  rollupOptions: {
164
157
  input: entryAbs,
165
158
  external: [
@@ -1,25 +1,33 @@
1
+ /**
2
+ * Logs Vite build and rebuild status with rebuild count.
3
+ * Distinguishes initial build vs subsequent rebuilds in watch mode.
4
+ *
5
+ * @param {boolean} watch - Whether running in watch mode
6
+ * @returns {import('vite').Plugin}
7
+ */
1
8
  const viteNotifierPlugin = (watch) => {
2
- let count = 0;
9
+ let isFirstBuild = true;
10
+ let rebuildCount = 0;
3
11
 
4
12
  return {
5
13
  name: "notifier-plugin-vite",
6
14
 
7
- buildStart() {
8
- count = 0;
9
- },
10
-
11
15
  buildEnd(error) {
12
16
  if (error) return;
13
17
 
14
- if (count++ === 0) {
15
- if (watch) {
16
- console.log(`1st build: ${Date.now()}`);
17
- } else {
18
- console.log("Successfully built");
19
- }
20
- } else {
21
- console.log(`Rebuilt: ${Date.now()}`);
18
+ if (!watch) {
19
+ console.log("Successfully built");
20
+ return;
22
21
  }
22
+
23
+ if (isFirstBuild) {
24
+ console.log(`1st build: ${Date.now()}`);
25
+ isFirstBuild = false;
26
+ return;
27
+ }
28
+
29
+ rebuildCount += 1;
30
+ console.log(`Rebuilt (${rebuildCount}): ${Date.now()}`);
23
31
  },
24
32
  };
25
33
  };
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Vite plugin to transform SVG imports based on project conventions.
3
+ *
4
+ * - SVGs outside `src/` or inside `src/assets/` are converted to base64 data URLs
5
+ * - SVGs inside `src/` are transformed into React components using SVGR
6
+ *
7
+ * @param {Object} [options] - SVGR configuration options
8
+ * @returns {import('vite').Plugin}
9
+ */
1
10
  const viteSvgPlugin = (options = {}) => {
2
11
  return {
3
12
  name: "vite-svgr-plugin",
@@ -1,94 +0,0 @@
1
- import { build, context } from "esbuild";
2
- import esbuild from "esbuild-plugin-external-global";
3
- import InlineCSSPlugin from "esbuild-plugin-inline-css";
4
- import CssModulesPlugin from "esbuild-css-modules-plugin";
5
- import minimist from "minimist";
6
- import HttpServer from "http-server";
7
-
8
- // eslint-disable-next-line import/no-named-as-default-member
9
- const externalGlobalPlugin = esbuild.externalGlobalPlugin;
10
-
11
- import notifierPlugin from "./notifierPlugin.mjs";
12
- import svgPlugin from "./svgPlugin.mjs";
13
-
14
- const {
15
- watch,
16
- entryPoint,
17
- externalGlobal,
18
- outbase = "src",
19
- } = minimist(process.argv.slice(2));
20
-
21
- const externalGlobals = externalGlobal
22
- ? (typeof externalGlobal === "string"
23
- ? [externalGlobal]
24
- : externalGlobal
25
- ).reduce((acc, curr) => {
26
- const [key, value] = curr.split("=");
27
- acc[key] = value;
28
- return acc;
29
- }, {})
30
- : {};
31
-
32
- const entryPoints = entryPoint
33
- ? typeof entryPoint === "string"
34
- ? [entryPoint]
35
- : entryPoint
36
- : ["src/index.js"];
37
-
38
- /**
39
- * @type {import('esbuild').BuildOptions}
40
- */
41
- const config = {
42
- entryPoints,
43
- outbase,
44
- outdir: "build",
45
- platform: "browser",
46
- bundle: true,
47
- target: "es2024",
48
- metafile: true,
49
-
50
- sourcemap: watch ? "linked" : "external",
51
-
52
- minify: watch === true ? false : process.env.IMTF_MINIFY_WEBAPP !== "false",
53
-
54
- loader: {
55
- // Enable JSX in .js files too
56
- ".js": "jsx",
57
- // Embed fonts
58
- ".eot": "dataurl",
59
- ".ttf": "dataurl",
60
- ".woff": "dataurl",
61
- ".woff2": "dataurl",
62
- // Embed images
63
- ".gif": "dataurl",
64
- ".jpg": "dataurl",
65
- ".png": "dataurl",
66
- },
67
-
68
- plugins: [
69
- CssModulesPlugin({ inject: true }),
70
- InlineCSSPlugin(),
71
- svgPlugin(),
72
- externalGlobalPlugin({
73
- react: "window.React",
74
- "react-dom": "window.ReactDOM",
75
- "react-router-dom": "window.reactRouterDom",
76
- IMTFPlugins: "window.IMTFPlugins",
77
- ...externalGlobals,
78
- }),
79
- notifierPlugin(watch),
80
- ],
81
- };
82
-
83
- if (watch) {
84
- const ctx = await context(config);
85
-
86
- // Enable watch mode
87
- await ctx.watch();
88
-
89
- // Create a server for the build directory
90
- const server = HttpServer.createServer({ root: "build" });
91
- server.listen(3010, () => console.log("Running at http://localhost:3010"));
92
- } else {
93
- await build(config);
94
- }
@@ -1,24 +0,0 @@
1
- const notifierPlugin = (watch) => ({
2
- name: "svgr",
3
- setup(build) {
4
- let count = 0;
5
-
6
- build.onEnd((result) => {
7
- if (result.errors.length > 0) {
8
- return;
9
- }
10
-
11
- if (count++ === 0) {
12
- if(watch) {
13
- console.log(`1st build: ${new Date().getTime()}`);
14
- } else {
15
- console.log(`Successfully built`);
16
- }
17
- } else {
18
- console.log(`Rebuilt: ${new Date().getTime()}`);
19
- }
20
- });
21
- },
22
- });
23
-
24
- export default notifierPlugin;
@@ -1,44 +0,0 @@
1
- import { transform } from "@svgr/core";
2
- import fs from "fs";
3
- import path from "path";
4
-
5
- const svgPlugin = (options = {}) => ({
6
- name: "svgr",
7
- setup(build) {
8
- build.onLoad({ filter: /\.svg$/ }, async (args) => {
9
- // Read file content
10
- const svg = await fs.promises.readFile(args.path, "utf8");
11
-
12
- // Determine relative path
13
- const relativePath = path.relative(process.cwd(), args.path);
14
-
15
- // This is an asset ==> dataurl
16
- if (
17
- !relativePath.startsWith("src/") ||
18
- relativePath.startsWith("src/assets/")
19
- ) {
20
- return {
21
- contents: svg,
22
- loader: "dataurl",
23
- };
24
- }
25
-
26
- // Otherwise it's a react component
27
- const contents = await transform(
28
- svg,
29
- {
30
- plugins: ["@svgr/plugin-svgo", "@svgr/plugin-jsx"],
31
- ...options,
32
- },
33
- { filePath: args.path }
34
- );
35
-
36
- return {
37
- contents,
38
- loader: "jsx",
39
- };
40
- });
41
- },
42
- });
43
-
44
- export default svgPlugin;