DEV Community

Cover image for Mergers & Acquisitions - For the JavaScript Developer
Austin
Austin

Posted on • Edited on

Mergers & Acquisitions - For the JavaScript Developer

Introduction

Technology changes at lightning speed and for larger businesses to grow their core business but stay innovative and capture new markets, it's almost always easier to acquire a smaller organization and roll them into their broader product offering than actually build something. One thing that I've learned over the years is that for a business, the time to market or opportunity loss cost largely outweighs actual dollars.

Over my career, I've been part of 4 acquisitions, including my own company ( https://crft.app ) last year. If you've ever been part of an acquisition after the champaign popping is over, it's right to how can we integrate these applications so they feel like one offering. This is always messy because it's almost never the case that both companies have the same technology, languages, tools that are the same. The first acquisition I was apart of our technology stack was heavy C# / .NET and the acquiring company was Java. At the time, there wasn't any frontend tooling like we have today and putting ASPX pages in JSP pages was pretty much a iframe and then a rewrite.

During the acquisition of my company last year, the dreams and aspirations of React finally were realized to me. In this article, I want to take you through how we took two completely different applications written in React and made one unified page ( no iframes required ).

The Theory

A React application is just a set of components pieced together to build a larger application. If you have two different applications written in React, theoretically you should be able to treat one of them like any other component you might install from NPM.

In our particular case, we wanted to create two experiences; one experience that was embedded in the larger application and another that was a stand alone version. This would allow us to create a unified experience for the existing customers but also serve two distinct personas ( since one buyer might not want the other product and vice versus ). This increases code and deployment complexity but it can also help keep some things isolated which makes merging easier.

I hypothesized that we could keep the same code bases and products and then build/package one of the apps and just have on consume the other.

Audit the tech

The first thing you need to do is audit the overlap and figure out what 'container' level items are shared and what can't be. This list usually looks something like:

  • Authentication ( Custom / Okta / Auth0 / etc )
  • Store ( Redux / Recoil / etc )
  • Component Libraries ( Material / Custom / etc )
  • Style Systems ( SASS / PostCSS / CSS-IN-JS )
  • Test Systems ( Cypress / Jest / etc )
  • Build Systems ( Webpack / CRA / etc )
  • Programming Languages ( JavaScript / TypeScript / CoffeeScript < sorry if thats you )
  • Backend APIs ( Rest / GraphQL / SOAP < sorry if thats you )
  • Backend Cloud Providers ( GCP / AWS / Azure / etc )

Once we have an understanding of the tech, we can start to tease out the overlap and put together a plan.

Where do things overlap?

The only thing that overlapped in the frontends was the authentication providers. Luckily most organizations are not writing their own auth providers anymore and so if you have 2 organizations both using let's say Okta, you can actually share the same authentication token minters and its like magic, no seriously I couldn't believe it just worked ( < This might be a future article ).

To make things even more fun, one application is JavaScript + custom webpack and the other is TypeScript CRA. Now I know what your about to say, TypeScript is just JavaScript but believe me converting a large mature code base from JavaScript to TypeScript is quite a lift, mainly because you find so many things that might be 3 years old that the types don't match up and you have to go in circles if this is even used anymore and how to fix it. The last time I did this migration on a mature large code base, it took us about 3 weeks of 3 full time developers on it. I won't argue that knocking out this tech debt and making the code bases the same language has quite a bit of merit but the business wants the product out yesterday and a code migration is just scary for most PMs.

Given the language differences, a copy / paste into the same directory was not really an option. Going back to my original theory, since both systems are React which is just a bunch of components, why can't we build and bundle one of the applications and publish it to NPM just like I do on my open-source projects ( shameless plug: https://github.com/reaviz ).

Rollup to the rescue

Webpack is not really designed to package things for consuming them externally; its possible but not ideal. Rollup on the other hand is perfect for this, it can help you include / exclude / compile to different targets in a ( somewhat ) easy fashion.

We already defined our overlap above, in our case:

  • React / React DOM
  • React Router
  • Moment
  • Classnames
  • Okta React / Signin Widget

We want to make sure we have both applications on the same versions and then we can exclude these items from our Rollup build. This will help make the integration easier and the package size smaller.

The next thing we need to do is tease out the 'provider' level items that don't overlap. This typically looks like:

  • Theme
  • Notifications
  • Error Boundaries
  • HTTP Providers ( Apollo / etc )
  • State management

and then make a new component called something like Embed.tsx that encapsulates those items and defines the sub applications routes. Here's a rough outline of what mine looked like:

export const Embed: FC = () => (
  <Theme>
    <Notifications>
      <ApolloProvider>
        <ErrorBoundary>
          <Suspense fallback={<Loader />}>
            <Switch>
              <Route
                path="/embed/home"
                component={Home}
              />
              { /* rest of your routes */ }
            </Switch>
          </Suspense>
        </ErrorBoundary>
      </ApolloProvider>
    </Notifications>
  </Theme>
);
Enter fullscreen mode Exit fullscreen mode

This is the component that we will embed in the parent application and the component that will be in the rollup build path.

Next thing is the rollup config. This is going to differ based on your toolchain but they roughly look something like this:

const resolve = require('@rollup/plugin-node-resolve').default;
const commonjs = require('@rollup/plugin-commonjs');
const postcss = require('rollup-plugin-postcss');
const svgr = require('@svgr/rollup').default;
const url = require('@rollup/plugin-url');
const babel = require('@rollup/plugin-babel').default;
const json = require('@rollup/plugin-json');
const replace = require('rollup-plugin-re');
const peerDepsExternal = require('rollup-plugin-peer-deps-external');

const pkg = require('./package.json');

const extensions = ['.js', '.jsx', '.ts', '.tsx', '.svg', '.css', '.json'];

module.exports = [
  {
    input: './src/Embed/Embed.tsx',
    output: [
      {
        file: pkg.browser,
        sourcemap: true,
        format: 'umd',
        name: 'emebed',
        strict: false,
        inlineDynamicImports: true
      }
    ],
    plugins: [
      peerDepsExternal(),
      resolve({
        browser: true,
        extensions,
        preferBuiltins: false
      }),
      postcss({
        plugins: [
          require('postcss-preset-env')({ stage: 1 }),
          require('autoprefixer')
        ]
      }),
      replace({
        patterns: [
          {
            test: /(['`"])(\/home)/g,
            replace: '$1/embed/home'
          }
          /** rest of your routes */
        ]
      }),
      json(),
      url(),
      svgr(),
      commonjs(),
      babel()
    ]
  }
];
Enter fullscreen mode Exit fullscreen mode

Let's take a moment to review what this is doing:

  • The input defines the entry path for our new Embed component.
  • The output defines how we want to package the application
  • The plugins defines things that tools like CRA typically handle for us that we need to include a rollup version for.

Inside the plugins, we have 2 things I want to make sure and note:

  • peerDepsExternal plugin will resolve the packages we deteremined overlapped and exclude them from the build. I defined these packages in the peerDependencies section of the package.json.
  • replace plugin will rewrite the paths of anchors and history.push type components. Typically you can use the Redirect component in react-router to do this but it causes us quite a bit of issues so we opted for this approach.

This configuration will now compile the TypeScript to JavaScript and all the styles and images in a friendly format that any React app can consume.

Bringing the code together

Another fun challenge was that one company used Github and the other Gitlab and of course different build systems.

NPM provides an excellent way to publish and version packages so instead of manually copying or submodules, why not take this approach?

At this point, it truly became just like any other NPM dependency we would include in a React app.

Wrapping up

Every organization has different requirements, tools, timelines and priorities but more than likely in these situations its mainly focused around time to market. Hopefully this brain dump can help make someone's life easier the next time you get a new step-parent of a company.

I hope you enjoyed the post, if you liked it follow me on Twitter and Github for more JavaScript tips/opinions/projects/articles/etc!

Top comments (0)