New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modernize #38
Comments
Did you consider using Rollup instead of Webpack? It seems to produce smaller builds. As a proof of concept, this is the Webpack outputvar 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 Outputvar 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 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
}
]
}; |
@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. |
Btw, I don't think @SkaterDad 馃憤 thought to mention that too. edit: Babili isn't ready enough, and uglify is still better and gives smaller sizes. |
@tunnckoCore I used 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). |
@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? |
@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. |
@tunnckoCore Totally! Can you try to make our dist bundle smaller? 馃槃 馃檹 |
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 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 I'll run the whole index.js through it now. |
@SkaterDad Are you including In the future you may want to use https://www.npmjs.com/package/yo-yoify so you only include |
The build I'm talking about in my posts is just replicating your library bundle Using yo-yoify for a client app build is definitely a good idea, but outside the scope of this thread, I think. |
@SkaterDad Yes, indeed, my bad for losing the thread of the conversation haha. |
@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. |
@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 |
I know that. But it can't be integrated in our dev flow, because we are using Webpack/Rollup. ;d |
@tunnckoCore I'm talking about consumers / users using yo-yoify, not us / HyperApp repo. |
@maraisr Maybe you can look into this? Rollup instead of webpack thing. Specially if it means less or no configuration at all. |
Yea it means. :) I can start it soon. |
@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. |
I believe there is no support for such thing currently. We will need separate |
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. |
@tunnckoCore I'm trying to cram everything in a one liner, so that I can just put everything in package.json. |
@jbucaran This issue on Rollup's repo discusses the options: rollup/rollup#703 Simplest thing might be to create a |
@tunnckoCore How can I call rollup in the cli and specify plugins? |
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. |
@maraisr Smallest bundle wins. |
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. |
@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. |
Just use it. You're the author, you ultimately have final say. Let's just agree to disagree? |
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. |
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. |
With this - I mean like, you may as well use proper function statements and not const everything
|
@tunnckoCore I can't repro this. Rollup generates a smaller bundle size for the first example using both arrow functions, what did I miss? |
Smallest. const defer = (fn, data) => {
setTimeout(_ => {
fn(data)
}, 0)
} |
Doesn't make sense. Just think a bit. :) In ES2015 (first case / you "smallest") there would have 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
output
|
@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. |
@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. |
TL;DR 馃憢
If you are reading this issue now, just be aware we considered rewriting the source in ES6 at some point, but eventually decided not to. The rationale is ES6 would make it slightly more difficult for developers to hack with Hyperapp without introducing a build system and still no CommonJS support out of the box.
Hyperapp is a minimal library and with an unchanging goal of staying forever lean and beginner friendly.
You can take an hour and read through the entire 250ish LOC here and here and you'd understand pretty much everything. Zero dependency. It's all there, the virtual DOM, the state container, etc.
See original discussion
@maraisr Summarizing the discussion in #29, #30, #31 and #32 we'd like to modernize the code base to use more ES6 idioms and introduce webpack to bundle the distribution.
Rationale
Using more ES6 idioms may encourage more contributions.
Using more ES6 idioms means a smaller bundle for users targetting really modern browsers. For example, in production users may consume our
src
directory directly with their application and don't babelify (or maybe babelify a some things via plugins) and use something like babili to minify modern JavaScript. This means even smaller bundles.Other than prototyping, examples, presentations and maybe even real small-ish apps, users will want to bundle their apps using a bundler like browserify, webpack, rollup, etc.
The text was updated successfully, but these errors were encountered: