Skip to content

Commit

Permalink
fix: port modern polyfill code to add caching in internal call (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
imranbarbhuiya authored Jun 26, 2023
1 parent c345cf6 commit ed22079
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 89 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"root": true,
"extends": ["mahir/common", "mahir/node", "mahir/typescript", "mahir/prettier"],
"ignorePatterns": ["**/dist/*"]
"ignorePatterns": ["**/dist/*"],
"rules": {
"unicorn/prefer-string-replace-all": "off"
}
}
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@
"node-modules-polyfill",
"esbuild",
"esbuild-plugin",
"polyfill"
"polyfill",
"node-modules",
"node-polyfill"
],
"dependencies": {
"modern-node-polyfills": "^1.0.0"
"@jspm/core": "^2.0.1",
"local-pkg": "^0.4.3",
"resolve.exports": "^2.0.2"
},
"devDependencies": {
"@commitlint/cli": "^17.6.6",
Expand Down
3 changes: 2 additions & 1 deletion src/lib/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { builtinModules } from 'node:module';
import path from 'node:path';

import { escapeRegex, commonJsTemplate, getCachedPolyfillPath, getCachedPolyfillContent } from './utils/util.js';
import { getCachedPolyfillContent, getCachedPolyfillPath } from './polyfill.js';
import { escapeRegex, commonJsTemplate } from './utils/util.js';

import type { OnResolveArgs, Plugin } from 'esbuild';
import type esbuild from 'esbuild';
Expand Down
84 changes: 84 additions & 0 deletions src/lib/polyfill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* `polyfillPath` and `getCachedPolyfillContent` are taken from below source with some modifications for my use case.
* https://github.com/Aslemammad/modern-node-polyfills
* @author Aslemammad
* @license MIT
*/

import { builtinModules } from 'node:module';
import { resolve, join } from 'node:path';

import { build } from 'esbuild';
import { loadPackageJSON, resolveModule } from 'local-pkg';
import { resolve as resolveExports } from 'resolve.exports';

import { normalizeNodeBuiltinPath } from './utils/util.js';

async function polyfillPath(importPath: string) {
if (!builtinModules.includes(importPath))
throw new Error(`Node.js does not have ${importPath} in its builtin modules`);

const jspmPath = resolve(
require.resolve(`@jspm/core/nodelibs/${importPath}`),
// ensure "fs/promises" is resolved properly
'../../..' + (importPath.includes('/') ? '/..' : ''),
);

const jspmPackageJson = await loadPackageJSON(jspmPath);
const exportPath = resolveExports(jspmPackageJson, `./nodelibs/${importPath}`, {
browser: true,
});

const exportFullPath = resolveModule(join(jspmPath, exportPath?.[0] ?? ''));

if (!exportPath || !exportFullPath) {
throw new Error(
'resolving failed, please try creating an issue in https://github.com/imranbarbhuiya/esbuild-plugins-node-modules-polyfill',
);
}

return exportFullPath;
}

const polyfillPathCache: Map<string, Promise<string>> = new Map();
export const getCachedPolyfillPath = (importPath: string): Promise<string> => {
const normalizedImportPath = normalizeNodeBuiltinPath(importPath);

const cachedPromise = polyfillPathCache.get(normalizedImportPath);
if (cachedPromise) {
return cachedPromise;
}

const promise = polyfillPath(normalizedImportPath);
polyfillPathCache.set(normalizedImportPath, promise);
return promise;
};

export const polyfillContentAndTransform = async (importPath: string) => {
const exportFullPath = await getCachedPolyfillPath(importPath);

const content = (
await build({
write: false,
format: 'esm',
bundle: true,
entryPoints: [exportFullPath],
})
).outputFiles[0]!.text;

return content.replace(/eval\(/g, '(0,eval)(');
};

const polyfillContentCache: Map<string, Promise<string>> = new Map();
export const getCachedPolyfillContent = (_importPath: string): Promise<string> => {
const normalizedImportPath = normalizeNodeBuiltinPath(_importPath);

const cachedPromise = polyfillContentCache.get(normalizedImportPath);
if (cachedPromise) {
return cachedPromise;
}

const promise = polyfillContentAndTransform(normalizedImportPath);
polyfillContentCache.set(normalizedImportPath, promise);
return promise;
};
38 changes: 1 addition & 37 deletions src/lib/utils/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/* eslint-disable unicorn/prefer-string-replace-all -- node v14 doesn't supports string.replaceAll*/
import { polyfillContent, polyfillPath } from 'modern-node-polyfills';

export const escapeRegex = (str: string) => {
return str.replace(/[$()*+.?[\\\]^{|}]/g, '\\$&').replace(/-/g, '\\x2d');
};
Expand All @@ -9,39 +6,6 @@ export const commonJsTemplate = ({ importPath }: { importPath: string }) => {
return `export * from '${importPath}'`;
};

const normalizeNodeBuiltinPath = (path: string) => {
export const normalizeNodeBuiltinPath = (path: string) => {
return path.replace(/^node:/, '').replace(/\/$/, '');
};

const polyfillPathCache: Map<string, Promise<string>> = new Map();
export const getCachedPolyfillPath = (_importPath: string): Promise<string> => {
const normalizedImportPath = normalizeNodeBuiltinPath(_importPath);

const cachedPromise = polyfillPathCache.get(normalizedImportPath);
if (cachedPromise) {
return cachedPromise;
}

const promise = polyfillPath(normalizedImportPath);
polyfillPathCache.set(normalizedImportPath, promise);
return promise;
};

const polyfillContentAndTransform = async (importPath: string) => {
const content = await polyfillContent(importPath);
return content.replace(/eval\(/g, '(0,eval)(');
};

const polyfillContentCache: Map<string, Promise<string>> = new Map();
export const getCachedPolyfillContent = (_importPath: string): Promise<string> => {
const normalizedImportPath = normalizeNodeBuiltinPath(_importPath);

const cachedPromise = polyfillContentCache.get(normalizedImportPath);
if (cachedPromise) {
return cachedPromise;
}

const promise = polyfillContentAndTransform(normalizedImportPath);
polyfillContentCache.set(normalizedImportPath, promise);
return promise;
};
3 changes: 1 addition & 2 deletions tests/fixtures/input/globalProcess.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// eslint-disable-next-line no-restricted-globals, n/prefer-global/process
/* eslint-disable no-restricted-globals, n/prefer-global/process */
console.log(process.version);

// Ensure that environment variables can still be injected via `define`
// eslint-disable-next-line no-restricted-globals, n/prefer-global/process
console.log(process.env.NODE_ENV);
2 changes: 1 addition & 1 deletion tests/scenarios/__snapshots__/polyfill.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Polyfill Test > GIVEN a file that imports a node builtin file THEN polyfill it 1`] = `
exports[`Polyfill Test > GIVEN a file that imports a node builtin THEN polyfill it 1`] = `
"\\"use strict\\";
(() => {
// node-modules-polyfills:util
Expand Down
2 changes: 1 addition & 1 deletion tests/scenarios/polyfill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function createConfig(pluginOptions?: NodePolyfillsOptions): BuildOptions {
}

describe('Polyfill Test', () => {
test('GIVEN a file that imports a node builtin file THEN polyfill it', async () => {
test('GIVEN a file that imports a node builtin THEN polyfill it', async () => {
const config = createConfig();

await esbuild.build(config);
Expand Down
54 changes: 10 additions & 44 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -948,22 +948,6 @@ __metadata:
languageName: node
linkType: hard

"@rollup/pluginutils@npm:^5.0.2":
version: 5.0.2
resolution: "@rollup/pluginutils@npm:5.0.2"
dependencies:
"@types/estree": ^1.0.0
estree-walker: ^2.0.2
picomatch: ^2.3.1
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0
peerDependenciesMeta:
rollup:
optional: true
checksum: edea15e543bebc7dcac3b0ac8bc7b8e8e6dbd46e2864dbe5dd28072de1fbd5b0e10d545a610c0edaa178e8a7ac432e2a2a52e547ece1308471412caba47db8ce
languageName: node
linkType: hard

"@rushstack/eslint-patch@npm:^1.3.2":
version: 1.3.2
resolution: "@rushstack/eslint-patch@npm:1.3.2"
Expand Down Expand Up @@ -1045,13 +1029,6 @@ __metadata:
languageName: node
linkType: hard

"@types/estree@npm:^1.0.0":
version: 1.0.1
resolution: "@types/estree@npm:1.0.1"
checksum: e9aa175eacb797216fafce4d41e8202c7a75555bc55232dee0f9903d7171f8f19f0ae7d5191bb1a88cb90e65468be508c0df850a9fb81b4433b293a5a749899d
languageName: node
linkType: hard

"@types/json-schema@npm:^7.0.9":
version: 7.0.11
resolution: "@types/json-schema@npm:7.0.11"
Expand Down Expand Up @@ -2608,15 +2585,17 @@ __metadata:
"@commitlint/config-conventional": ^17.6.6
"@favware/cliff-jumper": ^2.1.1
"@favware/npm-deprecate": ^1.0.7
"@jspm/core": ^2.0.1
cz-conventional-changelog: ^3.3.0
esbuild: ^0.18.8
eslint: ^8.43.0
eslint-config-mahir: ^0.0.27
husky: ^8.0.3
lint-staged: ^13.2.2
modern-node-polyfills: ^1.0.0
local-pkg: ^0.4.3
pinst: ^3.0.0
prettier: ^2.8.8
resolve.exports: ^2.0.2
tsup: ^7.1.0
typescript: ^5.1.3
vitest: ^0.32.2
Expand Down Expand Up @@ -3249,13 +3228,6 @@ __metadata:
languageName: node
linkType: hard

"estree-walker@npm:^2.0.2":
version: 2.0.2
resolution: "estree-walker@npm:2.0.2"
checksum: 6151e6f9828abe2259e57f5fd3761335bb0d2ebd76dc1a01048ccee22fabcfef3c0859300f6d83ff0d1927849368775ec5a6d265dde2f6de5a1be1721cd94efc
languageName: node
linkType: hard

"esutils@npm:^2.0.2, esutils@npm:^2.0.3":
version: 2.0.3
resolution: "esutils@npm:2.0.3"
Expand Down Expand Up @@ -5144,19 +5116,6 @@ __metadata:
languageName: node
linkType: hard

"modern-node-polyfills@npm:^1.0.0":
version: 1.0.0
resolution: "modern-node-polyfills@npm:1.0.0"
dependencies:
"@jspm/core": ^2.0.1
"@rollup/pluginutils": ^5.0.2
local-pkg: ^0.4.3
peerDependencies:
esbuild: ^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0
checksum: 2cf0c7fe4c15efa02702576e799ce20e0ff85e4db39516cc566c2c17cd45702ec56f1f1b57e40d4126f4c94f2bcff6923ed48191d2323c0f059d51c2e2b45d7b
languageName: node
linkType: hard

"modify-values@npm:^1.0.1":
version: 1.0.1
resolution: "modify-values@npm:1.0.1"
Expand Down Expand Up @@ -5977,6 +5936,13 @@ __metadata:
languageName: node
linkType: hard

"resolve.exports@npm:^2.0.2":
version: 2.0.2
resolution: "resolve.exports@npm:2.0.2"
checksum: 1c7778ca1b86a94f8ab4055d196c7d87d1874b96df4d7c3e67bbf793140f0717fd506dcafd62785b079cd6086b9264424ad634fb904409764c3509c3df1653f2
languageName: node
linkType: hard

"resolve@npm:^1.10.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.2":
version: 1.22.3
resolution: "resolve@npm:1.22.3"
Expand Down

0 comments on commit ed22079

Please sign in to comment.