Giter Site home page Giter Site logo

browser-pack-flat's Introduction

browser-pack-flat

Bundle browserify modules into a single scope, a la rollup.

Caveats:

  • Modules are executed fully, one after another, instead of inline. This is a potential difference from Node.js and the default browserify behaviour. Usually this does not matter, but rarely the order that some things are executed in may change.
  • This rewrites require() calls to simple variable assignments. If a module wraps require() somehow it probably will not work. In practice this is quite rare.
  • Using factor-bundle to split output code into separate files will not work with this plugin.

Install

npm install --save-dev browser-pack-flat

Usage

browserify /path/to/app.js | browser-unpack | browser-pack-flat

Or as a plugin:

browserify /path/to/app.js -p browser-pack-flat

The plugin replaces the browser-pack module used by default by browserify.

With the Node API:

var browserify = require('browserify')
var packFlat = require('browser-pack-flat')

browserify({ entries: './src/app.js' })
  .plugin(packFlat, { /* options */ })
  .bundle()
  .pipe(fs.createWriteStream('bundle.js'))

What exactly?

browserify uses browser-pack to output a bundle. browser-pack uses a small require-like runtime and wraps modules in functions to get a module loading behaviour that's almost identical to Node.js. However this resolution can take a few milliseconds across an entire bundle.

Input:

var unique = require('uniq');

var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];

console.log(unique(data));

With browser-pack, this bundle would output:

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var unique = require('uniq');

var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];

console.log(unique(data));
},{"uniq":2}],2:[function(require,module,exports){
"use strict"

/* -- snip -- */

function unique(list, compare, sorted) {
  if(list.length === 0) {
    return list
  }
  if(compare) {
    if(!sorted) {
      list.sort(compare)
    }
    return unique_pred(list, compare)
  }
  if(!sorted) {
    list.sort()
  }
  return unique_eq(list)
}

module.exports = unique

},{}]},{},[1]);

browser-pack-flat instead rewrites require() calls and module.exports assignments to simple variables, and sorts the modules so that the module that would be executed first, is at the top of the bundle. It doesn't need a runtime in most cases, and no function calls to execute modules.

(function(){
"use strict"

/* -- snip -- */

function unique(list, compare, sorted) {
  if(list.length === 0) {
    return list
  }
  if(compare) {
    if(!sorted) {
      list.sort(compare)
    }
    return unique_pred(list, compare)
  }
  if(!sorted) {
    list.sort()
  }
  return unique_eq(list)
}

var _$unique_2 = unique

var _$main_1 = {};
/* removed: var _$unique_2 = require('uniq'); */;

var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];

console.log(_$unique_2(data));
}());

Instead of require('uniq'), the main module simply refers to _$unique_2, which is the exports value of the uniq module. The only function wrapper is the outermost one, which prevents variables from leaking into the window (global scope).

Sometimes it's not possible to sort modules in their execution order, because in the Node.js module system, a module can require another module that requires the first module: a circular dependency. browser-pack-flat addresses this with a small runtime, to lazily execute modules that are part of a circular dependency chain. This works similarly to how the Node.js module system works, and to how the standard browser-pack works too. Instead of rewriting require()s to variables and module.exports to a variable assignment, in "circular modules" browser-pack-flat adds a function wrapper. When a circular module is require()d, browser-pack-flat will call the function wrapper, which executes the module and caches the exports.

Below, a.js depends on b.js, and b.js depends on a.js:

// app.js
console.log(
  require('./a')()
)
// a.js
var b = require('./b')
module.exports = function () {
  return b()
}
// b.js
module.exports = function () {
  return require('./a').toString()
}

With browser-pack-flat, this becomes:

(function(){
var createModuleFactory = function createModuleFactory(factory) {
  var module; return function () { if (!module) { module = { exports: {} }; factory(module, module.exports) } return module.exports }
};
var _$a_1 = createModuleFactory(function (module, exports) {
var b = _$b_3()
module.exports = function () {
  return b()
}

});
var _$b_3 = createModuleFactory(function (module, exports) {
module.exports = function () {
  return _$a_1().toString()
}

});
var _$app_2 = {};
console.log(
  _$a_1()()
)

}());

The createModuleFactory helper returns the exports of the module it wraps, evaluating the module on the first call. Instead of replacing require('./a') with _$a_1 like browser-pack-flat normally would, it replaced it with _$a_1().

browser-pack-flat does some more things like rewriting top-level variables in modules in case there is another variable with the same name in another module, but that's most of the magic!

Related

License

MIT

browser-pack-flat's People

Contributors

brechtcs avatar dependabot-preview[bot] avatar edsrzf avatar goto-bus-stop avatar kgryte avatar ralphtheninja avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

browser-pack-flat's Issues

Is it possible to retain browserify's deduping functionality?

Thanks for the great module!

I got caught out by this when using a monorepo set up:

// browserify is clever about deduping modules with the same source code,
// but it needs the browser-pack runtime in order to do so.
// we don't have that runtime so this … re-dupes those modules.
if (dedupedRx.test(row.source)) {
var n = row.source.match(dedupedRx)[1]
var dedup = rows.filter(function (other) {
return String(other.id) === n
})[0]
row.source = dedup.source
}

Vanilla browserify was able to successfully dedupe because even though the paths were different, the content was the same, but this process discards that and so you end up with n copies of a dependency in a bundle where n is the number of times it exists on your file system.

I'm assuming arguments[4] is the require() function from the module wrapper? Is it possible to figure out which module it is requiring, put a placeholder to that module in and then reference it by its eventual variable name? e.g. _$validators_14.

Happy to figure out how to PR this but I want to check there wasn't a reason this was avoided in the first place. Cheers!

merge `module.exports=` assignments

currently a module like

module.exports = function () {
  // do something
}

ends up in the bundle as

var __module_1 = {};
__module_1 = function () {
  // do something
}

It'd be cool if it could be

var __module_1 = function () {
  // do something
}

instead.

Cannot declare a let variable twice

HI!

We're getting an error on iOS 10's Safari where when browser-pack-flat is packing flat it doesn't catch a redeclaration of a let bound variable.

I am trying to get a small scale case for replication. Oddly this doesn't occur in Safari on iOS 11.

api

should copy the normal browser-pack api

stack source maps

currently this plugin ignores source maps in the input files and only outputs a source map for its own transformations. default browser-pack uses https://github.com/thlorenz/combine-source-map to combine its own transformations with the source maps in input files, maybe that can be done here too (although this plugin does significantly more to the input files too)

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on all branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because we are using your CI build statuses to figure out when to notify you about breaking changes.

Since we did not receive a CI status on the greenkeeper/initial branch, we assume that you still need to configure it.

If you have already set up a CI for this repository, you might need to check your configuration. Make sure it will run on all new branches. If you don’t want it to run on every branch, you can whitelist branches starting with greenkeeper/.

We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on all branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because we are using your CI build statuses to figure out when to notify you about breaking changes.

Since we did not receive a CI status on the greenkeeper/initial branch, we assume that you still need to configure it.

If you have already set up a CI for this repository, you might need to check your configuration. Make sure it will run on all new branches. If you don’t want it to run on every branch, you can whitelist branches starting with greenkeeper/.

We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

b._options is undefined

I am trying to run plugin as browserify package transform and get

C:\projects\regl-scatter2d\node_modules\browser-pack-flat\plugin.js:6
    debug: opts.debug || b._options.debug,
                                   ^

TypeError: Cannot read property 'debug' of undefined
    at apply (C:\projects\regl-scatter2d\node_modules\browser-pack-flat\plugin.js:6:36)
    at nr (C:\projects\regl-scatter2d\node_modules\module-deps\index.js:301:23)
    at C:\projects\regl-scatter2d\node_modules\resolve\lib\async.js:45:21
    at onfile (C:\projects\regl-scatter2d\node_modules\resolve\lib\async.js:190:27)
    at onex (C:\projects\regl-scatter2d\node_modules\resolve\lib\async.js:104:32)
    at C:\projects\regl-scatter2d\node_modules\resolve\lib\async.js:24:24

It should not be difficult to fix I guess?

problem bundling hypercore: 'require is not defined'

please see issue 522 on choojs/bankai issue tracker here -> choojs/bankai#522 as well as POC with steps to reproduce here -> https://github.com/rhodey/bankai-hypercore-poc

hypercore has a conditional call to require('fd-lock'). browser-pack-flat is overeager about inlining it. fd-lock doesn't work in the browser, so hypercore should probably explicitly exclude it using the browser field in package.json:

"browser": {
  "fd-lock": false
}

but it's also something that should be addressed in browser-pack-flat. it doesn't currently try to check if a require call may be conditional.

I will bring this issue up in the Dat irc channel to share the package.json recommendation.

support more of module.parent

properly supporting module.parent is probably not possible, since the module that required the current module is executed after the current one, so we don't have a reference to the "parent" module yet.


Maybe when module.parent is used in if checks and other boolean contexts, browser-pack-flat could replace it with true. Or replace it with an empty object in all contexts.
this is done as of #17

ReferenceError: require is not defined

@ungoldman found a problem with the change in #38:

15:05 < ungoldman> final note, error seems to be originating from here
                   https://github.com/puleos/object-hash/blob/master/dist/object_hash.js
15:05 < ungoldman> https://usercontent.irccloud-cdn.com/file/6MtxkWve/Screen%20Shot%202019-05-24%20at%2012.05.22%20PM.png
15:05 < ungoldman> source map is broken so hard to find exact spot
15:05 < ungoldman> ok bbiab
15:06 < goto-bus-stop> ohhhh
15:07 < goto-bus-stop> ok i see it
15:07 < goto-bus-stop> i think :P
15:07 < goto-bus-stop> it's already a browserified bundle, and the browserify prelude looks for `require` and
                       tries to use it at runtime if it exists
15:08 < goto-bus-stop> but now i'm claiming it exists while it does not

browserify standalone bundles do

var externalRequire = typeof require === 'function'&&require

…which now breaks.

not sure what the best fix is, but it might be enough to simply add a check for the && require bit—default browserify and browser-pack-flat both use that pattern for external requires and we shouldn't muck with it. Worth checking out what webpack does (though i would guess they don't actually check for external require at all)

Import order is not preserved

The order of imports is not preserved when using this plugin. Ideally, imports would not have side effects that are visible to each other and this wouldn't matter, but in practice this isn't always the case. 😢

I know there can be differences in execution vs. Node (as noted in the README), but it seems like if all require calls are at the top of all files, it would be possible for execution order to be preserved.

Example

I have this index.js:

require('./one')
require('./two')
require('./three')

one.js, two.js, and three.js each contain a console.log line that logs the name of the file.

Node gives the expected output:

$ node index.js
one.js
two.js
three.js

Browserify with browser-pack-flat outputs:

$ browserify -p browser-pack-flat index.js|node
one.js
three.js
two.js

(Note that running Browserify without -p browser-pack-flat yields the same output as plain Node, with the import order preserved.)

node version: 10.15.3
browserify version: 16.2.3
browser-pack-flat version: 3.3.0

rewrite imports

var imported = require('elsewhere')
imported()

Currently imports are rewritten like:

var __imported_2 = __module_1
__imported_2()

But it would be shorter if imports were instead rewritten to the module variable they refer to

__module_1()

doesn't work if entry module is part of a cyclical dep chain

i tried to make index.js auto detect if it was used as a plugin, and then require('./plugin') if it was. but, that means index.js and plugin.js depend on each other. that didn't work. i think to make it work, browser-pack-flat needs to add calls to the cyclical entry modules.

Bundling into an ES module

I'm writing a new Browserify plugin called webrunify which wraps this one. Its purpose is to bundle a CommonJS module with all its dependencies into a single ES module, which can then be used directly in runtimes that support this. I'm planning to use it mainly for Webrun—hence the name—and Beaker. But it could be equally useful for other modern browsers or for people who want to port node modules to Deno.

To make this work, I've had to add two features to this plugin:

  1. An option to disable wrapping everything in an IIFE
  2. Make it possible to set sourceType = 'module'

I've prepared two separate branches implementing these changes, so we can discuss the implementations separately, if necessary.

cycles

// a.js
exports.b = require('./b')
exports.c = require('./c')
// b.js
module.exports = 10
// c.js
module.exports = 10 + require('./a').b
// result
(function(){var __module_1 = {};__module_1 = 10

var __module_3 = {};__module_3 = __module_2.a + 20

var __module_2 = {};__module_2.a = __module_1
__module_2.b = __module_3

module.exports = __module_2
}());

this doesn't work because the dependency is in a different place than it would be if it were executed by node. maybe a solution would be to inline it right before the first time it's used …

External module issue

I'm running into an issue when using external modules, such as when splitting code into separate app and dependency bundles.

Consider a trivial "Hello, world!" React application:

const React = require('react');
const ReactDOM = require('react-dom');

ReactDOM.render(
  React.createElement('h1', null, 'Hello, World!'),
  document.getElementById('root')
);

(I'm not using JSX syntax to avoid needing Babel.)

Bundle with Browserify (with or without browser-pack-flat) works great:

# works
browserify index.js -o app.js
# also works
browserify -p browser-pack-flat index.js -o app.js

Now imagine I want to externalize react and react-dom to keep dependencies separate, so I run:

browserify -p browser-pack-flat -r react -r react-dom -o vendor.js
browserify -p browser-pack-flat -x react -x react-dom index.js -o app.js

The bundles no longer work correctly. In a browser, after including both vendor.js and app.js, in that order, I get a console error:

Uncaught TypeError: React.createElement is not a function

If I take a look at the value of the React variable through the debugger, it's a function with no additional properties. If I call it with no arguments, it returns an object that contains the properties I'd expect the React variable to have (createElement, createContext, memo, etc.).

The same Browserify commands work fine if I exclude -p browser-pack-flat.

Browserify version: 16.2.3
browser-flat-pack version: 3.4.1 (also tried 3.4.0)

I tried browser-flat-pack 3.3.0 and it worked correctly, so it appears this has regressed since that release. (Which included a fix for another one of my bugs in #36. 😅)

I'm working on finding a reduced test case for this that doesn't involve such large dependencies, but thought I'd go ahead and file this since it definitely seems like a bug to me.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.