Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple solution to share modules loaded via NPM across multiple Browserify or Webpack bundles

Pulling my hair out here looking for a simple solution to share code, required via NPM, across multiple Browserify or Webpack bundles. Thinking, is there such a thing as a file "bridge"?

This isn't due to compile time (I'm aware of watchify) but rather the desire to extract out all of my vendor specific libs into vendor.js so to keep my app.js filesize down and to not crash the browser with massive sourcemaps. Plus, I find it way cleaner should the need to view the compiled js arise. And so:

// vendor.js

require('react');
require('lodash');
require('other-npm-module');
require('another-npm-module');

Its very important that the code be loaded from NPM as opposed to Bower, or saved into some 'vendor' directory in order to be imported via a relative path and identified via a shim. I'd like to keep every library reference pulled via NPM except for my actual application source.

In app.js I keep all of my sourcecode, and via the externals array, exclude vendor libraries listed above from compilation:

// app.js 

var React = require('react');
var _     = require('lodash');

var Component = React.createClass()

// ...

And then in index.html, I require both files

// index.html
<script src='vendor.js'></script>
<script src='app.js'></script>

Using Browserify or Webpack, how can I make it so that app.js can "see" into those module loaded via npm? I'm aware of creating a bundle with externals and then referencing the direct file (in, say, node_modules) via an alias, but I'm hoping to find a solution that is more automatic and less "Require.js" like.

Basically, I'm wondering if it is possible to bridge the two so that app.js can look inside vendor.js in order to resolve dependencies. This seems like a simple, straightforward operation but I can't seem to find an answer anywhere on this wide, wide web.

Thanks!

like image 764
cnp Avatar asked Oct 28 '14 08:10

cnp


People also ask

How do I use Browserify bundles?

Bundle up your first module With Browserify you can write code that uses require in the same way that you would use it in Node. Browserify parses the AST for require() calls to traverse the entire dependency graph of your project. Drop a single <script> tag into your html and you're done!

What is Browserify in NPM?

Browserify is an open-source JavaScript bundler tool that allows developers to write and use Node. js-style modules that compile for use in the browser.

What is Browserify and webpack?

Browserify is used to read the strings available in the static files, and the node uses the native read file function, whereas the webpack uses a common object to overload the needed function and applies a distinct loader to load the files, and its names should have a suitable pattern.


1 Answers

Listing all the vendor files/modules and using CommonChunkPlugin is indeed the recommended way. This gets pretty tedious though, and error prone.

Consider these NPM modules: fastclick and mprogress. Since they have not adopted the CommonJS module format, you need to give webpack a hand, like this:

require('imports?define=>false!fastclick')(document.body);
require('mprogress/mprogress.min.css');
var Mprogress = require('mprogress/mprogress.min.js'),

Now assuming you would want both fastclick and mprogress in your vendor chunk, you would probably try this:

module.exports = {
  entry: {
    app: "./app.js",
    vendor: ["fastclick", "mprogress", ...]

Alas, it doesn't work. You need to match the calls to require():

module.exports = {
  entry: {
    app: "./app.js",
    vendor: [
      "imports?define=>false!fastclick", 
      "mprogress/mprogress.min.css", 
      "mprogress/mprogress.min.js", 
      ...]

It gets old, even with some resolve.alias trickery. Here is my workaround. CommonChunkPlugin lets you specify a callback that will return whether or not you want a module to be included in the vendor chunk. If your own source code is in a specific src directory, and the rest is in the node_modules directory, just reject the modules based on their path:

var node_modules_dir = path.join(__dirname, 'node_modules'),
    app_dir          = path.join(__dirname, 'src');

module.exports = {
  entry: {
    app: "./app.js",
  },
  output: {
    filename: "bundle.js"
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin(
      /* chunkName= */"vendor",
      /* filename= */"vendor.bundle.js"
      function (module, count) {
       return module.resource && module.resource.indexOf(app_dir) === -1;
      }
    )
  ]
};

Where module.resource is the path to the module being considered. You could also do the opposite, and include only the module if it is inside node_modules_dir, i.e.:

       return module.resource && module.resource.indexOf(node_modules_dir) === 0;

but in my situation, I'd rather say: "put everything that is not in my source source tree in a vendor chunk".

Hope that helps.

like image 69
sebastien.b Avatar answered Oct 06 '22 11:10

sebastien.b