How to create standalone bundle of React & React with Addons using Grunt + Browserify?

964 views Asked by At

I am trying to configure Grunt & Browserify to output a standalone bundle containing, among other things, React as CommonJS module so that it can be referenced by other bundles.

The problem that I am having now is that the aliasing does not seem to work. Despite having specified aliases in my external bundle vendor below, and having specified that those modules should be loaded externally in all the other models, I'm still getting an error at run time, stating that the 'react' module cannot be found.

It would be great if anyone knows what might be wrong about my grunt-browserify syntax here:

var externals = [
  'react',
  'react/addons',
  'jquery',
  'backbone',
  'react-router'
];

module.exports = function(grunt) {

  grunt.config.set('browserify', {
    main: {
      src: 'assets/js/main.jsx',
      dest: '.tmp/public/js/main.js',
      options: {
        debug: true,
        extensions: ['.jsx'],
        external: externals,
        transform: [
          ['babelify', {'stage': 0}]
        ]
      }
    },
    signup: {
      src: 'assets/js/signup.jsx',
      dest: '.tmp/public/js/signup.js',
      options: {
        debug: true,
        extensions: ['.jsx'],
        external: externals,
        transform: [
          ['babelify', {'stage': 0}]
        ]
      }
    },
    login: {
      src: 'assets/js/login.jsx',
      dest: '.tmp/public/js/login.js',
      options: {
        debug: true,
        insertGlobals: true,
        extensions: ['.jsx'],
        external: externals,
        transform: [
          ['babelify', {'stage': 0}]
        ]
      }
    },
    vendor: {
      src: [
        './node_modules/react/dist/react.js',
        './node_modules/react/dist/react-with-addons.js',
        './node_modules/jquery/dist/jquery.js',
        './node_modules/backbone/backbone.js',
      ],
      dest: '.tmp/public/js/dependencies/vendor.js',
      options: {
        debug: false,
        alias: {
          'react:': './node_modules/react/dist/react.js',
          'react/addons': './node_modules/react/dist/react-with-addons.js',
          'jquery': './node_modules/jquery/dist/jquery.js',
          'backbone': './node_modules/backbone/backbone.js',
          'react-router': './node_modules/react-router/lib/index.js'
        },
        shim: {
          react_router: {
            path: './node_modules/react-router/lib/index.js',
            exports: 'react-router'
          }
        },
        external: null
      }
    }
  });

  grunt.loadNpmTasks('grunt-browserify');
};
3

There are 3 answers

0
dim2man On

I've found this link helpful for me. Following this approach your vendor section should look like

vendor: { src: ['.'], dest: '.tmp/public/js/dependencies/vendor.js', options: { debug: false, alias: externals.map(function(module) { return module + ':'; }), shim: { react_router: { path: './node_modules/react-router/lib/index.js', exports: 'react-router' } }, external: null } }

I'm not sure about the shim section above, because I've tried this only for the react module.

0
Chris L. On

I am working on a Sails 0.11.0 project with ReactJS. I start my grunt watchify task to handle rebundling/transforming my app code, while using the vendor task to bundle the moduled code. I additionally had to add the vendor.js to my layout file.

I am fairly new to the grunt world, so there may be more efficient ways to do this.

browserify.js

var externals = [
  'react',
  'react/addons',
  'jquery',
  'react-router',
  'events'
 ]

module.exports = function(grunt){
  grunt.config.set('browserify', {
    dist: {
      options: {
        external: externals,
        transform: [
          ['babelify', {
            loose: 'all'
          }]
        ]
      },
      files: {
        ".tmp/public/js/bundle.js": ["assets/js/bundle.js", "react/**/*"]
      }
    },
    vendor: {
      src: [
        './node_modules/react/dist/react.js',
        './node_modules/react/dist/react-with-addons.js',
        './node_modules/jquery/dist/jquery.js',
        './node_modules/react-router/lib/index.js',
        './node_modules/events/events.js'
      ],
      dest: '.tmp/public/js/dependencies/vendor.js',
      options: {
        alias: {
          'react': './node_modules/react/dist/react.js',
          'react/addons': './node_modules/react/dist/react-with-addons.js',
          'jquery': './node_modules/jquery/dist/jquery.js',
          'react-router': './node_modules/react-router/lib/index.js',
          'events': './node_modules/events/events.js'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-browserify');
}

tasks/config:

module.exports = function(grunt) {

    grunt.config.set('watch', {
        api: {

            // API files to watch:
            files: ['api/**/*', '!**/node_modules/**']
        },
        assets: {

            // Assets to watch:
            files: ['assets/**/*', 'tasks/pipeline.js', '!**/node_modules/**'],

            // When assets are changed:
            tasks: ['syncAssets' , 'linkAssets', 'browserify:dist']
        },
    react: {
      files: ['react/**/*'],
      tasks: ['browserify:dist']
    }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
};
0
koshkarov On

Here is the full Grunt configuration for the React:

I've created a dummy project so you can build it to test.

Gruntfile.js for the project:

module.exports = function (grunt) {

let concat = {};
let clean = {};
let uglify = {};
let copy = {};
let htmlmin = {};
let cssmin = {};
let browserify = {};
let watch = {};
let template = {};
let run = {};

/* React configuration. */

const reactSourcePath = './source';
const reactCompiledPath = './client';
const reactHtmlPathDest = './client/index.html'
const reactTargetName = "react";
const reactFileName = "react_main";

/* ### TASK CONFIGURATIONS ### */ 

/* Clean compiled files. */
clean[reactTargetName] = [
    `${reactCompiledPath}`
];

/* Concatenate all CSS files to one. */
const cssConcatSourceTemplate = `${reactSourcePath}/**/**.css`;
const cssDestinationFile = `${reactCompiledPath}/css/${reactFileName}.css`;

concat[reactTargetName] = {
    src: [cssConcatSourceTemplate],
    dest: cssDestinationFile
};

/* Convert JSX to JS, prepare JS files for a browser and copy to the destination. */
const jsSourceFile = `${reactSourcePath}/index.js`;
const jsDestinationFile = `${reactCompiledPath}/js/${reactFileName}.js`;

browserify[reactTargetName] = { 
    options: {
        transform: [['babelify', {presets: ['es2015', 'react']}]]
    },
    files: {
        [jsDestinationFile]: jsSourceFile
    }
};

/* Replace js/css placeholders and copy html file to destination. */
const applicationData = {
    css: [
        './css/react_main.css'
    ],
    js: [
        './js/react_main.js'
    ]
};

var jsFiles = "";
var cssFiles = "";

applicationData.css.forEach(function(item) {
    cssFiles = cssFiles + `\n<link rel="stylesheet" type="text/css" href=${item}>`;
});

applicationData.js.forEach(function(item) {
    jsFiles = jsFiles + `\n<script type="text/javascript" src=${item}></script>`;
});

template[reactTargetName] = {
    options: {
        data: {
            appName: '<%= pkg.name %>' + '-react',
            productVersion: '<%= pkg.version %>',
            reactEmbeddedCssFiles: cssFiles,
            reactEmbeddedJsFiles: jsFiles
        }
    },
    files: {
        [`${reactHtmlPathDest}`]: `${reactSourcePath}/index.template.html`,
    }
};

/* Uglify react JS file. */
uglify[reactTargetName] = { 
    files: {
    [jsDestinationFile]: jsDestinationFile
}
};

/* Copy bootstrap CSS/JS files. */
copy[reactTargetName] = {
    files: {
        [`${reactCompiledPath}/css/bootstrap.min.css`]: 'node_modules/bootstrap/dist/css/bootstrap.min.css',
        [`${reactCompiledPath}/js/bootstrap.min.js`]: 'node_modules/bootstrap/dist/js/bootstrap.min.js',
        [`${reactCompiledPath}/js/jquery.min.js`]: 'node_modules/jquery/dist/jquery.min.js',
    }
}

/* Minify HTML files. */
htmlmin[reactTargetName] = {
    options: {
        removeComments: true,
        collapseWhitespace: true
    },
    files: {
        [`${reactHtmlPathDest}`]: `${reactHtmlPathDest}`
    }
};

/* Minify react CSS file. */
cssmin[reactTargetName] = {
    files: {
        [cssDestinationFile]: cssDestinationFile 
    }
};

/* Watch for any changes in react app. 
There are three separate watches for css, js, and html files. */
watch[reactTargetName + '_css'] = {
    files: [`${reactSourcePath}/**/*.css`],
    tasks: [`concat:${reactTargetName}`],
    options: {
        livereload: true
    }
};

watch[reactTargetName + '_js'] = {
    files: [`${reactSourcePath}/**/*.js`],
    tasks: [`browserify:${reactTargetName}`],
    options: {
        livereload: true
    }
};

watch[reactTargetName + '_hmtl'] = {
    files: [`${reactSourcePath}/**/*.html`],
    tasks: [`template:${reactTargetName}`],
    options: {
        livereload: true
    }
};

/* Jest tests */
jestTestsTaskName = reactTargetName + '_jest_tests';
run[jestTestsTaskName] = {
    exec: 'npm test'
  };

/* Generate task names for react. */

var reactTasks = {
    debug: [
        "clean", 
        "browserify", 
        "concat", 
        "copy", 
        "template"
    ].map(x => x + `:${reactTargetName}`),
    release: [
        "clean", 
        "browserify", 
        "concat", 
        "copy", 
        "template", 
        "htmlmin", 
        "uglify", 
        "cssmin"
    ].map(x => x + `:${reactTargetName}`)
};

grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch:watch,
    copy:copy,
    concat:concat,
    clean:clean,
    uglify:uglify,
    template:template,
    browserify: browserify,
    htmlmin: htmlmin,
    cssmin: cssmin,
    run:run
});

grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-template');
grunt.loadNpmTasks("grunt-browserify");
grunt.loadNpmTasks("grunt-contrib-htmlmin");
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-run');

grunt.registerTask('react_build_debug', reactTasks.debug);
grunt.registerTask('react_build_release', reactTasks.release);

}