Comments (36)
@tunnckoCore I don't think he was saying to use it in the library bundles, but for the end users.
In my own apps, I'm more likely to just use raw h
function calls anyway. One less dep to introduce bugs.
from hyperapp.
Yea it means. :) I can start it soon.
from hyperapp.
I'd vote for Rollup too. I spent a lot of time comparing them a month ago, and Rollup does indeed produce smaller bundles and it's way simpler to configure. In my opinion webpack is a bit overkill for this project, where the purpose is to keep it simple and tiny.
from hyperapp.
@maraisr HyperApp should not get larger by principle. It should remain simple. I want to start building stuff with HyperApp, not continue to build HyperApp ad infinitum.
What do you mean by optional imports? We already have optional stuff right? h or html, and so forth.
from hyperapp.
Btw, ES2015-ing also brings more bytes to the final bundle. :D
Because things like that
const defer = (fn, data) => {
setTimeout(_ => fn(data), 0)
}
a lot smaller would be if it is just defined as function
function defer (fn, data) {
setTimout(() => {
fn(data)
})
}
just think a bit. So arrows should be used only when it make sense not just for the sake of ES6.
from hyperapp.
from hyperapp.
@jorgebucaran If you're aiming for smallest bundle size (I know you are 😉) go with Rollup & Bublé. With only quick glances, I see nothing in Hyperapp that inhibits this combo.
Webpack has a future goal of beating Rollup in footprint size, but they have a way to go.
Bublé is always the right choice.
from hyperapp.
Did you consider using Rollup instead of Webpack? It seems to produce smaller builds.
As a proof of concept, this is the app.js
bundle without minification or Babili applied. Webpack adds quite a lot more overhead to each of the bundles.
Webpack output
var app =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 3);
/******/ })
/************************************************************************/
/******/ ({
/***/ 3:
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _arguments = arguments;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
console.log("DEV");
module.exports = function (options) {
var defer = function defer(fn, data) {
setTimeout(function (_) {
return fn(data);
}, 0);
};
var merge = function merge(a, b) {
var obj = {},
key;
if (isPrimitive(typeof b === "undefined" ? "undefined" : _typeof(b)) || Array.isArray(b)) {
return b;
}
for (key in a) {
obj[key] = a[key];
}
for (key in b) {
obj[key] = b[key];
}
return obj;
};
var isPrimitive = function isPrimitive(type) {
return type === "string" || type === "number" || type === "boolean";
};
var render = function render(model, view, lastNode) {
patch(root, node = view(model, msg), lastNode, 0);
};
var shouldUpdate = function shouldUpdate(a, b) {
return a.tag !== b.tag || (typeof a === "undefined" ? "undefined" : _typeof(a)) !== (typeof b === "undefined" ? "undefined" : _typeof(b)) || isPrimitive(typeof a === "undefined" ? "undefined" : _typeof(a)) && a !== b;
};
var patch = function patch(parent, node, oldNode, index) {
if (oldNode === undefined) {
parent.appendChild(createElementFrom(node));
} else if (node === undefined) {
while (index > 0 && !parent.childNodes[index]) {
index--;
}
if (index >= 0) {
var element = parent.childNodes[index];
if (oldNode && oldNode.data) {
var hook = oldNode.data.onremove;
if (hook) {
defer(hook, element);
}
}
parent.removeChild(element);
}
} else if (shouldUpdate(node, oldNode)) {
parent.replaceChild(createElementFrom(node), parent.childNodes[index]);
} else if (node.tag) {
var element = parent.childNodes[index];
updateElementData(element, node.data, oldNode.data);
var len = node.tree.length,
oldLen = oldNode.tree.length;
for (var i = 0; i < len || i < oldLen; i++) {
patch(element, node.tree[i], oldNode.tree[i], i);
}
}
};
var createElementFrom = function createElementFrom(node) {
var element;
if (isPrimitive(typeof node === "undefined" ? "undefined" : _typeof(node))) {
element = document.createTextNode(node);
} else {
element = node.data && node.data.ns ? document.createElementNS(node.data.ns, node.tag) : document.createElement(node.tag);
for (var name in node.data) {
if (name === "oncreate") {
defer(node.data[name], element);
} else {
setElementData(element, name, node.data[name]);
}
}
for (var i = 0; i < node.tree.length; i++) {
element.appendChild(createElementFrom(node.tree[i]));
}
}
return element;
};
var setElementData = function setElementData(element, name, value, oldValue) {
if (name === "style") {
for (var i in value) {
element.style[i] = value[i];
}
} else if (name.substr(0, 2) === "on") {
var event = name.substr(2);
element.removeEventListener(event, oldValue);
element.addEventListener(event, value);
} else {
if (value === "false" || value === false) {
element.removeAttribute(name);
element[name] = false;
} else {
element.setAttribute(name, value);
element[name] = value;
}
}
};
var removeElementData = function removeElementData(element, name, value) {
element.removeAttribute(name === "className" ? "class" : name);
if (typeof value === "boolean" || value === "true" || value === "false") {
element[name] = false;
}
};
var updateElementData = function updateElementData(element, data, oldData) {
for (var name in merge(oldData, data)) {
var value = data[name],
oldValue = oldData[name];
if (value === undefined) {
removeElementData(element, name, oldValue);
} else if (value !== oldValue) {
name === "onupdate" ? defer(value, element) : setElementData(element, name, value, oldValue);
}
}
};
var regexify = function regexify(path) {
var keys = [],
re = "^" + path.replace(/\//g, "\\/").replace(/:([A-Za-z0-9_]+)/g, function (_, key) {
keys.push(key);
return "([A-Za-z0-9_]+)";
}) + "/?$";
return { re: re, keys: keys };
};
var route = function route(routes, path) {
for (var route in routes) {
var re = regexify(route),
params = {},
match;
path.replace(new RegExp(re.re, "g"), function (_) {
for (var i = 1; i < _arguments.length - 2; i++) {
params[re.keys.shift()] = _arguments[i];
}
match = function match(model, msg) {
return routes[route](model, msg, params);
};
});
if (match) {
return match;
}
}
return routes["/"];
};
var msg = {};
var model = options.model;
var reducers = options.update || {};
var effects = options.effects || {};
var subs = options.subs || {};
var hooks = merge({
onAction: Function.prototype,
onUpdate: Function.prototype,
onError: function onError(err) {
throw err;
}
}, options.hooks);
var node;
var root = options.root || document.body.appendChild(document.createElement("div"));
var view = options.view || function (_) {
return root;
};
var routes = typeof view === "function" ? undefined : view;
if (routes) {
view = route(routes, location.pathname);
msg.setLocation = function (data) {
render(model, view = route(routes, data), node);
history.pushState({}, "", data);
};
window.addEventListener("popstate", function (_) {
render(model, view = route(routes, location.pathname), node);
});
window.addEventListener("click", function (e) {
if (e.metaKey || e.shiftKey || e.ctrlKey || e.altKey) {
return;
}
var target = e.target;
while (target && target.localName !== "a") {
target = target.parentNode;
}
if (target && target.host === location.host && !target.hasAttribute("data-no-routing")) {
var element = target.hash === "" ? element : document.querySelector(target.hash);
if (element) {
element.scrollIntoView(true);
} else {
msg.setLocation(target.pathname);
e.preventDefault();
}
}
});
}
var _loop = function _loop(name) {
msg[name] = function (data) {
hooks.onAction(name, data);
var effect = effects[name];
if (effect) {
return effect(model, msg, data, hooks.onError);
}
var update = reducers[name],
_model = model;
render(model = merge(model, update(model, data)), view, node);
hooks.onUpdate(_model, model, data);
};
};
for (var name in merge(reducers, effects)) {
_loop(name);
}
document.addEventListener("DOMContentLoaded", function (_) {
for (var sub in subs) {
subs[sub](model, msg, hooks.onError);
}
});
render(model, view);
};
/***/ })
/******/ });
Rollup Output
var app = (function () {
'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var _arguments = arguments;
console.log("DEV");
var app = (function (options) {
var defer = function defer(fn, data) {
setTimeout(function (_) {
return fn(data);
}, 0);
};
var merge = function merge(a, b) {
var obj = {},
key;
if (isPrimitive(typeof b === "undefined" ? "undefined" : _typeof(b)) || Array.isArray(b)) {
return b;
}
for (key in a) {
obj[key] = a[key];
}
for (key in b) {
obj[key] = b[key];
}
return obj;
};
var isPrimitive = function isPrimitive(type) {
return type === "string" || type === "number" || type === "boolean";
};
var render = function render(model, view, lastNode) {
patch(root, node = view(model, msg), lastNode, 0);
};
var shouldUpdate = function shouldUpdate(a, b) {
return a.tag !== b.tag || (typeof a === "undefined" ? "undefined" : _typeof(a)) !== (typeof b === "undefined" ? "undefined" : _typeof(b)) || isPrimitive(typeof a === "undefined" ? "undefined" : _typeof(a)) && a !== b;
};
var patch = function patch(parent, node, oldNode, index) {
if (oldNode === undefined) {
parent.appendChild(createElementFrom(node));
} else if (node === undefined) {
while (index > 0 && !parent.childNodes[index]) {
index--;
}
if (index >= 0) {
var element = parent.childNodes[index];
if (oldNode && oldNode.data) {
var hook = oldNode.data.onremove;
if (hook) {
defer(hook, element);
}
}
parent.removeChild(element);
}
} else if (shouldUpdate(node, oldNode)) {
parent.replaceChild(createElementFrom(node), parent.childNodes[index]);
} else if (node.tag) {
var element = parent.childNodes[index];
updateElementData(element, node.data, oldNode.data);
var len = node.tree.length,
oldLen = oldNode.tree.length;
for (var i = 0; i < len || i < oldLen; i++) {
patch(element, node.tree[i], oldNode.tree[i], i);
}
}
};
var createElementFrom = function createElementFrom(node) {
var element;
if (isPrimitive(typeof node === "undefined" ? "undefined" : _typeof(node))) {
element = document.createTextNode(node);
} else {
element = node.data && node.data.ns ? document.createElementNS(node.data.ns, node.tag) : document.createElement(node.tag);
for (var name in node.data) {
if (name === "oncreate") {
defer(node.data[name], element);
} else {
setElementData(element, name, node.data[name]);
}
}
for (var i = 0; i < node.tree.length; i++) {
element.appendChild(createElementFrom(node.tree[i]));
}
}
return element;
};
var setElementData = function setElementData(element, name, value, oldValue) {
if (name === "style") {
for (var i in value) {
element.style[i] = value[i];
}
} else if (name.substr(0, 2) === "on") {
var event = name.substr(2);
element.removeEventListener(event, oldValue);
element.addEventListener(event, value);
} else {
if (value === "false" || value === false) {
element.removeAttribute(name);
element[name] = false;
} else {
element.setAttribute(name, value);
element[name] = value;
}
}
};
var removeElementData = function removeElementData(element, name, value) {
element.removeAttribute(name === "className" ? "class" : name);
if (typeof value === "boolean" || value === "true" || value === "false") {
element[name] = false;
}
};
var updateElementData = function updateElementData(element, data, oldData) {
for (var name in merge(oldData, data)) {
var value = data[name],
oldValue = oldData[name];
if (value === undefined) {
removeElementData(element, name, oldValue);
} else if (value !== oldValue) {
name === "onupdate" ? defer(value, element) : setElementData(element, name, value, oldValue);
}
}
};
var regexify = function regexify(path) {
var keys = [],
re = "^" + path.replace(/\//g, "\\/").replace(/:([A-Za-z0-9_]+)/g, function (_, key) {
keys.push(key);
return "([A-Za-z0-9_]+)";
}) + "/?$";
return { re: re, keys: keys };
};
var route = function route(routes, path) {
for (var route in routes) {
var re = regexify(route),
params = {},
match;
path.replace(new RegExp(re.re, "g"), function (_) {
for (var i = 1; i < _arguments.length - 2; i++) {
params[re.keys.shift()] = _arguments[i];
}
match = function match(model, msg) {
return routes[route](model, msg, params);
};
});
if (match) {
return match;
}
}
return routes["/"];
};
var msg = {};
var model = options.model;
var reducers = options.update || {};
var effects = options.effects || {};
var subs = options.subs || {};
var hooks = merge({
onAction: Function.prototype,
onUpdate: Function.prototype,
onError: function onError(err) {
throw err;
}
}, options.hooks);
var node;
var root = options.root || document.body.appendChild(document.createElement("div"));
var view = options.view || function (_) {
return root;
};
var routes = typeof view === "function" ? undefined : view;
if (routes) {
view = route(routes, location.pathname);
msg.setLocation = function (data) {
render(model, view = route(routes, data), node);
history.pushState({}, "", data);
};
window.addEventListener("popstate", function (_) {
render(model, view = route(routes, location.pathname), node);
});
window.addEventListener("click", function (e) {
if (e.metaKey || e.shiftKey || e.ctrlKey || e.altKey) {
return;
}
var target = e.target;
while (target && target.localName !== "a") {
target = target.parentNode;
}
if (target && target.host === location.host && !target.hasAttribute("data-no-routing")) {
var element = target.hash === "" ? element : document.querySelector(target.hash);
if (element) {
element.scrollIntoView(true);
} else {
msg.setLocation(target.pathname);
e.preventDefault();
}
}
});
}
var _loop = function _loop(name) {
msg[name] = function (data) {
hooks.onAction(name, data);
var effect = effects[name];
if (effect) {
return effect(model, msg, data, hooks.onError);
}
var update = reducers[name],
_model = model;
render(model = merge(model, update(model, data)), view, node);
hooks.onUpdate(_model, model, data);
};
};
for (var name in merge(reducers, effects)) {
_loop(name);
}
document.addEventListener("DOMContentLoaded", function (_) {
for (var sub in subs) {
subs[sub](model, msg, hooks.onError);
}
});
render(model, view);
});
return app;
}());
//# sourceMappingURL=app.build.js.map
The only change in app.js
was changing module.exports =
to export default
.
Rollup config was pretty basic just to prove the concept. Naturally you can extend this with minification and such.
import babel from 'rollup-plugin-babel';
import babelrc from 'babelrc-rollup';
let pkg = require('./package.json');
let external = Object.keys(pkg.dependencies);
export default {
entry: 'src/app.js',
plugins: [
babel(babelrc()),
],
external: external,
targets: [
{
dest: 'dist/app.build.js',
format: 'iife',
moduleName: 'app',
sourceMap: true
}
]
};
from hyperapp.
@SkaterDad There was no consideration since I'm not versed particularly in either rollup or webpack, but I'll take whichever can produce smaller bundles.
from hyperapp.
Btw, I don't think iife
is the right thing.
@SkaterDad 👍 thought to mention that too.
edit: Babili isn't ready enough, and uglify is still better and gives smaller sizes.
from hyperapp.
@tunnckoCore I used iife
in that config since @jorgebucaran had Webpack outputting something similar.
Since that bundle is for the browser, I think it's correct (per this tutorial).
I've heard it said before that Rollup is a great choice for library authors. Vue.js uses it with Buble (instead of Babel).
from hyperapp.
@tunnckoCore We've actually got a smaller bundle, so I'd like to think the current setup is a step in the right direction. @SkaterDad I'd really like to give rollup a try though. Can you help with this?
from hyperapp.
@jbucaran @SkaterDad I use Rollup + Buble everywhere every day too. It is just amazing combo. But in some cases it is not cool, because Buble adds more bytes as needed - I'm fighting that today and I end up just to not use buble haha and write vanilla es5. In my current case 50-100 bytes is important - because the promo slogan. Here if use Rollup + Buble the case would be the same, because of the app.js#L152-L169 (defining function in loop).
I can PR with Rollup+Buble+UglifyJS setup if you want, but don't know if it worths.
from hyperapp.
@tunnckoCore Totally! Can you try to make our dist bundle smaller? 😄 🙏
from hyperapp.
Sounds like @tunnckoCore is the right guy for the PR. I've only been playing with Rollup since this morning.
I was able to change my config to use buble
, though, and it saved ~0.34 KB compared to the Babel w/ ES2015 preset once minified.
The nicest part is it took zero options!
import buble from 'rollup-plugin-buble'
import uglify from 'rollup-plugin-uglify';
export default {
entry: 'src/app.js',
plugins: [
buble(),
uglify()
],
targets: [
{
dest: 'dist/app.buble.js',
format: 'iife',
moduleName: 'app',
sourceMap: true
}
]
};
For the standalone app.js
build, here are more exact savings. (based on a clone I pulled down a couple hours ago, so your results may vary)
Webpack -> Babel w/ ES2015 & Babili presets -> Uglify = 3873 chars.
Rollup -> Babel ES2015 preset -> Uglify = 3443 chars.
Rollup -> Buble -> Uglify = 3078 chars.
I'll run the whole index.js through it now.
from hyperapp.
@SkaterDad Are you including html.min.js
as well right?
In the future you may want to use https://www.npmjs.com/package/yo-yoify so you only include h
or bable/buble for JSX.
from hyperapp.
The build I'm talking about in my posts is just replicating your library bundle app.min.js
.
Using yo-yoify for a client app build is definitely a good idea, but outside the scope of this thread, I think.
from hyperapp.
@SkaterDad Yes, indeed, my bad for losing the thread of the conversation haha.
from hyperapp.
@jbucaran yo-yoify is cool, but is part of the Browserify stack, so I don't see how we can integrate it if we use Rollup/Webpack.
from hyperapp.
I know that. But it can't be integrated in our dev flow, because we are using Webpack/Rollup. ;d
from hyperapp.
@tunnckoCore I'm talking about consumers / users using yo-yoify, not us / HyperApp repo.
from hyperapp.
@maraisr Maybe you can look into this? Rollup instead of webpack thing. Specially if it means less or no configuration at all.
from hyperapp.
@SkaterDad How did you get rollup to create multiple bundles? We have to generate one for each file h, html, app and a big one with everything too.
from hyperapp.
I believe there is no support for such thing currently. We will need separate rollup.config.js
files. Convention is to have build
dir with few rollup configs.
from hyperapp.
@tunnckoCore I'm trying to cram everything in a one liner, so that I can just put everything in package.json.
from hyperapp.
@jbucaran This issue on Rollup's repo discusses the options: rollup/rollup#703
Simplest thing might be to create a build.js
or something, and just programmatically run Rollup.
from hyperapp.
@tunnckoCore How can I call rollup in the cli and specify plugins?
from hyperapp.
Not sure if either of you are aware - but webpack recently had a massive rewrite. So I'd argue todo another comparison. But even then, I'd go webpack over rollup... you don't wanna box yourself in now with a tool that's limited in flexibility.
from hyperapp.
@maraisr Smallest bundle wins.
from hyperapp.
It's great and all.... but what about in the future when we want optional imports, like ssr. Feel like we're gonna spend more time getting roll up todo what we want it too.... oh well. Maybe there's value.... who knows. All I'll have to say is, you want a tool with a solid community, solid Eco system, and something that makes it almost unusable if you wanna try cli. CLI is bad. You'll find the larger a project gets, and the more contributes you get, you'll start to favour readability.
from hyperapp.
Just use it. You're the author, you ultimately have final say. Let's just agree to disagree?
from hyperapp.
And to answer your other question, Yes, I'll migrate to rollup, and amend this PR. Give me 2 hours, gotta get ready for work.
from hyperapp.
@tunnckoCore I can't repro this. Rollup generates a smaller bundle size for the first example using both arrow functions, what did I miss?
from hyperapp.
Smallest.
const defer = (fn, data) => {
setTimeout(_ => {
fn(data)
}, 0)
}
from hyperapp.
Doesn't make sense. Just think a bit. :) In ES2015 (first case / you "smallest") there would have var
and return
. Try that
defer.js
// defer1.js
// const defer = (fn, data) => {
// setTimeout(() => fn(data), 0)
// }
// defer2.js
// function defer () {
// setTimeout(() => {
// fn(data)
// }, 0)
// }
// defer3.js
const defer = (fn, data) => {
setTimeout(_ => {
fn(data)
}, 0)
}
export default defer
and rollup config: buble + uglify plugins
const buble = require('rollup-plugin-buble')
const uglify = require('rollup-plugin-uglify')
module.exports = {
plugins: [
buble(),
uglify({ compress: { warnings: false } })
]
}
cli
rollup -c -f iife -n defer -i defer.js -o dist/defer1.js
rollup -c -f iife -n defer -i defer.js -o dist/defer2.js
rollup -c -f iife -n defer -i defer.js -o dist/defer3.js
ls -al dist
output
-rw-r--r-- 1 charlike users 106 Feb 7 09:37 defer1.js
-rw-r--r-- 1 charlike users 95 Feb 7 09:37 defer2.js
-rw-r--r-- 1 charlike users 100 Feb 7 09:38 defer3.js
from hyperapp.
@lukeed The source is now pretty much ES3 + CommonJS, with a few ES5 idioms, probably Array.isArray is the only one, which is okay.
And, yes we ended up going with rollup, which yielded the smallest bundle.
Closing as this information is already out of date.
from hyperapp.
Related Issues (20)
- A way to insert raw Html HOT 1
- TypeError: can't access property 0, newSubs is null, when setting the state to undefined. HOT 4
- Issue with null-vnodes HOT 1
- prevent rerender node HOT 2
- The dispatch initializer ends in an endless loop on init when dispatching any action HOT 7
- Injected classes gets removed when using object/array to define class props HOT 1
- hyperapp version HOT 3
- Memo Data Gotcha HOT 5
- Confusing doc for actions -> wrapped actions HOT 5
- Passing arguments to init HOT 4
- [Question] Headless mode is still possible? HOT 1
- Destroying a child app HOT 8
- @hyperapp/html: use a Proxy? HOT 9
- Actions returning other Actions HOT 5
- Compile template tag to hyperscript HOT 17
- A challenge to hyperapp community HOT 1
- Has 2.0 been dropped from development? HOT 3
- oldSub[2] is not a function HOT 3
- Cannot read properties of null (reading 'length') HOT 5
- Unlikely Use Case bug in HTML and SVG Packages HOT 9
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from hyperapp.