Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
runtime: ['node', 'bun', 'deno']
node-version: [20.x]

Expand Down
36 changes: 36 additions & 0 deletions examples/windows-fetch-workaround/test-with-polyfill.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env node

/**
* Example: Using use-m on Windows with fetch polyfill
*
* This demonstrates the workaround for issue #45 where fetch is not defined
* in certain Windows contexts (Git Bash, shebang execution).
*
* Solution: Import the fetch-polyfill before loading use-m
*/

console.log('Platform:', process.platform);
console.log('Node version:', process.version);
console.log('Fetch available initially:', typeof fetch !== 'undefined');

// Import fetch polyfill to ensure fetch is available
// This handles the Windows Git Bash issue where fetch isn't in global scope
await import('../../fetch-polyfill.js');

console.log('Fetch available after polyfill:', typeof fetch !== 'undefined');

// Now load use-m as usual
const { use } = eval(
await (
await fetch('https://unpkg.com/use-m/use.js')
).text()
);

console.log('\nβœ… Successfully loaded use-m');

// Test with lodash
const _ = await use('[email protected]');
console.log('βœ… Successfully loaded lodash');
console.log('Test: _.add(1, 2) =', _.add(1, 2));

console.log('\nπŸŽ‰ All tests passed!');
38 changes: 38 additions & 0 deletions examples/windows-fetch-workaround/test-without-polyfill.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env node

/**
* Example: Testing fetch availability without polyfill
*
* This script reproduces issue #45 by attempting to use fetch without
* ensuring it's available first.
*/

console.log('Platform:', process.platform);
console.log('Node version:', process.version);
console.log('Fetch available:', typeof fetch !== 'undefined');

if (typeof fetch === 'undefined') {
console.error('\n❌ ERROR: fetch is not defined!');
console.error('\nThis is the issue described in #45.');
console.error('To fix this, use the fetch polyfill:');
console.error(' await import("use-m/fetch-polyfill.js");');
console.error('\nOr see: examples/windows-fetch-workaround/test-with-polyfill.mjs');
process.exit(1);
}

// Attempt to load use-m
try {
const { use } = eval(
await (
await fetch('https://unpkg.com/use-m/use.js')
).text()
);

console.log('βœ… Successfully loaded use-m');

const _ = await use('[email protected]');
console.log('βœ… Test passed: _.add(1, 2) =', _.add(1, 2));
} catch (error) {
console.error('❌ Failed:', error.message);
process.exit(1);
}
42 changes: 42 additions & 0 deletions experiments/test-fetch-availability.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env node

/**
* Test script to reproduce issue #45: fetch is not defined on Windows
*
* This script checks:
* 1. Node.js version
* 2. Whether fetch is available
* 3. Whether we can load use-m with fetch
*/

console.log('=== Fetch Availability Test ===');
console.log('Node version:', process.version);
console.log('Platform:', process.platform);
console.log('Fetch available:', typeof fetch !== 'undefined');
console.log('Global fetch:', typeof globalThis.fetch !== 'undefined');

// Test if fetch works
if (typeof fetch === 'undefined') {
console.error('\n❌ ERROR: fetch is not defined!');
console.log('\nThis is the bug described in issue #45');
console.log('Expected: fetch should be available in Node.js 18+');
console.log('Actual: fetch is undefined');
process.exit(1);
} else {
console.log('\nβœ… fetch is available');

// Try to load use-m
try {
console.log('\nTrying to load use-m...');
const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
console.log('βœ… Successfully loaded use-m');

// Test using a package
const _ = await use('[email protected]');
console.log('βœ… Successfully loaded lodash via use-m');
console.log('Test: _.add(1, 2) =', _.add(1, 2));
} catch (error) {
console.error('❌ Failed to load use-m:', error.message);
process.exit(1);
}
}
101 changes: 101 additions & 0 deletions experiments/test-fetch-polyfill.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env node

/**
* Test script demonstrating fetch polyfill for issue #45
*
* This shows how to ensure fetch is available before loading use-m
*/

console.log('=== Testing fetch polyfill approach ===');
console.log('Node version:', process.version);
console.log('Platform:', process.platform);
console.log('Initial fetch available:', typeof fetch !== 'undefined');

// Polyfill fetch if not available (for Node.js 18+ with Windows Git Bash issues)
if (typeof fetch === 'undefined') {
console.log('\n⚠️ fetch is not defined, attempting to use node:http/https as fallback...');

// For Node.js 18+, fetch should be available but might not be in certain contexts
// We can try to explicitly import it from node:
try {
// Try to import fetch from Node.js built-ins (Node 18+)
// This is a workaround for contexts where global fetch isn't initialized
const { default: nodeFetch } = await import('node:http').then(() => {
// Actually try to get fetch from the global after a tick
return new Promise(resolve => {
setImmediate(() => {
if (typeof fetch !== 'undefined') {
resolve({ default: fetch });
} else {
throw new Error('fetch still not available');
}
});
});
}).catch(async () => {
// If Node.js built-in fetch is not available, fall back to https module
const https = await import('node:https');
const http = await import('node:http');

return {
default: (url, options = {}) => {
return new Promise((resolve, reject) => {
const parsedUrl = new URL(url);
const protocol = parsedUrl.protocol === 'https:' ? https : http;

const req = protocol.request(parsedUrl, {
method: options.method || 'GET',
headers: options.headers || {},
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
resolve({
ok: res.statusCode >= 200 && res.statusCode < 300,
status: res.statusCode,
statusText: res.statusMessage,
headers: res.headers,
text: () => Promise.resolve(data),
json: () => Promise.resolve(JSON.parse(data)),
});
});
});

req.on('error', reject);

if (options.body) {
req.write(options.body);
}

req.end();
});
}
};
});

globalThis.fetch = nodeFetch;
console.log('βœ… fetch polyfill installed');
} catch (error) {
console.error('❌ Failed to install fetch polyfill:', error.message);
process.exit(1);
}
}

console.log('Final fetch available:', typeof fetch !== 'undefined');

// Now try to load use-m
try {
console.log('\nLoading use-m...');
const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
console.log('βœ… Successfully loaded use-m');

// Test using a package
const _ = await use('[email protected]');
console.log('βœ… Successfully loaded lodash via use-m');
console.log('Test: _.add(1, 2) =', _.add(1, 2));

console.log('\nβœ… All tests passed!');
} catch (error) {
console.error('❌ Failed:', error.message);
console.error(error.stack);
process.exit(1);
}
55 changes: 55 additions & 0 deletions experiments/test-polyfill-no-fetch.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env node

/**
* Test the fetch polyfill by simulating a scenario where fetch is not available
*/

console.log('=== Testing fetch polyfill with simulated missing fetch ===');
console.log('Node version:', process.version);
console.log('Platform:', process.platform);
console.log('Fetch initially available:', typeof fetch !== 'undefined');

// Simulate the Windows Git Bash issue by removing fetch from global scope
const originalFetch = globalThis.fetch;
delete globalThis.fetch;

console.log('Fetch after deletion:', typeof fetch !== 'undefined');

// Now import the polyfill
try {
await import('../fetch-polyfill.js');
console.log('βœ… Polyfill imported successfully');
console.log('Fetch after polyfill:', typeof fetch !== 'undefined');

if (typeof fetch === 'undefined') {
console.error('❌ Polyfill failed to install fetch');
process.exit(1);
}

// Test that fetch works
console.log('\nTesting fetch...');
const response = await fetch('https://unpkg.com/use-m/package.json');
const data = await response.json();
console.log('βœ… Fetch works! Package name:', data.name);

// Now test loading use-m
console.log('\nTesting use-m...');
const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
console.log('βœ… use-m loaded successfully');

const _ = await use('[email protected]');
console.log('βœ… lodash loaded successfully');
console.log('Test: _.add(1, 2) =', _.add(1, 2));

console.log('\nπŸŽ‰ All tests passed!');
} catch (error) {
console.error('❌ Test failed:', error.message);
console.error(error.stack);

// Restore original fetch
if (originalFetch) {
globalThis.fetch = originalFetch;
}

process.exit(1);
}
108 changes: 108 additions & 0 deletions fetch-polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Fetch polyfill for Node.js environments where fetch is not available
*
* This addresses issue #45 where fetch is not defined in certain Windows contexts
* (e.g., Git Bash, shebang execution) even in Node.js 18+.
*
* Usage:
* // Ensure fetch is available before loading use-m
* await import('use-m/fetch-polyfill.js');
* const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
*
* Or with a local import:
* await import('./fetch-polyfill.js');
*/

// Only install polyfill if fetch is not already available
const needsPolyfill = typeof globalThis !== 'undefined' && typeof globalThis.fetch === 'undefined';

if (needsPolyfill) {
// Create a minimal fetch implementation using node:http/https
const installPolyfill = async () => {
try {
// First, try to import from undici (Node.js 18+)
let fetchImpl;

try {
const undici = await import('undici');
fetchImpl = undici.fetch;
} catch {
// If undici is not available, create a minimal fetch using node:http/https
const https = await import('node:https');
const http = await import('node:http');
const { URL } = await import('node:url');

fetchImpl = (url, options = {}, redirectCount = 0) => {
const MAX_REDIRECTS = 20;

return new Promise((resolve, reject) => {
const parsedUrl = new URL(url);
const protocol = parsedUrl.protocol === 'https:' ? https : http;

const requestOptions = {
method: options.method || 'GET',
headers: options.headers || {},
};

const req = protocol.request(parsedUrl, requestOptions, (res) => {
// Handle redirects (301, 302, 303, 307, 308)
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
if (redirectCount >= MAX_REDIRECTS) {
return reject(new Error('Too many redirects'));
}

const redirectUrl = new URL(res.headers.location, url).href;
return resolve(fetchImpl(redirectUrl, options, redirectCount + 1));
}

const chunks = [];

res.on('data', chunk => chunks.push(chunk));

res.on('end', () => {
const buffer = Buffer.concat(chunks);
const text = buffer.toString('utf-8');

resolve({
ok: res.statusCode >= 200 && res.statusCode < 300,
status: res.statusCode,
statusText: res.statusMessage,
headers: res.headers,
url: url,
redirected: redirectCount > 0,
type: 'basic',
text: () => Promise.resolve(text),
json: () => Promise.resolve(JSON.parse(text)),
blob: () => Promise.resolve(buffer),
arrayBuffer: () => Promise.resolve(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)),
});
});
});

req.on('error', reject);

if (options.body) {
req.write(options.body);
}

req.end();
});
};
}

// Install fetch polyfill globally
globalThis.fetch = fetchImpl;
} catch (error) {
// If polyfill installation fails, provide a helpful error message
console.error('Failed to install fetch polyfill:', error.message);
console.error('Please ensure you are running Node.js 18+ or install node-fetch manually.');
}
};

// Execute the polyfill installation
await installPolyfill();
}

// Export the fetch function (either native or polyfilled)
export const fetch = globalThis.fetch;
export default globalThis.fetch;
Loading
Loading