Skip to content

Instantly share code, notes, and snippets.

@luckylooke luckylooke/design.md

Last active Jun 15, 2017
Embed
What would you like to do?
Modern js library design

Modern js library design

How to design library the most efficient way?

Problem description

I have an idea about ideal lib design. But seems to be not easy to achieve it. I want to support standard ES6 module, also other common use cases supported by UMD. BUT there is one more use case I want to support and so there is where the problem begins. I want to be able to switch parts of a library to different implementation.

USE CASE 1 - ES6 module

The library is provided as pure standard ES6 module, without UMD or similar bundler specific wraps.

import MyLib from 'MyLib'

const mylib = new MyLib( {options:true} )

myLib.doSomethingAwesome( element )

USE CASE 2 - UMD

The library is provided as UMD module.

const MyLib require( 'MyLib' )

const mylib = new MyLib( {options:true} )

myLib.doSomethingAwesome( element )

or

<script src="mylib.js"></script>
const mylib = new MyLib( {options:true} )

myLib.doSomethingAwesome( element )

USE CASE 3 - switching implementation

The library is provided with switched implementation of its parts.

import MyLib from 'MyLibUnderscore'

const mylib = new MyLib( {options:true} )

myLib.doSomethingAwesome( element )
import MyLib from 'MyLibJQuey'

const mylib = new MyLib( {options:true} )

myLib.doSomethingAwesome( element )
import MyLib from 'MyLibAngular'
...
import MyLib from 'MyLibReact'
...

Implementation

mylib.js

import MyLib from '@MyLib/core'
import utils from '@MyLib/utils'
import otherPart from '@MyLib/other-part'

MyLib.use( utils ) // utils must be first if you want it to reuse in 'otherPart'
MyLib.use( otherPart )

export default MyLib

mylibUnderscore.js

import MyLib from '@MyLib/core'
import utils from '@MyLib/utilsUnderscore' // underscore implementation of utils
import otherPart from '@MyLib/other-part'

MyLib.use( utils ) // utils must be first if you want it to reuse in 'otherPart'
MyLib.use( otherPart )

export default MyLib

mylibReact.js

import MyLib from '@MyLib/core'
import utils from '@MyLib/utils'
import otherPart from '@MyLib/other-part-react' // react implementation of otherPart

MyLib.use( utils ) // utils must be first if you want it to reuse in 'otherPart'
MyLib.use( otherPart )

export default MyLib

They acts like presets, and you are also able to do custom configuration

mylibCustom.js

import MyLib from '@MyLib/core'
import utils from '@MyLib/utilsJQuery' // jQuery implementation of utils
import otherPart from '@MyLib/other-part-angular' // angular implementation of otherPart

MyLib.use( [utils, otherPart] ) // utils must be first if you want it to reuse in 'otherPart'

export default MyLib

Posible implementation of use method

use( plugins ) {

		if ( !Array.isArray( plugins ) )
			plugins = [ plugins ]

		plugins.forEach( plugin => this.using.indexOf( plugin ) > -1 ? 0 : plugin( this ) )

		return this; // optional chaining
	}

Reusing utils in otherPart

other-part.js

export default function otherPart( core ){

  const utils = core.utils
  let something = utils.giveMeSomething( 'fromThisString' )
  
  core.anotherMethod = ( element ) => utils.addClass( element, something )
}

Dynamic imports

Would be nice if we can somehow dynamicaly switch implementation in imports. Imports are nicely declarative, so you see listed dependencies on the top of the file.

other-part-dynamic.js ( imaginary implementation )

import { giveMeSomething, addClass } from '@MyLib/utils' + env.utils_implementation

export default function otherPart( core ){
  
  let something = giveMeSomething( 'fromThisString' )
  
  core.anotherMethod = ( element ) => addClass( element, something )
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.