One project with multiple package.json files

skrunic picture skrunic · May 22, 2018 · Viewed 25.7k times · Source

I'm relatively new to modern JS development and I need help or advice about this situation I'm in.

Situation: We have a React-Typescript-Redux project supporting IE8 (React 0.14). Now we're upgrading to IE11 and React 16 but IE8 should be supported.

Requirement: Reduce project maintenance between browser versions by using different packages and/ or config files for each build.

Problem: From research I made so far it seems impossible to use different package.json files and node_modules folders inside the same project with selected tools: npm, Webpack, React, Redux, Typescript. Yarn seems to support multiple package.json files but we'd like to avoid migrating from npm if possible.

Current project structure:

project_root/
  node_modules/
  src/
    components/
    utils/
    index.tsx
    components.css
  index.html
  package.json
  tsconfig.json
  webpack.config.json

What I thought might work was to introduce IE8 subfolder with its package.json and node_modules folder and then reference that folder for the build task somehow but now I'm oblivious how to tell npm to reference it on build.

Proposed project structure:

project_root/
  ie8/
   node_modules/
   package.json
  node_modules/
  src/
    components/
    utils/
    index.tsx
    components.css
  index.html
  package.json
  tsconfig.json
  webpack.config.json

I tried different things found on web, including resolve.modules: [__dirname + "/ie8/node_modules"] but it seems it doesn't work or I misunderstand what it does because I get Cannot find name 'require' errors on several files and Typescript 2.8.3 is referenced in terminal output instead 2.3.4. Without it, project builds with configuration for IE11.

So, can anybody tell me with certainty it's not possible or offer some guidance? This is the closest answer I found so far but doesn't sound final. Alternatively, can project structure like this support what is required or separating project into two is the best bet?

Thanks in advance.

Answer

skrunic picture skrunic · May 28, 2018

OK, so after some more research I stumbled upon Lerna which mostly allows me to do what I wanted (from what I've seen so far). It requires specific project tree setup, like this:

project_root/
  node_modules/
  packages/
    components/ // Components shared between projects
      components/
       MyComponent.jsx
      index.jsx

  legacy/
     output/
      build.js // React 0.14 build
     node_modules/
     package.json // project specific dependencies
     index.jsx // project specific entry
     .babelrc

  modern/
     output/
      build.js // React 16 build
     node_modules/
     package.json // project specific dependencies
     index.jsx // project specific entry
     .babelrc

  package.json // contains devDependencies shared between projects
  lerna.json
  webpack.config.js
  index.html

Then, in components/index.jsx I specified require commands for different versions based on global variable:

if(PROJECT_SRC == "legacy"){
    React = require('../legacy/node_modules/react');
    ReactDOM = require('../legacy/node_modules/react-dom');
} else {
    React = require('../modern/node_modules/react');
    ReactDOM = require('../modern/node_modules/react-dom');
}

Note: This is probably bad practice but the only way at the moment I could include different React versions in the build. I'll have to see what problems arise with this approach after the whole project changes to this model.

In webpack.config.js I configured two exports - one for modern and one for legacy. Each points to a different entry index.jsx file, uses webpack.DefinePlugin to set global variable to "legacy" or "modern", and specifies path to common components module to resolve: ['node_modules', path.resolve(__dirname, 'components')]

webpack.config for a single project output looks something like this:

{
        entry: "./packages/legacy/index.jsx",
        target: "web", 
        output: 
        {
            filename: "build.js",
            path: __dirname + "/packages/legacy/dist/",
            libraryTarget: "var",
            library: "lib_name"
        },
        devtool: "source-map",
        resolve: {
            extensions: [".js", ".jsx", ".json"],
            modules: ['node_modules', path.resolve(__dirname, 'components')]
        },
        plugins: plugins_legacy,
        module: {
            loaders: [
                {
                    test: /\.jsx?$/,
                    loader: "babel-loader",
                    exclude: /node_modules/
                }
            ]
        }  
    }

Feel free to comment or point to problems but I hope this will help somebody in the future! :)