sky99leaf
sky99leaf

Reputation: 11

Webpack and Babel for bundling React components, using "externals" tag

[this post began as a question; here is the answer, summarized]
Note: the solution to this question is posted at https://github.com/sky99leaf/npmPackageCodeExample
......
Below is a webpack.config.js for bundling React components. Note that without the externals entry the following error message comes up at the client importing the bundle:

HelperA.js:24 Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

Below is the file. Look at the notes for the externals tag.
This file has a lot of comments which cover what the tags are for
and an explanation of the above error and what was done to eliminate it.


// file webpack.config_build.js  
// Goal: take all the .js files for React.js, which contain jsx and pack all into a single
//   minified .js file which has the jsx converted to javascript which a browser can use directly.
// webpack and babel get loaded into node_modules according to package.json. They are used as
//   command line utilities only, and don't get used by any .js code in this example.
// run with command line: 
// [not this file] node node_modules/webpack/bin/webpack serve -c ./webpack.config_serve.js  // starts http server to index.html
// node node_modules/webpack/bin/webpack build -c ./webpack.config_build.js  // puts minified file "bundle" into output/path (see tag below)
// Webpack looks at *.js and *.jsx files (see "resolve" section) and looks at the
//   "include('./xyz.js')" lines and then combines the files all into one file "bundle". Because there is no 
//   longer an "xyz.js" to include from anymore, webpack also fixes those.
// Webpack also calls 'babel-loader' (see module/rule, first rule) to transpile React files 
//   having jsx to plain javascript which the browser understands, before loading into the bundle.
//   My guess is "loader: 'babel-loader'" calls "node_modules/babel-loader/lib/index.js" or something like that.
// ref: "https://webpack.js.org/configuration/module" used this to try do decipher whats going on here.
const path = require('path');

module.exports = 
{
  // entry means "start dependency graph at this file"
  // builds a dependency graph of the .js/.jsx files based on "include('./xyz.js')" lines. All files in graph
  // get put in the bundle.  It has one or more entry points. Combines all files into one or more bundles. 
  // Graph stops at node_modules (see "exclude") 
  entry: './src/WebpackDependencyGraphEntryPoint.js',
  optimization: {
    minimize: false // human readable
  },
  mode: 'development',  // development or production
  // resolve is a fancy way of saying "look here for files to build graph". Extensions says use .js .jsx 
  resolve: { 
    extensions: ['.js', '.jsx']
  },
  // setup where to put minified output file "the bundle", and any asses like jpegs. 
  output: {
    path: path.resolve(__dirname, 'dist-webpack'), // this just means put file into folder ./dist-webpack
    filename: 'bundle.js', // name of combined file, the "bundle"
    // https://webpack.js.org/configuration/output/#outputlibrarytype
    // library: { name: 'MyLibrary' , type: 'window' } // this works on local but not client
    library: { type: 'commonjs2' } // this works on client but not local serve or direct run
  },
  // https://webpack.js.org/configuration/externals/
  // "The externals configuration option provides a way of excluding dependencies from the output bundles. 
  //  Instead, the created bundle relies on that dependency to be present in the consumer's 
  //  (any end-user application) environment. This feature is typically most useful to library developers, 
  //  however there are a variety of applications for it."
  // *********************************************************************************************************
  // *** "EXTERNALS" FIXES ERROR ON CLIENT: WHEN REACT CONTROL IMPORTED FROM BUNDLE CALLS useEffect(), GET ***
  // ***  ERROR  MESSAGE ABOUT DUPLICATE COPIES OF REACT. BELOW ENTRY SAYS DONT BUNDLE REACT, JUST USE     ***
  // ***  CLIENT'S COPY OF REACT FOR THE GIVEN LIBRARY/TYPE ENTRIES.                                       ***
  // *** [OK for npmjs publish but not local bundle]                                                       ***
  // *********************************************************************************************************
  externals: {        
    react: {          
        commonjs: 'react',          
        commonjs2: 'react',          
        amd: 'React',          
        root: 'React',      
    },      
    'react-dom': {          
        commonjs: 'react-dom',          
        commonjs2: 'react-dom',          
        amd: 'ReactDOM',          
        root: 'ReactDOM',      
    },  
  },
  // modules appears to be chunks of processing to do. 
  // In this case, there's 1 module which does babel to convert jsx in React source to plain js
  module: {
    rules: [  // here's the first rule in the array of rules
      { // START of the babel rule
        test: /\.(js|jsx)$/,   // feed files *.js and *.js to babel. ref:https://webpack.js.org/configuration/module/#ruletest
        // not needed... include: [  path.resolve(__dirname, "src/zzz"), path.resolve(__dirname, "src/abc") ],
        exclude: /node_modules/, // dont send these hundreds of files to babel! Client will download these itself upon "npm i"
        use: {  // ref: https://webpack.js.org/configuration/module/#ruleuse
          loader: 'babel-loader', // node_modules/babel-loader/lib runs something here ...?
          // NOTE: options can be left out but you need a .babelrc with same presets.
          //       If you leave out both things, get fail without a description of whats wrong
          options: { "presets": ["@babel/preset-env", "@babel/preset-react"] }
        }, 
      }, // END of babel rule

      {
        test: /\.css$/,
        use: [
            { loader: 'style-loader' },
            { loader: 'css-loader' }
        ]
      }

    ]
  },
  // devServer does not apply to build
  // webpack includes a server. Run it with: "node node_modules/webpack/bin/webpack serve"
  // https://webpack.js.org/configuration/dev-server/#devserverclient
  // devServer: {
  //   static: path.join(__dirname, 'dist-webpack'),
  //   compress: true,
  //   port: 4000, // never run as server
  //   open: false, //true, // Tells dev-server to open the browser
  //   client: {
  //     overlay: {
  //       errors: true,
  //       // if warnings true, page in browser starts with a warning popup, its just warnings but looks like a crash!
  //       warnings: false, 
  //       runtimeErrors: true,
  //     },
  //   },
  // }
};

Upvotes: 0

Views: 118

Answers (0)

Related Questions