Giter Site home page Giter Site logo

amd-feature's Introduction

About

AMD-feature is a plugin for AMD loaders. It allows for easy cross-target development and code management.

The Idea

โ€ฆ behind this is, that your project's code consists of different features, and that some features might have different implementations. Very probably, these features are already organized as AMD modules.

AMD-feature allows you to list those features in an implementation map. And if there are multiple implementations for a given feature, you can provide a method for each implementation in this map, that decides whether a specific implementation should be used to satisfy the requirement for a certain feature.

Benefits

Using AMD-feature will ease working with different implementations a lot. No more code-branching inside of a module, but nicely organized files instead.

It allows you to dynamically load the right implementation for a given feature at runtime, as well as creating a specific build just for one platform.

AMD-feature is designed that it also allows to create a "dynamic" build. That is, to create a build that contains the implementation map as well as all possible implementations for a given feature. This is needed if you deploy to an unknown target and still want to keep bandwidth usage down.

How does it work?

The loader plugin requires an implementation map. This map is an object, with the names of your features as keys. The respective values can either be a simple string in case there is only one implementation of a feature, or an Object or an Array of Objects defining each of the possible implementations. Each of these objects must provide two keys:

  1. isAvailable - determines whether the implementation is currently suitable for the given feature. Can be a function that must return a boolean, or any other value that is interpreted as boolean value. The name of the feature will be passed as parameter into the isAvailable() function.
  2. implementation - determines the file (resource) that implements the feature. Can be a string which contains the path to the file (and maybe the loader plugin that should be used, for example, json!path/to/data.json), or a function that returns the path. The name of the feature will be passed as parameter into the implementation() function.
    It is possible to use the module property instead of the implementation property. The value of the module property defines implementation of the feature (see Direct Loading for details).

Example 1

You are developing an app for both Android and iOS, and you have a dropdown list in your app that works different for both platforms. You could throw all the code for both targets into one big, un-maintainable file. Or, you could separate them: because these are two implementations of one feature.

Example 2

You are building a web-app and your targets are different browsers with different capabilities. Again, you wouldn't want to put the code for all browsers in one file, but instead separate code for different targets into different implementations and use has.js to do runtime feature detection to load the best implementation for the current browser.

Example 3

You're building an app that has different capabilities in the free and premium version. So you can use the dynamic implementation map to easily switch between versions during development and use a specific map to build the code to deploy.

Usage

I've made up a really silly example that you can check out in the examples directory. It covers the whole thing, and you can nicely see what happens. However, here's a step-by-step guide:

Given the first example:

Let's call the feature 'dropdown'. So you create two implementations of this:

dropdown-ios.js
define(function() {
	var dropdown = function(){
		// ios specific dropdown here
	};
	
	return dropdown;
});

and:

dropdown-android.js
define(function() {
	var dropdown = function(){
		// android specific dropdown here
	};
	
	return dropdown;
});

Your implementation map would then look like this:

dynamic.js
define({
	'dropdown': [
		{
			isAvailable: function(){
				// test if we are on iOS
				return iOS ? true : false;
			},
			
			implementation: 'src/dropdown-ios'
		},
		{
			// if we end up here, we're not on iOS,
			// so we can just use true.
			isAvailable: true,
			
			implementation: 'src/dropdown-android'
		}
	]
});

A more concise alternative is the following:

dynamic-concise.js
define({
	'dropdown': {
		implementation: function(){
			return iOS ? 'src/dropdown-ios' : 'src/dropdown-android';
		}
	}
});

In your code, you would load your feature like this:

define(['feature!dropdown'], function(dropdown){
	
	// The variable 'dropdown' now contains
	// the right implementation - no matter
	// what platform the code is executed on,
	// and you can just do this:
	var myDropdown = new dropdown();
});

When you want to deploy your code for a specific platform, e.g. for Android, you create a so-called 'specific implementation map':

android.js
define({
	'dropdown': 'src/dropdown-android'
});

When you feed this implementation map to AMD-feature, it will of course load only the Android implementation of the dropdown feature.

Now whats left is to tell your AMD loader and the feature plugin what implementation map to use. For RequireJS, you do it in the config object:

<script>
	var require = {
		baseUrl: './',
		paths: {
			// we need to point the plugin to the 
			// implementation map that it should use:
			'implementations': 'path/to/impl-map'
		}
	};
</script>
<script type="text/javascript" src="require.js"></script>

Direct Loading

In some cases, the implementation of a feature doesn't require an own file, e.g. if your feature has a native implementation or if it is a plain object.

You can then use the module property in the implementation map instead of the implementation property to tell the plugin that no file needs to be loaded. If the value of the module property is not a function, the value will be used as implementation of the feature. If the value of the module property is a function, its return value will be taken to satisfy the request for the feature. The name of the feature will be passed as parameter into the module() function.

dynamic.js
define({
	'JSON': [
		{
			isAvailable: function(){
				// test if native JSON is available
				return typeof JSON != 'undefined';
			},

			// if so, directly use the JSON object as module
			module: function() {
			  return JSON;
			}
		},
		{
			// This is the fallback
			isAvailable: true,

			// return the path to some JSON implementation
			implementation: 'src/json-impl'
		}
	]
});

To use direct loading in a specific implementation map, pass an object containing the module property instead of a string:

native-json.js
define({

  JSON: {
    module: function () {
      return JSON;
    }
  }

});

See the code in the examples/direct-load directory for an example of this.

Builds

Creating builds follows the same idea as deploying: In your build profile, just point to the right implementation map. If you want a dynamic build that includes all possible implementations for a given feature, point to the dynamic implementation map. If you want a build for a specific target, that only contains the implementation for the target, point to the specific implementation map.

See the build-profile-*.js files in the example directory for example build profiles.

detect.js

Just a little helper: If you require the detect.js module, it will generate and return a map with the currently used implementations. See the detect.html file in the examples directory to see how it works.

amd-feature's People

Contributors

jensarps avatar lbernau avatar tbranyen avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

amd-feature's Issues

feature.js 404 (Not Found)

After building my project with r.js, I get 404 (Not Found) for feature.js.

The only way that I have seen to fix this is to explicitly include feature as a dependency before calling feature!. This isn't suitable for most usages of feature!.

There doesn't seem to be anything in the optimization documentation explaining how to include a loader into the built assets. Perhaps something else is wrong?

Build related error

I haven't dug into the issue yet as I don't know much about requirejs's plugin arch yet -- but I got the following error when trying to use this with r.js to build:

Loader plugin did not call the load callback in the build: feature

This is when using a map that looks like this:

define({
  'core/web': [
    {
      isAvailable: function () {
        return LiftWithoutServer;
      },

      implementation: 'core/dev-web'
    },
    {
      isAvailable: function () {
        return true;
      },

      implementaiton: 'core/web'
    }
  ]
});

a require line that looks like:

var Web = require('feature!core/web'); // using commonjs style

and an app config that looks like (simplified for the purpose of removing seemingly irrelevant parts of config):

requirejs.config({
  baseUrl: 'scripts',
  paths: {
    'feature'               : 'vendor/feature-nover',  // i renamed 'feature.js' to 'feature-nover.js'
    'implementations'       : 'core/feature-map',
  }
});

I imagine this is just something I'm doing wrong but a pointer in the readme (maybe an FAQ or pitfalls section or something?) would have allowed me to skip over learning more about requirejs plugins to figure out whats going on.

Thanks!

A typo in README.md

There is a typo in README.md inside Direct Loading section (actually it originates from my pull request).
Insead of

The name of the feature will be passed as parameter into the implementation() function.

should be

The name of the feature will be passed as parameter into the module() function.

Also I suppose that is better changing part of the example inside the section from

isAvailable: function(){
      // This is the fallback
      return true;
},

to

// This is the fallback
isAvailable: true,

example request: loading a JSON poly-fill only when required

Love the plugin so far, but I am not quite sure how to use it to conditionally load a JSON poly-fill only in older browsers that don't have a built-in JSON global object.

Any hints and tips in getting this plugin to:

  • do nothing sometimes
  • return a pre-existing browser global as a module sometimes

Cheers!

Issues creating a polyfill for matchMedia

Hey,

I was happy to find that you'd included functionality recently to allow for direct-loading of Javascript objects.

I took your JSON example and have tried to re-implement it but this time doing matchMedia, a native object used for media queries. It uses this polyfill as a fallback.

While it pretty much looks exactly the same as your example I can't seem to get it to work. Neither in a build or in a non-supporting browser (IE9). Yet if I use it in a supporting browser it works fine. Just for testing sake I swapped the isAvailable test to be return typeof matchMedia === 'undefined'; and it correctly loaded matchMedia.js into Chrome thinking it needed a polyfill.

I keep getting the error matchMedia is undefined on the first line of dynamic.js.

define( {
    'matchMedia': [
        {
            isAvailable: function () {
                // test if native matchMedia is available
                return typeof matchMedia != 'undefined';
            },

            // if so, directly use the matchMedia object as module
            module: matchMedia
        },
        {
            isAvailable: function () {
                // This is the fallback
                return true;
            },

            // return the path to some matchMedia implementation
            implementation: 'matchMedia'
        }
    ]
} );

Any ideas?

Update: I've also found it does the same if I swap it out for the JSON example and try it in IE7 (which has no JSON support). It tells me JSON is undefined yet if I try it in IE8 and reverse the isAvailable test around, it loads the polyfill in IE8 with no problems.

Thanks,
Pat

Publish to webjars.org

Hi,

It would be great to also publish amd-feature as a maven artifact (via webjars.org).

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.