├── README.md └── research ├── dependencies.md ├── module.md ├── name.md └── package-json.md /README.md: -------------------------------------------------------------------------------- 1 | # package.json research 2 | 3 | ## Goal 4 | 5 | A research analysis on the usage of `package.json` throughout the entire JavaScript ecosystem. 6 | 7 | ## Research/Contribution Process 8 | 9 | > Contributions welcome! Reach out in the [Slack channel][slack-channel] for help! 10 | 11 | Within the `research` directory are a collection of markdown files each containing the analysis for one or more top-level `package.json` properties. The file, [package-json.md][package-json], contains a property research index. 12 | 13 | Research should primarily focus on a property's existing rule-set and use-cases. Details should be presented in such a way to not infer bias to any single tool; however, since many tools share the same behavior, those details can be presented as fact. 14 | 15 | For example, the `dependencies` property has a number of ways a dependency can be specified. From using SemVer, workspace syntax, or even specifying a git link, many package manager tools will resolve the value in a similar way (though the lockfile output may be unique). 16 | 17 | Research should accurately and clearly declare when it is presenting tool-specific behavior about a certain package.json property. 18 | 19 | ### Contributing New Research: 20 | - Make sure that an analysis has not already been conducted on the property or properties you are analyzing. 21 | - Check the index in [package-json.md][package-json]. 22 | - Check in progress pull requests. 23 | - If a PR seems stale, politely reach out to the author on the PR or within the [Slack channel][slack-channel]. 24 | - Open a draft PR including the following details: 25 | - Create a new file for your research in the `research` directory. 26 | - The file title should be short and representative of the property or properties being analyzed. 27 | - The file title should use [kebab-case](https://www.freecodecamp.org/news/programming-naming-conventions-explained/#what-is-kebab-case). 28 | - Add the property or properties and the new file to the index in [package-json.md][package-json]. 29 | - Use reference links so they can be reused throughout the file reliably. 30 | - The PR title should follow the format: `Property: ` or `Properties: , `. 31 | - The PR should have the label `research` (if you cannot add labels a contributor will) 32 | - Once your research is complete, mark the PR as "ready to review". 33 | 34 | ### Contributing to Existing Research: 35 | - Open a PR with the title `[research] Edit: `. 36 | 37 | [package-json]: <./research/package-json.md> 38 | [slack-channel]: 39 | -------------------------------------------------------------------------------- /research/dependencies.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | Analysis of the following properties: 4 | - `"dependencies"` 5 | - `"devDependencies"` 6 | - `"peerDependencies"` 7 | 8 | ## Analysis 9 | 10 | > TODO -------------------------------------------------------------------------------- /research/module.md: -------------------------------------------------------------------------------- 1 | # module 2 | 3 | This describes the main entry point for ESM packages. 4 | Historically, `main` has been the *only* way to declare a package's modules to Node.js. Node.js more recently supports the more flexible `exports`. 5 | In the meantime, bundlers converged around adding top-level field, with the most common being `module`, though this was never adopted by `node`. 6 | 7 | Where used by bundlers `module` is used in parallel with top-level `main`, et al. These are collectively referred to as `mainFields`.[^2] 8 | Common values include: `main`, `browser`, `source`, `jsnext:main`, `jsnext` [^4][^5] 9 | 10 | The Node docs [mention this field](https://github.com/nodejs/node/blob/9edf4a0856681a7665bd9dcf2ca7cac252784b98/doc/api/packages.md?plain=1#L889-L893), saying that `node` does not support it. 11 | 12 | ## Tooling 13 | 14 | - This field is [described on SchemaStore](https://github.com/SchemaStore/schemastore/blob/c668421350214c96b249771ca37678b8c7877584/src/schemas/json/package.json#L755-L758) as "An ECMAScript module ID that is the primary entry point to your program." 15 | - This field is included in DefinitelyTyped[^3] 16 | - This field is unused by Node.js 20.17.0, Bun 1.1.25, and Deno 1.46.1. 17 | - This field is unused by npm. 18 | - npm [documents the `browser` top-level property](https://github.com/npm/cli/blob/e674987c8dc5634c3b2a8a4d0f024d15041ba23c/docs/lib/content/configuring-npm/package-json.md?plain=1#L354-L359). This field was [not used by npm](https://github.com/npm/npm/pull/18382#pullrequestreview-101752559) at the time and seems to only be documented because it's widely supported by bundler tools. 19 | - This field is supported but documented as deprecated by yarn, recommending to use the top-level `exports` field instead [^1] 20 | - Supported universally by bundlers: 21 | - [Parcel](https://github.com/parcel-bundler/parcel/blob/0e08d8c69243e104aaba52c2393d528bb6872450/packages/utils/node-resolver-core/src/Wrapper.js#L796-L818) main fields: `main`, `module`, `source`, `browser`, `types` 22 | - [Rollup via @rollup/plugin-node-resolve](https://github.com/rollup/plugins/blob/8550c4b1925b246adbd3af48ed0e5f74f822c951/packages/node-resolve/README.md?plain=1#L130) main fields: `browser`, `jsnext:main`, `module`, `main` 23 | - [Vite](https://github.com/vitejs/vite/blob/561b940f6f963fbb78058a6e23b4adad53a2edb9/packages/vite/src/node/constants.ts#L11-L16): `browser`, `module`, `jsnext:main`, `jsnext` 24 | - [Esbuild](https://github.com/evanw/esbuild/blob/332727499e62315cff4ecaff9fa8b86336555e46/internal/resolver/resolver.go#L23-L55): `browser`, `module`, `main` 25 | - [Webpack](https://webpack.js.org/configuration/resolve/#resolvemainfields): `browser`, `module`, `main` 26 | 27 | 28 | [^1]: https://yarnpkg.com/configuration/manifest#module 29 | [^2]: [rollup](https://github.com/rollup/plugins/blob/8550c4b1925b246adbd3af48ed0e5f74f822c951/packages/node-resolve/README.md?plain=1#L126-L132), [webpack](https://github.com/webpack/webpack/blob/09543e7d8e0e7dd1703207193bcc3c3252874636/declarations/WebpackOptions.d.ts#L1619-L1622), [vite](https://github.com/vitejs/vite/blob/561b940f6f963fbb78058a6e23b4adad53a2edb9/docs/config/shared-options.md?plain=1#L142-L147) 30 | [^3]: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/78c20f65b205e1c6af590a685921aeb796747ee4/types/npmcli__package-json/index.d.ts#L365-L369 31 | [^4]: main fields according to [Parcel]() https://github.com/parcel-bundler/parcel/blob/d517132890318586c0ccd45905dc66bf52425844/src/Resolver.js#L269-L279 https://github.com/vitejs/vite/blob/561b940f6f963fbb78058a6e23b4adad53a2edb9/packages/vite/src/node/constants.ts#L11-L16 32 | [^5]: a good writeup with some other main fields can be found at https://github.com/stereobooster/package.json 33 | -------------------------------------------------------------------------------- /research/name.md: -------------------------------------------------------------------------------- 1 | # Name 2 | 3 | Analysis of the `name` property 4 | 5 | ## Analysis 6 | 7 | - The `name` and `version` together form an identifier that is assumed to be completely unique[1] 8 | - In practice, the unique identifier for a package is also comprised of its registry. It is valid to have two packages with the same `name` and `version`, except one must be aliased so that the package manager can correctly resolve its registry value. 9 | - The `name` property is optional if the package will not be published.[1] 10 | - Generally, it is also best practice to specify the [`"private": true`](./private.md) property so that the package is not accidentally published. 11 | - > [TODO: Is `name` actually optional?](https://github.com/openjs-foundation/package-json-research/issues/9) 12 | - It must be less than or equal to 214 characters (including scope)[1] 13 | - The names of scoped packages can begin with a dot or an underscore. This is not permitted without a scope.[1] 14 | - It cannot contain uppercase letters[1] 15 | - Historically, uppercase characters were allowed, but npm enforces all new packages are lowercase only[1] 16 | - It must contain only URL-safe characters[1] 17 | - > [TODO: What are "URL-safe characters"?](https://github.com/openjs-foundation/package-json-research/issues/4) 18 | > - Is this based on the WHATWG URL specification or something else? 19 | > - Relevant stackoverflow: https://stackoverflow.com/questions/695438/what-are-the-safe-characters-for-making-urls 20 | > - Is the validation for this in the npm cli open-source? 21 | 22 | ### Scope 23 | 24 | - While all packages have a name, some also have a scope.[2] 25 | - A scope follows the same rules as a name (URL-safe characters, no leading dots or underscores).[2] 26 | - When used in package names, scopes are preceded by an `@` and followed by a `/`. 27 | - Example: `@scope/pkg`[2] 28 | 29 | ## Platform Specific Behavior 30 | 31 | ### npm 32 | 33 | - Installing a scoped package saves it to the scoped folder by the same name (including the `@`, excluding the `/`).[2] 34 | - Example: Both `@scope/pkg-1` and `@scope/pkg-2` would be installed to the `node_modules/@scope` directory. 35 | - If the `@` is omitted, npm will automatically attempt to install the package from GitHub.[3] 36 | - All npm users have their username reserved as a scope.[2] 37 | - Example: User `jack123` has the scope `@jack123` reserved specifically for themselves. 38 | - Similarly, npm requires other scopes (non-username) to be registered first as an npm organization, then permitted users of that organization can publish to that scope.[2] 39 | - Scopes can be associated with a registry.[2] 40 | - `npm config set :registry ` or `npm login --registry= --scope=` (`` must include the `@` symbol). 41 | - One scope must only ever point to one registry. 42 | - One registry can host multiple scopes. 43 | 44 | ### Node.js 45 | 46 | - Node.js does not require a `name` field. 47 | - Importing a package from another package is based on the path it is installed at in `node_modules`, NOT the `name` field in `package.json`. 48 | - If a package is named `foo` but is installed or symlinked from `node_modules/bar`, it is found as `import('bar')` 49 | - If a package is installed under two paths (e.g. with `npm install cowsay1@npm:cowsay cowsay2@npm:cowsay`) then importing either will result in *different* module objects. If the package is instead *linked* from two paths in `node_modules` (e.g. with `pnpm install cowsay1@npm:cowsay cowsay2@npm:cowsay`), then importing either will result in the *same* module object. 50 | - The `name` field *can* be used within a package to refer to the same package[4]. That is, if you have a `package.json` file with `"name":"foo"`, then inside that directory, `import("foo")` or `import("foo/bar")` will look at that directory before consulting `node_modules`. 51 | - In order to enable self-imports by name, `package.json` must define an `exports` field. 52 | - Neither a `main` field, nor a package-relative subpath will suffice instead of `exports`. Self-imports might *seem* to work if the package happens to be installed into `node_modules`. 53 | - The scope is part of the package name for purposes of self-reference. In a package with `"name": "@scope/foo"`, you must `import('@scope/foo')`, not e.g. `import('foo')`. 54 | 55 | ## Sources 56 | 57 | 1. [npm `name` field documentation][1] 58 | 2. [npm _Scope_ concept documentation][2] 59 | 3. [npm `install` command documentation][3] 60 | 4. [node `packages` documentation][4] 61 | 62 | [1]: 63 | [2]: 64 | [3]: 65 | [4]: 66 | -------------------------------------------------------------------------------- /research/package-json.md: -------------------------------------------------------------------------------- 1 | # package.json Analysis 2 | 3 | ## Property Research Directory 4 | - [`"dependencies"`][dependencies] 5 | - [`"devDependencies"`][dependencies] 6 | - [`"peerDependencies"`][dependencies] 7 | - [`"name"`][name] 8 | 9 | [dependencies]: <./dependencies.md> "Dependencies" 10 | [name]: <./name.md> "Name Property" --------------------------------------------------------------------------------