Skip to content

Commit

Permalink
fix(ui5-tooling-modules): improved packaging of npm packages (#929)
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 authored Dec 25, 2023
1 parent 5b9d5b7 commit b8c6b25
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 b8c6b25

Please sign in to comment.