Is "browser" field of package.json useless? - javascript

I recently do some search about front-end project management and get a confusion.
I notice that there is a field "browser" in package.json, but I don't know what role it plays.
In my opinion, we use package pack tool like Rollup to build .esm.js for browser, and in browser, just import the .esm.js file by <script> tag, we can't import any npm package in browser. So, I think field "browser" is useless.
For example:
Project structure:
packageA---
|--dist
|--main.esm.js
|--main.common.js
|--main.js
|--package.json
package.json:
{
"name": "packageA",
"main": "./dist/main.common.js",
"browser": "./dist/main.esm.js"
}
// this is a js file which use packageA.
// when we use Node to execute this file, will return "./dist/main.common.js".
const p = require("packageA")
Q: in browser, how to import packageA ? and who will follow the field "browser" and return "./dist/main.esm.js" to us?
Thanks a lot.

Perhaps you'd like to read the specification of the browser
field of package.json. Npm doesn't select platform-specific files directly, rather it's a hint to the bundler (such as webpack or rollup) about which files to use depending on whether it's bundling for the browser or for something else (e.g.: for Node.js).
This way, the same code can be used between node and browser platforms. When node requires the file, the default node-specific files are used, but when the bundler bundles it, the browser-specific files are used (substituted) instead.
-- That is hardly useless! --
Examples from the spec:
alternate main - basic
"browser": "./browser/specific/main.js"
replace specific files - advanced
"browser": {
"module-a": "./shims/module-a.js",
"./server/only.js": "./shims/client-only.js"
}

Related

How can I change in Rollup.js an import module of a dependency package to a local file replacing that module?

I have a JavaScript project that must be bundled using Rollup.js which depends on a package A which in turn depends on a package B:
"mypackage" ---import--> "A" ----import----> "B"
Let's say that my package import a function "connect" from package A, which in turn import a "connect" function exported by the module B. Something like:
//mypackage index.js
import { connect } from 'A'
//A index.js
import { connect } from 'B'
//B index.js
export function connect() {}
Since my package requires a bundled version of the package B (let's say "B.bundle.js"), how can i configure Rollup.js in order to replace for each dependency of my project requiring B (A in this case) to use my local bundled version (i.e. B.bundle.js, which of course exports the "connect" function too)?
When Rollup.js creates the bundled version of my project i would like to achieve something like the following:
//A index.js after being processed by Rollup
import { connect } from './src/B.bundle.js'
Is something like this possible with Rollup or with a plugin? Sorry for the question, but I'm new to rollup and bundling in general.
I solved this issue using some configuration of my package package.json and the rollup plugin #rollup/plugin-node-resolve.
In the package.json of my package I inserted the browser option that specifies how modules should be resolved when my package is used in the browser context. From the npm doc on the browser option of the package.json:
If your module is meant to be used client-side the browser field should be used instead of the main field. This is helpful to hint users that it might rely on primitives that aren't available in Node.js modules. (e.g. window)
So considering the example provided in the original question the npm package contains something like this:
{
"name": "mypackage",
"version": "1.5.1",
"description": "A brand new package",
"main": "index.js",
"browser": {
"B": "./B.bundle.js"
},
}
This means that when mypackage is used in the context of the browser the module B import will load from the file located in "./B.bundle.js".
Now, with rollup i need to specify that the bundle i am creating is intended for browser context. The plugin that handle imports of node modules is #rollup/plugin-node-resolve. There is an option is this plugin that specify that the context is browser. From the plugin documentation about the option browser:
If true, instructs the plugin to use the browser module resolutions in package.json and adds 'browser' to exportConditions if it is not present so browser conditionals in exports are applied. If false, any browser properties in package files will be ignored. Alternatively, a value of 'browser' can be added to both the mainFields and exportConditions options, however this option takes precedence over mainFields.
So that in my rollup config file i have something like:
// rollup.config.js
import commonjs from "#rollup/plugin-commonjs";
import resolve from "#rollup/plugin-node-resolve";
import nodePolyfills from "rollup-plugin-node-polyfills";
export default {
input: "index.js",
output: {
file: "dist/mypackage.bundle.js",
format: "es",
},
plugins: [
nodePolyfills(),
resolve({
browser: true, //<- tells to rollup to use browser module resolution
}),
commonjs(),
],
},
The answer of #vrajpaljhala seems feasable for the purpose but imho #rollup/plugin-replace its kind of too tricky and raw approach to the problem because it involves direct replacmeent of strings surrounded by "". We may face some very "hard to discover" errors if the package name we are going to replace is a common word that can be present as a string in the code but not in the context of an import statement.
We had the exact same requirement and we resolved it using #rollup/plugin-replace package.
Basically, our project uses a monorepo structure but we are not using any tools like learna or workspace for managing it. So we have two packages which are interdependent. Our ui-kit uses icons package for different icons and icons package uses the Icon component from ui-kit package. We just replaced the imports for ui-kit with local path with the following:
import replace from '#rollup/plugin-replace';
...
...
export default {
...
...
plugins: [
replace({
'ui-kit': JSON.stringify('../../../../src/components/Icon'),
delimiters: ['"', '"'],
}),
]
...
...
}

How to get the typescript compiler to find bokeh's "*d.ts" files

I have recently moved from Bokeh's nice, inline extension framework to their npm based out of line build system, I am trying go get my extension to build, but Bokeh installs all of the TypeScript *.ts.d in a separate tree, for example:
bash$ find node_modules -name 'serialization.*'
node_modules/#bokeh/bokehjs/build/js/types/core/util/serialization.d.ts
node_modules/#bokeh/bokehjs/build/js/lib/core/util/serialization.js
bash$
In the inline build system this file is imported like import { is_NDArray_ref, decode_NDArray } from "core/util/serialization".
Is there a way using tsconfig.json options to allow my extension files to continue to use core/util/serialization for import and find both the JavaScript and the TypeScript description with Bokeh's node installation layout.
The only dependency in my package.json is:
"dependencies": {
"#bokeh/bokehjs": "^2.3.1"
},
Even if I change the import paths to use .../lib/..., TypeScript does not find the *.d.ts files, and if I change the import path to use .../types/... if finds the types and compiles but the linker fails to find the JavaScript. I used Bokeh's bokeh init to create my build sandbox... Thanks for any advice...
I found the answer in a Bokeh ticket. In a nutshell, adding:
"paths": {
"#bokehjs/*": [
"./node_modules/#bokeh/bokehjs/build/js/lib/*",
"./node_modules/#bokeh/bokehjs/build/js/types/*"
]
}
to the tsconfig.json file in the compilerOptions property allows imports like:
import { is_NDArray_ref, decode_NDArray } from "#bokehjs/core/util/serialization"
which seems very reasonable.

How do I make my declaration files for the typescript library available for install at #types/mylibrary

I have created my Library and published it to npm and works fine. The problem is I can't get the intellisence on my editor(vs code). Putting the declarations files manually on node_modules/#types
works perfectly. I want my types to be available for download on npm using npm i #types/mylibrary.
On my search I came accross this https://github.com/DefinitelyTyped/DefinitelyTyped which requires pulling all the available types and sending a PR for the new Type.
Is there any simple way of archiving the same?
I don't think that's necessary.
Does your current ts.config already produce types?
Just add types to your package.config, for instance like so:
{
"main": "dist/index.js",
"types": "dist/index.d.ts",
...
}
(assuming outDir is /dist/)

How to choose 'module' instead of 'main' file in package.json

I have created some npm modules and compile them to:
commonJS (using exports.default =) and
esm (using export default)
I set up my package.json like so:
main: "index.cjs.js",
module: "index.esm.js"
When I npm install the package and I simple import it like:
import myPackage from 'my-package'
It will automatically choose the main file, not the module.
My question:
Is there a way to import the module file instead when doing import myPackage from 'my-package' in a JavaScript file?
Why I choose the commonJS file for "main":
I noticed that using Node, importing an esm file is not possible because of export default, it has to be commonJS. I have some simple helper JS functions like this and this, and I would want them to be usable to the widest audience. That's why I chose cjs for the "main" path in package.json.
Why I define a separate "module" in package.json:
Lots of famous libraries like Vue.js are already doing this. See further information on this Stackoverflow thread:
What is the "module" package.json field for?
Why do I want to be able to import the "module" file instead of the "main" file:
There is currently a bug in Rollup where it will not properly show JSDoc comments when coding after having imported a cjs file, but it does work when importing a es file.
The workaround I want to avoid:
Just set "main" to the esm file in package.json, right? But then all users who are using my packages in Node apps will not be able to use it anymore...
→ I'm really confused about all this as well, but I think I did enough research to make sense of all it. That being said, if anyone knows a better approach or any other advice, please do tell me in the comments down below!!
Just don't use extension for main file and have es6 and CommonJS version as two separate files with the same name and in the same directory, but with different extension, so:
index.js // transpiled CommonJS code for old nodejs
index.mjs // es6 module syntax
and in package.json:
{
"main": "index"
}
If node is launched with --experimental-modules flag, it would use *.mjs file, otherwise *.js.
Nodejs does not support "module" but does support the newer "exports" spec.
https://nodejs.org/api/packages.html#exports
https://github.com/nodejs/node/blob/v16.14.0/lib/internal/modules/esm/resolve.js#L910
"exports": {
"import": "./main-module.js",
"require": "./main-require.cjs"
},

Compiling typescript path aliases to relative paths for NPM publishing?

I have a typescript project that uses paths for imports. For example:
"paths": {
"#example/*": ["./src/*"],
}
Thus the project can import files directly from using statement like:
import { foo } from "#example/boo/foo";
For publishing to NPM I have I'm compiling the typescript files and then copying the result to a dist folder. Thus all the *.d.ts and corresponding *.js files are in the dist folder. I also copy package.json to the dist folder.
I now test this by generation a new typescript project and then run npm i -S ../example/dist, in order to install the project and attempt to run some of the compiled typescript code.
However the relative imports no longer work. For example if boo.ts depends on foo.ts it will say that it can't resolve foo.ts.
When I look at the *.d.ts files they contain the same paths that were used the source code before it was compiled. Is it possible to turn these into relative paths?
Update
I looks as if generating relative paths for Node is something Typescript does not perform automatically. If you would like this feature, as I would, please provide feedback on this bug report.
As a brief follow-up to arhnee's suggestion, it seems that as of Aug 2020, Microsoft still refuses to implement custom transformers for whatever reason, so these modules remain relevant.
So to future readers, here's how you can actually compile TS path aliases to relative paths. ttypescript is merely a transformer framework that requires a "path transformer" in order to actually convert the TS path aliases. Thus you will need to install both ttypescript and typescript-transform-paths.
npm i --save ttypescript typescript-transform-paths
Then, it's easy as just specifying usage by adding the following property to the compilerOptions object in tsconfig.json:
"plugins": [
{ "transform": "typescript-transform-paths" }
]
And finally, run ttsc instead of tsc.
There is a project called ttypescript that you can use for this. If you use it with the module typescript-transform-paths I beleive it will acheive what you want.

Categories

Resources