Review and reflection

In the previous section, We explained how to build a workflow using WebPack.

We say that Webpack has many loaders for compiling and packaging static resources, and gulp has many work modules named in gulp-* format for processing various resource files. What is the relationship between Webpack and gulp? What’s the difference?

Are Webpack and gulp the same type of tools?

Webpack, which focuses on handling various resources, and GULp, which focuses on task management, have different functions. For example:

Gulp is like a big boss. Normally, he has to manage various departments, including operation, product, design, development, finance, logistics and so on. But in fact, the big boss just wants to know how the product is done and how much money is left in finance. Now a young man named Webpack came and told the big boss, “I can help you manage the operation, design, development and logistics of these departments, you can give me a vice president, and then you only need to manage the product, finance and me.” The two hit it off and live happily ever after.

Yeah, I kid you not. That’s how it works.

While webpack alone can build a decent workflow, Gulp is doing fine without it. Gulp is very good at task management and WebPack is very convenient for handling resources. Why not use it together?

Well, let’s do it! In this section we will try to use gulp+ WebPack to build a workflow that is easy to use and extend. We take GULP as the big framework and integrate webpack to carry out.

Second, compile and package

We will remove the gulpfile.js file from the first section. As usual, we will implement the packaging first, so we will shave off the dev content first:

var gulp = require('gulp') var sass = require('gulp-sass') var swig = require('gulp-swig') gulp.task('sass', function () { return gulp.src('src/sass/*.scss') .pipe(sass({ outputStyle: }).pipe(gulp.dest('dist/static')}) gulp.task('js', function () { return gulp.src('src/js/*.js') .pipe(gulp.dest('dist/static')) }) gulp.task('tpl', function () { return gulp.src('src/tpl/*.swig') .pipe(swig({ defaults: { cache: False / / this configuration is forced to compile the file does not cache}})) pipe (gulp. Dest (' dist ')}) gulp. Task (' build '[' sass', 'js',' the TPL '])Copy the code

We agreed that the processing of relevant resources should be entrusted to Webpack, so we also need to remove the three gulP tasks of Sass, JS and TPL, and replace them with the Loader of Webpack:

var gulp = require('gulp')
var webpack = require('webpack')

gulp.task('webpack', function () {
  // do something...
})

gulp.task('build', ['webpack'])Copy the code

Well, we can almost anticipate the next step, populate the Webpack task, and we tried to work with the WebPack API directly in the previous section, using the webpack.config.js file in webpack_base:

var gulp = require('gulp')
var webpack = require('webpack')
var config = require('./webpack.config.js')

gulp.task('webpack', function () {
  return webpack(config)
})

gulp.task('build', ['webpack'])Copy the code

Seems so simple, everything is going well how to do, the mood is good to fly, so can’t wait to input in the command line:

gulp webpackCopy the code

Return to the car happily!

Ok, there is no error, perfectly compatible!

Take a look back at the root directory! Ah… What about the dist directory? Why is nothing being generated? !!!!!!!!!

Since webpack processes resources asynchronously and Gulp does not know when you are finished with the resources, for Gulp, your Webpack task starts and ends synchronously by default. Once the task starts, I finish the task before you finish processing it. So Webpack needs an action to tell Gulp when WEBPack has finished processing these resources.

Let’s start with the final code:

var gulp = require('gulp')
var webpack = require('webpack')
var config = require('./webpack.config.js')

gulp.task('webpack', function (cb) {
  webpack(config, function () {
    cb()
  })
})

gulp.task('build', ['webpack'])Copy the code

In this case, cb (callback) is a callback operation. We call gulp’s callback function in the webpack callback to notify gulp that the task is complete. This might be a little confusing, but let’s think about it a little bit.

After compiling on the command line, you can see the dist folder generated in the root directory, which also holds the packaged files.

Here’s a problem we can only see on the command line:

[23:35:49] Using gulpfile ~/gulp-webpack_base/gulpfile.js
[23:35:49] Starting 'webpack'...
[23:35:49] Finished 'webpack' after 20 msCopy the code

And then there’s no more information. What do we expect? As MENTIONED above, webpack does not generate any error messages. In fact, if there is an error, it does not generate any error messages at all. In gulp, to export the module’s own information, we need to use gulp-util. We have already seen this tool when creating FTP tasks. The details are as follows:

var gulp = require('gulp')
var webpack = require('webpack')
var config = require('./webpack.config.js')
var gutil = require('gulp-util')

gulp.task('webpack', function (cb) {
  webpack(config, function (err, stats) {
    if (err) {
      throw new gutil.PluginError('webpack', err)
    }

    gutil.log('[webpack]', stats.toString({
      colors: true,
      chunks: false
    }))

    cb()
  })
})

gulp.task('build', ['webpack'])Copy the code

With that, the packing is done.

The development environment

I don’t know if you’ve heard of Express?

Express is a minimalist, flexible Web application development framework based on the Node.js platform. It provides a series of powerful features to help you create a variety of Web and mobile applications. — Express Chinese

Simply put, Express is a NodeJs-based Web framework that makes it easy to build a Web project. The purpose of using it this time is to build a local development server with it. In fact, the webpack-dev-server base we used in the last demo was implemented with Express + Webpack-dev-middleware, We also need to combine webpack’s Webpack-dev-middleware and Webpack-hot-middleware middleware to do something we haven’t done before — hot updates.

If you are not familiar with Express, you can go to the official website after reading this section to supplement some knowledge. This section only uses some simple usages.

Without further ado, we will import the required dependencies in gulpfile.js, and for convenience, we will also directly use the webpack.dev.config.js development configuration file from the previous section:

. var webpackHotMiddleware = require('webpack-hot-middleware') var webpackDevMiddleware = require('webpack-dev-middleware') var devConfig = require('./webpack.dev.config.js') var express = require('express') var app = express() ...Copy the code

The app here is the local server we will use, corresponding to the webpackDevServer in the previous section, and we will write the server task:

gulp.task('server', function () {
  app.listen(8080, function (err) {
    if (err) {
      console.log(err)
      return
    }
    console.log('listening at http://localhost:8080')
  })
})Copy the code

At this point, we’re almost done building a server, so we type:

gulp serverCopy the code

Press Enter and type http://localhost:8080 in the browser. We can see that the service is already running. We can only see Cannot GET/on the page because we haven’t defined the routing rules yet.

Let’s start by specifying a root path that returns hello, world! Try:

gulp.task('server', function () { app.use('/', function (req, res) { res.send('hello, world! ') }) app.listen(8080, function (err) { if (err) { console.log(err) return } console.log('listening at http://localhost:8080') }) })Copy the code

Gulp server: hello, world! Gulp Server: gulp Server ?

Smart people immediately thought of another use for Express: writing interfaces for projects that return bogus data. Of course, you can try it yourself.

To get back to the point, we made the code clearer and more specific. We moved the task of defining the route from server to another task, Server :init, which made the task thinner and easier to debug:

gulp.task('server:init', function () { app.use('/', function (req, res) { res.send('hello, world! ') }) }) gulp.task('server', ['server:init'], function () { app.listen(8080, function (err) { if (err) { console.log(err) return } console.log('listening at http://localhost:8080') }) })Copy the code

Okay, so here’s a quick tip about middleware:

Middleware is an independent system software or service program that distributed applications use to share resources between different technologies. — Baidu Encyclopedia

A bit abstract, middleware is simply a component that provides a service that can be integrated into various frameworks based on a common protocol or convention.

Webpack-dev-middleware and Webpack-hot-middleware are two pieces of middleware, the former providing webpack-compiled packages and the latter providing hot-updated services. Together, they are webpack-dev-server.

We took it out separately because we needed to customize our own set of webpack-dev-server. In the last section, when we talked about webpack-dev-server hot update, we ran into a problem that the HTML template could not automatically refresh, so we had to abandon hot-replacement and use reload, although the effect was not much worse. But since we are using WebPack, there is no reason to abandon this feature, and our goal is to make it to the west.

We’ve set up a local server with Express, we’ve removed the route from the test, and now we’re trying to integrate Webpack-dev-middleware:

gulp.task('server:init', function () {
  var compiler = webpack(devConfig)
  var devMiddleware = webpackDevMiddleware(compiler, {
    stats: {
      colors: true,
      chunks: false
    }
  })

  app.use(devMiddleware)
})Copy the code

Is it similar to the webDevServer configuration?

Let’s run through the command line, refresh the browser, and it comes out la la la la la! Perfect.

We then integrate Webpack-hot-middleware, which the document says:

  1. Add the following plugin:new webpack.optimize.OccurenceOrderPlugin(),new webpack.HotModuleReplacementPlugin(),new webpack.NoErrorsPlugin();
  2. Increase each entrancewebpack-hot-middleware/client? reload=true.

After operation, we get:

gulp.task('server:init', function () { for (var key in devConfig.entry) { var entry = devConfig.entry[key] entry.unshift('webpack-hot-middleware/client? reload=true') } devConfig.plugins.unshift( new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ) var compiler = webpack(devConfig) var devMiddleware = webpackDevMiddleware(compiler, { hot: true, stats: { colors: true, chunks: false } }) var hotMiddleware = webpackHotMiddleware(compiler) app.use(devMiddleware) app.use(hotMiddleware) })Copy the code

Let’s run the command line and modify the index. SCSS, save, and we can see the changes directly in the browser!

However, if we modify index. Swig and save it, we still do not see the browser refresh, which is the same as the last time we used webpackDevServer.

The way to solve this problem is rather tortuous, and we need to solve two problems:

  1. How to know if the user has modified and saved HTML;
  2. How to manually notify the browser to refresh the page.

The first problem is easier to solve, which is to register a callback for the htML-webpack-plugin to tell us that the file has been modified after it has been modified and saved:

compiler.plugin('compilation', function(compilation) { compilation.plugin('html-webpack-plugin-after-emit', Function (data, callback) {callback()})Copy the code

For detailed usage, please refer to the official documentation of the HTml-webpack-plugin.

Second question, we need to know webpack-hot-middleware/client? What exactly is reload=true?

We’ll just call it client for convenience.

In fact, a client is a script that we inject into the browser, a set of changes we make in the editor, the browser updates automatically, and the communication is done by it. Basically, my project files are modified, and the server sends instructions to the client. After receiving the command, the client completes the corresponding work according to the content of the command, such as refreshing the page, updating resources and so on.

Let’s say it has a single instruction called reload, so we can do this:

compiler.plugin('compilation', function(compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function(data, callback) {
    hotMiddleware.publish({ action: 'reload' })
    callback()
  })
})Copy the code

We publish a reload directive using hotMiddleware. Well, that works. Now we need to implement this Reload.

The client does not have the content of this directive, so we need to extend it. We create a new client.js file in the root directory, with the following content:

var client = require('webpack-hot-middleware/client? reload=true') client.subscribe(function (obj) { if (obj.action === 'reload') { window.location.reload() } })Copy the code

We introduced the client that we had configured in the portal, then extended it with a Reload action type and defined the refresh script. In this way, we have completed the client function extension, and when modifying HTML, issue a reload command to the client.

As a final step, let’s add the client we introduced earlier (aka Webpack-hot-middleware /client? Reload =true) replace with our own client to get the final server:init:

gulp.task('server:init', function () {
  for (var key in devConfig.entry) {
    var entry = devConfig.entry[key]
    entry.unshift('./client.js')
  }

  devConfig.plugins.unshift(
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  )

  var compiler = webpack(devConfig)
  var devMiddleware = webpackDevMiddleware(compiler, {
    hot: true,
    stats: {
      colors: true,
      chunks: false
    }
  })

  var hotMiddleware = webpackHotMiddleware(compiler)

  compiler.plugin('compilation', function(compilation) {
    compilation.plugin('html-webpack-plugin-after-emit', function(data, callback) {
      hotMiddleware.publish({ action: 'reload' })
      callback()
    })
  })

  app.use(devMiddleware)
  app.use(hotMiddleware)
})Copy the code

Ok, let’s go to the command line and type gulp server and press Enter!

Modify the file, save, the browser automatically refresh! CSS is hot updated, swig files can automatically refresh the page!

At this point, our development environment is built and ready to handle development and packaging.

But I’m not up to my word limit, so I need to keep going.

Since the title says we want to build an easy-to-use, easily extensible workflow, how can we extend something to see?

Yeah, well, it’s a lie. It’s got to be a lie.

mock&proxy

Let’s talk about how to mock data in a project.

We are now talking about the front and back end separation pattern based on retrieving data through the API. Suppose we currently have the following two situations:

  1. The interface is not written on the back end, so we need to generate some fake data ourselves;
  2. With interfaces written on the back end, we need to address cross-domain issues when developing calls locally.

The first problem is easy to solve. We’ve already touched on it a little bit earlier in the Express integration. We can write our own routing to satisfy the calls. We’ve left out the middleware content here to focus on mock) :

var mockMiddleware = require('./mock-middleware.js')

gulp.task('server:init', function () {
  app.use('/api/:method', mockMiddleware)
})Copy the code

Let’s implement mock-Middleware, which is pretty simple:

var map = { hello: { data: [1, 2, 3], msg: null, status: 0 } } module.exports = function (req, res, next) { var apiKey = req.params.method if (apiKey in map) { res.json(map[apiKey]) } else { res.status(404).send('api no found! ')}}Copy the code

The code is not much, and all are literal meaning, let’s briefly introduce the past: We first get the method in the URL and save it as the apiKey. Then we pre-define a map that contains all the mock data, define a Hello interface, and match the map with the apiKey. If it matches, it returns the default data, and if it doesn’t, it returns a 404 page.

So we solved the first problem, let’s look at the second problem, the key is: to solve the cross-domain problem.

How to solve cross-domain problems? Use the server proxy interface.

Proxy (English: Proxy), also known as network Proxy, is a special network service that allows a network terminal (generally a client) to indirectly connect to another network terminal (generally a server) through this service. — Baidu Encyclopedia

Baidu this explanation is too difficult to understand, or I will say. A data such as you need to access the server, but some reason cause you can’t directly access to or access is difficult, so this time have A server B, you visit without obstacles, B and B to access A also have no obstacle, so I let help me to visit A, B I want access to B, B, receive my visit, then go to A relative should be to fetch the data above, Fetch the data and return it to me. This B is the legendary scalper proxy server.

Now when I access the back-end interface of another server on the local page and encounter cross-domain problems, I can proxy the interface to my local through the server proxy interface. When I access the local interface, IT is equivalent to accessing the interface of the back-end server, and there is no cross-domain problems.

Here’s a random proxy-middleware package that you can choose from:

var url = require('url')
var proxy = require('proxy-middleware')

app.use(proxy(url.parse('http://tx2.biz.lizhi.fm')))Copy the code

Once you’re running, try localhost:8080/audio/hot? In your browser. Page =1, you can see the result:

{ data: { content: [ { coverBig: "http://cdn103.img.lizhi.fm/audio_cover/2016/07/29/30278047895714567.jpg", coverThumb: "http://cdn103.img.lizhi.fm/audio_cover/2016/07/29/30278047895714567_80x80.jpg", createTime: "2016-07-29 11:48:39", duration: 49, file: "http://cdn5.lizhi.fm/audio/2016/07/29/2548065522170814982_hd.mp3", id: 9, mediaId: "2548065522170814982", name: "fuxi ", status: 0, type: "app", uid: "2543827817207562796", vote: 42559},... , pageIndex: 1, pageSize: 10, queryAll: false, totalCount: 200, totalPage: 20 }, msg: null, status: 0 }Copy the code

Done! (This interface don’t play too much ah, in case my project server failed I blame you, I suggest taking Baidu practice ~)

Attach the final server:init code:

gulp.task('server:init', function () {
  for (var key in devConfig.entry) {
    var entry = devConfig.entry[key]
    entry.unshift('./client.js')
  }

  devConfig.plugins.unshift(
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  )

  var compiler = webpack(devConfig)
  var devMiddleware = webpackDevMiddleware(compiler, {
    hot: true,
    stats: {
      colors: true,
      chunks: false
    }
  })

  var hotMiddleware = webpackHotMiddleware(compiler)

  compiler.plugin('compilation', function(compilation) {
    compilation.plugin('html-webpack-plugin-after-emit', function(data, callback) {
      hotMiddleware.publish({ action: 'reload' })
      callback()
    })
  })

  app.use('/api/:method', mockMiddleware)
  app.use(proxy(url.parse('http://tx2.biz.lizhi.fm')))

  app.use(devMiddleware)
  app.use(hotMiddleware)
})Copy the code

Ok, at this point, we have completed most of the usage scenarios, satisfied basically all the requirements, and finally added the FTP task that we talked about in the introduction of gulp, and we are complete.

conclusion

Apart from some of our extended knowledge points, we just look at the big framework gulp+ Webpack, such a combination has a lot of advantages? We can see that for gulP projects alone, the project’s ability to handle resources has increased, while for WebPack projects, the project’s functionality is much more complete and extensible.

Do you still think gulp and Webpack are the same thing? Are you still confused?

Well, there’s nothing I can do about it. It’s up to you.

Finally, I will give you some tasks. Due to the length, we have not done a good job in many details:

  1. Js, webpack.config.js, webpack.dev.config.js, server.js, etc. These files are a bit messy, and they are project-independent files that belong to tools. We can create a build folder to store it. There are a lot of paths that need to be modified, so we can do it on our own.
  2. In addition to the first gulp lecture, I didn’t bother to remind you of such things asgulp build,gulp serverThese commands are encapsulated in package.json as default scripts, which affects the aesthetics to a certain extent. In fact, I did it myself, and I hope you can do it consciously.
  3. This is added every time you run the build commandrimraf dist, remove expired content;
  4. There are some types of resources I haven’t covered, such as image, font, react, etc., but I leave it up to you to try on your own. You need to be able to draw inferior-not to mention it’s very simple.

That’s the end of this series, I hope you can see the end of the harvest.

The git address for this demo project is gulp-webpack_base

Step up: Build a project with version management capability

(If there are any fallacies in the article, please leave comments.)