Giter VIP home page Giter VIP logo

has's Introduction

The @dojo/has repository has been deprecated and merged into @dojo/framework

You can read more about this change on our blog. We will continue providing patches for has and other Dojo 2 repositories, and a CLI migration tool is available to aid in migrating projects from v2 to v3.


@dojo/has

Build Status codecov npm version

A feature detection library.

This package provides an API for expressing feature detection to allow developers to branch code based upon the detected features. The features can also be asserted statically, thereby allowing integration into a build optimization tool that can be used to create "dead" code branches which can be elided during a build step. The has module is also capable of allowing conditional loading of modules with certain loaders.

Usage

To use @dojo/has, simply install the package:

npm install @dojo/has

and import it into your application,

import has from '@dojo/has';

if (has('some-feature')) {
	/* use some feature */
}

Features

Feature Branching

The most common use case is branching in code based upon a feature flag. has() essentially manages feature flags, returning either a truthy value if the feature is present or a falsey value if the feature isn't present. The has() module is the default export of the main module of the package:

For example:

import has from '@dojo/has';

if (has('host-browser')) {
	/* Browser Related Code */
}
else {
	/* Non-Browser Related Code */
}

has() can be used in any conditional expression, like a ternary operator:

function getArrayOrString(): number[] | string {
	return has('some-feature') ? [ 1, 2, 3 ] : '[ 1, 2, 3 ]';
}

Included Features

Other Dojo 2 packages leverage the has() package to express features that are then used within the package. Because of this, there are very few features expressed in this foundational package. The intention is that this package is used to enable a developer to express other features. The flags though that are included in this package are:

Feature Flag Description
debug Provides a way to create a code path for code that is only usable when debugging or providing enhanced diagnostics that are not desired in a production build. Defaults to true but should be configured statically as false in production builds.
host-browser Determines if the current environment contains a window and document object in the global context, therefore it is generally safe to assume the code is running in a browser environment.
host-node Attempts to detect if the environment appears to be a node environment.

Adding a Feature Test/Feature Detection

The main module of the package exports a function named add() which allows the addition of features flags. The feature tests can be expressed as a static value, a function which will be lazily evaluated when the feature flag is first requested from has(), or a thenable (an object with a then method, like a Promise). Once evaluated, the value is cached.

An example of adding a feature:

import { add } from '@dojo/has';

add('my-feature', true);
add('my-lazy-feature', () => {
	/* will not be called yet */
	return true;
});
add('my-promise', new Promise((resolve) => {
	// start some asynchronous task
	resolve(true);
}));

if (has('my-lazy-feature')) { /* feature function called here */
	/* do something */
}

If a feature flag is already added, the value can be overridden by supplying true as the 3rd argument of the add() function:

add('my-feature', false, true);

The module also has an exists() function which returns true if the feature flag has been added to has():

import { exists, add } from '@dojo/has';

if (!exists('my-feature')) {
	add('my-feature', false);
}

Note that if a thenable is passed to add, exists and has will return false until the thenable is resolved and a proper value can be returned.

Conditional Module Loading

When using an AMD loader that supports loader plugins (such as @dojo/loader) then @dojo/has can be used to conditionally load modules or substitute one module for another. The module ID is specified using the plugin syntax followed by the feature flag and a ternary operator of what to do if the feature is truthy or falsey:

import foo from '@dojo/has!host-browser?foo/browser:foo/node';

/* foo is now the default export from either `foo/browser` or `foo/node` */

The module IDs supplied in the ternary operator can be specified as absolute MIDs or relative MIDs based on the loading module, just as if you were directly importing the module.

When using TypeScript, TypeScript will not be able to automatically resolve the module shape, therefore you will often have to make a global declaration of the module that is in the scope of the project where the module name matches the full MID you will be importing:

declare module '@dojo/has!host-browser?foo/browser:foo/node' {
	export * from 'foo/browser'; /* Assumes that foo/browser and foo/node have the same shape */
}

Static Features

Features can also be defined statically, before the module is loaded, in the global scope. The main use case is when it is not desirable to detect these features from the environment (because they may not be accurate, for example when using a build tool). The features can only be specified before the module is loaded for the first time and cannot be changed once the module is loaded. The values specified in the static features will always be returned from has() irrespective of how those features are subsequently defined using add(), even if an override is specified. In addition, if a value is being added via add() that is already defined as a static feature, it will still complete and not throw. If specified as a function, the function will never be invoked.

To specify the features, the global variable DojoHasEnvironment needs to be specified with a property of staticFeatures which is a simple map of the features:

window.DojoHasEnvironment = {
	staticFeatures: {
		'host-node': true,
		'host-browser': false,
		'my-feature': 2
	}
};

staticFeatures can also be specified as a function, which returns a map of the features:

window.DojoHasEnvironment = {
	staticFeatures: function () {
		return { 'host-node': true, 'host-browser': false, 'my-feature': 2 };
	}
};

This function will be run once when the module is loaded and the values returned from the function will be used as the static features.

Code Style

This repository uses prettier for code styling rules and formatting. A pre-commit hook is installed automatically and configured to run prettier against all staged files as per the configuration in the project's package.json.

An additional npm script to run prettier (with write set to true) against all src and test project files is available by running:

npm run prettier

Installation

To start working with this package, clone the repository and run npm install.

In order to build the project run grunt dev or grunt dist.

Testing

Test cases MUST be written using Intern using the Object test interface and Assert assertion interface.

90% branch coverage MUST be provided for all code submitted to this repository, as reported by istanbul’s combined coverage results for all supported platforms.

To test locally in node run:

grunt test

To test against browsers with a local selenium server run:

grunt test:local

To test against BrowserStack or Sauce Labs run:

grunt test:browserstack

or

grunt test:saucelabs

Licensing information

The original Dojo 1 has() API was based upon Peter Higgin's has.js.

© 2018 JS Foundation & contributors. New BSD license.

has's People

Contributors

agubler avatar bryanforbes avatar dylans avatar edhager avatar kitsonk avatar lzhoucs avatar maier49 avatar msssk avatar nicknisi avatar rishson avatar rorticus avatar smhigley avatar tomdye avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

has's Issues

API Doc Review for has

Review the generated API documentation for has at http://dojo.io/api/. If any of the information is not accurate, please make the necessary adjustments to the API documentation within this repo.

Unregistered has() features fail silently...

@kitsonk commented on Tue Jun 21 2016

Three challenges with the has() API:

  • If a feature is not registered, it just fails silently returning undefined. This leads to potential logic errors, assuming a feature is being properly detected when in fact it isn't. We should throw.
  • Features are case sensitive, and while by convention, we keep labels all lower case, consumers of the API may unintentionally make mistakes... if we coerced the features to lowercase, it would avoid mistakes and errors.
  • When add is passed with override = false it simply returns a false if the add fails because it is already present. The way we currently consume the API, we don't handle those failures and it is likely that those downstream using the API will do the same. We should consider throwing when the override fails, as it is clearly again something someone should be explicit about, instead of unintentional behaviour.

@matt-gadd commented on Wed Jun 22 2016

  • this really does seem like a trap given you can have a test result of undefined, and also in the majority of cases the user has likely written a loose truthy/falsey if anyway. I think we should definitely throw, especially given we have an exists api anyway for what would be the only useful side effect of returning undefined currently.

  • although agreed it probably would make things safer, it does seem a bit opinion'y. would you co-erce to lowercase on both the set (adding) and getting? ie:
    has.add('aBc', true)
    has('ABC')

    are equivalent?

  • I don't have a problem with the current behaviour of add because the return values true (added) and false (not added) aren't overloaded like the first bullet point. It seems like we added the overwrite and no-overwrite paths for a reason, so I don't think i'd expect to get an error here?


@kitsonk commented on Wed Jun 22 2016

it does seem a bit opinion'y.

While I am on border on this upon further reflection, I found some issues when breaking out the shims where the feature was added as abc but was tested as abC. If we throw on missing features, then I think largely it negates this and while we might have our opinion about the feature naming conventions, we wouldn't have to enforce that on others.

It seems like we added the overwrite and no-overwrite paths for a reason, so I don't think i'd expect to get an error here?

But we have an exists() API... so in theory, if you were in doubt of it being registered. With that available, I cannot see the use case of just performing an add() without override "just in case"... clearly that is an error in the code and it would be better to throw, so that the developer addresses the logic error and doesn't depend upon things they think they have accomplished.


@matt-gadd commented on Wed Jun 22 2016

While I am on border on this upon further reflection, I found some issues when breaking out the shims where the feature was added as abc but was tested as abC. If we throw on missing features, then I think largely it negates this and while we might have our opinion about the feature naming conventions, we wouldn't have to enforce that on others.

the fact we've actually managed to fail at that in our own code does give more credence to the argument :).

But we have an exists() API... so in theory, if you were in doubt of it being registered. With that available, I cannot see the use case of just performing an add() without override "just in case"... clearly that is an error in the code and it would be better to throw, so that the developer addresses the logic error and doesn't depend upon things they think they have accomplished.

you could argue that the add function is already doing too much by having different behaviour based on a flag in the first place, and we should just pick the most common one as the api (both scenarios are possible via the exists api anyway).

Proper sniffing of browser features

Bug

We have run into several challenges of feature detecting in an environment where browser features aren't always part of the "global" context. This is likely to result in several sub issues, so this will be created as an epic.

In addition, ECMAScript is likely to introduce a global reference, which like the NodeJS global is the best way to operate the "global" scope when running in a strict mode. (See dojo/shim#80)

There are several points we need to make sure that our code is "safe":

  • Feature tests (and even code using browser features) should reference the those features via window or global.window with an appropriate guard against window being undefined.
  • We need to type our global export, to include global.window as an optional property of type Window.
  • There maybe further regressions of any dependency on global when the order of precedence is changed in dojo/shim#80, but global being first is the most logical.

What happened to "host-rhino" ?

Version and Environment:

Dojo2 version: recent

Environment(s): all (detection)

Code

if (has('host-node') || has('host-rhino')) {}

Expected behavior:

In dojo1 'host-rhino' was defined in has

Actual behavior:

TypeError: Attempt to detect unregistered has feature "host-rhino"

Allow Thenables as return values for feature tests

Currently all features tests must be async. It would be nice if has supported the ability to add a feature test that sets the result based on the resolution of a Thenable.

There are considerations of if a feature is being defined async, and that the feature is requested sync before the detection is resolved, could cause some false issues. has() could be modified to not error if a unresolved Thenable feature is being detected and exists() could return false.

The ability to return Promises and the like from the API would likely only complicate things and "bloat" the dependencies of has though, just handling Thenables would require no additional dependencies, as it would simply detect the Thenable and chain on the setting of the result.

Standardise Naming of has flags

@kitsonk commented on Mon Jul 27 2015

We should standardise has flag naming to be more expressive. The ideally should refer to the macro standard of which they were introduced in.

For example:

  • html5- for items that were introduced as part of HTML5
  • dom3- for items that were introduced as W3C DOM Level 3 Specification
  • es5- for items that were introduced as part of ECMAScript 5
  • es6- for items that were introduced as part of ECMAScript 6/2015
  • es7- for items that are likely to be ratified as part of ECMAScript 7 (e.g. Object.observe)
  • css3- for items that were introduced as part of CSS3

README suggestions

  1. make certain branches of code "dead" which can be elided

    Would be clearer as

    create "dead" code branches which can be elided

  2. The most common feature is branching in code based upon a feature flag

    Usage of features is a little ambigious, perhaps would be better written as

    The most common use case is...

  3. Provides a way to code path

    Should be

    Provides a way to create a code path

  4. just as if you were just directly importing

    Should be

    just as if you were directly importing

  5. how those features a subsequently

    Should be

    how those features are subsequently

  6. like when using a build tool
    Might be better phrased as
    for example when using a build tool

  7. it will still complete and not throw although if specified as function, the function will never be invoked.

    Could be cleaned up to say

    it will still complete and not throw. If specified as a function, the function will never be invoked.

  8. returns an map

    Should be

    returns a map

  9. projects package.json

    Should be

    project's package.json

Asserting Static Features

We need a way to assert feature flags that can be determined when the module loads.

It is proposed that we enable this by specifying a "auto-magic" global that then determines these features upon module load:

type FeatureTestResult = boolean | string | number;

interface StaticHasFeatures {
  [ feature: string ]: FeatureTestResult;
}

interface DojoHasEnvironment {
    staticFeatures: StaticHasFeatures | () => StaticHasFeatures;
}

declare const DojoHasEnvironment: DojoHasEnvironment;

Any feature flags that are specified in this fashion will be used, irrespective of their definition later on (even with the override argument being specified on add()).

Once the module has loaded, it will delete the DojoHasEnvironment.

Define `debug` feature

We should define a debug feature, which is set to true, but can be set as a static feature to false. Any code that is "debug" code can then code pathed to only be invoked if has('debug').

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.