4

I'm currently working on creating a new React component that needs to integrate with an existing Angular 1.x application, which itself already has its own build process centered around Grunt. At some point the app may be migrated to React entirely, but for now just this one component will be done in React.

The Angular app has an extensive build process built up around it using grunt, so any build of the new component has to be able to go through grunt as well.

So far, another developer has started developing the React component using a React starter kit which includes React, Webpack, Babel, a dev server that it can spin up, and various other bells and whistles.

This is where my initial stabs of research got me, as well as current roadblocks:

  • It looks like ngReact will be a good tool to integrate a React component into the application.
  • The current React setup can either spin up a dev server with an embedded build of the React app (with no real usable build file), or create a "production" build that's minified and ready to ship. This doesn't quite work, as I need it to create something consumable by the current app and ngReact.
  • The React app is being bundled using Webpack. There is a grunt tool that can run webpack configs.

How can I put all these moving pieces together, to bundle the React components into the existing Angular app, while preserving as much of the existing configurations as possible?

Aaron Brown
  • 407
  • 4
  • 17

2 Answers2

4

Here are the steps that were needed to solve this problem (component/file names changed to protect the innocent):

  1. Get Webpack to output a build that I could use later with ngReact. In webpack.config.js, I had to change output:{ path: 'build' } to build_react so that it wouldn't be later overwritten by the Angular build process. Also, in module.loaders, I had add the presets line seen below, in order to get it to output non-ES6, valid JavaScript:

    //Process JS with Babel.
    {
      test: /\.(js|jsx)$/,
      include: paths.appSrc,
      loader: 'babel-loader',
      query: {
        presets: ['env', 'react'],
      }
    }
    
  2. Install grunt-webpack using npm, and configure it using a link to the webpack config file. This file gets pulled in to the grunt initConfig with all other similar configurations.

    //webpackConfig.js
    module.exports.name = 'webpack';
    const webpackConfig = require('../webpack.config.dev.js');
    
    /**
     * Pull in our react webpack build
     */
    module.exports.getConfiguration = function(grunt) {
        return {
            options: {
                stats: !process.env.NODE_ENV || process.env.NODE_ENV === 'development'
            },
            prod: webpackConfig,
            dev: webpackConfig
        };
    };
    
  3. Include react.js, react-dom.js, ng-react.min.js, and the bundle built in #1 in our index.html file. (These would later be concatenated into one file for a production build.) Crucially, the bundle has to get included after all the Angular files including all application .js files, so that the React component will have access to the angular object for the next step.

  4. Add this line to the React component, so that ngReact will work: angular.module('angularModule').value('MyComponent', MyComponent);
  5. Add the following to the Angular template where the React component will be included: <react-component name="MyComponent"></react-component>

After all these steps, it finally worked! Hopefully this will help someone put these steps together if anyone runs across this problem in the future.

Aaron Brown
  • 407
  • 4
  • 17
0

I've had the same problem to integrate React into the current AngularJs build process, but as a separate app. It might help you.

Here is the full grunt config for the React:

It creates a separate tasks for react with unique name, so you can manipulate them how you want.

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);

}

koshkarov
  • 1
  • 1