Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add test for browser compatibility #721

Closed
wants to merge 1 commit into from
Closed
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: 2 additions & 0 deletions .changeset/honest-phones-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
3 changes: 3 additions & 0 deletions packages/fuels/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,8 @@
"scripts": {
"build": "tsup --dts",
"build:watch": "tsup --dts --watch"
},
"devDependencies": {
"prettier": "^2.8.0"
}
}
101 changes: 101 additions & 0 deletions packages/fuels/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
/* eslint-disable no-cond-assign */
import * as fs from 'fs';
import * as prettier from 'prettier';

import * as fuels from './index';

// this function parses a bundled file and returns a list of all modules that it references (import, require, export from, etc.)
const getListOfModules = (bundleContent: string) => {
const formattedBundleContent = prettier.format(bundleContent, {
parser: 'babel',
});
const listOfModules = new Set<string>();

const requireRegex = /(?:^|[^.])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g;
let match;
while ((match = requireRegex.exec(formattedBundleContent))) {
const moduleName = match[2];
listOfModules.add(moduleName);
}

const importRegex = /import\s+(?:.+\s+from\s+)?["'](.+)["'];?/g;
let importMatch;
while ((importMatch = importRegex.exec(formattedBundleContent))) {
const moduleName = importMatch[1];
listOfModules.add(moduleName);
}

const exportRegex = /export\s+(?:.+\s+from\s+)?["'](.+)["'];?/g;
let exportMatch;
while ((exportMatch = exportRegex.exec(formattedBundleContent))) {
const moduleName = exportMatch[1];
listOfModules.add(moduleName);
}
Comment on lines +14 to +33
Copy link
Member Author

@Dhaiwat10 Dhaiwat10 Jan 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach that I have gone with is scanning the bundled files for require and import statements to check for any references to Node.js built-in modules that are only available in a Node environment and not in a browser. I am not sure if this is the absolute best way to go about this, but I have checked that it does get the job done.

The moment I tried importing something from a module like fs, used it in a function and exported that function from one of the child packages, the test failed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale is valid, but it'd be hard to have certainty about the complete required stack, since we don't control all the libraries in use. For example, we can import an NPM package that uses some native module, which might be hard or impossible to detect, especially if we consider multiple nested dependencies.

In the end, I believe a declarative approach will suit us better.

One inspiration is how jest-runner-groups does it using manual tags.


return listOfModules;
};

describe('index.js', () => {
test('should export everything', async () => {
expect(fuels.AbiCoder).toBeTruthy();
Expand All @@ -11,4 +46,70 @@ describe('index.js', () => {
expect(fuels.TransactionType).toBeTruthy();
expect(fuels.ScriptResultDecoderError).toBeTruthy();
});

test('is browser-compatible (does not reference any built-in Node.js modules in the browser bundle)', async () => {
const NODE_BUILTIN_MODULES = [
'assert',
'buffer',
'child_process',
'cluster',
'console',
'constants',
'crypto',
'dgram',
'dns',
'events',
'fs',
'http',
'http2',
'https',
'module',
'net',
'os',
'path',
'perf_hooks',
'process',
'punycode',
'querystring',
'readline',
'repl',
'stream',
'string_decoder',
'sys',
'timers',
'tls',
'tty',
'url',
'util',
'v8',
'vm',
'zlib',
];

// get the list of all child packages by reading the `./packages/fuels/dist/index.mjs` file
const fuelsBundle = fs.readFileSync('./packages/fuels/dist/index.mjs', 'utf8');
const childPackages: string[] = [];
getListOfModules(fuelsBundle).forEach((packageName) => {
if (packageName.startsWith('@fuel-ts')) {
childPackages.push(packageName.replace('@fuel-ts/', ''));
}
});

// scans a bundled file for any references to built-in Node.js modules and throws an error if it finds any
const scanBundleForNodeBuiltIns = (packageName: string) => {
const bundle = fs.readFileSync(`./packages/${packageName}/dist/index.mjs`, 'utf8');
const listOfModules = getListOfModules(bundle);
listOfModules.forEach((moduleName) => {
if (NODE_BUILTIN_MODULES.includes(moduleName)) {
throw new Error(
`The package ${packageName} references the built-in Node.js module ${moduleName}, which is may cause problems in the browser.`
);
}
});
};

childPackages.forEach((packageName) => {
scanBundleForNodeBuiltIns(packageName);
});
});
});
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.