56

I'd like to use react.min.js from a CDN in production (e.g. https://unpkg.com/react@15.3.1/dist/react.min.js)

What is the best way to get Webpack to transform my import React from 'react' statements into const React = window.React instead of building node_modules/react into the bundle?

I've been doing it with resolve.alias like this:

In index.html:

<head>
  <script type="text/javascript" src="https://unpkg.com/react@15.3.1/dist/react.min.js"></script>
  <script type="text/javascript" src="/assets/bundle.js"></script>
</head>

In webpack.prod.config.js:

alias: {
  react$: './getWindowReact',
},

getWindowReact.js:

module.exports = window.React;

Note: In the old question I didn't realize that building React into a Webpack bundle with NODE_ENV=production would strip out the propTypes checks. One of the answers focuses on that.

Andy
  • 7,885
  • 5
  • 55
  • 61

3 Answers3

44

In your webpack config you can use the externals option which will import the module from the environment instead of trying to resolve it normally:

// webpack.config.js
module.exports = {
  externals: {
    'react': 'React'
  }
  ...
};

Read more here: https://webpack.js.org/configuration/externals/

Métoule
  • 13,062
  • 2
  • 56
  • 84
franky
  • 1,323
  • 1
  • 12
  • 13
  • So it turns out I was wrong about React being built with Webpack, so this wouldn't work. But it appears to be the correct answer for using a module that is built with Webpack, so I'll accept this answer. – Andy Jul 23 '15 at 21:47
  • 4
    @Andy I may not fully understand your comment, but what has been suggested here doesn't require that the library being loaded be built with Webpack. All this `externals` example does is tell webpack "if a module requests `react`, return `window.React`". – Aaronius Sep 22 '16 at 18:22
  • @Aaronius yeah, sorry, I misunderstood webpack `externals` at the time...the docs for them are kind of awkward. – Andy Sep 22 '16 at 23:07
  • When you defined `'react': 'React'` how does webpack knows from where to take it? I mean, there can be many versions or names – Raz Buchnik Jul 01 '19 at 12:37
  • 1
    @Raz `'react': 'React'` means "when I import `from 'react'`, instead go looking in `window.React`". The key is the package name, the value is the global property name. – Jacob Raihle Sep 11 '20 at 10:11
18

I created https://github.com/mastilver/dynamic-cdn-webpack-plugin which is doing exactly that out of the box

const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const DynamicCDNWebpackPlugin = require('dynamic-cdn-webpack-plugin')

module.exports = {
  entry: './main.js',
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  plugins: [
    new HTMLWebpackPlugin(),
    new DynamicCDNWebpackPlugin()
  ]
}

Will dynamically add unpkg.org urls and add the appropriate code in your bundle to load librairies from global

Jose A
  • 10,053
  • 11
  • 75
  • 108
mastilver
  • 665
  • 6
  • 18
  • badass, that's a great idea! I use the `https://github.com/kossnocorp/assets-webpack-plugin` instead of the manifest plugin, do you know if your plugin will work with `assets-webpack-plugin` as well? – Andy Jul 06 '17 at 23:14
  • @Andy I haven't tried, but I believe it does. If not feel free to raise an issue :) – mastilver Jul 07 '17 at 11:06
  • @mastilver `path` is where the CDN link goes? – Q.H. Jul 28 '17 at 19:34
  • @Q.H. no your links goes in the `index.html` created by `html-webpack-plugin` – mastilver Jul 31 '17 at 18:15
  • This works perfectly. There aren't many modules predefined in `modules-cdn` and it's not entirely obvious how to write a custom resolver to fill in the void - an example in the readme would be great! I went with `if (modulePath == 'lodash') return { name: 'lodash', url: 'https://unpkg.com/lodash@' + version + '/lodash.min.js' };` and it seems to work so far. – Roman Starkov Aug 12 '17 at 20:00
  • @RomanStarkov Almost! :) It should be: `if (modulePath == 'lodash') return { name: 'lodash', url: 'https://unpkg.com/lodash@' + version + '/lodash.min.js', version: version, var: '_' };` But that way it will replace `module-to-cdn`... Can I ask, why you didn't send a PR to `module-to-cdn`? You are not the only one, and I'm trying to understand how I can make it easier? – mastilver Aug 16 '17 at 10:52
  • Yes I realised the `var` part soon after but couldn't edit comment anymore. I needed it right there and then, so PR would be too slow. After the fact... just a mix of lazy, busy, and feeling like it needs more work. Do packages have to be enumerated? I believe unpkg boasts predictable URLs, so you could just construct one and hope the package is there. – Roman Starkov Aug 16 '17 at 12:37
  • No, if you look at https://github.com/mastilver/module-to-cdn/blob/master/modules.json you can see that there is a lot of varieties (even for the same package). Even M. Jackson the author of unpkg had a look at it and he didn't mention anything. I need to find a way to make editing easier :) – mastilver Aug 16 '17 at 12:57
  • 1
    @mastilver How does this plugin figure out which version of the module, say React, should be loaded? Do I still need to have the right version mentioned in package.json dependencies? Or can I omit React from package.json altogether? – hazardous Aug 29 '17 at 05:24
  • 1
    @hazardous your app should work the same way with or without this plugin, so please add react in your package.json . What's happening is that it's requiring the package.json of your dependency and use the version define there (https://github.com/mastilver/dynamic-cdn-webpack-plugin/blob/185e09314398f67f459084c94c9e153078168e31/src/index.js#L76) – mastilver Aug 29 '17 at 14:44
4

All the development-only portions of the React codebase, such as PropType checks, are guarded with:

if ("production" !== process.env.NODE_ENV) {
  ..
}

To strip these out from React in your own build, creating the equivalent of the minified React build in your own bundle, use DefinePlugin to replace references to process.env.NODE_ENV with "production".

plugins: [
  // ...
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  new webpack.optimize.UglifyJsPlugin({
    compressor: {
      warnings: false
    }
  })
  // ...
],

Uglify's dead code elimination will then strip it all out, as it will detect that code wrapped with a "production" !== "production" check is unreachable.

Jonny Buchanan
  • 61,926
  • 17
  • 143
  • 150
  • This is great to know too, though since my boss wants to use React from a CDN we'll stick with doing it that way. – Andy Jul 23 '15 at 21:46