{"id":274,"date":"2016-10-17T09:15:09","date_gmt":"2016-10-17T07:15:09","guid":{"rendered":"https:\/\/www.nonamehosts.com\/blog\/?p=274"},"modified":"2016-10-17T09:15:09","modified_gmt":"2016-10-17T07:15:09","slug":"webpack-or-browserify-gulp-which-is-better","status":"publish","type":"post","link":"https:\/\/www.nonamehosts.com\/blog\/readings\/webpack-or-browserify-gulp-which-is-better\/","title":{"rendered":"Webpack or Browserify &#038; Gulp: Which Is Better?"},"content":{"rendered":"<p>As web applications grow increasingly complex, making your web app scalable becomes of the utmost importance. Whereas in the past writing ad-hoc JavaScript and jQuery would suffice, nowadays building a web app requires a much greater degree of discipline and formal software development practices, such as:<\/p>\n<ul>\n<li>Unit tests to ensure modifications to your code don\u2019t break existing functionality<\/li>\n<li>Linting to ensure consistent coding style free of errors<\/li>\n<li>Production builds that differ from development builds<\/li>\n<\/ul>\n<p>The web also provides some of its own unique development challenges. For example, since webpages make a lot of asynchronous requests, your web app\u2019s performance can be significantly degraded from having to request hundreds of JS and CSS files, each with their own tiny overhead (headers, handshakes, and so on). This particular issue can often be addressed by bundling the files together, so you\u2019re only requesting a single bundled JS and CSS file rather than hundreds of individual ones.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/assets.toptal.io\/uploads\/blog\/image\/121261\/toptal-blog-image-1476209965834-ffc10bbd3f9342b632cee3642cd93875.png\" alt=\"Bundling tools tradeoffs: Webpack vs Browserify\" \/><\/p>\n<div class=\"pop_out_box is-full_width is-big\">Which bundling tool should you use: Webpack or Browserify + Gulp? Here is the guide to choosing.<\/div>\n<p><!--more--><\/p>\n<p>It\u2019s also quite common to use language preprocessors such as SASS and JSX that compile to native JS and CSS, as well as JS transpilers such as Babel, to benefit from ES6 code while maintaining ES5 compatibility.<\/p>\n<p>This amounts to a significant number of tasks that have nothing to do with writing the logic of the web app itself. This is where task runners come in. The purpose of a task runner is to automate all of these tasks so that you can benefit from an enhanced development environment while focusing on writing your app. Once the task runner is configured, all you need to do is invoke a single command in a terminal.<\/p>\n<p>I will be using <a href=\"http:\/\/gulpjs.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">Gulp<\/a> as a task runner because it is very developer friendly, easy to learn, and readily understandable.<\/p>\n<h2 id=\"a-quick-introduction-to-gulp\">A Quick Introduction to Gulp<\/h2>\n<p>Gulp\u2019s API consists of four functions:<\/p>\n<ul>\n<li><code>gulp.src<\/code><\/li>\n<li><code>gulp.dest<\/code><\/li>\n<li><code>gulp.task<\/code><\/li>\n<li><code>gulp.watch<\/code><\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/assets.toptal.io\/uploads\/blog\/image\/121259\/toptal-blog-image-1476200326032-4b5cb6936909edc16573dd2018707e43.png\" alt=\"\" \/><\/p>\n<p>Here, for example, is a sample task that makes use of three of these four functions:<\/p>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'my-first-task'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span><span class=\"hljs-params\">()<\/span> {<\/span>\n  gulp.src(<span class=\"hljs-string\">'\/public\/js\/**\/*.js'<\/span>)\n  .pipe(concat())\n  .pipe(minify())\n  .pipe(gulp.dest(<span class=\"hljs-string\">'build'<\/span>))\n});\n<\/code><\/pre>\n<p>When <code>my-first-task<\/code> is performed, all the files matching the glob pattern <code>\/public\/js\/**\/*.js<\/code> are minified and then transferred to a <code>build<\/code> folder.<\/p>\n<p>The beauty of this is in the <code>.pipe()<\/code> chaining. You take a set of input files, pipe them through a series of transformations, then return the output files. To make things even more convenient, the actual piping transformations, such as <code>minify()<\/code>, are often done by NPM libraries. As a result, it\u2019s very rare in practice that you need to write your own transformations beyond renaming files in the pipe.<\/p>\n<p>The next step to understand Gulp is understanding the array of task dependencies.<\/p>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'my-second-task'<\/span>, [<span class=\"hljs-string\">'lint'<\/span>, <span class=\"hljs-string\">'bundle'<\/span>], <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span><span class=\"hljs-params\">()<\/span> {<\/span>\n  ...\n});\n<\/code><\/pre>\n<p>Here, <code>my-second-task<\/code> only runs the callback function after the <code>lint<\/code> and <code>bundle<\/code> tasks are completed. This allows for separation of concerns: You create a series of small tasks with a single responsibility, such as converting <code>LESS<\/code> to <code>CSS<\/code>, and create a sort of master task that simply calls all the other tasks via the array of task dependencies.<\/p>\n<p>Finally, we have <code>gulp.watch<\/code>, which watches a glob file pattern for changes, and when a change is detected, runs a series of tasks.<\/p>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'my-third-task'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span><span class=\"hljs-params\">()<\/span> {<\/span>\n  gulp.watch(<span class=\"hljs-string\">'\/public\/js\/**\/*.js'<\/span>, [<span class=\"hljs-string\">'lint'<\/span>, <span class=\"hljs-string\">'reload'<\/span>])\n})\n<\/code><\/pre>\n<p>In the above example, any changes to a file matching <code>\/public\/js\/**\/*.js<\/code> would trigger the <code>lint<\/code> and <code>reload<\/code>task. A common use of <code>gulp.watch<\/code> is to trigger live reloads in the browser, a feature so useful for development that you won\u2019t be able to live without it once you\u2019ve experienced it.<\/p>\n<p>And just like that, you understand all you really need to know about <code>gulp<\/code>.<\/p>\n<h2 id=\"where-does-webpack-fit-in\">Where Does Webpack Fit In?<\/h2>\n<p><img decoding=\"async\" src=\"https:\/\/assets.toptal.io\/uploads\/blog\/image\/121258\/toptal-blog-image-1476174229140-3890202f75d94c0692549af41f9d652a.png\" alt=\"\" \/><\/p>\n<p>When using the CommonJS pattern, bundling JavaScript files isn\u2019t as simple as concatenating them. Rather, you have an entry point (usually called <code>index.js<\/code> or <code>app.js<\/code>) with a series of <code>require<\/code> or <code>import<\/code> statements at the top of the file:<\/p>\n<h4 id=\"es5\">ES5<\/h4>\n<pre><code class=\"language-js hljs\"><span class=\"hljs-keyword\">var<\/span> Component1 = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'.\/components\/Component1'<\/span>);\n<span class=\"hljs-keyword\">var<\/span> Component2 = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'.\/components\/Component2'<\/span>);\n<\/code><\/pre>\n<h4 id=\"es6\">ES6<\/h4>\n<pre><code class=\"language-js hljs\">import Component1 from <span class=\"hljs-string\">'.\/components\/Component1'<\/span>;\nimport Component2 from <span class=\"hljs-string\">'.\/components\/Component2'<\/span>;\n<\/code><\/pre>\n<p>The dependencies have to be resolved before the remaining code in <code>app.js<\/code>, and those dependencies may themselves have further dependencies to resolve. Furthermore, you might <code>require<\/code> the same dependency in multiple places in your application, but you only want to resolve that dependency once. As you can imagine, once you have a dependency tree a few levels deep, the process of bundling your JavaScript becomes rather complex. This is where bundlers such as <a href=\"http:\/\/browserify.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">Browserify<\/a> and Webpack come in.<\/p>\n<h2 id=\"why-are-developers-using-webpack-instead-of-gulp\">Why Are Developers Using Webpack Instead of Gulp?<\/h2>\n<p><a href=\"https:\/\/github.com\/webpack\/webpack\" target=\"_blank\" rel=\"noopener noreferrer\">Webpack<\/a> is a bundler whereas Gulp is a task runner, so you\u2019d expect to see these two tools commonly used together. Instead, there\u2019s a growing trend, especially among the React community, to use Webpack <em>instead<\/em> of Gulp. Why is this?<\/p>\n<p>Simply put, Webpack is such a powerful tool that it can already perform the vast majority of the tasks you\u2019d otherwise do through a task runner. For instance, Webpack already provides options for minification and sourcemaps for your bundle. In addition, Webpack can be run as middleware through a custom server called <code>webpack-dev-server<\/code>, which supports both live reloading and hot reloading (we\u2019ll talk about these features later). By using loaders, you can also add ES6 to ES5 transpilation, and CSS pre- and post-processors. That really just leaves unit tests and linting as major tasks that Webpack can\u2019t handle independently. Given that we\u2019ve cut down at least half a dozen potential gulp tasks down to two, many devs opt to instead use NPM scripts directly, as this avoids the overhead of adding Gulp to the project (which we\u2019ll also talk about later).<\/p>\n<p>The major drawback to using Webpack is that it is rather difficult to configure, which is unattractive if you\u2019re trying to quickly get a project up and running.<\/p>\n<h2 id=\"our-3-task-runner-setups\">Our 3 Task Runner Setups<\/h2>\n<p>I will set up a project with three different task runner setups. Each setup will perform the following tasks:<\/p>\n<ul>\n<li>Set up a development server with live reloading on watched file changes<\/li>\n<li>Bundle our JS &amp; CSS files (including ES6 to ES5 transpilation, SASS to CSS conversion and sourcemaps) in a scalable manner on watched file changes<\/li>\n<li>Run unit tests either as a standalone task or in watch mode<\/li>\n<li>Run linting either as a standalone task or in watch mode<\/li>\n<li>Provide the ability to execute all of the above via a single command in the terminal<\/li>\n<li>Have another command for creating a production bundle with minification and other optimizations<\/li>\n<\/ul>\n<p>Our three setups will be:<\/p>\n<ul>\n<li>Gulp + Browserify<\/li>\n<li>Gulp + Webpack<\/li>\n<li>Webpack + NPM Scripts<\/li>\n<\/ul>\n<p>The application will use <a href=\"https:\/\/www.toptal.com\/react\">React<\/a> for the front-end. Originally, I wanted to use a framework-agnostic approach, but using React actually simplifies the responsibilities of the task runner, since only one HTML file is needed, and React works very well with the CommonJS pattern.<\/p>\n<p>We will cover the benefits and drawbacks of each setup so you can make an informed decision on what type of setup best suits your project needs.<\/p>\n<p>I\u2019ve setup a Git repository with three branches, one for each approach (<a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\" target=\"_blank\" rel=\"noopener noreferrer\">link<\/a>). Testing each setup is as simple as:<\/p>\n<pre><code class=\"language-js hljs\">git checkout &lt;branch name&gt;\nnpm prune (optional)\nnpm install\ngulp (or npm start, depending on the setup)\n<\/code><\/pre>\n<p>Let\u2019s examine the code in each branch in detail\u2026<\/p>\n<h2 id=\"common-code\">Common Code<\/h2>\n<h4 id=\"folder-structure\">Folder Structure<\/h4>\n<pre><code>- app\n - components\n - fonts\n - styles\n- index.html\n- index.js\n- index.test.js\n- routes.js\n<\/code><\/pre>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-browserify-setup\/app\/index.html\" target=\"_blank\" rel=\"noopener noreferrer\">index.html<\/a><\/strong><\/p>\n<p>A straightforward HTML file. The React application is loaded into <code>&lt;div id=\"app\"&gt;&lt;\/div&gt;<\/code> and we only use a single bundled JS and CSS file. In fact, in our Webpack development setup, we won\u2019t even need <code>bundle.css<\/code>.<\/p>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-browserify-setup\/app\/index.js\" target=\"_blank\" rel=\"noopener noreferrer\">index.js<\/a><\/strong><\/p>\n<p>This acts as the JS entry point of our app. Essentially, we\u2019re just loading React Router into the <code>div<\/code> with id <code>app<\/code> that we mentioned earlier.<\/p>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-browserify-setup\/app\/routes.js\" target=\"_blank\" rel=\"noopener noreferrer\">routes.js<\/a><\/strong><\/p>\n<p>This file defines our routes. The urls <code>\/<\/code>, <code>\/about<\/code> and <code>\/contact<\/code> are mapped to the <code>HomePage<\/code>, <code>AboutPage<\/code>, and <code>ContactPage<\/code> components, respectively.<\/p>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-browserify-setup\/app\/index.test.js\" target=\"_blank\" rel=\"noopener noreferrer\">index.test.js<\/a><\/strong><\/p>\n<p>This is a series of unit tests that test native JavaScript behavior. In a real production quality app, you\u2019d write a unit test per React component (at least ones that manipulate state), testing React-specific behavior. However, for the purposes of this post, it\u2019s enough to simply have a functional unit test that can run in watch mode.<\/p>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-browserify-setup\/app\/components\/App.js\" target=\"_blank\" rel=\"noopener noreferrer\">components\/App.js<\/a><\/strong><\/p>\n<p>This can be thought of as the container for all our page views. Each page contains a <code>&lt;Header\/&gt;<\/code> component as well as <code>this.props.children<\/code>, which evaluates to the page view itself (ex\/ <code>ContactPage<\/code> if at <code>\/contact<\/code> in the browser).<\/p>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-browserify-setup\/app\/components\/home\/HomePage.js\" target=\"_blank\" rel=\"noopener noreferrer\">components\/home\/HomePage.js<\/a><\/strong><\/p>\n<p>This is our home view. I\u2019ve opted to use <code>react-bootstrap<\/code> since bootstrap\u2019s grid system is excellent for creating responsive pages. With proper use of bootstrap, the number of media queries you must write for smaller viewports is dramatically reduced.<\/p>\n<p>The remaining components (<code>Header<\/code>, <code>AboutPage<\/code>, <code>ContactPage<\/code>) are structured similarly (<code>react-bootstrap<\/code>markup, no state manipulation).<\/p>\n<p>Now let\u2019s talk more about styling.<\/p>\n<h3 id=\"css-styling-approach\">CSS Styling Approach<\/h3>\n<p>My preferred approach to styling React components is to have one stylesheet per component, whose styles are scoped to only apply to that specific component. You\u2019ll notice that in each of my React components, the top-level <code>div<\/code> has a class name matching the name of the component. So, for example, <code>HomePage.js<\/code> has its markup wrapped by:<\/p>\n<pre><code class=\"language-html hljs\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-attribute\">className<\/span>=<span class=\"hljs-value\">\"HomePage\"<\/span>&gt;<\/span>\n  ...\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-title\">div<\/span>&gt;<\/span>\n<\/code><\/pre>\n<p>There is also an associated <code>HomePage.scss<\/code> file that\u2019s structured as follows:<\/p>\n<pre><code class=\"language-scss hljs\"><span class=\"hljs-at_rule\">@<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'..\/..\/styles\/variables'<\/span>;<\/span>\n\n<span class=\"hljs-class\">.HomePage<\/span> {\n  <span class=\"hljs-comment\">\/\/ Content here<\/span>\n}\n<\/code><\/pre>\n<p>Why is this approach so useful? It results in highly modular CSS, largely eliminating the issue of unwanted cascading behavior.<\/p>\n<p>Suppose we have two React components, <code>Component1<\/code> and <code>Component2<\/code>. In both cases, we want to override the <code>h2<\/code> font size.<\/p>\n<pre><code class=\"language-css hljs\"><span class=\"hljs-comment\">\/* Component1.scss *\/<\/span>\n<span class=\"hljs-class\">.Component1<\/span> <span class=\"hljs-rules\">{\n  <span class=\"hljs-rule\"><span class=\"hljs-attribute\">h2 {\n    font-size<\/span>:<span class=\"hljs-value\"> <span class=\"hljs-number\">30<\/span>px<\/span><\/span>;\n  <span class=\"hljs-rule\">}<\/span><\/span>\n}\n\n<span class=\"hljs-comment\">\/* Component2.scss *\/<\/span>\n<span class=\"hljs-class\">.Component2<\/span> <span class=\"hljs-rules\">{\n  <span class=\"hljs-rule\"><span class=\"hljs-attribute\">h2 {\n    font-size<\/span>:<span class=\"hljs-value\"> <span class=\"hljs-number\">60<\/span>px<\/span><\/span>;\n  <span class=\"hljs-rule\">}<\/span><\/span>\n}\n<\/code><\/pre>\n<p>The <code>h2<\/code> font size of <code>Component1<\/code> and <code>Component2<\/code> are independent whether the components are adjacent, or one component is nested inside the other. Ideally, this means a component\u2019s styling is completely self-contained, meaning the component will look exactly the same no matter where it is placed in your markup. In reality, it\u2019s not always that simple, but it\u2019s certainly a huge step in the right direction.<\/p>\n<p>In addition to per-component styles, I like to have a <code>styles<\/code> folder containing a global stylesheet <code>global.scss<\/code>, along with SASS partials that handle a specific responsibility (in this case, <code>_fonts.scss<\/code> and <code>_variables.scss<\/code>for fonts and variables, respectively). The global stylesheet allows us to define the general look and feel of the entire app, while the helper partials can be imported by the per-component stylesheets as needed.<\/p>\n<p>Now that the common code in each branch has been explored in depth, let\u2019s shift our focus to the first task runner \/ bundler approach.<\/p>\n<h2 id=\"gulp--browserify-setup\">Gulp + Browserify Setup<\/h2>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-browserify-setup\/gulpfile.js\" target=\"_blank\" rel=\"noopener noreferrer\">gulpfile.js<\/a><\/strong><\/p>\n<p>This comes out to a surprisingly large gulpfile, with 22 imports and 150 lines of code. So, for the sake of brevity, I\u2019ll only review the <code>js<\/code>, <code>css<\/code>, <code>server<\/code>, <code>watch<\/code>, and <code>default<\/code> tasks in detail.<\/p>\n<h4 id=\"js-bundle\">JS bundle<\/h4>\n<pre><code class=\"language-js hljs\"><span class=\"hljs-comment\">\/\/ Browserify specific configuration<\/span>\n<span class=\"hljs-keyword\">const<\/span> b = browserify({\n  entries: [config.paths.entry],\n  debug: <span class=\"hljs-literal\">true<\/span>,\n  plugin: PROD ? [] : [hmr, watchify],\n  cache: {},\n  packageCache: {}\n})\n.transform(<span class=\"hljs-string\">'babelify'<\/span>);\nb.on(<span class=\"hljs-string\">'update'<\/span>, bundle);\nb.on(<span class=\"hljs-string\">'log'<\/span>, gutil.log);\n\n(...)\n\ngulp.task(<span class=\"hljs-string\">'js'<\/span>, bundle);\n\n(...)\n\n<span class=\"hljs-comment\">\/\/ Bundles our JS using Browserify. Sourcemaps are used in development, while minification is used in production.<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">bundle<\/span><span class=\"hljs-params\">()<\/span> {<\/span>\n  <span class=\"hljs-keyword\">return<\/span> b.bundle()\n  .on(<span class=\"hljs-string\">'error'<\/span>, gutil.log.bind(gutil, <span class=\"hljs-string\">'Browserify Error'<\/span>))\n  .pipe(source(<span class=\"hljs-string\">'bundle.js'<\/span>))\n  .pipe(buffer())\n  .pipe(cond(PROD, minifyJS()))\n  .pipe(cond(!PROD, sourcemaps.init({loadMaps: <span class=\"hljs-literal\">true<\/span>})))\n  .pipe(cond(!PROD, sourcemaps.write()))\n  .pipe(gulp.dest(config.paths.baseDir));\n}\n<\/code><\/pre>\n<p>This approach is rather ugly for a number of reasons. For one thing, the task is split into three separate parts. First, you create your Browserify bundle object <code>b<\/code>, passing in some options and defining some event handlers. Then you have the Gulp task itself, which has to pass a named function as its callback instead of inlining it (since <code>b.on('update')<\/code> uses that very same callback). This hardly has the elegance of a Gulp task where you just pass in a <code>gulp.src<\/code> and pipe some changes.<\/p>\n<p>Another issue is that this forces us to have different approaches to reloading <code>html<\/code>, <code>css<\/code> and <code>js<\/code> in the browser. Looking at our Gulp <code>watch<\/code> task:<\/p>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'watch'<\/span>, () =&gt; {\n  livereload.listen({basePath: <span class=\"hljs-string\">'dist'<\/span>});\n\n  gulp.watch(config.paths.html, [<span class=\"hljs-string\">'html'<\/span>]);\n  gulp.watch(config.paths.css, [<span class=\"hljs-string\">'css'<\/span>]);\n  gulp.watch(config.paths.js, () =&gt; {\n    runSequence(<span class=\"hljs-string\">'lint'<\/span>, <span class=\"hljs-string\">'test'<\/span>);\n  });\n});\n<\/code><\/pre>\n<p>When an HTML file is changed, the <code>html<\/code> task is re-run.<\/p>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'html'<\/span>, () =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> gulp.src(config.paths.html)\n  .pipe(gulp.dest(config.paths.baseDir))\n  .pipe(cond(!PROD, livereload()));\n});\n<\/code><\/pre>\n<p>The last pipe calls <code>livereload()<\/code> if the <code>NODE_ENV<\/code> is not <code>production<\/code>, which triggers a refresh in the browser.<\/p>\n<p>The same logic is used for the CSS watch. When a CSS file is changed, the <code>css<\/code> task is re-run, and the last pipe in the <code>css<\/code> task triggers <code>livereload()<\/code> and refreshes the browser.<\/p>\n<p>However, the <code>js<\/code> watch doesn\u2019t call the <code>js<\/code> task at all. Instead, Browserify\u2019s event handler <code>b.on('update', bundle)<\/code> handles the reload using a completely different approach (namely, hot module replacement). The inconsistency in this approach is irritating, but unfortunately necessary in order to have<em>incremental<\/em> builds. If we naively just called <code>livereload()<\/code> at the end of the <code>bundle<\/code> function, this would re-build the <em>entire<\/em> JS bundle on any individual JS file change. Such an approach obviously doesn\u2019t scale. The more JS files you have, the longer each rebundle takes. Suddenly, your 500 ms rebundles start taking 30 seconds, which really inhibits agile development.<\/p>\n<h4 id=\"css-bundle\">CSS bundle<\/h4>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'css'<\/span>, () =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> gulp.src(\n    [\n      <span class=\"hljs-string\">'node_modules\/bootstrap\/dist\/css\/bootstrap.css'<\/span>,\n      <span class=\"hljs-string\">'node_modules\/font-awesome\/css\/font-awesome.css'<\/span>,\n      config.paths.css\n    ]\n  )\n  .pipe(cond(!PROD, sourcemaps.init()))\n  .pipe(sass().on(<span class=\"hljs-string\">'error'<\/span>, sass.logError))\n  .pipe(concat(<span class=\"hljs-string\">'bundle.css'<\/span>))\n  .pipe(cond(PROD, minifyCSS()))\n  .pipe(cond(!PROD, sourcemaps.write()))\n  .pipe(gulp.dest(config.paths.baseDir))\n  .pipe(cond(!PROD, livereload()));\n});\n<\/code><\/pre>\n<p>The first issue here is the cumbersome vendor CSS inclusion. Whenever a new vendor CSS file is added to the project, we have to remember to change our gulpfile to add an element to the <code>gulp.src<\/code> array, rather than adding the import into a relevant place in our actual source code.<\/p>\n<p>The other main issue is the convoluted logic in each pipe. I had to add an NPM library called <code>gulp-cond<\/code> just to setup conditional logic in my pipes, and the end result isn\u2019t too readable (triple brackets everywhere!).<\/p>\n<h4 id=\"server-task\">Server Task<\/h4>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'server'<\/span>, () =&gt; {\n  nodemon({\n    script: <span class=\"hljs-string\">'server.js'<\/span>\n  });\n});\n<\/code><\/pre>\n<p>This task is very straightforward. It\u2019s essentially a wrapper around the command line invocation <code>nodemon server.js<\/code>, which runs <code>server.js<\/code> in a node environment. <code>nodemon<\/code> is used instead of <code>node<\/code> so that any changes to the file cause it to restart. By default, <code>nodemon<\/code> would restart the running process on <em>any<\/em> JS file change, which is why it\u2019s important to include a <code>nodemon.json<\/code> file to limit its scope:<\/p>\n<pre><code class=\"language-json hljs\">{\n  \"<span class=\"hljs-attribute\">watch<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"server.js\"<\/span>\n<\/span>}\n<\/code><\/pre>\n<p>Let\u2019s review our server code.<\/p>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-browserify-setup\/server.js\" target=\"_blank\" rel=\"noopener noreferrer\">server.js<\/a><\/strong><\/p>\n<pre><code class=\"language-js hljs\"><span class=\"hljs-keyword\">const<\/span> baseDir = process.env.NODE_ENV === <span class=\"hljs-string\">'production'<\/span> ? <span class=\"hljs-string\">'build'<\/span> : <span class=\"hljs-string\">'dist'<\/span>;\n<span class=\"hljs-keyword\">const<\/span> port = process.env.NODE_ENV === <span class=\"hljs-string\">'production'<\/span> ? <span class=\"hljs-number\">8080<\/span>: <span class=\"hljs-number\">3000<\/span>;\n<span class=\"hljs-keyword\">const<\/span> app = express();\n<\/code><\/pre>\n<p>This sets the base directory of the server and the port based on the node environment, and creates an instance of express.<\/p>\n<pre><code class=\"language-js hljs\">app.use(<span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'connect-livereload'<\/span>)({port: <span class=\"hljs-number\">35729<\/span>}));\napp.use(express.static(path.join(__dirname, baseDir)));\n<\/code><\/pre>\n<p>This adds <code>connect-livereload<\/code> middleware (necessary for our live reloading setup) and static middleware (necessary for handling our static assets).<\/p>\n<pre><code class=\"language-js hljs\">app.get(<span class=\"hljs-string\">'\/api\/sample-route'<\/span>, (req, res) =&gt; {\n  res.send({\n    website: <span class=\"hljs-string\">'Toptal'<\/span>,\n    blogPost: <span class=\"hljs-literal\">true<\/span>\n  });\n});\n<\/code><\/pre>\n<p>This is just a simple API route. If you navigate to <code>localhost:3000\/api\/sample-route<\/code> in the browser, you will see:<\/p>\n<pre><code class=\"language-js hljs\">{\n  website: <span class=\"hljs-string\">\"Toptal\"<\/span>,\n  blogPost: <span class=\"hljs-literal\">true<\/span>\n}\n<\/code><\/pre>\n<p>In a real backend, you would have an entire folder dedicated to API routes, separate files for establishing DB connections, and so on. This sample route was merely included to show that we can easily build a backend on top of the frontend we have set up.<\/p>\n<pre><code class=\"language-js hljs\">app.get(<span class=\"hljs-string\">'*'<\/span>, (req, res) =&gt; {\n  res.sendFile(path.join(__dirname, <span class=\"hljs-string\">'.\/'<\/span>, baseDir ,<span class=\"hljs-string\">'\/index.html'<\/span>));\n});\n<\/code><\/pre>\n<p>This is a catch-all route, meaning no matter what url you type into the browser, the server will return our lone <code>index.html<\/code> page. It is then the responsibility of the React Router to resolve our routes on the client side.<\/p>\n<pre><code class=\"language-js hljs\">app.listen(port, () =&gt; {\n  open(`http:<span class=\"hljs-comment\">\/\/localhost:${port}`);<\/span>\n});\n<\/code><\/pre>\n<p>This tells our express instance to listen to the port we specified, and open the browser in a new tab at the specified URL.<\/p>\n<p>So far the only thing I don\u2019t like about the server setup is:<\/p>\n<pre><code class=\"language-js hljs\">app.use(<span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'connect-livereload'<\/span>)({port: <span class=\"hljs-number\">35729<\/span>}));\n<\/code><\/pre>\n<p>Given that we are already using <code>gulp-livereload<\/code> in our gulpfile, this makes two separate places where livereload must be used.<\/p>\n<p>Now, last but not least:<\/p>\n<h4 id=\"default-task\">Default Task<\/h4>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'default'<\/span>, (cb) =&gt; {\n  runSequence(<span class=\"hljs-string\">'clean'<\/span>, <span class=\"hljs-string\">'lint'<\/span>, <span class=\"hljs-string\">'test'<\/span>, <span class=\"hljs-string\">'html'<\/span>, <span class=\"hljs-string\">'css'<\/span>, <span class=\"hljs-string\">'js'<\/span>, <span class=\"hljs-string\">'fonts'<\/span>, <span class=\"hljs-string\">'server'<\/span>, <span class=\"hljs-string\">'watch'<\/span>, cb);\n});\n<\/code><\/pre>\n<p>This is the task that runs when simply typing <code>gulp<\/code> into the terminal. One oddity is the need to use <code>runSequence<\/code> in order to get the tasks to run sequentially. Normally, an array of tasks are executed in parallel, but this isn\u2019t always the desired behavior. For example, we need to have the <code>clean<\/code> task run before <code>html<\/code> to ensure that our destination folders are empty before moving files into them. When gulp 4 is released, it will support <code>gulp.series<\/code> and <code>gulp.parallel<\/code> methods natively, but for now we have to leave with this slight quirk in our setup.<\/p>\n<p>Beyond that, this is actually pretty elegant. The whole creation and hosting of our app is performed in a single command, and understanding any portion of the workflow is as simple as examining an individual task in the run sequence. In addition, we can break the whole sequence into smaller chunks for a more granular approach to creating and hosting the app. For example, we could set up a separate task called <code>validate<\/code> that runs the <code>lint<\/code> and <code>test<\/code> tasks. Or we could have a <code>host<\/code> task that runs <code>server<\/code> and <code>watch<\/code>. This ability to orchestrate tasks is very powerful, especially as your application scales and requires more automated tasks.<\/p>\n<h4 id=\"development-vs-production-builds\">Development vs Production Builds<\/h4>\n<pre><code class=\"language-js hljs\"><span class=\"hljs-keyword\">if<\/span> (argv.prod) {\n  process.env.NODE_ENV = <span class=\"hljs-string\">'production'<\/span>;\n}\n<span class=\"hljs-keyword\">let<\/span> PROD = process.env.NODE_ENV === <span class=\"hljs-string\">'production'<\/span>;\n<\/code><\/pre>\n<p>Using the <code>yargs<\/code> NPM library, we can supply command line flags to Gulp. Here I instruct the gulpfile to set the node environment to production if <code>--prod<\/code> is passed to <code>gulp<\/code> in the terminal. Our <code>PROD<\/code> variable is then used as a conditional to differentiate development and production behavior in our gulpfile. For example, one of the options we pass to our <code>browserify<\/code> config is:<\/p>\n<pre><code class=\"language-js hljs\">plugin: PROD ? [] : [hmr, watchify]\n<\/code><\/pre>\n<p>This tells <code>browserify<\/code> to not use any plugins in production mode, and use <code>hmr<\/code> and <code>watchify<\/code> plugins in other environments.<\/p>\n<p>This <code>PROD<\/code> conditional is very useful because it saves us from having to write a separate gulpfile for production and development, which would ultimately contain a lot of code repetition. Instead, we can do things like <code>gulp --prod<\/code> to run the default task in production, or <code>gulp html --prod<\/code> to only run the <code>html<\/code> task in production. On the other hand, we saw earlier that littering our Gulp pipelines with statements such as <code>.pipe(cond(!PROD, livereload()))<\/code> aren\u2019t the most readable. In the end, it\u2019s a matter of preference whether you want to use the boolean variable approach or set up two separate gulpfiles.<\/p>\n<p>Now let\u2019s see what happens when we keep using Gulp as our task runner but replace Browserify with Webpack.<\/p>\n<h2 id=\"gulp--webpack-setup\">Gulp + Webpack Setup<\/h2>\n<p>Suddenly our gulpfile is only 99 lines long with 12 imports, quite a reduction from our previous setup! If we check the default task:<\/p>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'default'<\/span>, (cb) =&gt; {\n  runSequence(<span class=\"hljs-string\">'lint'<\/span>, <span class=\"hljs-string\">'test'<\/span>, <span class=\"hljs-string\">'build'<\/span>, <span class=\"hljs-string\">'server'<\/span>, <span class=\"hljs-string\">'watch'<\/span>, cb);\n});\n<\/code><\/pre>\n<p>Now our full web app setup only requires five tasks instead of nine, a dramatic improvement.<\/p>\n<p>In addition, we\u2019ve eliminated the need for <code>livereload<\/code>. Our <code>watch<\/code> task is now simply:<\/p>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'watch'<\/span>, () =&gt; {\n  gulp.watch(config.paths.js, () =&gt; {\n    runSequence(<span class=\"hljs-string\">'lint'<\/span>, <span class=\"hljs-string\">'test'<\/span>);\n  });\n});\n<\/code><\/pre>\n<p>This means our gulp watcher isn\u2019t triggering any type of rebundling behavior. As an added bonus, we don\u2019t need to transfer <code>index.html<\/code> from <code>app<\/code> to <code>dist<\/code> or <code>build<\/code> anymore.<\/p>\n<p>Returning our focus to the task reduction, our <code>html<\/code>, <code>css<\/code>, <code>js<\/code> and <code>fonts<\/code> tasks have all been replaced by a single <code>build<\/code> task:<\/p>\n<pre><code class=\"language-js hljs\">gulp.task(<span class=\"hljs-string\">'build'<\/span>, () =&gt; {\n  runSequence(<span class=\"hljs-string\">'clean'<\/span>, <span class=\"hljs-string\">'html'<\/span>);\n\n  <span class=\"hljs-keyword\">return<\/span> gulp.src(config.paths.entry)\n  .pipe(webpack(<span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'.\/webpack.config'<\/span>)))\n  .pipe(gulp.dest(config.paths.baseDir));\n});\n<\/code><\/pre>\n<p>Simple enough. Run the <code>clean<\/code> and <code>html<\/code> tasks in sequence. Once those are complete, grab our entrypoint, pipe it through Webpack, passing in a <code>webpack.config.js<\/code> file to configure it, and send the resulting bundle to our <code>baseDir<\/code> (either <code>dist<\/code> or <code>build<\/code>, depending on node env).<\/p>\n<p>Let\u2019s have a look at the Webpack config file:<\/p>\n<p><strong><a href=\"https:\/\/github.com\/ericgrosse\/task-runner-bundler-comparison\/blob\/gulp-webpack-setup\/webpack.config.js\" target=\"_blank\" rel=\"noopener noreferrer\">webpack.config.js<\/a><\/strong><\/p>\n<p>This is a pretty large and intimidating config file, so let\u2019s explain some of the important properties being set on our <code>module.exports<\/code> object.<\/p>\n<pre><code class=\"language-js hljs\">devtool: PROD ? <span class=\"hljs-string\">'source-map'<\/span> : <span class=\"hljs-string\">'eval-source-map'<\/span>,\n<\/code><\/pre>\n<p>This sets the type of sourcemaps that Webpack will use. Not only does Webpack support sourcemaps out of the box, it actually supports a wide array of sourcemap options. Each option provides a different balance of sourcemap detail vs. rebuild speed (the time taken to rebundle on changes). This means we can use a \u201ccheap\u201d sourcemap option for development to achieve fast reloads, and a more expensive sourcemap option in production.<\/p>\n<pre><code class=\"language-js hljs\">entry: PROD ? <span class=\"hljs-string\">'.\/app\/index'<\/span> :\n[\n  <span class=\"hljs-string\">'webpack-hot-middleware\/client?reload=true'<\/span>, <span class=\"hljs-comment\">\/\/ reloads the page if hot module reloading fails.<\/span>\n  <span class=\"hljs-string\">'.\/app\/index'<\/span>\n]\n<\/code><\/pre>\n<p>This is our bundle entry point. Notice that an array is passed, meaning it\u2019s possible to have multiple entry points. In this case, we have our expected entry point <code>app\/index.js<\/code> as well as the <code>webpack-hot-middleware<\/code>entry point that\u2019s used as part of our hot module reloading setup.<\/p>\n<pre><code class=\"language-js hljs\">output: {\n  path: PROD ? __dirname + <span class=\"hljs-string\">'\/build'<\/span> : __dirname + <span class=\"hljs-string\">'\/dist'<\/span>,\n  publicPath: <span class=\"hljs-string\">'\/'<\/span>,\n  filename: <span class=\"hljs-string\">'bundle.js'<\/span>\n},\n<\/code><\/pre>\n<p>This is where the compiled bundle will be output. The most confusing option is <code>publicPath<\/code>. It sets the base url for where your bundle will be hosted on the server. So, for example, if your <code>publicPath<\/code> is <code>\/public\/assets<\/code>, then the bundle will appear under <code>\/public\/assets\/bundle.js<\/code> on the server.<\/p>\n<pre><code class=\"language-js hljs\">devServer: {\n  contentBase: PROD ? <span class=\"hljs-string\">'.\/build'<\/span> : <span class=\"hljs-string\">'.\/app'<\/span>\n}\n<\/code><\/pre>\n<p>This tells the server which folder in your project to use as the server\u2019s root directory.<\/p>\n<p>If you ever get confused about how Webpack is mapping the created bundle in your project to the bundle on the server, simply remember the following:<\/p>\n<ul>\n<li><code>path<\/code> + <code>filename<\/code>: The exact location of the bundle in your project source code<\/li>\n<li><code>contentBase<\/code> (as the root, <code>\/<\/code>) + <code>publicPath<\/code>: The location of the bundle on the server<\/li>\n<\/ul>\n<pre><code class=\"language-js hljs\">plugins: PROD ?\n[\n  <span class=\"hljs-keyword\">new<\/span> webpack.optimize.OccurenceOrderPlugin(),\n  <span class=\"hljs-keyword\">new<\/span> webpack.DefinePlugin(GLOBALS),\n  <span class=\"hljs-keyword\">new<\/span> ExtractTextPlugin(<span class=\"hljs-string\">'bundle.css'<\/span>),\n  <span class=\"hljs-keyword\">new<\/span> webpack.optimize.DedupePlugin(),\n  <span class=\"hljs-keyword\">new<\/span> webpack.optimize.UglifyJsPlugin({compress: {warnings: <span class=\"hljs-literal\">false<\/span>}})\n] :\n[\n  <span class=\"hljs-keyword\">new<\/span> webpack.HotModuleReplacementPlugin(),\n  <span class=\"hljs-keyword\">new<\/span> webpack.NoErrorsPlugin()\n],\n<\/code><\/pre>\n<p>These are plugins that enhance Webpack\u2019s functionality in some way. For example, <code>webpack.optimize.UglifyJsPlugin<\/code> is responsible for minification.<\/p>\n<pre><code class=\"language-js hljs\">loaders: [\n  {test: <span class=\"hljs-regexp\">\/\\.js$\/<\/span>, include: path.join(__dirname, <span class=\"hljs-string\">'app'<\/span>), loaders: [<span class=\"hljs-string\">'babel'<\/span>]},\n  {\n    test: <span class=\"hljs-regexp\">\/\\.css$\/<\/span>,\n    loader: PROD ?\n      ExtractTextPlugin.extract(<span class=\"hljs-string\">'style'<\/span>, <span class=\"hljs-string\">'css?sourceMap'<\/span>):\n      <span class=\"hljs-string\">'style!css?sourceMap'<\/span>\n  },\n  {\n    test: <span class=\"hljs-regexp\">\/\\.scss$\/<\/span>,\n    loader: PROD ?\n      ExtractTextPlugin.extract(<span class=\"hljs-string\">'style'<\/span>, <span class=\"hljs-string\">'css?sourceMap!resolve-url!sass?sourceMap'<\/span>) :\n      <span class=\"hljs-string\">'style!css?sourceMap!resolve-url!sass?sourceMap'<\/span>\n  },\n  {test: <span class=\"hljs-regexp\">\/\\.(svg|png|jpe?g|gif)(\\?\\S*)?$\/<\/span>, loader: <span class=\"hljs-string\">'url?limit=100000&amp;name=img\/[name].[ext]'<\/span>},\n  {test: <span class=\"hljs-regexp\">\/\\.(eot|woff|woff2|ttf)(\\?\\S*)?$\/<\/span>, loader: <span class=\"hljs-string\">'url?limit=100000&amp;name=fonts\/[name].[ext]'<\/span>}\n]\n<\/code><\/pre>\n<p>These are loaders. Essentially, they pre-process files that are loaded through <code>require()<\/code> statements. They are somewhat similar to Gulp pipes in that you can chain loaders together.<\/p>\n<p>Let\u2019s examine one of our loader objects:<\/p>\n<pre><code class=\"language-js hljs\">{test: <span class=\"hljs-regexp\">\/\\.scss$\/<\/span>, loader: <span class=\"hljs-string\">'style!css?sourceMap!resolve-url!sass?sourceMap'<\/span>}\n<\/code><\/pre>\n<p>The <code>test<\/code> property tells Webpack that the given loader applies if a file matches the provided regex pattern, in this case <code>\/\\.scss$\/<\/code>. The <code>loader<\/code> property corresponds to the action the loader performs. Here we are chaining together the <code>style<\/code>, <code>css<\/code>, <code>resolve-url<\/code> and <code>sass<\/code> loaders, which are executed in reverse order.<\/p>\n<p>I must admit that I don\u2019t find the <code>loader3!loader2!loader1<\/code> syntax very elegant. After all, when do you ever have to read anything in a program from right to left? Despite this, loaders are a very powerful feature of webpack. In fact, the loader I just mentioned allows us to import SASS files directly into our JavaScript! For instance, we can import our vendor and global stylesheets in our entrypoint file:<\/p>\n<h4 id=\"indexjs\">index.js<\/h4>\n<pre><code class=\"language-js hljs\">import React from <span class=\"hljs-string\">'react'<\/span>;\nimport {render} from <span class=\"hljs-string\">'react-dom'<\/span>;\nimport {Router, browserHistory} from <span class=\"hljs-string\">'react-router'<\/span>;\nimport routes from <span class=\"hljs-string\">'.\/routes'<\/span>;\n<span class=\"hljs-comment\">\/\/ CSS imports<\/span>\nimport <span class=\"hljs-string\">'..\/node_modules\/bootstrap\/dist\/css\/bootstrap.css'<\/span>;\nimport <span class=\"hljs-string\">'..\/node_modules\/font-awesome\/css\/font-awesome.css'<\/span>;\nimport <span class=\"hljs-string\">'.\/styles\/global.scss'<\/span>;\n\nrender(<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-title\">Router<\/span> <span class=\"hljs-attribute\">history<\/span>=<span class=\"hljs-value\">{browserHistory}<\/span> <span class=\"hljs-attribute\">routes<\/span>=<span class=\"hljs-value\">{routes}<\/span> \/&gt;<\/span>, document.getElementById('app'));\n<\/span><\/code><\/pre>\n<p>Similarly, in our Header component we can add <code>import '.\/Header.scss'<\/code> to import the component\u2019s associated stylesheet. This also applies to all our other components.<\/p>\n<p><strong>In my opinion, this can almost be considered a revolutionary change in the world of JavaScript development.<\/strong> There\u2019s no need to worry about CSS bundling, minification, or sourcemaps since our loader handles all of this for us. Even hot module reloading works for our CSS files. Then being able to handle JS and CSS imports in the same file makes development conceptually simpler: More consistency, less context switching, and easier to reason about.<\/p>\n<p>To give a brief summary of how this feature works: Webpack inlines the CSS into our JS bundle. In fact, Webpack can do this for images and fonts as well:<\/p>\n<pre><code class=\"language-js hljs\">{test: <span class=\"hljs-regexp\">\/\\.(svg|png|jpe?g|gif)(\\?\\S*)?$\/<\/span>, loader: <span class=\"hljs-string\">'url?limit=100000&amp;name=img\/[name].[ext]'<\/span>},\n{test: <span class=\"hljs-regexp\">\/\\.(eot|woff|woff2|ttf)(\\?\\S*)?$\/<\/span>, loader: <span class=\"hljs-string\">'url?limit=100000&amp;name=fonts\/[name].[ext]'<\/span>}\n<\/code><\/pre>\n<p>The URL loader instructs Webpack to inline our images and fonts as data urls if they are under 100 KB, otherwise serve them as separate files. Of course, we can also configure the cutoff size to a different value such as 10 KB.<\/p>\n<p>And that\u2019s Webpack configuration in a nutshell. I will admit there\u2019s a fair amount of setup, but the benefits of using it are simply phenomenal. Although Browserify does have plugins and transforms, they simply cannot compare to Webpack loaders in terms of added functionality.<\/p>\n<h2 id=\"webpack--npm-scripts-setup\">Webpack + NPM Scripts Setup<\/h2>\n<p>In this setup, we use npm scripts directly instead of relying on a gulpfile for automating our tasks.<\/p>\n<h4 id=\"packagejson\">package.json<\/h4>\n<pre><code class=\"language-json hljs\"><span class=\"hljs-string\">\"scripts\"<\/span>: {\n  \"<span class=\"hljs-attribute\">start<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm-run-all --parallel lint:watch test:watch build\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">start:prod<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm-run-all --parallel lint test build:prod\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">clean-dist<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"rimraf .\/dist &amp;&amp; mkdir dist\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">clean-build<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"rimraf .\/build &amp;&amp; mkdir build\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">clean<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm-run-all clean-dist clean-build\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">test<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"mocha .\/app\/**\/*.test.js --compilers js:babel-core\/register\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">test:watch<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm run test -- --watch\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">lint<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"esw .\/app\/**\/*.js\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">lint:watch<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm run lint -- --watch\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">server<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"nodemon server.js\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">server:prod<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"cross-env NODE_ENV=production nodemon server.js\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">build-html<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"node tools\/buildHtml.js\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">build-html:prod<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"cross-env NODE_ENV=production node tools\/buildHtml.js\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">prebuild<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm-run-all clean-dist build-html\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">build<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"webpack\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">postbuild<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm run server\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">prebuild:prod<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm-run-all clean-build build-html:prod\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">build:prod<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"cross-env NODE_ENV=production webpack\"<\/span><\/span>,\n  \"<span class=\"hljs-attribute\">postbuild:prod<\/span>\": <span class=\"hljs-value\"><span class=\"hljs-string\">\"npm run server:prod\"<\/span>\n<\/span>}\n<\/code><\/pre>\n<p>To run development and production builds, enter <code>npm start<\/code> and <code>npm run start:prod<\/code>, respectively.<\/p>\n<p>This is certainly more compact than our gulpfile, given we\u2019ve cut 99 to 150 lines of code down to 19 NPM scripts, or 12 if we exclude the production scripts (most of which just mirror the development scripts with the node environment set to production). The drawback is that these commands are somewhat cryptic compared to our Gulp task counterparts, and not quite as expressive. For example, there\u2019s no way (at least that I know of) to have a single npm script run certain commands in series and others in parallel. It\u2019s either one or the other.<\/p>\n<p>However, there is a huge advantage to this approach. By using NPM libraries such as <code>mocha<\/code> directly from the command line, you don\u2019t need to install an equivalent Gulp wrapper for each (in this case, <code>gulp-mocha<\/code>).<\/p>\n<p>Instead of NPM installing<\/p>\n<ul>\n<li>gulp-eslint<\/li>\n<li>gulp-mocha<\/li>\n<li>gulp-nodemon<\/li>\n<li>etc<\/li>\n<\/ul>\n<p>We install the following packages:<\/p>\n<ul>\n<li>eslint<\/li>\n<li>mocha<\/li>\n<li>nodemon<\/li>\n<li>etc<\/li>\n<\/ul>\n<p>Quoting Cory House\u2019s post, <em>Why I Left Gulp and Grunt for NPM Scripts<\/em>:<\/p>\n<blockquote><p>I was a big fan of Gulp. But on my last project, I ended up with hundreds of lines in my gulpfile and around a dozen Gulp plugins. I was struggling to integrate Webpack, Browsersync, hot reloading, Mocha, and much more using Gulp. Why? Well, some plugins had insufficient documentation for my use case. Some plugins only exposed part of the API I needed. One had an odd bug where it would only watch a small number of files. Another stripped colors when outputting to the command line.<\/p><\/blockquote>\n<p>He specifies three core issues with Gulp:<\/p>\n<ol>\n<li>Dependence on plugin authors<\/li>\n<li>Frustrating to debug<\/li>\n<li>Disjointed documentation<\/li>\n<\/ol>\n<p>I would tend to agree with all of these.<\/p>\n<h3 id=\"dependence-on-plugin-authors\">1. Dependence on Plugin Authors<\/h3>\n<p>Whenever a library such as <code>eslint<\/code> gets updated, the associated <code>gulp-eslint<\/code> library needs a corresponding update. If the library maintainer loses interest, the gulp version of the library falls out of sync with the native one. The same goes for when a new library is created. If someone creates a library <code>xyz<\/code> and it catches on, then suddenly you need a corresponding <code>gulp-xyz<\/code> library to use it in your gulp tasks.<\/p>\n<p>In a sense, this approach just doesn\u2019t scale. Ideally, we would want an approach like Gulp that can use the native libraries.<\/p>\n<h3 id=\"frustrating-to-debug\">2. Frustrating to Debug<\/h3>\n<p>Although libraries such as <code>gulp-plumber<\/code> help alleviate this issue considerably, it\u2019s nonetheless true that error reporting in <code>gulp<\/code> just isn\u2019t very helpful. If even one pipe throws an unhandled exception, you get a stack trace for an issue that seems completely unrelated to what\u2019s causing the issue in your source code. This can make debugging a nightmare in some cases. No amount of searching on Google or Stack Overflow can really help you if the error is cryptic or misleading enough.<\/p>\n<h3 id=\"disjointed-documentation\">3. Disjointed Documentation<\/h3>\n<p>Oftentimes I find that small <code>gulp<\/code> libraries tend to have very limited documentation. I suspect this is because the author usually makes the library primarily for his or her own use. In addition, it\u2019s common to have to look at documentation for both the Gulp plugin and the native library itself, which means lots of context switching and twice as much reading to do.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>It seems pretty clear to me that Webpack is preferable to Browserify and NPM scripts are preferable to Gulp, although each option has its benefits and drawbacks. Gulp is certainly more expressive and convenient to use than NPM scripts, but you pay the price in all the added abstraction.<\/p>\n<p>Not every combination may be perfect for your app, but if you want to avoid an overwhelming number of development dependencies and a frustrating debugging experience, Webpack with NPM scripts is the way to go. I hope you will find this article useful in choosing the right tools for your next project.<\/p>\n<p>Source: <a href=\"https:\/\/www.toptal.com\/front-end\/webpack-browserify-gulp-which-is-better\">Toptal<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>As web applications grow increasingly complex, making your web app scalable becomes of the utmost importance. Whereas in the past writing ad-hoc JavaScript and jQuery would suffice, nowadays building a web app requires a much greater degree of discipline and formal software development practices, such as: Unit tests to ensure modifications to your code don\u2019t [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[15,52,130,154],"class_list":["post-274","post","type-post","status-publish","format-standard","hentry","category-readings","tag-browser","tag-gulp","tag-tools","tag-webpack"],"_links":{"self":[{"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/posts\/274","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/comments?post=274"}],"version-history":[{"count":0,"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/posts\/274\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/media?parent=274"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/categories?post=274"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.nonamehosts.com\/blog\/wp-json\/wp\/v2\/tags?post=274"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}