Code Monkey home page Code Monkey logo

tapable's Introduction

Tapable

The tapable package expose many Hook classes, which can be used to create hooks for plugins.

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");

Installation

npm install --save tapable

Usage

All Hook constructors take one optional argument, which is a list of argument names as strings.

const hook = new SyncHook(["arg1", "arg2", "arg3"]);

The best practice is to expose all hooks of a class in a hooks property:

class Car {
	constructor() {
		this.hooks = {
			accelerate: new SyncHook(["newSpeed"]),
			brake: new SyncHook(),
			calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
		};
	}

	/* ... */
}

Other people can now use these hooks:

const myCar = new Car();

// Use the tap method to add a consument
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());

It's required to pass a name to identify the plugin/reason.

You may receive arguments:

myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));

For sync hooks, tap is the only valid method to add a plugin. Async hooks also support async plugins:

myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => {
	// return a promise
	return google.maps.findRoute(source, target).then(route => {
		routesList.add(route);
	});
});
myCar.hooks.calculateRoutes.tapAsync("BingMapsPlugin", (source, target, routesList, callback) => {
	bing.findRoute(source, target, (err, route) => {
		if(err) return callback(err);
		routesList.add(route);
		// call the callback
		callback();
	});
});

// You can still use sync plugins
myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, routesList) => {
	const cachedRoute = cache.get(source, target);
	if(cachedRoute)
		routesList.add(cachedRoute);
})

The class declaring these hooks need to call them:

class Car {
	/**
	  * You won't get returned value from SyncHook or AsyncParallelHook,
	  * to do that, use SyncWaterfallHook and AsyncSeriesWaterfallHook respectively
	 **/

	setSpeed(newSpeed) {
		// following call returns undefined even when you returned values
		this.hooks.accelerate.call(newSpeed);
	}

	useNavigationSystemPromise(source, target) {
		const routesList = new List();
		return this.hooks.calculateRoutes.promise(source, target, routesList).then((res) => {
			// res is undefined for AsyncParallelHook
			return routesList.getRoutes();
		});
	}

	useNavigationSystemAsync(source, target, callback) {
		const routesList = new List();
		this.hooks.calculateRoutes.callAsync(source, target, routesList, err => {
			if(err) return callback(err);
			callback(null, routesList.getRoutes());
		});
	}
}

The Hook will compile a method with the most efficient way of running your plugins. It generates code depending on:

  • The number of registered plugins (none, one, many)
  • The kind of registered plugins (sync, async, promise)
  • The used call method (sync, async, promise)
  • The number of arguments
  • Whether interception is used

This ensures fastest possible execution.

Hook types

Each hook can be tapped with one or several functions. How they are executed depends on the hook type:

  • Basic hook (without “Waterfall”, “Bail” or “Loop” in its name). This hook simply calls every function it tapped in a row.

  • Waterfall. A waterfall hook also calls each tapped function in a row. Unlike the basic hook, it passes a return value from each function to the next function.

  • Bail. A bail hook allows exiting early. When any of the tapped function returns anything, the bail hook will stop executing the remaining ones.

  • Loop. When a plugin in a loop hook returns a non-undefined value the hook will restart from the first plugin. It will loop until all plugins return undefined.

Additionally, hooks can be synchronous or asynchronous. To reflect this, there’re “Sync”, “AsyncSeries”, and “AsyncParallel” hook classes:

  • Sync. A sync hook can only be tapped with synchronous functions (using myHook.tap()).

  • AsyncSeries. An async-series hook can be tapped with synchronous, callback-based and promise-based functions (using myHook.tap(), myHook.tapAsync() and myHook.tapPromise()). They call each async method in a row.

  • AsyncParallel. An async-parallel hook can also be tapped with synchronous, callback-based and promise-based functions (using myHook.tap(), myHook.tapAsync() and myHook.tapPromise()). However, they run each async method in parallel.

The hook type is reflected in its class name. E.g., AsyncSeriesWaterfallHook allows asynchronous functions and runs them in series, passing each function’s return value into the next function.

Interception

All Hooks offer an additional interception API:

myCar.hooks.calculateRoutes.intercept({
	call: (source, target, routesList) => {
		console.log("Starting to calculate routes");
	},
	register: (tapInfo) => {
		// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... }
		console.log(`${tapInfo.name} is doing its job`);
		return tapInfo; // may return a new tapInfo object
	}
})

call: (...args) => void Adding call to your interceptor will trigger when hooks are triggered. You have access to the hooks arguments.

tap: (tap: Tap) => void Adding tap to your interceptor will trigger when a plugin taps into a hook. Provided is the Tap object. Tap object can't be changed.

loop: (...args) => void Adding loop to your interceptor will trigger for each loop of a looping hook.

register: (tap: Tap) => Tap | undefined Adding register to your interceptor will trigger for each added Tap and allows to modify it.

Context

Plugins and interceptors can opt-in to access an optional context object, which can be used to pass arbitrary values to subsequent plugins and interceptors.

myCar.hooks.accelerate.intercept({
	context: true,
	tap: (context, tapInfo) => {
		// tapInfo = { type: "sync", name: "NoisePlugin", fn: ... }
		console.log(`${tapInfo.name} is doing it's job`);

		// `context` starts as an empty object if at least one plugin uses `context: true`.
		// If no plugins use `context: true`, then `context` is undefined.
		if (context) {
			// Arbitrary properties can be added to `context`, which plugins can then access.
			context.hasMuffler = true;
		}
	}
});

myCar.hooks.accelerate.tap({
	name: "NoisePlugin",
	context: true
}, (context, newSpeed) => {
	if (context && context.hasMuffler) {
		console.log("Silence...");
	} else {
		console.log("Vroom!");
	}
});

HookMap

A HookMap is a helper class for a Map with Hooks

const keyedHook = new HookMap(key => new SyncHook(["arg"]))
keyedHook.for("some-key").tap("MyPlugin", (arg) => { /* ... */ });
keyedHook.for("some-key").tapAsync("MyPlugin", (arg, callback) => { /* ... */ });
keyedHook.for("some-key").tapPromise("MyPlugin", (arg) => { /* ... */ });
const hook = keyedHook.get("some-key");
if(hook !== undefined) {
	hook.callAsync("arg", err => { /* ... */ });
}

Hook/HookMap interface

Public:

interface Hook {
	tap: (name: string | Tap, fn: (context?, ...args) => Result) => void,
	tapAsync: (name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void,
	tapPromise: (name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void,
	intercept: (interceptor: HookInterceptor) => void
}

interface HookInterceptor {
	call: (context?, ...args) => void,
	loop: (context?, ...args) => void,
	tap: (context?, tap: Tap) => void,
	register: (tap: Tap) => Tap,
	context: boolean
}

interface HookMap {
	for: (key: any) => Hook,
	intercept: (interceptor: HookMapInterceptor) => void
}

interface HookMapInterceptor {
	factory: (key: any, hook: Hook) => Hook
}

interface Tap {
	name: string,
	type: string
	fn: Function,
	stage: number,
	context: boolean,
	before?: string | Array
}

Protected (only for the class containing the hook):

interface Hook {
	isUsed: () => boolean,
	call: (...args) => Result,
	promise: (...args) => Promise<Result>,
	callAsync: (...args, callback: (err, result: Result) => void) => void,
}

interface HookMap {
	get: (key: any) => Hook | undefined,
	for: (key: any) => Hook
}

MultiHook

A helper Hook-like class to redirect taps to multiple other hooks:

const { MultiHook } = require("tapable");

this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]);

tapable's People

Contributors

alexander-akait avatar amzotti avatar chandlerprall avatar chengcyber avatar chenxsan avatar daweilv avatar developit avatar domfarolino avatar iamakulov avatar iguessitsokay avatar jon-simpkins avatar maysam avatar newyork-anthonyng avatar noscripter avatar ooflorent avatar opichals avatar pirosikick avatar ryanclark avatar shenchang618 avatar snyamathi avatar soda-x avatar sokra avatar spacek33z avatar taig avatar thelarkinn avatar timneutkens avatar wmertens avatar wtgtybhertgeghgtwtg avatar yvanwangl avatar zzs1020 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  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

tapable's Issues

tapPromise doesn't return my resolved value

Hi, there,

Thanks for the awesome jobs. I encountered an issue while trying to use tapPromise. I'm using version 1.1.1

if I use my defined plugin this way:

xx.hooks.endpoint.tapPromise('EndpointPlugin', (options) => {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve({ retcode: 0, res: { msg: 'hello world' } });
        }, 0);
      }).then(res => {
        return res;
      })
    });

with

  request(options) {
    return this.hooks.endpoint.promise(options).then((res) => {
    // **res is undefined!**
      return res;
    });
  }

res is not returned by tapPromise.

But if I change my code to following;

xx.hooks.endpoint.tapPromise('EndpointPlugin', (options, result) => {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve({ retcode: 0, res: { msg: 'hello world' } });
        }, 0);
      }).then(res => {
        result.res = res;
        return res;
      })
    });

with

 request(options) {
    const result = {};
    return this.hooks.endpoint.promise(options, result).then((res) => {
      return result.res;
    });
  }

I still can't get res of course, but with this workaround, I can at least get what I wanted. I saw this is also the way noted on your README(google map ROUTE example).

I'm wondering if you designed it this way or this is a bug? because the usage is weird, I have to define an additional 'container variable' all the time, which actually means nothing to users.

---------- new input after 1 day ------------
this also happens to tap(), for example:

myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => {return newSpeed === 5;});
setSpeed(newSpeed) {
    // returned value is still undefined!
    const value = this.hooks.accelerate.call(newSpeed);
    return value;
}

I looked code, seems they all are using _tap(), so the behavior may happen on all of them. I'll try looking code and make changes and PR, but your help is definitely better! thanks.

Is there a way to unapply a plugin?

Webpack applies many plugins by default e.g.

compiler.apply(
    new JsonpTemplatePlugin(options.output),
    new FunctionModulePlugin(options.output),
    new NodeSourcePlugin(options.node),
    new LoaderTargetPlugin("web")
);

I'd like to, for example, use a different JsonpTemplatePlugin which handles error callbacks however I'm unable to remove the current one meaning I have to monkey patch the JsonpTemplatePlugin class... Is there a better way to achieve this?

Query regarding coding paradigm used in this library.

Why in the code string concatenation is used to create the function? I can see, this approach is being followed in almost every file. Why shouldn't it be written as a normal function? For example like this in HookCodeFactory .

switch(this.options.type) { case "sync": return new Function(this.args(), "\"use strict\";\n" + this.header() + this.content({ onError: err => throw ${err};\n, onResult: result => return ${result};\n, onDone: () => "", rethrowIfPossible: true }));

Error: Cannot find module 'upath'

when i upgrade my node version to 10.0.0, boom ↓

any idea?

ENV

$ node -v
v10.0.0
$ npm -v
6.0.0

DETAIL

10:46:33 webpack.1   | Error: Cannot find module 'upath'
10:46:33 webpack.1   |     at Function.Module._resolveFilename (internal/modules/cjs/loader.js:571:15)
10:46:33 webpack.1   |     at Function.Module._load (internal/modules/cjs/loader.js:497:25)
10:46:33 webpack.1   |     at Module.require (internal/modules/cjs/loader.js:626:17)
10:46:33 webpack.1   |     at require (/Users/developer/Documents/Sources/server/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
10:46:33 webpack.1   |     at Object.<anonymous> (/Users/developer/Documents/Sources/server/node_modules/chokidar/index.js:13:13)
10:46:33 webpack.1   |     at Module._compile (/Users/developer/Documents/Sources/server/node_modules/v8-compile-cache/v8-compile-cache.js:178:30)
10:46:33 webpack.1   |     at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
10:46:33 webpack.1   |     at Module.load (internal/modules/cjs/loader.js:589:32)
10:46:33 webpack.1   |     at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
10:46:33 webpack.1   |     at Function.Module._load (internal/modules/cjs/loader.js:520:3)
10:46:33 webpack.1   |     at Module.require (internal/modules/cjs/loader.js:626:17)
10:46:33 webpack.1   |     at require (/Users/developer/Documents/Sources/server/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
10:46:33 webpack.1   |     at Object.<anonymous> (/Users/developer/Documents/Sources/server/node_modules/watchpack/lib/DirectoryWatcher.js:9:16)
10:46:33 webpack.1   |     at Module._compile (/Users/developer/Documents/Sources/server/node_modules/v8-compile-cache/v8-compile-cache.js:178:30)
10:46:33 webpack.1   |     at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
10:46:33 webpack.1   |     at Module.load (internal/modules/cjs/loader.js:589:32)
10:46:33 webpack.1   |     at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
10:46:33 webpack.1   |     at Function.Module._load (internal/modules/cjs/loader.js:520:3)
10:46:33 webpack.1   |     at Module.require (internal/modules/cjs/loader.js:626:17)
10:46:33 webpack.1   |     at require (/Users/developer/Documents/Sources/server/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
10:46:33 webpack.1   |     at WatcherManager.getDirectoryWatcher (/Users/developer/Documents/Sources/server/node_modules/watchpack/lib/watcherManager.js:14:25)
10:46:33 webpack.1   |     at WatcherManager.watchFile (/Users/developer/Documents/Sources/server/node_modules/watchpack/lib/watcherManager.js:28:14)
10:46:33 webpack.1   |     at Watchpack.<anonymous> (/Users/developer/Documents/Sources/server/node_modules/watchpack/lib/watchpack.js:38:49)
10:46:33 webpack.1   |     at Array.map (<anonymous>)
10:46:33 webpack.1   |     at Watchpack.watch (/Users/developer/Documents/Sources/server/node_modules/watchpack/lib/watchpack.js:37:28)
10:46:33 webpack.1   |     at NodeWatchFileSystem.watch (/Users/developer/Documents/Sources/server/node_modules/webpack/lib/node/NodeWatchFileSystem.js:53:16)
10:46:33 webpack.1   |     at Watching.watch (/Users/developer/Documents/Sources/server/node_modules/webpack/lib/Watching.js:115:48)
10:46:33 webpack.1   |     at compiler.hooks.done.callAsync (/Users/developer/Documents/Sources/server/node_modules/webpack/lib/Watching.js:102:10)
10:46:33 webpack.1   |     at AsyncSeriesHook.eval [as callAsync] (eval at create (/Users/developer/Documents/Sources/server/node_modules/tapable/lib/HookCodeFactory.js:24:12), <anonymous>:6:1)
10:46:33 webpack.1   |     at AsyncSeriesHook.lazyCompileHook [as _callAsync] (/Users/developer/Documents/Sources/server/node_modules/tapable/lib/Hook.js:35:21)
10:46:33 webpack.1   | npm ERR! code ELIFECYCLE
10:46:33 webpack.1   | npm ERR! errno 1

Way to tap before other taps? Are `stage` and `before` options supported?

We're thinking about using tapable as a replacement for typedoc's internal eventing stuff. Is there a way to tap a hook before other already-registered taps? I see Hook options before and stage. Are they supported, or will they go away when v2 is released? If we start using tapable, should we use the v2 beta or stick to v1?

Thanks for any insight you can offer. Let me know if I should be looking elsewhere for answers.

Error using interception API with SyncBailHook

Here is a simple configuration I had while linked up to webpack#next

webpack.config.js

const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: './src',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'chunk.js'
    },
    plugins: [
        new webpack.ProgressPlugin(),
        function () {
            const compiler = this;

            Object.keys(compiler.hooks).forEach((hookName) => {
                compiler.hooks[hookName].intercept(interceptorFor(hookName));
            });
        }
    ]
};

const interceptorFor = (hookName) => ({
    call: (...args) => {
        console.log(`Intercepting Call: ${hookName}\n Args: ${JSON.stringify([...args])}`);
    },
    tap: (tapDetails) => {
        console.log(`Intercepting Tap: ${JSON.stringify(tapDetails)}`);
    } 
});

This is the error that threw:

console output

TypeError: Cannot read property 'fn' of undefined
    at SyncBailHook.eval [as call] (eval at compile (/mnt/c/Users/selarkin/Code/webpack/node_modules/tapable/lib/Hook.js:21:10), <anonymous>:12:28)
    at SyncBailHook.eval [as _call] (eval at createCompileDelegate (/mnt/c/Users/selarkin/Code/webpack/node_modules/tapable/lib/Hook.js:55:10), <anonymous>:5:16)
    at WebpackOptionsApply.process (/mnt/c/Users/selarkin/Code/webpack/lib/WebpackOptionsApply.js:264:30)
    at webpack (/mnt/c/Users/selarkin/Code/webpack/lib/webpack.js:35:48)
    at processOptions (/mnt/c/Users/selarkin/Code/webpack/bin/webpack.js:357:15)
    at yargs.parse (/mnt/c/Users/selarkin/Code/webpack/bin/webpack.js:418:2)
    at Object.Yargs.self.parse (/mnt/c/Users/selarkin/Code/webpack/node_modules/yargs/yargs.js:533:18)
    at Object.<anonymous> (/mnt/c/Users/selarkin/Code/webpack/bin/webpack.js:154:7)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

Feature request: method to remove a tap

(I may make a PR for this eventually)
I know that tapable is mainly intended for plugins that are registered once at startup. But this is the first package I turn to when I need something more powerful than an EventEmitter, since I can't do anything like an AsyncSeriesHook with EventEmitter. Overall it's a pretty good general-purpose replacement.
However, one thing EventEmitter can do that tapable can't is remove hooks. I have a few use cases where I would like to use tapable but I need to be able to remove the hooks at certain times.

Hook Descriptions

In regards to webpack/webpack.js.org#1754 and this comment.

The documentation on the next branch of webpack.js.org now points to this package and it's readme more prominently. In standardizing and updating the new v4 hook documentation, I used this repo's README a lot. I found most of it very helpful, but the actual hook types still feel a bit mysterious.

Now that we note each hook type in the description of each hook, it would be great if users could come over here to learn more about the different types of hooks and what the do (besides just sync vs async). Something like the following for each hook would be fantastic:

SyncHook

Creates a hook that is executed synchronously... (etc.)

Methods: tap

I'd be happy to help with grammar or clarification if needed.

Question: Why using Array.prototype.slice

I noticed in lib/Tapable.js there are several calls to Array.prototype.slice.call(arguments, ..) example here. I'm just curious as to why this is done when the arguments object is an array, and as such has slice in its [[Prototype]] chain.

Is it some sort of an optimization? Is a call to Array.prototype.method.call(...) faster than someArray.method()?

Better technical documentation for API

Let's try and break up a way to make the new phases and hooks easier to understand.

Public API (for Tapable instance)
Public API for a plugin registered to an instance.

How each hook works
Explanation of evals in code
Description of Interceptors

Verbose documentation

Could you please write more verbose documentation? E.g. I don't understand what method plugin does

has no exported member 'Tapable'. error appears when trying to build a TypeScript project

I'm getting the following errors when trying to build a TypeScript project:

> functions@ build /Users/wujekbogdan/htdocs/rss-parser/functions
> tsc

../../../node_modules/@types/webpack/index.d.ts:25:10 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'Tapable'.

25 import { Tapable, HookMap,
            ~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:25:19 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'HookMap'.

25 import { Tapable, HookMap,
                     ~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:26:10 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'SyncBailHook'.

26          SyncBailHook, SyncHook, SyncLoopHook, SyncWaterfallHook,
            ~~~~~~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:26:24 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'SyncHook'.

26          SyncBailHook, SyncHook, SyncLoopHook, SyncWaterfallHook,
                          ~~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:26:34 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'SyncLoopHook'.

26          SyncBailHook, SyncHook, SyncLoopHook, SyncWaterfallHook,
                                    ~~~~~~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:26:48 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'SyncWaterfallHook'.

26          SyncBailHook, SyncHook, SyncLoopHook, SyncWaterfallHook,
                                                  ~~~~~~~~~~~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:27:10 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'AsyncParallelBailHook'.

27          AsyncParallelBailHook, AsyncParallelHook, AsyncSeriesBailHook, AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable';
            ~~~~~~~~~~~~~~~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:27:33 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'AsyncParallelHook'.

27          AsyncParallelBailHook, AsyncParallelHook, AsyncSeriesBailHook, AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable';
                                   ~~~~~~~~~~~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:27:52 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'AsyncSeriesBailHook'.

27          AsyncParallelBailHook, AsyncParallelHook, AsyncSeriesBailHook, AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable';
                                                      ~~~~~~~~~~~~~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:27:73 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'AsyncSeriesHook'.

27          AsyncParallelBailHook, AsyncParallelHook, AsyncSeriesBailHook, AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable';
                                                                           ~~~~~~~~~~~~~~~

../../../node_modules/@types/webpack/index.d.ts:27:90 - error TS2305: Module '"/Users/wujekbogdan/htdocs/node_modules/@types/tapable/index"' has no exported member 'AsyncSeriesWaterfallHook'.

27          AsyncParallelBailHook, AsyncParallelHook, AsyncSeriesBailHook, AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable';
                                                                                            ~~~~~~~~~~~~~~~~~~~~~~~~

npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! functions@ build: `tsc`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the functions@ build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/wujekbogdan/.npm/_logs/2019-10-08T12_40_23_813Z-debug.log
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I tried:

  • Different node versions (8, 10, 12)
  • Updating all depdnencies/devDependencies
  • installing @types/tapable with yarn addd @types/tapable --dev

I'm not using tabable explicitly, it's a dependency of one of my dependencies.


The weird thing is that the project I'm trying to build used to work fine. I didn't change anything in the code since a half a year. Now I'm having these errors.

Support for promises in async plugin apply strategies

ES2015 Promise is a standard now and Node.js is shipping with them enabled. With
the proposed (and now closer to being a standard) async/await syntax the
support for promises will be even stronger across the ecosystem. Another benefit of
promises is that they make handling errors much easier as user code doesn't need
to propagate error argument of callbacks explicitly.

I propose adding support for promises for async plugin applying strategies in
Tapable in a backward compatible way. Both for provider and consumer code paths.

That would enable Promise based API for plugins for Webpack and also a
way to convert Webpack codebase in the future to Promise based async primitives.

If you are ok with an idea I'm eager to start implementing a draft of the
proposal. What do you think?

TS errors

I'm getting these errors upon building my project:

../node_modules/@types/webpack/index.d.ts(586,28): error TS2507: Type 'typeof "[...]/node_modules/@types/tapable/index"' is not a constructor function type.
../node_modules/@types/webpack/index.d.ts(628,46): error TS2694: Namespace '"[...]/node_modules/@types/tapable/index"' has no exported member 'Plugin'.
../node_modules/@types/webpack/index.d.ts(632,53): error TS2694: Namespace '"[...]/node_modules/@types/tapable/index"' has no exported member 'Plugin'.

Is args[args.length - 1] faster than args.pop()/push() ?

I notice that only applyPluginsParallelBailResult updates args[args.length - 1] directly. And other methods, like applyPluginsAsync/applyPluginsAsyncSeriesBailResult/applyPluginsParallel, pop the last argument first, then push a new one.

Shall we always use the faster one?

Error: Plugin could not be registered at 'html-webpack-plugin-alter-asset-tags'. Hook was not found.

Hello,

Unfortunately, I cannot build my react project because of this error. I'm puzzled, because it's a dependency of webpack, so I can't change or fixed that error.

I tried with 8.16.0 and 10.16.0 node, reinstalled node_modules with yarn an npm, but I got same error.

It's so urgent, because I must hand the project over to my client in this week.

Thank you!

tzs007@MSI:~/_localhost/tennissignal$ npm run build

> [email protected] build /home/tzs007/_localhost/tennissignal
> webpack --progress --bail --env dist -p

/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/Tapable.js:69
                throw new Error(
                ^

Error: Plugin could not be registered at 'html-webpack-plugin-alter-asset-tags'. Hook was not found.
BREAKING CHANGE: There need to exist a hook at 'this.hooks'. To create a compatibility layer for this hook, hook into 'this._pluginCompat'.
    at Compilation.plugin (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/Tapable.js:69:9)
    at Compilation.deprecated [as plugin] (internal/util.js:66:15)
    at compiler.plugin (/home/tzs007/_localhost/tennissignal/node_modules/script-ext-html-webpack-plugin/lib/plugin.js:37:19)
    at SyncHook.eval [as call] (eval at create (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:17:1)
    at SyncHook.lazyCompileHook (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/Hook.js:154:20)
    at Compiler.newCompilation (/home/tzs007/_localhost/tennissignal/node_modules/webpack/lib/Compiler.js:631:26)
    at hooks.beforeCompile.callAsync.err (/home/tzs007/_localhost/tennissignal/node_modules/webpack/lib/Compiler.js:667:29)
    at AsyncSeriesHook.eval [as callAsync] (eval at create (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:6:1)
    at AsyncSeriesHook.lazyCompileHook (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/Hook.js:154:20)
    at Compiler.compile (/home/tzs007/_localhost/tennissignal/node_modules/webpack/lib/Compiler.js:662:28)
    at readRecords.err (/home/tzs007/_localhost/tennissignal/node_modules/webpack/lib/Compiler.js:321:11)
    at Compiler.readRecords (/home/tzs007/_localhost/tennissignal/node_modules/webpack/lib/Compiler.js:529:11)
    at hooks.run.callAsync.err (/home/tzs007/_localhost/tennissignal/node_modules/webpack/lib/Compiler.js:318:10)
    at AsyncSeriesHook.eval [as callAsync] (eval at create (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:6:1)
    at AsyncSeriesHook.lazyCompileHook (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/Hook.js:154:20)
    at hooks.beforeRun.callAsync.err (/home/tzs007/_localhost/tennissignal/node_modules/webpack/lib/Compiler.js:315:19)
    at AsyncSeriesHook.eval [as callAsync] (eval at create (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)
    at AsyncSeriesHook.lazyCompileHook (/home/tzs007/_localhost/tennissignal/node_modules/tapable/lib/Hook.js:154:20)
    at Compiler.run (/home/tzs007/_localhost/tennissignal/node_modules/webpack/lib/Compiler.js:312:24)
    at processOptions (/home/tzs007/_localhost/tennissignal/node_modules/webpack-cli/bin/cli.js:353:14)
    at yargs.parse (/home/tzs007/_localhost/tennissignal/node_modules/webpack-cli/bin/cli.js:364:3)
    at Object.parse (/home/tzs007/_localhost/tennissignal/node_modules/webpack-cli/node_modules/yargs/yargs.js:567:18)
    at /home/tzs007/_localhost/tennissignal/node_modules/webpack-cli/bin/cli.js:49:8
    at Object.<anonymous> (/home/tzs007/_localhost/tennissignal/node_modules/webpack-cli/bin/cli.js:366:3)
    at Module._compile (internal/modules/cjs/loader.js:776:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Module.require (internal/modules/cjs/loader.js:690:17)
    at require (internal/modules/cjs/helpers.js:25:18)
    at Object.<anonymous> (/home/tzs007/_localhost/tennissignal/node_modules/webpack/bin/webpack.js:156:2)
    at Module._compile (internal/modules/cjs/loader.js:776:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build: `webpack --progress --bail --env dist -p`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/tzs007/.npm/_logs/2019-10-27T23_59_07_654Z-debug.log

AsyncParallelHook seems not fit as promise

const {
  AsyncParallelHook
} = require('./lib/index')

const hook = new AsyncParallelHook(['name'])

hook.tapAsync('x', function(name, cb) {
  setTimeout(function() {
    console.log(1)
    cb(name)
  }, 500)
})

hook.tapAsync('y', function(name, cb) {
  setTimeout(function() {
    console.log(2)
    cb(name)
  }, 1000)
})


hook.callAsync('leeem', function(name) {
  console.log('start', name)
})

/// 1
/// start leeem
/// 2

the runtime variable count is not injected in callback, is it correct?

Doesn't work with IE11 (ducks for cover)

I am using this in an SPA and it's really great, but it turns out that the npm version uses class in the JS, so it's not ES5.

Would it be possible to provide the npm code as ES5 code? I don't know what the current best way to provide ES6 code is, but this is the only package that causes problems in the build for our IE users.

In the meantime I'll try some workaround like forcing a babel compilation of this package.

problem about tap method of interceptor

recently i try to use interceptor of tapable, in the Interception section of readme:

tap: (tap: Tap) => void Adding tap to your interceptor will trigger when a plugin taps into a hook. Provided is the Tap object. Tap object can't be changed.

it means if i use a intecptor of tap method, when a plugin taps into a hook, the corresponding function will be invoked. However, the following code snippet does not work as expected.

code snippet:

const { SyncBailHook } = require('tapable')
const hook = new SyncBailHook(['name'])
hook.intercept({
    tap:(tapInfo) => {
        console.log(tapInfo)
    }
})
hook.tap('1', function (name) {
    console.log(name, '1')
})
hook.tap('2', function (name) {
    console.log(name, '2')
    return 'wrong'
})
hook.tap('1', function (name) {
    console.log(name, '2')
})

tap of interceptor only be called when it execute hook.call('webpack').

version of tapable: 1.1.0

Playing around with tapable [questions]

Hello there,

I'm playing around with tapable outside of the Webpack world.

Basically, I'd like to use the async super-powers of tapable to expose hooks from a class method.

const { AsyncSeriesHook } = require('tapable')
const { ObjectId } = require('mongodb')
const capitalize = require('lodash/capitalize')
const { get, close } = require('./db')

// Root class
class Document {
  constructor(collection) {
    this.collection = collection
    this.data = {}
    this.hooks = {
      beforeCreate: new AsyncSeriesHook(['data', 'output']),
      afterCreate: new AsyncSeriesHook(['doc', 'output'])
    }
  }
  registerHook(hook, signature, promise) {
    const hookNames = Object.keys(this.hooks)
    if (!hookNames.includes(hook)) throw new Error(`Document do not contains "${hook}" hook`)
    this.hooks[hook].tapPromise(signature, promise)
  }
  async create(data) {
    const db = await get()
    const preHooksResult = new DataMap(data)
    await this.hooks.beforeCreate.promise(data, preHooksResult)
    const doc = await db
      .collection(this.collection)
      .insertOne(preHooksResult.get())
      .then(op => op.ops[0])
    const postHooksResult = new DataMap(doc)
    await this.hooks.afterCreate.promise(doc, postHooksResult)
    return postHooksResult.get()
  }
// ...
}

// instance creation
const user = new Document('users')
user.registerHook('beforeCreate', 'userType', async (data, output) => {
  output.merge({ type: 'user' })
})
user.registerHook('beforeCreate', 'validator', async (data, output) => {
  output.merge({ valid: true })
})
user.registerHook('afterCreate', 'idConverter', async (data, output) => {
  output.merge({ _id: data._id.toString() })
})

// call create
const res = await User.create({ firstName: 'Nico', lastName: 'D' })
console.log('res', res)

Here everything is working perfectly, so questions:

  1. Is tapable can/should be used for this type of implementation?
  2. Whatever I tried it's not possible to remove a hook previously set up, I imagine there is a rationale behind this, can you guys tell me about it?
  3. What is the role of the name in this.hooks[hook].tapPromise(name, promise)?

Thank you a lot

PS: You've done an amazing job with the design of this lib 😍

Proper way to remove plugin

Hi
Seems there is no correct way to remove a plugin. Modifying _plugins directly is definitely not correct since it requires accessing and modifying the private field.
I would appreciate something like

void remove(names: string|string[], handler: Function)

Would you accept such a PR?

SyncHook vs AsyncSeriesHook

I still have a hard time to understand the difference between these two hooks. If I understand correctly, both are invoking tapped functions sequentially. SyncHook is like a for-loop to call functions one after another, while AsyncSeriesHook uses nested callback to trigger the next function call. It would be really helpful to compare different types of hooks and their use-cases in the documentation.

Tapable.plugin deprecation

I updated webpack on my application and have these deprecation warnings now in my app:

(node:48495) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead

I did find out, that this package raises them, but couldn't find where they originated, meaning which other package might use Tapable wrong.

Can you give me some hints how I can best trace the packages down?

Documentation

Can you please provide a link to documentation of the library? I know it is mostly used for WebPack internals (Compilers) but I would like to use it for a personal project.
Pardon but my English is not helping me understand different hooks like
AsyncParallelBailHook and their purpose.

A simple explanation and example of each type of hook would be a great help.

More TS Errors

Possibly related to: #53

This afternoon started getting a ton of errors from webpack.
Included the @types/tapable types and now i'm getting more errors still

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:19:10 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'Tapable'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:19:19 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'HookMap'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:20:10 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'SyncBailHook'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:20:24 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'SyncHook'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:20:34 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'SyncLoopHook'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:20:48 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'SyncWaterfallHook'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:21:10 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'AsyncParallelBailHook'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:21:33 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'AsyncParallelHook'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:21:52 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'AsyncSeriesBailHook'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:21:73 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'AsyncSeriesHook'.

../../srv/service_npm_modules/node_modules/@types/webpack/index.d.ts:21:90 - error TS2305: Module '"/srv/service_npm_modules/node_modules/@types/tapable/index"' has no exported member 'AsyncSeriesWaterfallHook'.

Just curious why do "new Function(code)" ?

Hey I was looking at the source today just curious why it constructs code from taps in string then do "new Function".
Is it about perf ? How much it wins over plain code ?

Do something with hook results

In my app, I would like to return early if one of the hooks returns some specific value. Would that be possible?

So for example hook.callUntil({args: [...], condition: result => result.foo}).

New major version

What about a new major version with more performance and more features?

  • Never use arguments, compile fixed arguments function.
  • Create a function per hooks which makes it monomorphic.
  • Maybe something magic like new Function(`function x(${args}) ...`)
  • Different apply function depending on number of registered plugins
    • no plugins: noop apply for max performance
    • special version to 1 plugin: optimized
    • general version for more plugin
  • You need to register hooks before using them. You need to specify number of arguments and kind (sync/async/parallel/waterfall).
  • Instead of compiler.plugin("before-run", ...) compiler.hooks.beforeRun(...) to allow have typings for these hooks.
  • Each plugin can have a name compiler.hooks.beforeRun("HotModuleReplacementPlugin", () => ...)
  • Insert plugins at specify locations instead of always to the end. hooks.beforeRun({ name: ..., before: "HotModuleReplacementPlugin" }, () => ...)
  • Maybe a middleware api, if this doesn't affect performance: hooks.beforeRun.intercept(...)
    • Would be great of the progress display and profiling.
  • The plugin(...) method can be provided for backward compatibility for plugins.
    • We may generate plugin name from stack trace (depending on performance cost for this)
  • The old applyPluginsXXX methods can also be offered for backward compatibility, but trigger a deprecation.
  • Using a unknown hook name in the plugin or applyPluginsXXX automatically register a compat hook which handle them.
  • The compatibility methods convert dash-case to camelCase.
  • This would be a big breaking change, but I guess acceptable with the compatibility layer.

Can we remove tap from hook?

var hook = new SyncHook(['param'])
var fn = param => console.log(param)

hook.tap('example', fn)
hook.call('frist')
// frist

setTimeout(() => {
   hook.remove(fn)
}, 3000)

Auto generate documents with typedoc

If possible, I will sumbit a PR to use typedoc to automatically generate documentation once we have migrated to typescript. Then maybe we can use a github page to host the documents. So people can easily see the apis provided by the lib.

Discussion is more than welcome 👏

Typings questions

I'm currently working on typing the tapable library, and so far it is going well, but I'm running across some differences between the tests and the documentation. It would be great to get some guidance on some of the really obfuscated aspects of the compiler.

So, to start off with, I have just a couple questions:

  1. What is the Result type which @sokra documented in the README as the return values of calls? In my tests, I'm consistently getting undefined returns from console.log({result: hook.call()}); in the Hook.js test file.
  2. Should users pass in the type: "sync" | "async" | "promise" option when creating hooks? It will either greatly complicate (or makes the types less accurate depending on direction) when someone can use Hookable::tap({name: "SomePlugin", type: "promise"}, callback) to declare tap type as "promise" rather than Hookable::tapPromise("SomePlugin", callback) as in these cases we'd want both callback instances to be the inferred to the same type...

@sokra, thank you for updating the readme with some basic interfaces a couple weeks ago, it really helped me start off with a lot of momentum.

Thank you!

Migrate to Typescript

If we wanted to tackle migrating all of webpack to typescript, we would need to start with all of the supporting libs. Especially this one. Stubbing this for implementation down the road.

is there something missed

exports.AsyncSequencialHook = require("./AsyncSequencialHook");
exports.AsyncSequencialBailHook = require("./AsyncSequencialBailHook");
exports.AsyncWaterfallHook = require("./AsyncWaterfallHook");

Is there a way to introduce alias anywhere in the build lifecycle, other than config?

Some of my module may import a manifest which will have list of libraries that are available to be use for the app. My intent is to create a namespace so that app can assume a pattern to access the imported code.

I am looking to somehow inject alias but after reviewing ResolverFactory looks like AliasPlugin is all registered, for respective alias' and done by the time I get handle to the external code. Hence, I wonder if there is a Webpack way to apply AliasPlugin during lifecycle. If not, I will have to some script to obtain and construct the alias prior to Webpack process.

Cannot read property 'html-webpack-plugin-after-emit' of undefined

webpack 1.14
node 6.3.1

Cannot read property 'html-webpack-plugin-after-emit' of undefined | at build/product/newbuild/webpack/node_modules/tapable/lib/Tapable.js:75:19) | at Object.tryCatcher build/product/newbuild/webpack/node_modules/bluebird/js/main/util.js:26:23) | at ret (eval at build/product/newbuild/webpack/node_modules/bluebird/js/main/promisify.js:163:12), :13:39) | at build/product/newbuild/webpack/node_modules/html-webpack-plugin/index.js:642:12 | at build/product/newbuild/webpack/node_modules/html-webpack-plugin/index.js:192:16 | at tryCatcher build/product/newbuild/webpack/node_modules/bluebird/js/main/util.js:26:23) | at build/product/newbuild/webpack/node_modules/bluebird/js/main/promise.js:510:31) | at build/product/newbuild/webpack/node_modules/bluebird/js/main/promise.js:584:18) | at build/product/newbuild/webpack/node_modules/bluebird/js/main/promise.js:700:14) | at build/product/newbuild/webpack/node_modules/bluebird/js/main/async.js:123:16) | at build/product/newbuild/webpack/node_modules/bluebird/js/main/async.js:133:10) | at build/product/newbuild/webpack/node_modules/bluebird/js/main/async.js:15:14) | at tryOnImmediate (timers.js:543:15) | at processImm

Could you help me to solve this issue (fails on debian - ok on xubuntu)

Throw inside a function suppressed

Coming from webpack, but I assume the origin is here in tappable, let me know if this would be better filed elsewhere!

If you add a plugin function and there is an error inside that function which would cause a throw, there is no handling. The callback is simply never called and webpack exits without compiling or throwing an error. A try/catch wrapper around the plugin function when it's invoked would take care of this cleanly 😁

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.