Order of keys in package.json exports - node.js

I believe I understand the basic functioning of the exports key in package.json files:
// package.json
{
"exports": {
".": {
// used by typescript
"types": "./file_with_type_defs.d.ts",
// used by ESM resolution
"import": "./file_to_import.mjs",
// used by CJS resolution
"require": "./file_to_require.cjs",
// used by ...others?
"default": "./file_one_more.js"
}
}
}
Question: Does the order of the "types", "import", "require", and "default" keys matter? Normally I would think no way, since JSON object keys are unordered. From json.org:
An object is an unordered set of name/value pairs. An object begins with { ...
But Typescript documentation says that "types" must come first:
Entry-point for TypeScript resolution - must occur first!
"types": "./types/index.d.ts"
and the NodeJS documentation says "default" should come last
"default" - the generic fallback that always matches. Can be a CommonJS or ES module file. This condition should always come last.
So... Does the order of the export keys matter? If not, what do the NodeJS and Typescript documentation mean when they talk about "first" and "last"?
Having swapped the order of "types" with other keys, its order seems not to matter.

Webpack, which also uses the export key explains this field as follows:
Notes about ordering
In an object where each key is a condition, order of properties is significant. Conditions are handled in the order they are specified.
Rather than see this as a plain object, consider it being like this if-else case:
let file;
if (platform_supports('types')) {
file = "./file_with_type_defs.d.ts";
} else if (platform_supports('import')) {
file = "./file_to_import.mjs";
} else if (platform_supports('require')) {
file = "./file_to_require.cjs";
} else if (true) { // default
file = "./file_one_more.js";
}
If you were to swap the order, it might be like this:
let file;
if (true) { // default
file = "./file_one_more.js";
} else if (platform_supports('types')) {
file = "./file_with_type_defs.d.ts";
} else if (platform_supports('import')) {
file = "./file_to_import.mjs";
} else if (platform_supports('require')) {
file = "./file_to_require.cjs";
}
Even though Typescript understands .d.ts files it would use file_one_more.js since it matches first.
I have tried swapping the order of "types" with other keys. It seems not to matter.
It might be that Typescript prioritizes the types condition over others. However, I'd stick with what they advise—"must occur first" is not "ought to occur first", after all.
As a side-note, historically JavaScript object keys are unordered / the order is not guaranteed. In practice, browsers did preserve the key order and this behavior was standardized in ES2015: non-integer keys are preserved in insertion order.
The JSON standard doesn't make the same promise, as there are many implementations of JSON decoders in different languages and it the predates ES2015 standard.

JSON objects are "unordered set" as "key do not have a particular order(i.e. never have to be sorted)", not as "do not have an order"
Parsed JS object DOES have an order of keys matching the orger in JSON
Depending on if the code uses for (let k in json) or if (json.types) the behaviour may be different
So by whatever reason it's recommended to order keys in the way some parsers may expect
In your case I'd recommend you to try swapping them, seeing that nothing happens, and swapping them back

Related

Can you use a Map instance as an easy-peasy store property?

ie.
const store = {
values: new Map(),
// (gross trivial accessor)
setValue: action( (state, payload) => {
state.values.set(payload.key, payload.value);
}
}
I'm curious because easy-peasy uses a Proxy on the store object (and objects nested within) so that in your action you can safely mutate the state object directly (https://easy-peasy.now.sh/docs/tutorials/primary-api.html#modifying-the-state). I don't know if this also works when using non Plain Old JavaScript Objects, such as Maps.
It looks like this is possible on certain versions, but not without first declaring support for the feature (so the code above will not work right out of the box, as of now). See here for more info: https://github.com/ctrlplusb/easy-peasy/issues/440

How to develop an Import/Export Functionality for my node application

I have a configuration application in Nodejs. It has a Component with name and uuid. A Component can have many Schemas. A Schema has a uuid, name, componentId, json. A Schema can have many Configurations. A Configuration has name, schemaId, json and uuid. A Schema can contain reference of many other Schemas in it. Now I want to create a functionality of exporting all the data from one instance of the application and import it in another. What should be the simplest way to do it? a few questions are
How to tell application what to export. for now i think there should be separate arrays for components, schemas and configurations. Like
{
components: ['id1', 'id2'],
schemas: ['s1', 's2'],
configuration: ['c1', 'c2'],
}
this data should be sent to application to return a file with all information that will later be used for importing in another instance
The real question is how should my export file look like keeping in mind that dependencies are also involved and dependencies can also overlap. for example a schema can have many other schemas referenced in its json field. eg schema1 has schema2 and schema4 as its dependencies. so there is another schema schema5 that also require schema2. so while importing we have to make sure that schema2 should be saved first before saving schema1 and schema5. how to represent such file that requires order as well as overlapped dependencies, making sure that schema2 is not saved twice while importing. json of schema1 is shown below as an example
{
"$schema": "http://json-schema.org/draft-04/schema#",
"p1": {
"$ref": "link-to-schema2"
},
"p2": {
"$ref": "link-to-schema4"
},
}
What should be the step wise sudo algorithm i should follow while importing.
This is a perfect occasion for a topological sort.
Taking away components, schemas and configurations terminology, what you have is objects (of various kinds) which depend on other objects existing first. A topological sort will create an order that has only forward dependencies (assuming you don't have circular ones, in which case it is impossible).
But the complication is that you have dependency information in a mix of directions. A component has to be created before its schema. A schema has to be created after the schemas that it depends on. It is not impossible that those schemas may belong to other components that have to be created as well.
The first step is to write a function that takes an object and returns a set of dependency relationships discoverable from the object itself. So we want dependencyRelations(object1 to give something like [[object1, object2], [object3, object1], [object1, object4]]. Where object1 depends on object2 existing. (Note, object1 will be in each pair but can be first or second.)
If every object has a method named uniqueName that uniquely identifies it then we can write a method that works something like this (apologies, all code was typed here and not tested, there are probably syntax errors but the idea is right):
function dependencyInfo (startingObject) {
const nameToObject = {};
const dependencyOf = {};
const todo = [startingObject];
const visited = {};
while (0 < todo.length) {
let obj = todo.pop();
let objName = obj.uniqueName();
if (! visited[ objName ]) {
visited[ objName ] = true;
nameToObject[objName] = obj;
dependencyRelations(obj).forEach((pair) => {
const [from, to] = pair;
// It is OK to put things in todo that are visited, we just don't process again.
todo.push(from);
todo.push(to);
if (! dependencyOf[from.uniqueName()]) {
dependencyOf[from.uniqueName()] = {}
}
dependencyOf[from.uniqueName()] = to.uniqueName();
});
}
}
return [nameToObject, dependencyOf];
}
This function will construct the dependency graph. But we still need to do a topological sort to get dependencies first.
function objectsInOrder (nameToObject, dependencyOf) {
const answer = [];
visited = {};
// Trick for a recursive function local to my environment.
let addObject = undefined;
addObject = function (objName) {
if (! visited[objName]) {
visited[objName] = true; // Only process once.
// Add dependencies
Object.keys(dependencyOf[objName]).forEach(addObject);
answer.push(nameToObject[objName]);
}
};
Object.keys(dependencyOf).forEach(addObject);
return answer;
}
And now we have an array of objects such that each depends on the previous ones only. Send that, and at the other end you just inflate each object in turn.

Terraform: can module's name be resolved dynmically?

Given a series of modules whose names follow a pattern:
module "mod-a" {
// ...
}
module "mod-b" {
// ...
}
module "mod-b" {
// ...
}
and assuming each module defines an output called my_output, can I refer to a particular module on the basis of a dynamically resolved name?
e.g.
...
// some_module = "mod-a"
some_value = module[some_module].my_output
...
The syntax above gives the error:
The "module" object cannot be accessed directly. Instead, access one of its
attributes.
Is there another way to access a module whose name is only known at runtime?
To achieve this in Terraform today (Terraform 0.12.13), you'll need to construct a suitable map explicitly as a local value, and then index that map:
locals {
modules = {
mod_a = module.mod_a
mod_b = module.mod_b
mod_c = module.mod_c
}
}
Elsewhere in the configuration you can then use an expression like local.modules[local.dynamic_module_key], selecting the desired object from the map.
Terraform requires static references to objects like this so that it can properly construct a dependency graph. In this case, Terraform infers that local.modules depends on all of the outputs from all three of these modules, and thus anything that refers to local.modules must wait until all of the outputs from all of these modules are ready to ensure that the final index operation has a complete value to work with.

Type hints of flow not stripped by babel

I have a React.JS project which uses a custom 'theme' with UI components.
This theme also provides build scripts (webpack config, babel configs, etc.).
I want to start using Flow in this project.
I installed the needed npm packages and added flow to babel's presets, then I added props = {mytestprop: string} to one of my React` classes.
Webpack compiled my code successfully, but the type hints were not stripped! Of course, the browser was not able to execute this code - when I try to run it, it raisesReferenceError: string is not defined.
The current list of presets from .babelrc is: ["es2015", "react", "stage-2", "flow"]. I'm sure that this is the actual list used by babel because if I delete any of the first 3 presets, compilation fails.
Do you have any ideas on what could lead to this behavior when stripping Flow types?
It's not that type annotations are not being stripped. It's that { mytestprop: string } is not a valid type annotation on the right-hand side of an assignment because it clashes with the syntax for defining an object.
Specifically, when Flow's parser sees the statement { mytestprop: string } it will interpret this as an attempt to create an object with a field named mytestprop with its value set to the value of the variable string, so it will leave the statement alone as it is, and you'll get the error you've seen in the browser.
The correct way to type object declarations is to type the left-hand side of the declaration.
For instance,
let myProps: { myTestProp: string } = { myTestProp: "testProp" };
if you aren't declaring your props separately, you could declare a custom type:
type myPropType = { myTestProp: string }
// ...
const myComponent = (props: myPropType) => //render your component
Since the type statement is exclusive to Flow and not a valid JavaScript statement, it will be stripped correctly.

Require.js best practise when loading different scripts depending on local conditions

For the sake of argument, say I want to load Zepto by default, but use jQuery instead for IE (all versions).
What would be a sensible way to do this when using Require.js?
There are two approaches: "proper" but long and "sleight of hand" but short.
"Proper" but long:
require.config({
paths: {
jquery:'path/to/jquery'
, zepto: 'path/to/zepto'
}
})
var iNeed = []
if (!('__proto__' in {})) {
// This is IE
iNeed.push('jquery')
} else {
// Everything else
iNeed.push('zepto')
}
require(iNeed, callback)
"Sleight of hand" but short:
var AMDConfig = {
paths: {
jquery:'path/to/zepto'
}
}
if (!('__proto__' in {})) {
// This is IE
AMDConfig.paths.jquery = 'path/to/jquery'
}
require.config(AMDConfig)
require(['jquery'], callback)
The reason the "sleight of hand" is not "proper" is that you are masking the real nature of what stands behind "jquery" As you grow your app, some jQuery plugins may come and not work over zepto, but it will not be immediately clear what the issue is.
The "proper" solution is also a problem in one respect - if you define the requirements array dynamically, the build tool like r.js will not be able to find other dependencies you put there.
Your pick..

Resources