Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions internal/npm-integration-poc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Build outputs
dist/

# Node modules
node_modules/
13 changes: 13 additions & 0 deletions internal/npm-integration-poc/REUSE.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company
# SPDX-License-Identifier: Apache-2.0

version = 1
SPDX-PackageName = "@ui5/npm-integration-poc"
SPDX-PackageSupplier = "SAP SE <opensource@sap.com>"
SPDX-PackageDownloadLocation = "https://github.com/SAP/ui5-cli"

[[annotations]]
path = ["node_modules/**", "examples/**", "consolidated-app/webapp/thirdparty/**"]
precedence = "aggregate"
SPDX-FileCopyrightText = "none"
SPDX-License-Identifier = "CC0-1.0"
21 changes: 21 additions & 0 deletions internal/npm-integration-poc/consolidated-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Build output directories
dist/
dist-webc-reuse/
dist-standalone/

# Generated webcomponents
webapp/gen/
webapp/@ui5/

# Dependencies
node_modules/

# IDE
.vscode/
.idea/

# UI5 Tooling
.ui5-tooling-modules/

# Coverage
coverage/
125 changes: 125 additions & 0 deletions internal/npm-integration-poc/consolidated-app/bundler-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Shared bundler configuration for task and middleware.
* SCAN_RESULT will be generated by AST scanner in production.
*/

import {join, dirname} from "path";
import {fileURLToPath} from "url";
import replace from "@rollup/plugin-replace";

const __dirname = dirname(fileURLToPath(import.meta.url));

/**
* Scan result - defines what to bundle and how.
* Keys are package names, values define entry points and dependencies.
*/
export const SCAN_RESULT = {
// Standalone packages (no externals)
"chart.js": {
imports: ["Chart", "BarController", "BarElement", "CategoryScale", "LinearScale", "Title", "Tooltip", "Legend"],
entryPoints: ["chart.js"]
},
"validator": {
imports: ["isEmail", "isURL", "escape"],
entryPoints: ["validator"]
},

// React ecosystem - ordered by dependency (react first, then react-dom, then components)
"react": {
imports: ["useState", "useEffect", "createElement"],
entryPoints: ["react"]
},
"react-dom": {
imports: ["createRoot"],
entryPoints: ["react-dom/client"], // Actual entry differs from package name
externals: ["react"],
outputName: "react-dom"
},
"react-colorful": {
imports: ["HexColorPicker"],
entryPoints: ["react-colorful"],
externals: ["react", "react-dom"]
},

// App-specific React wrapper component
"react-colorpicker": {
imports: ["mountColorPicker"],
entryPoints: [join(__dirname, "webapp/react/ColorPickerWrapper.js")],
externals: ["react", "react-dom"],
outputName: "react-colorpicker",
outputOptions: {exports: "named"}
}
};

/** Get externals for a package (mapped to actual entry points) */
export function getExternals(packageName) {
const scanInfo = SCAN_RESULT[packageName];
if (!scanInfo?.externals) return [];

// Map dependency names to their actual entry points
return scanInfo.externals.flatMap((dep) => {
const depInfo = SCAN_RESULT[dep];
return depInfo ? depInfo.entryPoints : [dep];
});
}

/** Generate AMD paths mapping for externalized dependencies */
export function getPathsMapping(packageName) {
const scanInfo = SCAN_RESULT[packageName];
if (!scanInfo?.externals) return {};

const paths = {};
for (const dep of scanInfo.externals) {
const depInfo = SCAN_RESULT[dep];
if (depInfo) {
const outputName = depInfo.outputName || sanitizePackageName(dep);
for (const entry of depInfo.entryPoints) {
paths[entry] = `thirdparty/${outputName}`;
}
}
}
return paths;
}

/** Get packages in dependency order (topological sort based on externals) */
export function getBundleOrder() {
const result = [];
const visited = new Set();

function visit(pkg) {
if (visited.has(pkg)) return;
visited.add(pkg);

const info = SCAN_RESULT[pkg];
if (info?.externals) {
for (const dep of info.externals) {
if (SCAN_RESULT[dep]) visit(dep);
}
}
result.push(pkg);
}

Object.keys(SCAN_RESULT).forEach(visit);
return result;
}

/** Create production mode replace plugin */
export function createProductionPlugin() {
return replace({
preventAssignment: true,
"process.env.NODE_ENV": JSON.stringify("production")
});
}

/** Create development mode replace plugin */
export function createDevelopmentPlugin() {
return replace({
preventAssignment: true,
"process.env.NODE_ENV": JSON.stringify("development")
});
}

/** Sanitize package name for file path (handle scoped packages) */
export function sanitizePackageName(packageName) {
return packageName.replace(/^@/, "").replace(/\//g, "-");
}
Loading