{"componentChunkName":"component---src-templates-blog-post-js","path":"/distribute-react-component-via-npm/","result":{"data":{"site":{"siteMetadata":{"title":"Rope and Tire","author":"Suprada Urval"}},"markdownRemark":{"id":"e87cd3dd-5b55-5206-9c03-da2497022231","excerpt":"I wrote a React component, transpiling using Babel, bundling and building using Webpack. I wanted to use it in another application via NPM. My NPM publish package needed to include component behavior, styles and images. So how difficult is it to…","html":"<p>I wrote a React component, transpiling using Babel, bundling and building using Webpack. I wanted to use it in another application via NPM. My NPM publish package needed to include component behavior, styles and images. So how difficult is it to package my React Component for distribution via NPM? An hour or so of work maybe right?</p>\n<p>Well, it took me a lot longer to figure this out because of the following speed-bumps.</p>\n<p>– React version conflict</p>\n<p>– How to bundle and consume styles from a component</p>\n<p>– How to include and bundle images</p>\n<p>Here are the steps.</p>\n<h3>1. Make a package npm publishable</h3>\n<p><code class=\"language-text\">npm init</code></p>\n<p>In the <code class=\"language-text\">package.json</code>, make sure these fields are populated:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">package.json\n\n\n{\n    &quot;name&quot;: &quot;myUnflappableComponent&quot;,\n    &quot;version&quot;: &quot;0.0.29&quot;,\n    &quot;main&quot;: &quot;dist/index.js&quot;,\n    &quot;publishConfig&quot;: {\n       &quot;access&quot;: &quot;restricted&quot;\n    },\n    ...\n}</code></pre></div>\n<h3>2. Don’t bundle React. Use the parent’s React and react-dom.</h3>\n<ol>\n<li>In <code class=\"language-text\">package.json</code>, add <code class=\"language-text\">React</code> and <code class=\"language-text\">react-dom</code> in the project’s <code class=\"language-text\">peerDependencies</code> (And remove it from <code class=\"language-text\">dependencies</code>, but add it to devDependencies for development)\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">{\n     &quot;peerDependencies&quot;: {\n     &quot;react&quot;: &quot;&gt;=15.0.1&quot;,\n     &quot;react-dom&quot;: &quot;&gt;=15.0.1&quot;\n     },\n     &quot;devDependencies&quot;: {\n     &quot;react&quot;: &quot;&gt;=15.0.1&quot;,\n     &quot;react-dom&quot;: &quot;&gt;=15.0.1&quot;\n     },\n     ...\n}</code></pre></div>\n</li>\n</ol>\n<ol start=\"2\">\n<li>In your webpack configuration, create a UMD bundle</li>\n</ol>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">module.exports = {\n    ...\n    output: {\n        path: path.join(\\_\\_dirname, &#39;./dist&#39;),\n        filename: &#39;myUnflappableComponent.js&#39;,\n        library: libraryName,\n        libraryTarget: &#39;umd&#39;,\n        publicPath: &#39;/dist/&#39;,\n        umdNamedDefine: true\n   },\n   plugins: {...},\n   module: {...},\n   resolve: {...},\n   externals: {...}\n}</code></pre></div>\n<p>And super-duper important, don’t bundle React</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">module.exports = {\n    output: {...},\n    plugins: {...},\n    module: {...},\n    resolve: {\n        alias: {\n            &#39;react&#39;: path.resolve(__dirname, &#39;./node_modules/react&#39;) ,\n            &#39;react-dom&#39;: path.resolve(__dirname, &#39;./node_modules/react-dom&#39;),\n        }\n    },\n    externals: {\n        // Don&#39;t bundle react or react-dom\n        react: {\n            commonjs: &quot;react&quot;,\n            commonjs2: &quot;react&quot;,\n            amd: &quot;React&quot;,\n            root: &quot;React&quot;\n        },\n        &quot;react-dom&quot;: {\n            commonjs: &quot;react-dom&quot;,\n            commonjs2: &quot;react-dom&quot;,\n            amd: &quot;ReactDOM&quot;,\n            root: &quot;ReactDOM&quot;\n        }\n    }\n}</code></pre></div>\n<h3>3. Set up your .npmignore file</h3>\n<p>If you don’t set up a .npmignore file, npm uses your <code class=\"language-text\">.gitignore</code> file and bad things will happen. An empty <code class=\"language-text\">.npmignore</code> file is allowed. This is what mine looks like:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">webpack.local.config.js\nwebpack.production.config.js\n.eslintrc\n.gitignore</code></pre></div>\n<h3>4. Add a ‘prepublish’ script to your <code class=\"language-text\">package.json</code></h3>\n<p>To build before publishing.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">    &quot;scripts&quot;: {\n         &quot;prepublish&quot;: &quot;rm -rf ./dist &amp;&amp; npm run build&quot;,\n        ...\n    }</code></pre></div>\n<h3>5. Extract out your CSS files for use</h3>\n<p>We use SCSS files for our styles. These are compiled into css and extracted out by Webpack.</p>\n<p>Install the following:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">    npm install --save-dev extract-text-webpack-plugin node-sass style-loader css-loader sass-loader</code></pre></div>\n<p>Update your <code class=\"language-text\">webpack.config</code></p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">    const ExtractTextPlugin = require(&#39;extract-text-webpack-plugin&#39;);\n\n    module.exports = {\n        ...\n        plugins:[\n            new ExtractTextPlugin({\n                filename: &#39;myUnflappableComponent.css&#39;,\n            }),\n        ],\n        module:{\n            rules:[\n                {\n                    test: /\\.*css$/,\n                    use : ExtractTextPlugin.extract({\n                        fallback : &#39;style-loader&#39;,\n                        use : [\n                            &#39;css-loader&#39;,\n                            &#39;sass-loader&#39;\n                        ]\n                    })\n                },\n                ....\n            ]\n        }\n    }</code></pre></div>\n<h3>6. Images in CSS</h3>\n<p>The way you include images in your component will determine if the consumer of your component will get them.</p>\n<p>I include them in the css file using the <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/content\">content property</a>. For example</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">.mySky{\n    width: 20px;\n    height: 20px;\n    content: url(&#39;../assets/images/thunderSky.png&#39;);\n}</code></pre></div>\n<h3>7. Make sure your images are available outside your component</h3>\n<p>The issue I ran into was the relative paths of the images in the published CSS files were messed up. After a lot of searching, <a href=\"https://shakacode.gitbooks.io/react-on-rails/content/docs/additional-reading/rails-assets-relative-paths.html\">this article</a> (also in the links below) helped.</p>\n<p>Install the following:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">    npm install --save-dev file-loader url-loader</code></pre></div>\n<p>Update your <code class=\"language-text\">webpack.config</code> like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">    module.exports = {\n        ...\n        module: {\n            rules: [\n                {\n                    test: /\\.(png|svg|jpg|gif)$/,\n                    use: [\n                        {\n                            loader: &#39;url-loader&#39;,\n                            options:{\n                                fallback: &quot;file-loader&quot;,\n                                name: &quot;[name][md5:hash].[ext]&quot;,\n                                outputPath: &#39;assets/&#39;,\n                                publicPath: &#39;/assets/&#39;\n                            }\n                        }\n                    ]\n                },\n                ...\n                resolve: {\n                    alias:{\n                        ...\n                        &#39;assets&#39;: path.resolve(__dirname, &#39;assets&#39;)\n                    }\n                }\n            ]\n        }\n    }</code></pre></div>\n<h2>Shoutouts and References:</h2>\n<ol>\n<li>\n<p><a href=\"https://hackernoon.com/how-to-publish-your-package-on-npm-7fc1f5aae600\">How to publish your package on npm(all about <code class=\"language-text\">package.json</code>)</a></p>\n</li>\n<li>\n<p><a href=\"https://maxisam.github.io/2017/03/29/publish-beta-to-npm/\">Publish Beta to NPM</a></p>\n</li>\n<li>\n<p><a href=\"https://shakacode.gitbooks.io/react-on-rails/content/docs/additional-reading/rails-assets-relative-paths.html\">Exporting images via webpack (<code class=\"language-text\">webpack.config.js</code>)</a></p>\n</li>\n<li>\n<p>My full webpack configuration:</p>\n</li>\n</ol>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">    const webpack = require(&#39;webpack&#39;);\n    const ExtractTextPlugin = require(&#39;extract-text-webpack-plugin&#39;);\n    const pkg = require(&#39;./package.json&#39;);\n    const path = require(&#39;path&#39;);\n\n    const libraryName= pkg.name;\n\n    module.exports = {\n        entry: path.join(__dirname, &quot;./src/index.js&quot;),\n        output: {\n            path: path.join(__dirname, &#39;./dist&#39;),\n            filename: &#39;myUnflappableComponent.js&#39;,\n            library: libraryName,\n            libraryTarget: &#39;umd&#39;,\n            publicPath: &#39;/dist/&#39;,\n            umdNamedDefine: true\n        },\n        plugins: [\n            new ExtractTextPlugin({\n                filename: &#39;myUnflappableComponent.css&#39;,\n            }),\n        ],\n        node: {\n          net: &#39;empty&#39;,\n          tls: &#39;empty&#39;,\n          dns: &#39;empty&#39;\n        },\n        module: {\n            rules : [\n                {\n                test: /\\.(png|svg|jpg|gif)$/,\n                use: [\n                    {\n                        loader: &#39;url-loader&#39;,\n                        options:{\n                            fallback: &quot;file-loader&quot;,\n                            name: &quot;[name][md5:hash].[ext]&quot;,\n                            outputPath: &#39;assets/&#39;,\n                            publicPath: &#39;/assets/&#39;\n                        }\n                    }\n                ]\n            },\n            {\n                test: /\\.*css$/,\n                use : ExtractTextPlugin.extract({\n                    fallback : &#39;style-loader&#39;,\n                    use : [\n                        &#39;css-loader&#39;,\n                        &#39;sass-loader&#39;\n                    ]\n                })\n            },\n            {\n                test: /\\.(js|jsx)$/,\n                use: [&quot;babel-loader&quot;],\n                include: path.resolve(__dirname, &quot;src&quot;),\n                exclude: /node_modules/,\n            },\n            {\n                test: /\\.(eot|ttf|woff|woff2)$/,\n                use: [&quot;file-loader&quot;],\n            },\n            {\n                test: /\\.(pdf|doc|zip)$/,\n                use: [&quot;file-loader&quot;],\n            }]\n        },\n        resolve: {\n            alias: {\n                &#39;react&#39;: path.resolve(__dirname, &#39;./node_modules/react&#39;) ,\n                &#39;react-dom&#39;: path.resolve(__dirname, &#39;./node_modules/react-dom&#39;),\n                &#39;assets&#39;: path.resolve(__dirname, &#39;assets&#39;)\n            }\n        },\n        externals: {\n            // Don&#39;t bundle react or react-dom\n            react: {\n                commonjs: &quot;react&quot;,\n                commonjs2: &quot;react&quot;,\n                amd: &quot;React&quot;,\n                root: &quot;React&quot;\n            },\n            &quot;react-dom&quot;: {\n                commonjs: &quot;react-dom&quot;,\n                commonjs2: &quot;react-dom&quot;,\n                amd: &quot;ReactDOM&quot;,\n                root: &quot;ReactDOM&quot;\n            }\n        }\n    };</code></pre></div>","frontmatter":{"title":"How to package your React Component for distribution via NPM","date":"February 26, 2018","url":"/distribute-react-component-via-npm/","tags":["React","web-development"]}}},"pageContext":{"slug":"/distribute-react-component-via-npm/","previous":{"fields":{"slug":"/introducing-book-notes-highlights/2017-10-02-introducing-book-notes-highlights/"},"frontmatter":{"title":"Introducing Book-Notes – my notes and highlights from the books I have read","url":"/introducing-book-notes-highlights/","tags":["Books","Thoughts","web-development"]}},"next":{"fields":{"slug":"/2019-02-06-how-i-moved-my-blog-to-gatsby/"},"frontmatter":{"title":"How I moved my blog to Gatsby","url":"/how-i-moved-my-blog-to-gatsby/","tags":["React","web-development"]}}}},"staticQueryHashes":["3128451518","426816048"]}