MithrilJS / mithril.js

A JavaScript Framework for Building Brilliant Applications
https://mithril.js.org
MIT License
14.02k stars 926 forks source link

Make Mithril modular #651

Closed srolel closed 8 years ago

srolel commented 9 years ago

I love the framework and have integrated it into my application. However, I'm using Angular (and am stuck with it) and don't need Mithril's router and http libraries. Would it make sense to make the code base use modules so that I can import just the parts I need?

barneycarroll commented 9 years ago

The cynic in me believes the ~1000 bytes you'd save would be others' loss via boilerplate.

…But I have an interest in making Mithril more modular for the sake of code transparency and maintenance, so I'm intrigued by the idea :)

Do you have any ideas for a process? I'd be up for helping on a fork…

srolel commented 9 years ago

Well, the routing and request parts (line 676 onward) are ~500 (unminified) lines out of Mithril's ~1200, so I wouldn't say the savings are negligible.

The process is fairly straightforward, figure out function dependencies, split them into sensible (CommonJS) modules (router, http, DOM, core, utils, for example, or as granular as desired) and add another build step (Browserify, RequireJS, webpack etc.).

The build output will be functionally the same as the current script (no boilerplate), while people could also use require('mithril/DOM') for example.

I've broken up several large scripts like this, it's not too painful and should be almost painless considering Mithril.js is small and modular in nature. I'll try to have something up in a couple days.

barneycarroll commented 9 years ago

:+1:

darsain commented 9 years ago

I'd recommend looking at Duo, imo the most comfy build system & package manager right now.

You don't even have to publish to 3rd party sites, as it uses github repos as its registry.

l-cornelius-dol commented 9 years ago

I'd recommend leaving it alone until ES6 modules are in all the browsers that matter.

barneycarroll commented 9 years ago

@darsain I'm inclined against Duo, since it piggy-backs on the established and de-facto-standard CommonJS/Node end-use API but violates the package identification strategy embraced by every major standard, which is to defer resolution to external methods (ie package.json). Duo distinguishes itself on the ability to resolve Github ownerID/repoID strings without external reference and CSS packaging / delivery – both of which are superfluous to Mithril since it is pure JS and has no external dependencies.

@lawrence-dol providing modular builds does not mandate that Mithril application developers (or Mithril application users) be able to use any particular module import / registry / resolution method or have any particular stack, it just makes it possible to use Mithril modularly. Mithril is already UMD-compliant, in that it will work with Node, Browserify, RequireJS, SystemJS, or as a plain global script injection. We should absolutely maintain that. For those of us using vanilla JS included by script, this would mean either simply including mithril.js as per usual, or including discrete components such as mithril.core.js + mithril.dom.js etc.

elektronik2k5 commented 9 years ago

+1 for modularity!

markmarijnissen commented 9 years ago

+1 for modularity! I love webpack. It can bundle everything. You can write source in CommonJS format, then bundle it unto an universal library build (which supports global, AMD, commonJS modules).

darsain commented 9 years ago

violates the package identification strategy embraced by every major standard, which is to defer resolution to external methods (ie package.json)

Duo has component.json, which is where you specify dependencies when doing anything serious. Manifest-less dependency resolution from code, such as require('user/repo@1.x.x') is mostly only for prototyping, and code snippets. When nearing production you'd move the dependencies to component.json, and resolve only with require('repo').

CSS packaging superfluous

Mithril perhaps doesn't need CSS packaging, but every website, and possibly modules/components that consume mithril definitely would. It seems silly to dismiss a tool because it has a feature your particular case doesn't need. And duo also supports packaging and consumption of almost any other files, such as json, fonts, html templates, ... very useful.

Mithril is already UMD-compliant, in that it will work with Node, Browserify, RequireJS, SystemJS, or as a plain global script injection. We should absolutely maintain that.

Making mithril modular would not take this away. You'd still have a final monolithic file for people that use raw scripts, which would be just a composition of mithril modules:

module.exports = exports = require('mithril/m');
exports.route = require('mithril/route');
exports.component = require('mithril/component');
// ...

and build with duo --standalone which creates an UMD export for you, so you don't have to wrap your code in factories and do it by hand like a cave man :)

Making mithril modular would also mean better and more maintainable source code structure. You'd be able to split big internal functions into files like lib/build.js, lib/setattribute.js and than only consume inside the main file with:

var build = require('/lib/build');

You'd also be able to remove inlined stuff which should be left for dependencies, such as your own promise, ajax, routing, data binding, etc. solutions. You could split them to modules, and add them to the monolithic file, leaving people who want to use mithril modularily with an option to pick their preferred solutions.

From all the front-end package managers I've tried duo was the best to use. The only issue is that it's not backed by any large company, and that hinders its adoption.

Anyway, there's my 2¢.

ghost commented 9 years ago

I'm using the full Mithril feature set in production with just the plain vanilla JS script and grunt reduce [1]. I quite like it this way. I don't want to see it get more complicated than it needs to be. My 2 cents.

[1] https://github.com/Munter/grunt-reduce

barneycarroll commented 9 years ago

@Jafula to re-iterate what @darsain was saying above, the proposal would maintain total backward compatibility. jQuery and Lodash are fully modular builds – it doesn't impact basic plug-and-play at all.

Mithril currently uses Grunt as a build system, but that's completely orthogonal to how you want to use it.

StreetStrider commented 9 years ago

I might add to what @darsain said, that if Mithril has no dependencies now (and maybe already has, but «melded»), it is not a eternal state of things. Every non-utility project one day acquire some deps. And when this day comes some builder/bundler would be required. But even without deps Mithril will gain benefits of modularity.

pelonpelon commented 9 years ago

kb

The three convenience services built into Mithril that some developers choose to circumvent with their own solutions are m.request, m.route, and m.deferred. It's often the case that Mithril is being integrated into an established code base that already provides theses services. Nevertheless, they're not essential to MVC.

Importing an xhr solution would require importing promises as well. I'd venture that the combination of these imports would occupy more real estate than the current implementation.

m.request occupies about ~10% of the code. Promises take up about ~8% of the code.

xhr and routing both require access to the autoredraw system which would have to be re-written to allow for a registration/hook mechanism. Current access to redraws is too powerful to be buried in a plugin. autoredraw() is just 8 lines of code. I imagine it would have to grow.

Current 3rd party routing options are just wrappers around m.route.

m.route occupies about ~10% of the code.

@lhorie has demonstrated that 3rd party promises are an option now. This might be the easiest byte saver.


That's nearly a 30% reduction in code base. It's not clear how much of that savings would be lost to making Mithril plugin friendly. But the benefits of opening the code up for a plugin ecosystem might be well worth the extra bytes.

BTW I would still prefer a default configuration to have all the conveniences of the current version of Mithril.

srolel commented 9 years ago

665 I've made a PR. The cost is 5kb unminified, 2kb minified. Building just the virtual DOM functionality is ~34kb. All the tests pass for the bundled script, but I haven't made any extra tests or tried testing any one module.

The router and http modules import the DOM module since they cause redraws but changes can be fairly easily made to opt out if the functionality isn't detected.

mithril.js in the PR is functionally the same as the original.

MattMcFarland commented 9 years ago

I believe it would be great if we could separate the router from mithril.js - and call it mithril-router.js - as I do not think everyone would want to use it. The router is nice, but if it's not being used, then it could be a single node module that is pluggable.

pdfernhout commented 9 years ago

For a build tool needed to put Mithril modules together into one script, instead of Grunt, Gulp, Duo, Webpack, or something similar, I wonder if just plain npm might suffice? See Keith Cirkel's approach: "Last month I noted my opinions on why we should stop using Grunt, Gulp et al. I suggested we should start using npm instead. npm's scripts directive can do everything that these build tools can, more succinctly, more elegantly, with less package dependencies and less maintainence overhead. ... npm is a fantastic tool that offers much more than meets the eye. ..."

I feel just using npm with some simple scripts might be more in keeping with Mithril's elegant less-layers-to-go-wrong design.

ybybzj commented 9 years ago

I remember this request has been made by some one else before. I just don't want to wait to be resolved, so I build some stuff myself just for fun:). I call it mReact, only using the rendering part of Mithril, and code is modularized. Check it out if you are interested.

jakobdamjensen commented 9 years ago

Would very much like if m.sync, m.request and others were moved into separate packages so that mithril would focus on the view aspect only. I personally use es6-promise polyfil for promises and have been using superagent for async http requests... I don't mind using m.request at all but it would be neat if I could choose on my own.

But overall I really like Mithril alot. But I'm afraid that it could loose focus. Many packages exists for promises, http and routing. But few does what Mithril do on the view layer. Why not focus on making that even more awesome and move the other parts into separate packages with their own release cycles?

Perhaps there could be a bundled version called Mithril which includes mithril-ui/dom/vdom/whatever, mithril-request, mothril-router etc.

Just an idea. And as said - the build process for the release of Mithril would remove all the boiler plate. Would make it possible for others to just use certain parts.

Don't know if this is possible of even a good idea.

dead-claudia commented 9 years ago

@jakobdamjensen WRT m.sync/m.deferred, see #800. It's come up quite a few times on Gitter. Also, m.request is otherwise largely independent of the rest of the library. Most of the small utilities are standalone.

The rendering is so highly coupled, forget about modularizing it. And half of m.route and m.mount overlap with the renderer. The incredibly tight coupling in about 50% of the framework is a known problem. And it's not an easy thing to fix at the moment.

suhaotian commented 8 years ago

I want the mithril.js's source code like Vuejs's structure. that's comfortable and more readable

veggiemonk commented 8 years ago

Why not make another repo/branch/whatever and keep the monolithic version in master and npm? Mainly for new comers Clearly, more advanced mithril users want this. The best case I know is lodash:

// first time I used it
import _ from 'lodash' 

//later, when I'm more comfortable with the lib
import sortBy from 'lodash/collection/sortBy'

What's wrong about that?

By the way, it was kinda hard to know how to import only the function I was interested in. I think it was on someone else's code on github that I saw that and it was a good idea.

I think mithril should keep its concept of simplicity and in this case, unity. Just create a new branch and a new npm module with files split up, hoping that it should make advanced users happy and new comers not to give up . I would interested to see a 'mithril-modular' or something similar.

barneycarroll commented 8 years ago

@veggiemonk nobody suggested lodash was doing things wrong. And, as your example shows, it's entirely possible to do this without separate branches, using the same package. There's basically nothing to worry about with modularising the source code — novice users shouldn't even notice anything's changed :)

veggiemonk commented 8 years ago

Hey @barneycarroll ,

novice users shouldn't even notice anything's changed :)

Exactly the goal I was trying to ouline. Thanks for that!

Novice users should be able to load the whole lib. Advanced users should be able to load only what they want/need.

If it is just like the example of lodash. I think everybody will be pleased.

dead-claudia commented 8 years ago

Ideas that spawned on #665:

  1. Use Rollup
    • Pros: small build size
    • Cons: still experimental and unstable
  2. Use Webpack/Browserify
    • Pros: lot of existing tooling
    • Cons: large build size
  3. Use a concatenation boilerplate/syntax
    • Pros: very small build size
    • Cons: order is important, have to roll own tooling

Currently, the consensus is leaning towards the third, but I thought I would put something here.

/cc @barneycarroll @pygy @lawrence-dol @veggiemonk This summary accurate?

pygy commented 8 years ago

Another Rollup pro / concatenation con is the fact that it documents interdependencies.

import {foo} from './foo'

l-cornelius-dol commented 8 years ago

I am trying hard not to have much of an opinion, because as @barneycarroll has pointed out I am not an active contributor yet (in part because I work too much already, and it part because (gasp) I've only just started using Git recently).

So I'll just repeat my post with my reasoning from #665 for the sake of this thread and bow out in respect to those who are actively contributing. (I added a little.)


My vote is far, far and away for a simple concatenation of plain JS and no magical tool chain.

I do want to preserve my ability to contribute to Mithril, and I doggedly refuse to use the complex and unnecessary 10-million-dependency tool chains that others seem to love so much.

Equally as much, if Mithril dies, goes south or off and away to becoming a "jack of all trades, master of none" framework, I can simply go on with whatever was the last version that made sense. The tight focus and tiny footprint is the primary reason I chose to build on top of Mithril; heck it does a thousand times more for me than moment.js at a fraction of the size. That means I don't want to inherit a thumping great big complicated tool-chain with Node.js and a gazillion dependencies, or a refactor-it-back-to-just-JavaScript job in the development side of Mithril.

I've tried Sencha, Sencha Touch and KendoUI; they all started off well and rapidly got to the point where they sucked harder than a black hole at the event horizon. Not to mention the 5,000 files they added to every project. No thanks, not any more. Not again.

My $1.99 (inflation).

StephanHoyer commented 8 years ago

I don't think rollup will add too much since it flattens out all dependencies. Also I think it's prematuraty is not a big problem since we can run the tests against the build and we should be fine. In the first place I would create 2 bundles one for mithril beeing globally available and one common-js version.

In the next steps we can create custom builds for the different parts of mithril. Until then rollup might be in a good shape. It will certainly not die, if google is already using it.

For me it would also be ok to use browserify. The overhead is relavant compared to the size of mithril but not to the size of a decent sized project. For the people who care about the last bit we can also provide a custom build where all require stuff is flattend out. I think there is an option in browserify for this.

I understand @lawrence-dol concerns: If we have the modularized mithril in place, it's easy to add more and more features to it. But in the current state, it's hard for people to contribute I think (I did'n make any PR so far).

It's a matter of discipline not to overload the framework with stuff that might be already solved anywhere else. An for the books: First thing we want to do with modularisation is to strip things out of mithril :)

veggiemonk commented 8 years ago

@impinball Thanks for summing that up.

TL;DR

Build steps add complexity. Modules are great unit computation!


From my experience, build steps are a pain. Unless you're willing to include your own like lodash custom builds which I've never used by the way because there is a dist folder with the unminified source and minified source + maps. To illustrate, look at the branch: screen shot 2015-11-26 at 18 18 43 It needs one branch just to keep track of all the different possible builds it has to make. It seems time-consuming to maintain. The problem with those build steps is that they are so custom it is difficult to change. It takes time to read the build code and look for what you need let alone understanding the app that uses those build steps. This means complexity and that doesn't fit in mithril mindset as I understand it. I am very much in favour of modularisation, I love those compact units (functions basically) that do one thing only but well. For example, look at some of the packages sindresorhus has developed for inspiration. They are not so likely to change but they are easy to switch when they stay that small. Mithril is like that, a compact unit of auto-redraw :wink: But if some people more advanced found a way to please everybody and make mithril modular without adding a build step, then great, problem solved.

I use mithril for its simplicity and great documentation. Doesn't matter much to me if it splits in a few files or just stay in one. I will still use it. Mithril is so small that stripping a few kb just because I don't use all functions available sounds like a joke when most website don't optimize their images. Average page load is good 2 mb. If a project use a css framework like bootstrap, do they strip the 99% of code they don't use? Some people do, the rest didn't because it's adding more steps and complications to the build steps. Plus now you have to test the build to make sure everything is going smoothly. But most people don't write test in JS because it's hard to start and it hurts feelings!!!

So adding a custom build step sounds like a terrible idea, unless someone make it great (you never know).

This is a table taken from @nordfjord presentation about mithril ( link to repo )

Core Routing Data
m m.route m.request
m.prop m.route.param m.deferred
m.component m.sync
m.mount
m.withAttr
Rendering Html
m.render m.trust
m.redraw
m.startComputation
m.endComputation

Is that really too much of an api that we need to strip it down? I am using almost all of them (except request, route and sync, I don't really know what sync does, so I am going to check it out as soon as I finish this)

I think that it's @lhorie decision and whatever is decided I will be ok with it. ( and a new release would be pretty nice too :smile: )

dead-claudia commented 8 years ago

The biggest hindrance to modularizing is that Mithril has so much coupling in core. I had to rewrite the Deferred implementation as well as make a breaking change in m.prop in #853 to actually decouple it from the rest. And high coupling results in bugs like #857, where you have tests pass but cause others to fail. In that case, m.route shouldn't affect the internals of m.mount or m.render.

Or to use an analogy, Mithril uses the stove's wiring to also power the TV and bathroom lights, even though they're all in opposite corners of the house. The only way to fix the wiring requires literally tearing down walls and replacing wiring, one stage at a time.

Do it all in one pass, though, and you've displaced the inhabitants. You can't ensure everything automatically works.

Ignore the problem, and seemingly unrelated things will stop working when a single breaker flips. Good luck with all the electrical and duct tape.

You want to make each room independent of the others? You first have to redo the wiring before you can even start separating rooms. Otherwise, you're not really making independent rooms. You're just placing arbitrary boundaries.


Or in other words, this will take a while to fix. I'm probably going to have to write a tool for this as well. (I'll make it more general to fill a tooling gap.) It'll require a build step on release, but I can figure out a way to make it not require a build step to test, using a JSON file or something for files.

On Thu, Nov 26, 2015, 13:43 Julien Bisconti notifications@github.com wrote:

@impinball https://github.com/impinball Thanks for summing that up. TL;DR

Build steps add complexity. Modules are great unit computation!

From my experience, build steps are a pain. Unless you're willing to include your own like lodash custom builds https://lodash.com/custom-builds which I've never used by the way because there is a dist folder with the unminified source and minified source + maps. To illustrate, look at the branch: [image: screen shot 2015-11-26 at 18 18 43] https://cloud.githubusercontent.com/assets/5487021/11428434/a64d00fc-946b-11e5-98d4-fe49bc5827e5.png It needs one branch just to keep track of all the different possible builds it has to make. It seems time-consuming to maintain. The problem with those build steps is that they are so custom it is difficult to change. It takes time to read the build code and look for what you need let alone understanding the app that uses those build steps. This means complexity and that doesn't fit in mithril mindset as I understand it. I am very much in favour of modularisation, I love those compact units (functions basically) that do one thing only but well. For example, look at some of the packages sindresorhus has developed https://github.com/sindresorhus?tab=repositories for inspiration. They are not so likely to change but they are easy to switch when they stay that small. Mithril is like that, a compact unit of auto-redraw [image: :wink:] But if some people more advanced found a way to please everybody and make mithril modular without adding a build step, then great, problem solved.

I use mithril for its simplicity and great documentation. Doesn't matter much to me if it splits in a few files or just stay in one. I will still use it. Mithril is so small that stripping a few kb just because I don't use all functions available sounds like a joke when most website don't optimize their images. Average page load is good 2 mb. If a project use a css framework like bootstrap, do they strip the 99% of code they don't use? Some people do, the rest didn't because it's adding more steps and complications to the build steps. Plus now you have to test the build to make sure everything is going smoothly. But most people don't write test in JS because it's hard to start and it hurts feelings!!!

So adding a custom build step sounds like a terrible idea, unless someone make it great (you never know).

This is a table taken from @nordfjord https://github.com/nordfjord presentation about mithril ( link to repo https://github.com/nordfjord/mithrilexamples ) Core Routing Data m m.route m.request m.prop m.route.param m.deferred m.component m.sync m.mount m.withAttr Rendering Html m.render m.trust m.redraw m.startComputation m.endComputation

Is that really too much of an api that we need to strip it down? I am using almost all of them (except request, route and sync, I don't really know what sync does, so I am going to check it out as soon as I finish this)

I think that it's @lhorie https://github.com/lhorie decision and whatever is decided I will be ok with it. ( and a new release would be pretty nice too [image: :smile:] )

— Reply to this email directly or view it on GitHub https://github.com/lhorie/mithril.js/issues/651#issuecomment-159975629.

pygy commented 8 years ago

While decoupling Mithril may be nice, it is not a prerequisite to use rollup, since it manages circular deps just fine.

dead-claudia commented 8 years ago

Still, would you prefer arbitrary modules, but with all the coupling and the inability to e.g. not include m.route because it relies on core internals, or the modules separated correctly? Quick and dirty works with smaller changes, but with complete design changes, quick and dirty results in fundamentally broken code.

On Thu, Nov 26, 2015, 21:26 Pierre-Yves Gérardy notifications@github.com wrote:

While decoupling Mithril may be nice, it is not a prerequisite to use rollup, since it manages circular deps just fine.

— Reply to this email directly or view it on GitHub https://github.com/lhorie/mithril.js/issues/651#issuecomment-160029107.

barneycarroll commented 8 years ago

Brace yourself for what may sound like heresy: shared references are exported as properties from a discrete internal 'model' module. The module is only imported internally and is not exposed in the public export. It would be possible for a consumer (using CommonJS) to import that module from the file system and mess with these properties if they really wanted to, but they'd have to go out of their way to do so.

Even if this leaves a bad taste in people's mouths, it might at least help to isolate and rationalise the shared internal model and map dependencies — it could be a valuable interim to refactoring the 'one big closure' nature of Mithril.

You may be thinking it's not as easy as all that because some of these variables need initialisation and persistent reference, but we can get around that with closured getter / setters, ie custom m.props.

pygy commented 8 years ago

That's also where I was coming to when I said it would make it easier to navigate the source. In the process, you'll document internal dependencies.

re. initialization/persistent references, I haven't been in Mithril's source for a while, but unless you must mutate said references from another module, ES6 modules support it out of the box

pygy commented 8 years ago

Another great thing about Rollup is that rolled up test suite could import and test otherwise private and previously untestable parts of Mithril.

Edit: I mean, without any extra boiler plate in the final package.

veggiemonk commented 8 years ago

@impinball Now I understand why it is important to make mithril modular. I didn't really understood it until I tried to modify mithril's code myself. Sorry for my intervention, I was reasoning with only ideas and i think now it is important to make mithril modular in order to allow further improvement and extension such as es6 modules.

lhorie commented 8 years ago

This is in the roadmap, so I'm going to close this issue for now