Skip to content

Instantly share code, notes, and snippets.

@caridy

caridy/README.md Secret

Last active August 29, 2015 14:11
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save caridy/46f81be648b7ae4d3164 to your computer and use it in GitHub Desktop.
Save caridy/46f81be648b7ae4d3164 to your computer and use it in GitHub Desktop.
Bundling vs Formatting

Bundling vs Formatting

Proposal

  • bundling should be a separate process (completely optional).
  • bundling should produce a ES6 module with import and export statements that defines the vortex of the module graph that forms the bundle.
  • resolvers should be configurable so we can bundle things that can be resolved, e.g.: import "./foo.js" or import "intl-messageformat-parser", if we have a resolver that understands how to resolve the ES6 implementation of module intl-messageformat-parser.
  • formatters become a 1-1 mapping, without variable renaming or any other artifact associated with bundling.
  • formatters transform export and import statements for the target format (commonjs, browser/global, system-register, amd, yui, etc.).
  • formatters don't need to validate input and exports anymore, they just do a blind transformation of them.

Examples

Two basic examples, the first one that demonstrates the different steps to bundle 3 files, and then apply CJS and Browser formatters to output production code. The second one, in the other hand, bundles two files, but imports from an external module, while the process remains the same.

First

main.js ──> bundle [a.js] ──> bundle [b.js] ──> output [bundle.js] ──┬──> transpile [cjs.js] 
                                                                     └──> transpile [browser.js]

Second

main.js ──> bundle [a.js] ──> output [bundle.js] ──┬──> transpile [cjs.js] 
                                                   └──> transpile [browser.js]

Workflow

Bundling introduces a performance penalty due to its nature of N-1 file transformation. Even if the bundling process is quick, post processing the bundle with esnext and/or transpilation will have a huge cost for complex applications. The proposal to avoid this performance penalty is to pay a toll by using a loader during development, and using the bundling process only to produce production code.

Bundling

[entry point file] -> [bundle-up resolved ImportDeclarations and ExportFromDeclarations] -> [output file]
  • ImportDeclarations and ExportFromDeclarations that cannot be resolved should be ignored. Potentially renaming their identifiers.
  • ExportFromDeclarations from non-entry points should be transformed into ImportDeclarations (see open questions for more details about this one).

Validation

[file] -> [validate resolved ImportDeclarations and ExportFromDeclarations] -> [file]
  • The validation process should rely on resolvers to locate other ES6 implementations (or equivalent) to statically verify everything that the file is trying to import from other modules.

Transpilation

[file] -> [transform ImportDeclarations] -> [transform ExportDeclarations] -> [output file]

This can be done by doing a simple transpilation process in a 1-1 fashion, producing CJS or System.register() output. Transpiling each file individually will means:

  • only export and import statements has to be transformed (not need to do recursive analysis on the AST).
  • import/export validation can be skipped, and developers can rely on the runtime execution to catch errors (for the sake of a fast build).

Open Questions

  • Do we need a single entry point (main.js)? What if we have more than one entry point? What will be strategy if more than one exports? What if there are conflicts?
  • How to specify the namespace CustomNameSpace for browser formatter? Maybe a mapping in package.json?
  • Where should we validate imports and exports? Should the import/export validations happen after the bundling process?
  • What if a non-entry point uses ExportFrom clause for an external dependency? How can the bundle manipulate this is such a way that the bundle itself does not re-export that? Does this means we really have to have a distinction between entry points and non-entry points? e.g.: second > b.js doing export {xyz} from "recast";
// a.js
import {something} from "./b.js";
export default function () {
something();
}
// b.js
export function something() {}
// main.js
import a from "./a.js";
export default a;
export function extra() {}
console.log(a);
//-------------------------
// bundle.js
function b$something() {}
function a$default() {
b$something();
}
export default a$default;
export function extra() {}
console.log(a$default);
//-------------------------
// cjs.js
function b$something() {}
function a$default() {
b$something();
}
exports = module.exports = a$default;
Object.defineProperty(exports, 'default', {value: a$default});
function extra() {}
Object.defineProperty(exports, 'extra', {value: extra});
console.log(a$default);
//-------------------------
// browser.js
(function() {
function b$something() {}
function a$default() {
b$something();
}
window['CustomNameSpace'] = a$default;
window['CustomNameSpace'].default = a$default;
function extra() {}
window['CustomNameSpace'].extra = extra;
console.log(a$default);
}).call(this);
// a.js
import {render} from "react";
export default function () {
render();
}
// main.js
import a from "./a.js";
export default a;
export function extra() {}
console.log(a);
//-------------------------
// bundle.js
import {render as a$render} from "react";
function a$default() {
a$render();
}
export default a$default;
export function extra() {}
console.log(a$default);
//-------------------------
// cjs.js
var a$render = require('react').render;
function a$default() {
a$render();
}
exports = module.exports = a$default;
Object.defineProperty(exports, 'default', {value: a$default});
function extra() {}
Object.defineProperty(exports, 'extra', {value: extra});
console.log(a$default);
//-------------------------
// browser.js
(function() {
var a$render = window.React.render;
function a$default() {
a$render();
}
window['CustomNameSpace'] = a$default;
window['CustomNameSpace'].default = a$default;
function extra() {}
window['CustomNameSpace'].extra = extra;
console.log(a$default);
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment