Reputation: 565
I am building a Typescript package which should be running on both front end and back end. I am using this package to recognise if environment is browser or node: https://www.npmjs.com/package/browser-or-node The entry point is a single index.js file
I can build and publish the application with no issue however when I import it and try to run it in the browser I get the error message
Module not found: Error: Can't resolve 'fs'
This seems to come from the fact that I am importing fs which is a node method and the browser doesn't know what to do.
What is the best practices in this case to avoid this issue? If I don't import that module the library works just fine.
My package.json:
"license": "MIT",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "mocha -r ts-node/register src/**/*.spec.ts"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.6",
"@types/sinon": "^7.0.11",
"@types/sinon-chai": "^3.2.2",
"chai": "^4.2.0",
"mocha": "^6.1.4",
"sinon": "^7.3.2",
"sinon-chai": "^3.3.0",
"ts-node": "^8.1.0",
"typescript": "^3.4.4"
},
"dependencies": {
"@types/es6-promise": "^3.3.0",
"axios": "^0.18.0",
"browser-or-node": "^1.2.1",
"dotenv": "^7.0.0"
}
My tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"strict": true,
"noImplicitAny": false,
"lib": [ "es2015" ]
},
"exclude":[
"node_modules",
"./dist",
"./**/*.spec.ts",
"./test/**/*.ts"
]
}
The function that causes the issue:
import * as fs from 'fs';
export function saveTokensNode(data: any, path: string) {
try {
fs.writeFileSync(path, JSON.stringify(data))
} catch (err) {
console.error(err)
}
}
Where I am using it:
setCredentials(tokens) {
if (isBrowser) {
saveTokensStorage(tokens)
}
if (isNode) {
saveTokensNode(tokens, 'temp/tokensToStore.json')
}
}
Upvotes: 1
Views: 235
Reputation: 11824
The basic answer is that you can use the "browser" package.json field to exclude a module from browser bundles. It's supported by all major bundlers.
{
"browser": {
"fs": false
}
}
Then, in browserify, you'll get an empty object when you import fs
. I'm not sure what other bundlers do, though!
But you can actually do this slightly differently and avoid the need for a browser-or-node package. You could have two files for the node and browser implementations of token storage. For example,
// token-storage.js
import * as fs from "fs"
export function saveTokens() { /* Node implementation */ }
// token-storage.browser.js
export function saveTokens() { /* localStorage implementation */ }
You can switch between them, again using the "browser" field.
{
"browser": {
"./path/to/token-storage.js": "./path/to/token-storage.browser.js"
}
}
Then, in your module, you can do:
import { saveTokens } from "./token-storage.js"
function setCredentials (tokens) {
saveTokens(tokens, 'temp/tokensToStore.json')
}
The browser implementation could ignore the file path, or use it as a localStorage key name. It keeps the node/browser decision localised to the token-storage file and you don't have to think about it when you use it anywhere else.
Upvotes: 1