Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

BTI (Build Time Instructions) + LAZY LOAD proposal #312

Closed
nchanged opened this issue Feb 25, 2017 · 25 comments
Closed

BTI (Build Time Instructions) + LAZY LOAD proposal #312

nchanged opened this issue Feb 25, 2017 · 25 comments

Comments

@nchanged
Copy link
Contributor

BTI

BTI stands for Build Time Instructions and will allow users to split the code base painlessly A developer will be allowed to define the rules in the code, which will be analysed by FuseBox. Config can be involved for the sake of verbosity / for advanced users.

But the primary goal here is to make as simple as possible without functionality sacrifices.

Implicit case

Imagine a structure
screenshot 2017-02-25 23 26 41

In this case, FuseBox will understand that these 2 files should be bundled beforehand and ignored by the primary. Therefore it will create instructions as follows:

bundle(`[components/Bar.ts]`)

Lazy modules will be isolated by default as it's important to make them lightweight. A configuration will be copied from the primary config, adding standalone : false (not implemented yet) to it.

A filename will be generated (hash) and put next to the primary bundle. In our case, we will have 3 files.

- bundle.js
- 3e83n3.js
- 9ek8ejk.js

FuseBox will be able to map components/Bar.ts to 3e83n3.js file since the information will be baked into bundle.js (primary)

FuseBox will be able to check which files are already in the primary bundle, however, one should move to the advanced mode (below) when having a non-trivial case.

Explicit case

To allow customisation, one would define keys that start with @, which will be mapped to the primary configuration.

screenshot 2017-02-25 23 37 15

An advanced configuration would look like:

FuseBox.init({
    bundles : {
        "Bar" : {
            instructions : `
                + [components/Bar.ts]
                + path
                - fs
            `,
            plugins : [],
            import : "components/Bar.ts",
            // where to write
            outFile : "build/bar-{hash}.js",
            // how to resolve at runtime
            resolve : "bar-{hash}.js",
            // override any configuration key if needed
            plugins : [JSONPlugin()],
            // watch
            watch : "some path to re-trigger"
        }
    }
})

Or like this:

fuse.addBundle("name", {
})

Simpler:

fuse.addBundle("name", ">[components/Bar.ts]")

Building

In both cases, FuseBox will launch a separate process per instruction, AFTER the primary build is complete to ensure files uniqueness (We should avoid duplicates). HMR will support generic functionality to flush and reload affected scope.

For Production

fuse.createBundles()

In the case above FuseBox will create bundle @Bar.

You could continue using FuseBox.import("./filename) or FuseBox.lazy("@bar")


Your ideas are invaluable, please express your concerns, suggest ideas!

@devmondo
Copy link
Member

i believe for the advanced mode above we should be able to cascade shared config like plugins,etc... to all bundles instead of just redefine them in each bundle

@nchanged
Copy link
Contributor Author

@devmondo all options are copied! But you can override them!

@devmondo
Copy link
Member

@nchanged perfecto!!!! that is the behavior i was trying to suggest

@wagerfield
Copy link
Contributor

This is really freaking nice. Suggestions are as follows:

  1. Wherever FuseBox is used/referenced within a file like in index.ts above—it should be imported/required like any other module import FuseBox from 'fuse-box'. I think this should be enforced where possible. I don't much care for FuseBox being implicitly available in scope like window or process—but perhaps that's just me! Main reason for this is if someone decides to migrate from FuseBox to another build tool in future, fuse-box could be uninstalled from package deps and then any fuse-box import errors would be caught at build time as opposed to runtime—which is much safer.
  2. In the config passed to fsbx.init(), it would be nice to specify how bundles are named, so that hashes could be optionally used for different environments and also used for the entry/primary bundle. A few suggestions below:
// Suggestion 1
fsbx.init({
  homeDir: './src',
  outDir: 'dist',
  outFile: '[name]-[hash]' // Could just be [name] for dev or just [hash] for prod or whatever
}).bundle('>[index.ts]')

// Suggestion 2
fsbx.init({
  homeDir: './src',
  // Having an output object might be a little more future-proof 
  // as/when more configuration options are added?
  out: {
    dir: 'dist',
    name: '[name]-[hash]'
  }
}).bundle('>[index.ts]')
  1. Code splitting with hashed file names will absolutely need a way of spitting out a manifest.json file that maps modules to file names:
{
  "index.ts": "index-sdg3sj.js",
  "components/Bar.ts": "Bar-as728d.js",
  "components/Foo.ts": "Foo-jff82p1.js",
  "some/image.jpg": "image-r5k3sj.jpg",
}

Appreciate that FuseBox takes care of mapping modules to the hashed file names inside the primary bundle, but it's really useful to be able to extract this information for things like server-side rendering. If the primary bundle were to have a hashed name so that it could be cached, this manifest file would be required by the server to reference the latest primary bundle when creating the script tag.

@leon-andria
Copy link
Contributor

leon-andria commented Feb 26, 2017

Fuse Code Splitting proposal.pdf

@wagerfield I fully agree with. I share again my proposal on this way.

I see an issue to ask someone to have the API FuseBox.load, this will lead to a big confusion about when I use this or not and what is the best practices.
We should keep as much as possible the current workflow that is standard, because imagine you don't have fuse or not optimized as bundle, your code should still work as usual.
For me, imposing at the start point you as a developer, you need below requirements, this is not right.
I believe that bundling should completely driven outside of the code. When you work with a Team, Dev, Architect, DevOps, some of these people can build some kind of "Orchestration" of their application will be deployed in such environments, and doesn't require someone to look inside the code which piece need lazy load or whatever.
In addition, the moment we write in code, this means that your packaging design strategy is strongly depends on your code, any changes means rebuild of your entire application.
As @wagerfield highlighted about manifest for SSR usage, this is so important and I have pointed it on my proposal. We are creating framework and product on that way, and it's really shame not to think about this as it's open another horizon of design, and it's futur bulletproof.
The pdf tells more about my requirements.

@leon-andria
Copy link
Contributor

On the pdf, i didnt mention some ideas to avoid to polluate the intended goal.
But imagine that you have your chrome extension that that tells you all about your application behaviors? A kind of inspector and the ext. give you the feature to organize your packaging for instrumentation.

@geowarin
Copy link
Contributor

geowarin commented Feb 26, 2017

@nchanged I like the idea of implicit bundles and the addBundle API.

I'm not sure I fully understand the rest of the proposition. It also seems that you threw in some content hashing that I believe is not directly related to code splitting.

I'll describe my use case: code splitting + server side rendering with react, greatly inspired by what next.js is doing.
The idea is to bundle common dependencies in a common bundle (mostly libs - webpack has the common-chunk plugin which makes it easy).

The code executed on the server is a bundle (renderer.ts) containing function that calls ReactDom.renderToString().

The client (client.ts) code contains ReactDom.render() and is used only on the client and for HMR.

There is a special directory pages that contain all the routes (simple react components)

Routing is handled internally with FuseBox.import('@pages/page1.jsx').

Ideal API:

const fb = fuse.init({
// common config
  homeDir: `./src`
})


fb.addBundles('pages/[name].js', '[pages/*.tsx]')
// notice the plural 

fb.addBundle('renderer.js', '[lib/renderer.ts]', {serverOnly: true})

fb.addBundle('client.js', '> [lib/client.ts]', {clientOnly: true})
// currently I need to wrap code in `!FuseBox.isServer`)

fb.addBundle('common.js', '+react +react-dom')
// ideally fb.addCommonBundle('common.js')

fb.watch()

@wagerfield
Copy link
Contributor

A number of different things being discussed in this thread, so let me chime in on them separately.

Environment Flags

@geowarin what do the serverOnly and clientOnly flags do exactly? Having server/client configuration as boolean flags limits the scope of FuseBox when dealing with different environments because it doesn't cover all bases.

Instead, I propose that FuseBox adopt something like webpack's target field which can be set to a string/enum like 'node', 'electron' or 'browser' and then could be used conditionally within source code with something like process.env.TARGET as opposed to FuseBox.isServer—as well as configuring how modules are bundled/not bundled by FuseBox internally.

const fsbx = new FuseBox({
  target: 'browser' // default, so not necessary here
})

fsbx.bundle('>[client.ts]') // will use 'browser' target

fsbx.bundle('[server.ts]', {
  // this object simply overrides any values set in fsbx.init()
  target: 'node'
})

Code Splitting

Thinking about it a little more, I agree with @leon-andria and @geowarin —code splitting should be offloaded to FuseBox's config and kept out of the source code. There are simply too many complex cases that cannot be resolved when code splitting in the way you proposed above @nchanged.

Consider webpack's Common Chunks Plugin—this is completely dynamic way of code splitting your app based on the number of times a module is imported/required. This could not be done within the source—it would have to be done in the config.

It would seem that the main incentive for handling both code splitting and lazy loading with the same mechanism using a proprietary FuseBox API is to make dynamic configuration of code splitting "easy" and to allow HMR to work. These are both compelling reasons, but when you consider how infrequently you configure code splitting in the real world (such as when adding new third-party libs to a vendor/common.js bundle or branching off heavy/infrequently viewed pages in your app), I don't think it should be a driving factor.

Instead, I propose that when running FuseBox in dev mode with the devServer or watch mode as @geowarin suggested (which I like)—would it be possible to watch the fuse.js file with your FuseBox config code in it and simply restart FuseBox dev server whenever it changes? That would give us the best of both worlds—dynamic configuration of code splitting (which would allow HMR to work) and also move the code splitting out of the source and into the config.

Lazy Loading

Lazy loading should adopt proposals from the official ES spec rather than use a proprietary FuseBox API. Again, this would mean that source code is kept decoupled from the build tool.

@nchanged as we have discussed on Gitter, there is is an ES6 proposal for an import() function to be implicitly available in scope when using ES6 modules. Details on this proposal can be found here.

This would mean that you can both statically import modules using import SomeModule from 'some-module' as well as dynamically load modules at runtime using import('./some-module.js').then(module => ...). The API of module would need some thought (this isn't covered in the spec proposal I linked to).

Using the import() proposal could work in exactly the same way as you proposed above (though it would use Promise as opposed to a callback). import() could simply be added to the FuseBox runtime—no more FuseBox.import or FuseBox.load.

The mapping between dynamically loaded modules and their hashed bundle names is where FuseBox could really shine. Since FuseBox knows about all of your bundles as well as their hashed names, you could dynamically import them with friendly bundle names and FuseBox takes care of loading the hashed bundle names under the hood:

// fuse.js
const fb = FuseBox.init({
  homeDir: 'src', // this could be 'inputDir' to compliment the output object I/O ftw :)
  target: 'browser', // not needed here since 'browser' would be default—just for demonstration purposes
  output: {
    dir: 'dist',
    name: '[name]-[hash]', // bundle file name format with placeholders like [name] and [hash]
    path: 'https://cdn.foo.bar/some/path/' // prefixed to all dynamic imports
  },
  manifest: 'manifest.json' // spit out a file that maps all friendly bundle names to their hashed file paths
})

fb.add({
  'client': '>[client.ts]',          // dist/client-sdf561.js
  'pages/home': '[pages/home.ts]',   // dist/pages/home-ksdg02.js
  'pages/about': '[pages/about.ts]', // dist/pages/about-iw7dns.js
  'pages/feed': '[pages/feed.ts]',   // dist/pages/feed-yrn655q.js
  'pages/*': '[pages/*.ts]',         // perhaps the above 3 lines could be written with a wildcard as @geowarin suggested
  'vendor': `
    + react
    + react-router
    + redux
    + redux-react
  `
})

fb.add({
  'server': '>[server.ts]'
}, {
  target: 'node' // config overrides could be passed as an optional second arg
})

fb.watch()

----------------------------------------

// routes.js
function loadPageAsync(pageName) {
  import(pageName).then(module => {
    // do something with 'module' to require/import the loaded module
  })
}
// some URL matching logic...
loadPageAsync('pages/home')  // notice that there is no file ext here
loadPageAsync('pages/about') // these are just the friendly bundle names
loadPageAsync('pages/feed')  // replaced by `${output.path}${output.name}.${ext}`
// import('pages/feed').then(...) would become:
// import('https://cdn.foo.bar/some/path/pages/feed-sdf561.js').then(...)

Sorry for the essay!

@cellog
Copy link
Contributor

cellog commented Feb 26, 2017

On first look, my mind says the code-based approach to code splitting looks really cool. But my intuition has 4 or 5 alarm bells going off.

Basically, when I sat down to figure out why, I came to one simple conclusion: magic is really dangerous in code. If there is no obvious link to the bundling, or how code is split up, it becomes difficult to maintain, especially for a newcomer to the code.

Imagine Joe writes a huge project with implicit code splitting, defined by a few Fusebox.load() calls in code. Joe moves to Google, and Jane takes over. Jane is unfamiliar with Fusebox, sees the bundle configuration, and thinks ok cool, I've got this. She is digging in the source, and sees the Fusebox.load() call and makes a minor change, replacing it with import because she likes standards. Suddenly, the production code load time goes through the roof, nobody knows why, users start fleeing the app, and the company goes under. Don't you love melodrama? :)

So, maintainability is sacrificed with this magic. Now, another thing: if you force users to use fusebox in their code, it ties them to fusebox and also removes flexibility to change the fusebox API. I have the same problem with require.ensure in webpack. Put another way, if your app changes down the road, and you reshuffle which file is bundled where, You'd want to be able to handle that in the bundle configuration, and not care in the source.

Testing code that has dynamic imports is tricky. Providing a synchronous version of the loader for testing purposes would be awesome. The most obvious way to do this is to use standard require syntax for loading, and rewrite this when bundling.

To summarize:

Code splitting is a bundling concern, and it would be great to keep the code separated from those concerns.

Do code split definitions using an explicit section of the bundle configuration. This allows doing fancy stuff like splitting off most-used code, or simply splitting by page content, or splitting by file system, or thing-we-have-not-yet-thought-of.

Then allow the Javascript to focus on simply stating unconditional dependencies (top-level import) and conditional dependencies (other imports/require) and have fusebox rewrite them after splitting is set up.

The bundler is already doing magic by allowing separate files that will be bundled to pretend they are separate, this would just augment that. For me, this kind of magic is acceptable, because there is no ambiguity about what is happening. The code says it wants a dependency and then the dependency is there. Even bugs in fusebox related to this are easy to isolate to a single line as the source of the bug.

Magic that can be debugged easily by an outsider is OK, in my book. Hope this perspective is useful.

BTW for a case study in magic out of control, check out Meteor. They designed it so that you can't easily pull in outside sources. For example, wallabyjs is impossible to configure with meteor, unless you literally use about a thousand lines of mocking code. You can only test with their built in testing framework, which is very slow. The problem comes down to the way they decided to implement import. If you import "meteor/anything" it will instead magically rewrite that to including a meteor package (which is different from an nom package). This is why a huge stub has to be written to support wallabyjs.

In the end, the time saved by having that magic is offset by the time wasted trying to work around its unintended consequences. I'd love to see fusebox avoid that kind of design flaw.

@devmondo
Copy link
Member

@cellog I like melodrama :)

if you force users to use fusebox in their code, it ties them to fusebox and also removes flexibility to change the fusebox API.

Code splitting is a bundling concern, and it would be great to keep the code separated from those concerns.

Fully agree!

@devmondo
Copy link
Member

@wagerfield regarding webpack's commons-chunk-plugin agreed or not, we don't find it intuitive at all, and we should definitely solve this in FuseBox in a better way!

@devmondo
Copy link
Member

regarding import statement with a promise to follow the specs, we fully agree but the problem is that typsceipt will blow up on this

import("hello").then(module => {

})

So our proposal is to convert FuseBox.import to return a promise now to align with the specs, and at the same time we offer the spec proposed syntax so when it is supported by typescript everyone can move to it.

@nchanged
Copy link
Contributor Author

I love our community! Quite compelling arguments you got here guys.

import("module").then(module => {})

import Will be available in javascript. But for typescript, (for the time being)

FuseBox.import("module").then(module => {})

@wagerfield
Copy link
Contributor

wagerfield commented Feb 26, 2017

Great, so we're all agreed on lazy loading using the proposed import() spec.

Issue now becomes—what's the best way for dynamically importing modules/bundles that have both a hashed file name as well as some arbitrary path to them (such as a CDN).

import('foo.js').then(module => {...})
// Needs to become
import('https://cdn.foo.com/path/foo-sdgi75.js').then(module => {...})

I proposed a solution above with my output object in the init config object. What do people think of this?

@cellog
Copy link
Contributor

cellog commented Feb 26, 2017

My alarm bells are going off about import().then(). Remember when everyone was using @decorators? And the spec shifted and now all that code is obsolete. I'd rather not plan for future until it's sure. That's how one shoots oneself in the foot.

I do have another idea, but before I do, some background would be useful. I've been using react and redux a fair amount recently, and for asynchronous stuff, redux-saga. Redux-saga solves the asynchronous nesting hell of both callbacks and promises using generator syntax. For those unfamiliar, it's similar to async/await syntax. If you don't know that, basically it allows using asynchronous code as if it were synchronous. This principle can be used for conditional includes.

function thing() {
  const it = require 'something' 
  
  it.shouldWork()
} 

If the code is loaded asynchronously, the above code fails, of course. But using generator syntax, we can pause execution and resume when ready, just like a promise or callback, but without the nesting:

function *thing() {
  const it = yield require 'something'

  it.shouldWork()
} 

The fusebox code would need to call the generator, and on anything yielded, check to see if a promise is returned. If a promise isn't returned, then it immediately resumes execution with the result (synchronous require). Otherwise it attaches a then, and returns control to the main code. Upon resolution of the promise, it resumes execution with the result.

There are a couple major issues with this idea. First, if we change a function definition into a generator, it will break all code that uses that function. So fusebox would have to instead create a wrapper function and a call to the generator Handler. This should work OK for 99% of circumstances, but would increase complexity of the run time. The bundler would have to detect all require statements that could be conditional and wrap them in the generator. Of course, this is mitigated by the fact that it would only be necessary to do this if the user explicitly requests code splitting.

In any case, with this idea, the code would not need to know about code splitting. Fusebox could simply wrap any function that contains an import/require, and pop a "yield" in front of the require, and it would create instant asynchronous loading support without needing to change the original source.

Let me know if I need to clarify this idea!

@leon-andria
Copy link
Contributor

leon-andria commented Feb 26, 2017

@wagerfield this is solved by aliasing because, in theory, you never put the CDN path but the key.
So like I mention in my pdf, fusebox acting like a reverse proxy or sentinel is the key to dispatch requests to import.

Fusebox will know exactly what to do based on the manifest it has.
As we all agree here, a well-known config can any time shape and reshape your bundle strategy.
Now, about the target (production, pre-prod, staging,...) this is just a kind of orchestration.
Fusebox could have a file .fusebox-env where env is replacement by your current env. Like a la .net core.

So you can have .fusebox-prod, .fusebox-stg, etc.. and these files are watched for any updates.
Going more further with hot module reload with web sockets, we can do multi-directional updates from browser and config. In addition, if we have a great chrome extension, you can in real time inspect your fusebox runtime and shape it the way you desire.

What is really important is that fusebox should be flexible and focuses on what it does best. Extensibilities are keys because each one has is own use cases simple or complex.
We spoke less here about fusebox events but believe me, its the secret key!

@nchanged
Copy link
Contributor Author

nchanged commented Feb 26, 2017

@cellog I love the idea!

But you can't use import statement other than in the file's head. require statements can be wrapped. And now a tricky one:

We can't use generators. Some people target their code to run in IE. Transpiling? Yes. But a single generator targeted for es5 generates just an enormous amount of code.

Wrapping all require statements will add an unnecessary overhead if we want to make it compatible with everything.

However, fusebox could detect that a require statement is used to load a bundle (we know it from the config) for example, and wrap underlying code with a promise. And remember we have typescript and babel. And it's all should be the same.

Sure, generators are nice but less straightforward. Some people would want just a promise.

And yes, it goes sideways with the official es6 proposal. But it's nice. Wonder what other people think.

@cellog
Copy link
Contributor

cellog commented Feb 26, 2017

I started out writing this response by agreeing with you, but by the time I got to the end, I thought of a condition that shatters the entire idea, and I have a new proposal at the bottom. If you are willing to follow the journey of how I got there, it will make more sense, I think, so here's the story.

If we do use promises, it would transform something like:

function thing() {
  const it = require('something')

  it.shouldWork()
}

into:

function thing() {
  require('something').then(result => {
    const it = result

    it.shouldWork()
  })
}

but we run into a snag if the function expects to return a value from the conditionally loaded file:

function thing() {
  const it = require('something')

  return it.shouldWork()
}

const x = thing()

we need to be able to suspend all execution until the required thing is loaded. Most people implement this in their router, of course.

Perhaps another way to look at this problem is how to keep things easily separated so coupling is minimal?

  1. code should just declare dependencies, using import or require, so that errors with missing dependencies are easy to find at build and at run time for dynamic requires
  2. bundling should do any code splitting/generation of multiple bundles/rewriting source code to do run-time requiring properly.
  3. asynchronous code loading should happen in a routing context. The bundler should provide a preferred method of asynchronous loading, either by documentation or by code.

In other words, I'm contradicting my last idea :). Here's what I'm proposing:

  1. fusebox detects conditional requires, and if any are found, makes sure that the error message clearly states which bundles are not yet loaded if a file is not present. run-time debugging of missing dependencies becomes easy.
  2. fusebox detects requires of static, unbundled files, and errors out at build time
  3. fusebox provides a mechanism for adding files to the runtime, and for unloading files. also for adding or removing bundles at run time, so that require will work.
  4. fusebox provides a helper convenience method for asynchronous loading of code that is compatible with both development and production so the same code can be used on both, with support for HMR and others.

What this means is that code should literally not try to do any asynchronous loading of other code. Instead, expect the user to implement that in their router (or equivalent state management system), and provide easy 1-liners to load/add code to fusebox. This way, the code is consistent, and isolated from how it gets loaded, making both debugging and testing simpler. Loading can be tested as part of the router tests, which is simpler, and easier to maintain. No need for injection of promises or generators (as clever as all that feels) into the source. The source simply replaces require/import as it does now.

This will be far more maintainable in the long run, and make it easier to re-tool both fusebox and the end-user code if things change in the future.

@devmondo
Copy link
Member

devmondo commented Feb 26, 2017

guys, awesome suggestions, and proposals. but let us think for a moment here about spec compliance.

we all know that the specs are driven by people we don't have much control over (politics and power!). and history proved that they would change or push things down our throat even if we don't agree with.

That said, we think the best approach would be to align with specs if they offer a stable, set in stone features. otherwise, we are just wasting time and resources on proposals that might not even make it past the editor they were written in.

Another point to be highlighted is the fact that specs will never be as fast as us, we can't wait, and if we did so, none of the advanced and awesome FuseBox features would have been here. for example, how long would it take for them to give us something like FuseBox Dynamic Modules? we think you get the picture :)

we introduced FuseBox because we were tired of the typical approach, and just because most of us out there use specific tools or trends, it does not necessarily mean it is the best approach, and this is how we conceived FusBox because we wanted to think outside the BOX :)

To summarize it up, if there is a feature you guys (the community) see it needs to be implemented, we will listen to you first and not the specs! unless we all agree that the specs and your requirements align. We want to empower you and we want you to enlighten us, it is true that there is establishment behind FuseBox but it was decided with the first line of code we wrote that it is a community product and it shall always be this way. so please please please keep giving us your awesome ideas here, we want them and we can't live without them, you all Rock 💯

@wagerfield
Copy link
Contributor

wagerfield commented Feb 27, 2017

@cellog @devmondo totally agree about being cautious with non-stable specs while not wanting to wait around for them to solidify. ES6/7 wouldn't be where it is today if it were not for languages like CoffeeScript and transpilers like Babel experimenting and driving things forwards.

@cellog I think I understand your latest proposal and it sounds sensible—though I would like some clarification on how you see FuseBox 'detecting' conditional require statements? I completely agree with:

magic is really dangerous in code

Transpiling should be opt-in. When using FuseBox with vanilla JS (not TS) with no transpiling plugins like Babel, bundled source code should look and behave the same par the require sugar. As @cellog expressed—this is the most predictable, debuggable behaviour.

Re. generators and promises, I now agree with @nchanged—adding polyfills for generators and the like adds unnecessary bloat to the FuseBox runtime that might not be used by the majority.

So if we don't want to adopt non-stable specs like import() and we don't want to perform magic on user-land code, then what we're talking about is a utility library like any other as @cellog suggested in point 4 of his proposal:

FuseBox provides a helper convenience method for asynchronous loading of code that is compatible with both development and production so the same code can be used on both, with support for HMR and others.

In places where a user wants to dynamically load files or bundles they would do so with helpers from the fusebox library within their code. However, FuseBox should be imported/required like any other library.

// This is really important
import FuseBox from 'fusebox'

function loadPage(pageName) {
  // This is what you already have I think
  FuseBox.load(pageName, (module) => { // No Promise here, no polyfill needed 
  })
}

Taking this approach would mean that your project source code now has a dependency on fusebox. Not a devDependency—a dependency.

This would allow FuseBox to develop it's own API and it's own ways of doing things—experiment and provide cool new features. As a developer you can then chose to use it in your source code, or not use it and do your own thing with Promises and HTTP requests or whatever.

The advantage of making fusebox a dependency in your source code is that it can be replaced with something else should you decide to migrate to another build tool or another way of loading files and bundles in the future (such as using a stable spec).

The big question for me is:

How do we handle the mapping between friendly bundle names and their respective file paths?

FuseBox.load('some-bundle', module => {
  const SomeModule = require('some-bundle')
  // or
  const SomeModule = FuseBox.import('some-bundle')
})

// We need to map 'some-bundle' to 'http://cdn.foo.com/some-bundle-yw7ja1.js'
FuseBox.load('http://cdn.foo.com/some-bundle-yw7ja1.js', module => {...})

FuseBox bundler would still need a way of spitting out this manifest.json file that has been mentioned so that devs wanting to load bundles their own way could require/load this manifest files and then load the bundle file by doing something like:

const manifest = require('manifest.json')

fetch(manifest['some-bundle']).then(response => {
  // Do something with the response and 'require' the loaded bundle
})

@nchanged
Copy link
Contributor Author

nchanged commented Feb 27, 2017

thanks @wagerfield all good points.
Now I would go for import for javascript and FuseBox.import for typescript (for now)

How about:

import("awesome").then( module=> {}) // npm package
import("@awesome").then( module=> {}) // for pretty names
import("./actualfile.js").then( module=> {}) // file system
import("> actualfile.js").then( module=> {}) // FuseBox magic

However @awesome could be a valid NPM name as well. Open for suggestions.

@leon-andria
Copy link
Contributor

leon-andria commented Feb 27, 2017

Everyone, these are some really valid concerns and great suggestion.

I may sound little bit critical here, but I feel we are maybe deviating from the main goal and the required end result. We are talking a lot about WHAT but not much on HOW.

We should continue to elaborate more on how FuseBox internally will handle and resolve modules or packages. The more we provide insights to the Core Team the better and more transparent the approach would be. this would immensely allow them and us to have a better ideas of how to instrument a solid and straightforward API that fulfill the vast requirements for many of us out there.

The key point to remember here is that not everyone uses webpack nor everyone have the same pipeline or set of standards to build their projects, to be more precise, we need to take into consideration that some would start immediately with FuseBox and would not to think at all about other techniques or methodologies out there, thus it is imperative to keep the personality and philosophy of FuseBox standout, at the same time i Do agree that we need to follow the Specs whenever it is possible but again, not on the expense of having the ability to be flexible and pragmatic.

Personally I really want to talk about FuseBox events, metadata, or manifest and what FuseBox bake in as an abstraction to consume its API. coming from .NET and JAVA and working on a day to day basis with fortune 500 companies i can assure you that the image is not always easy on the eyes. and what JavaScript has achieved yet is not fully compatible with those environments, but with FuseBox i was able to start breaking this wall now! and my team is looking forward to make the transition, so i will try my best to chime in on this from that perspective as much as possible.

thank you all 👍

@cellog
Copy link
Contributor

cellog commented Feb 27, 2017

Quick answer (hopefully):

Detecting conditional require. One could of course parse and tokenize the source, or make a babel plugin, but the way I would detect it is with a simple convention: top-level import and require are like so:

import blah from 'blah' 
var blah = require('blah') 

Whereas conditional are indented. So look for whitespace before the declaration, and there you go.

@nchanged
Copy link
Contributor Author

@cellog we already do analysis on each file. Whitespace often can lead to errors and confusion. I don't think that's the best approach here.

@nchanged
Copy link
Contributor Author

Closing this for now. Please feel free to check the latest bleeding edge version fuse-box@2.0.0-beta.8
and documentation for it http://fuse-box.org:3333

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants