Skip to content

Commit

Permalink
fix(ui5-tooling-modules): improved packaging of npm packages
Browse files Browse the repository at this point in the history
The packaging of npm packages resolves the browser modules also from
the exports section of the package.json. This provides a better
coverage to detect the browser modules inside the npm packages.

In addition, this fix also allows to add polyfill overrides in the
current working directory.

Fixes #927
Fixes #915
  • Loading branch information
petermuessig committed Dec 25, 2023
1 parent d4520e3 commit 54db475
Show file tree
Hide file tree
Showing 27 changed files with 83,930 additions and 57,422 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/**/*.png

# Additionally ignore folders for lint-staged
/showcases/ui5-app/_polyfill-overrides_/**
/showcases/ui5-app/bundledefs/**
/packages/ui5-middleware-onelogin/lib/**
/packages/ui5-middleware-approuter/test/**
Expand Down
10 changes: 8 additions & 2 deletions packages/ui5-tooling-modules/lib/rollup-plugin-logger.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
module.exports = function ({ log } = {}) {
let c_id = 0,
c_l = 0;
return {
name: "logger",
resolveId(source) {
log?.verbose(`Bundling resource ${source}`);
resolveId(importee, importer) {
log?.verbose(`${c_id++} Bundling resource ${importee} from ${importer?.split("/").pop()}`);
return undefined;
},
load(importee) {
log?.verbose(` ${c_l++} Loading resource ${importee}`);
return undefined;
},
};
Expand Down
21 changes: 17 additions & 4 deletions packages/ui5-tooling-modules/lib/rollup-plugin-pnpm-resolve.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
const path = require("path");
const fs = require("fs");
module.exports = function ({ resolveModule } = {}) {
return {
name: "pnpm-resolve",
resolveId(source) {
// ignore absolute paths
if (path.isAbsolute(source)) {
resolveId: function (importee, importer) {
if (path.isAbsolute(importee)) {
// ignore absolute paths
return null;
} else if (importee.startsWith("./") || importee.startsWith("../")) {
// resolve relative paths
const file = path.resolve(path.dirname(importer), importee);
if (fs.existsSync(file) && fs.statSync(file).isFile()) {
return file;
} else if (fs.existsSync(`${file}.js`)) {
return `${file}.js`;
} else if (fs.existsSync(`${file}.cjs`)) {
return `${file}.cjs`;
} else if (fs.existsSync(`${file}.mjs`)) {
return `${file}.mjs`;
}
}
// needs to be in sync with nodeResolve
return resolveModule(source);
return resolveModule(importee);
},
};
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,37 +1,72 @@
/* eslint-disable no-unused-vars */
const { existsSync, readFileSync } = require("fs");
const { join } = require("path");

// reuse logic from polyfill-node plugin
const nodePolyfills = require("rollup-plugin-polyfill-node");
module.exports = function ({ log } = {}) {
const { resolveId } = nodePolyfills();
const PREFIX = `\0polyfill-node.`;
const PREFIX_LENGTH = PREFIX.length;
const DIRNAME_PATH = "\0node-polyfills:dirname";
const FILENAME_PATH = "\0node-polyfills:filename";
const inject = require("@rollup/plugin-inject");

const isBuiltInModule = function isBuiltInModule(module) {
try {
if (!require("path").isAbsolute(module) && require.resolve(module) === module) {
return true;
}
} catch (ex) {
/* */
}
return false;
};

module.exports = function nodePolyfillsOverride({ log, cwd } = {}) {
const { resolveId, load, transform } = nodePolyfills();
const overridesDir = join(cwd, "_polyfill-overrides_");
return {
name: "polyfill-node-override",
resolveId: function (importee, importer, options) {
if (importee === "http2") {
return { id: "http2?node-polyfill-override", moduleSideEffects: false };
}
if (importee === "async_hooks") {
return { id: "async_hooks?node-polyfill-override", moduleSideEffects: false };
}
if (importee.startsWith("node:")) {
return resolveId.call(this, importee.substr("node:".length), importer, options);
if (isBuiltInModule(importee)) {
const builtInModule = importee.startsWith("node:") ? importee.substr("node:".length) : importee;
if (existsSync(join(overridesDir, `${builtInModule}.js`))) {
return { id: `${PREFIX}${builtInModule}.js`, moduleSideEffects: false };
}
const resolvedId = resolveId.call(this, builtInModule, importer, options);
if (resolvedId) {
return resolvedId;
} else {
return { id: `${builtInModule}?polyfill-node-ignore`, moduleSideEffects: false };
}
}
return null;
},
load: function (source) {
if (source === "http2?node-polyfill-override") {
return `export const constants = {
HTTP2_HEADER_AUTHORITY: "authority",
HTTP2_HEADER_METHOD: "method",
HTTP2_HEADER_PATH: "path",
HTTP2_HEADER_SCHEME: "scheme",
HTTP2_HEADER_CONTENT_LENGTH: "content-length",
HTTP2_HEADER_EXPECT: "expect",
HTTP2_HEADER_STATUS: "status"
};`;
}
if (source === "async_hooks?node-polyfill-override") {
return `export class AsyncResource {};`;
load: function (importee) {
if (importee.startsWith(PREFIX)) {
const builtInModule = importee.substr(PREFIX_LENGTH);
let content;
if (existsSync(join(overridesDir, `${builtInModule}`))) {
content = readFileSync(join(overridesDir, `${builtInModule}`), { encoding: "utf8" });
} else {
content = load.apply(this, arguments);
}
return content;
} else if (importee.endsWith("?polyfill-node-ignore")) {
return "";
}
return null;
},
};
};

// prevent the renaming of global, process, Buffer in any module
// => needs to be used as a standalone rollup plugin in build
// !!!! KEEP IN SYNC WITH >> rollup-plugin-polyfill-node << !!!!
module.exports.inject = function nodePolyfillsOverrideInject() {
return inject({
process: PREFIX + "process",
Buffer: [PREFIX + "buffer", "Buffer"],
global: PREFIX + "global",
__filename: FILENAME_PATH,
__dirname: DIRNAME_PATH,
});
};
77 changes: 56 additions & 21 deletions packages/ui5-tooling-modules/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ const { existsSync } = require("fs");
const { readFile, stat } = require("fs").promises;

const rollup = require("rollup");
const { nodeResolve } = require("@rollup/plugin-node-resolve");
const commonjs = require("@rollup/plugin-commonjs");
const json = require("@rollup/plugin-json");
const nodePolyfills = require("rollup-plugin-polyfill-node");
const nodePolyfillsOverride = require("./rollup-plugin-polyfill-node-override");
const nodePolyfillsIgnore = require("./rollup-plugin-polyfill-node-ignore");
const amdCustom = require("./rollup-plugin-amd-custom");
const skipAssets = require("./rollup-plugin-skip-assets");
const injectESModule = require("./rollup-plugin-inject-esmodule");
Expand All @@ -31,6 +29,9 @@ const chunkToModulePath = {};
// local cache of negative modules (avoid additional lookups)
const modulesNegativeCache = [];

// main field processing order (for resolveModule)
const defaultExportsFields = ["browser", "import", "require", "default"];

// main field processing order (for nodeResolve and resolveModule)
const defaultMainFields = ["browser", "module", "main"];

Expand Down Expand Up @@ -104,10 +105,11 @@ module.exports = function (log) {
* @param {string} options.cwd current working directory
* @param {string[]} options.depPaths paths of the dependencies (in addition for cwd)
* @param {string[]} options.mainFields an order of main fields to check in package.json
* @param {string[]} options.exportsFields an order of exports fields to check in package.json
* @returns {string} the path of the module in the filesystem
*/
// ignore module paths starting with a segment from the ignore list (TODO: maybe a better check?)
resolveModule: function resolveModule(moduleName, { cwd, depPaths, mainFields } = {}) {
resolveModule: function resolveModule(moduleName, { cwd, depPaths, mainFields, exportsFields } = {}) {
// default the current working directory
cwd = cwd || process.cwd();
// if a module is listed in the negative cache, we ignore it!
Expand All @@ -116,8 +118,9 @@ module.exports = function (log) {
}
// package.json of app
const pkg = require(path.join(cwd, "package.json"));
// default the mainFields
// default the mainFields and exportsFields
mainFields = mainFields || defaultMainFields;
exportsFields = exportsFields || defaultExportsFields;
// retrieve or create a resolved module
log.verbose(`Resolving ${moduleName} [${mainFields}]...`);
// no module found => resolve it
Expand All @@ -131,16 +134,42 @@ module.exports = function (log) {
try {
const pckJsonModuleName = path.join(moduleName, "package.json");
const pkgJson = require(pckJsonModuleName);
// resolve the main field from the package.json
for (const field of mainFields) {
if (typeof pkgJson?.[field] === "string") {
modulePath = path.join(path.dirname(resolveNodeModule(pckJsonModuleName, cwd, depPaths)), pkgJson?.[field]);
break;
const resolveModulePath = function (exports, fields) {
for (const field of fields) {
if (typeof exports[field] === "string") {
modulePath = path.join(path.dirname(resolveNodeModule(pckJsonModuleName, cwd, depPaths)), exports[field]);
// reset the module path if it doesn't exist
if (!existsSync(modulePath)) {
modulePath = undefined;
} else {
return modulePath;
}
}
}
};
// resolve the module path from exports information
// 1.) the browser field in package.json > exports > "."
if (typeof pkgJson?.exports?.["."] === "object" && typeof pkgJson?.exports?.["."].browser === "object") {
modulePath = resolveModulePath(pkgJson.exports["."].browser, exportsFields);
}
// reset the module path if it doesn't exist
if (!existsSync(modulePath)) {
modulePath = undefined;
// 2.) the browser field in package.json
if (!modulePath) {
modulePath = resolveModulePath(pkgJson, ["browser"]);
}
// 3.) the fields in package.json > exports > "."
if (!modulePath && typeof pkgJson?.exports?.["."] === "object") {
Object.values(pkgJson?.exports?.["."]).forEach((entry) => {
if (!modulePath && typeof entry === "object") {
modulePath = resolveModulePath([entry, exportsFields]);
}
});
if (!modulePath) {
modulePath = resolveModulePath([pkgJson?.exports?.["."], exportsFields]);
}
}
// 4.) the fields in package.json
if (!modulePath) {
modulePath = resolveModulePath(pkgJson, mainFields);
}
} catch (err) {
// gracefully ignore the error
Expand Down Expand Up @@ -174,35 +203,38 @@ module.exports = function (log) {
* @returns {string} the bundle
*/
createBundle: async function createBundle(moduleName, { cwd, depPaths, mainFields, beforePlugins, afterPlugins, isMiddleware } = {}) {
// create a bundle
const bundle = await rollup.rollup({
input: moduleName,
context: "exports" /* this is normally converted to undefined, but should be exports in our case! */,
plugins: [
...(beforePlugins || []),
replace({
preventAssignment: false,
delimiters: ["\\b", "\\b"],
values: {
"process.env.NODE_ENV": JSON.stringify("development"),
"process.env.NODE_ENV": JSON.stringify(isMiddleware ? "development" : "production"),
"process.versions.node": JSON.stringify("18.15.0"), // needed for some modules to select features
},
}),
nodePolyfillsOverride(),
nodePolyfills(),
nodePolyfillsIgnore(),
injectESModule(),
skipAssets({
log,
extensions: ["css"],
}),
json(),
commonjs({
defaultIsModuleExports: true,
}),
amdCustom(),
json(),
nodeResolve({
mainFields,
preferBuiltins: false,
// node polyfills/resolution must happen after
// commonjs and amd to ensure e.g. exports is
// properly handled by those plugins
nodePolyfillsOverride({
log,
cwd,
}),
nodePolyfills(),
nodePolyfillsOverride.inject(),
pnpmResolve({
resolveModule: function (moduleName) {
return that.resolveModule(moduleName, { cwd, depPaths, mainFields });
Expand Down Expand Up @@ -233,6 +265,7 @@ module.exports = function (log) {
amd: {
define: "sap.ui.define",
},
//generatedCode: "es2015",
entryFileNames: `${moduleName}.js`,
chunkFileNames: `${moduleName}/[hash].js`,
sourcemap: false, // isMiddleware ? "inline" : true
Expand Down Expand Up @@ -261,6 +294,7 @@ module.exports = function (log) {
*/
getResource: async function getResource(moduleName, { skipCache, debug, keepDynamicImports } = {}, { cwd, depPaths, skipTransform, isMiddleware } = {}) {
let bundling = false;
cwd = cwd || process.cwd();

try {
// in case of chunks are requested, we lookup the original module
Expand Down Expand Up @@ -369,6 +403,7 @@ module.exports = function (log) {
if (bundling) {
log.error(`Couldn't bundle ${moduleName}: ${err}`, err);
}
console.error(err);
}
},

Expand Down
2 changes: 1 addition & 1 deletion packages/ui5-tooling-modules/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"@buxlabs/amd-to-es6": "^0.16.3",
"@javascript-obfuscator/escodegen": "^2.3.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-inject": "^5.0.5",
"@rollup/plugin-json": "^6.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/pluginutils": "^5.1.0",
"espree": "^9.6.1",
Expand Down
Loading

0 comments on commit 54db475

Please sign in to comment.