demo14源码地址

1.为什么需要开发模式?

这十几节来我们使用的最多的就是生产环境,也就是npm run build的命令,打包项目中各种文件及压缩。

而开发模式就是制定mode为development。对应我们在package.json中配置的,就是npm run dev,在第二小节也涉及到了这一点。

在开发模式下,我们需要对代码进行调试,对应的配置就是:devtool 设置为 source-map。在非开发模式西啊,需要关闭此选项,以减小打包体积。详情见: devtool

在开发模式下,还需要热重载,路由重定向,设置代理等,webpack4.0已经提供了devServer选项,启动一个本地服务器,让开发者使用这些功能。

目录结构:
image.png

安装依赖:

npm i webpack-dev-server —save-dev

修改package.json

  1. {
  2. "name": "demo1",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "index.js",
  6. "scripts": {
  7. "dev": "webpack --mode development",
  8. "build": "webpack --mode production",
  9. "test": "echo \"Error: no test specified\" && exit 1"
  10. },
  11. "author": "",
  12. "license": "ISC",
  13. "devDependencies": {
  14. "@babel/core": "^7.4.5",
  15. "@babel/plugin-transform-runtime": "^7.4.4",
  16. "@babel/preset-env": "^7.4.5",
  17. "autoprefixer": "^9.5.1",
  18. "babel-loader": "^8.0.6",
  19. "clean-webpack-plugin": "^2.0.2",
  20. "css-loader": "^2.1.1",
  21. "html-loader": "^0.5.5",
  22. "html-webpack-plugin": "^3.2.0",
  23. "image-webpack-loader": "^5.0.0",
  24. "lodash": "^4.17.11",
  25. "mini-css-extract-plugin": "^0.7.0",
  26. "node-sass": "^4.12.0",
  27. "optimize-css-assets-webpack-plugin": "^5.0.1",
  28. "postcss-loader": "^3.0.0",
  29. "postcss-sprites": "^4.2.1",
  30. "sass-loader": "^7.1.0",
  31. "style-loader": "^0.23.1",
  32. "url-loader": "^1.1.2",
  33. "webpack": "^4.29.6",
  34. "webpack-cli": "^3.3.2",
  35. "webpack-dev-server": "^3.7.1"
  36. },
  37. "dependencies": {
  38. "@babel/polyfill": "^7.4.4",
  39. "@babel/runtime": "^7.4.5",
  40. "core-js": "^2.6.8",
  41. "file-loader": "^4.0.0",
  42. "jquery": "^3.4.1",
  43. "lodash-es": "^4.17.11"
  44. },
  45. "browserslist": [
  46. "> 1%",
  47. "last 2 version",
  48. "not ie <= 8"
  49. ]
  50. }

因为我们在package.json中配置了script,所以开启开发模式直接npm run dev 即可
image.png
虽然控制台输出了打包信息(假设我们已经配置了热重载),但是磁盘上并没有创建/dist/文件夹和打包文件。控制台和打包文件的相关内容是存储在内存之中的。

修改index.html文件

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  7. <title>webpack-dev-server</title>
  8. </head>
  9. <body>
  10. This is Index html
  11. </body>
  12. </html>

按照项目目录,简单封装下/vendor/下的三个js文件,以方便app.js调用:

  1. // minus.js
  2. module.exports = function(a, b) {
  3. return a - b
  4. }
  5. // multi.js
  6. define(function(require, factory) {
  7. 'use strict'
  8. return function(a, b) {
  9. return a * b
  10. }
  11. })
  12. // sum.js
  13. export default function(a, b) {
  14. console.log('I am sum.js')
  15. return a + b
  16. }

在app.js中使用三种引入方式引入js文件:

import sum from './vendor/sum'
console.log('sum(1, 2) = ', sum(1, 2))

var minus = require('./vendor/minus')
console.log('minus(1, 2) = ', minus(1, 2))

require(['./vendor/multi'], function(multi) {
  console.log('multi(1, 2) = ', multi(1, 2))
})

更改webpack.config.js,完整的配置如下:

const webpack = require('webpack')
const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: {
    app: './src/app.js'
  },
  output: {
    publicPath: '/', // js 引用的路径或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
    filename: '[name].bundle.js', // 代码打包后的文件名
    chunkFilename: '[name].js' // 代码拆分后的文件名
  },
  mode: 'development', // 开发模式
  devtool: 'source-map', // 开启调试
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 8000, // 本地服务器端口号
    hot: true, // 热重载
    overlay: true, // 如果代码出错,会在浏览器页面弹出“浮动层”。类似于 vue-cli 等脚手架
    proxy: {
      // 跨域代理转发
      '/comments': {
        target: 'https://m.weibo.cn',
        changeOrigin: true,
        logLevel: 'debug',
        headers: {
          Cookie: ''
        }
      }
    },
    historyApiFallback: {
      // HTML5 history模式
      rewrites: [{ from: /.*/, to: '/index.html' }]
    }
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      // 打包输出HTML
      title: '自动生成 HTML',
      minify: {
        // 压缩 HTML 文件
        removeComments: true, // 移除 HTML 中的注释
        collapseWhitespace: true, // 删除空白符与换行符
        minifyCSS: true // 压缩内联 css
      },
      filename: 'index.html', // 生成后的文件名
      template: 'index.html', // 根据此模版生成 HTML 文件
      chunks: ['app'] // entry中的 app 入口才会被打包
    }),
    new webpack.HotModuleReplacementPlugin(), // 热部署模块
    new webpack.NamedModulesPlugin(),
    new webpack.ProvidePlugin({
      $: 'jquery', // npm
      jQuery: 'jQuery' // 本地Js文件
    })
  ]
}

对上面的配置进行单独分析:

  • 模块热更新

模块热更新需要 HotModuleReplacementPluginNamedModulesPlugin 这两个插件,并且顺序不能错,并且指定 devServer.hot 为 true

const webpack = require('webpack') // 引入 webpack

module.exports = {
  plugins: [
    new webpack.HotModuleReplacementPlugin(), // 热部署模块
    new webpack.NamedModulesPlugin()
  ]
}

有了这两个插件,在项目的js代码中可以针对侦测到的更变的文件并且做出相关处理,也就不用写完代码重新刷新页面,它会自动检测变更的代码并且在页面上更改

注意的js代码,如果你去改动index.html文件,保存后,页面并不会更改,反之你去修改了js文件,保存后页面就会更新

比如,我们启动开发模式后,修改了vendor/sum.js 这个文件,此时,需要在浏览器的控制台打印一些信息。那么,app.js 中就可以这么写:

if (module.hot) {
  // 检测是否有模块热更新
  module.hot.accept('./vendor/sum.js', function() {
    // 针对被更新的模块, 进行进一步操作
    console.log('/vendor/sum.js is changed')
  })
}

每当 sum.js 被修改后,都可以自动执行回调函数。

浏览器控制台输出信息如下:
image.png
但是我们日常开发中使用vue脚手架根本就没有写过这样的代码,也能热更新,是因为 vue-loader中内置了这种方法,css-loader中也有,所以我们改完js和css代码就直接能看到更新。

跨域代理

随着前后端分离开发的普及,跨域请求变得越来越常见。为了快速开发,可以利用 devServer.proxy 做一个代理转发,来绕过浏览器的跨域限制。
devServer 模块的底层是使用了 http-proxy-middleware,能配置的东西非常多
按照前面的配置文件,如果想调用微博的一个接口:m.weibo.cn/comments/ho… /comments/hotflow 进行请求即可,在 app.js 中添加如下代码:

$.get(
  '/comments/hotflow',
  {
    id: '4263554020904293',
    mid: '4263554020904293',
    max_id_type: '0'
  },
  function(data) {
    console.log(data)
  }
)

上面代码是使用 jQuery 发送 get 请求,如果是在 vue 项目中,一般是使用 axios 来发送请求
修改完 app.js 后保存,打开之前的 localhost:8000 网页,可以看到 Network 发送的请求
image.png
image.png

  • HTML5–History

当项目使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。
在 SPA(单页应用)中,任何响应直接被替代为 index.html。
在 vuejs 官方的脚手架 vue-cli 中,开发模式下配置如下:

historyApiFallback: {
  // HTML5 history模式
  rewrites: [{ from: /.*/, to: '/index.html' }]
}

最终app.js中的代码如下:

import sum from './vendor/sum'
console.log('sum(1, 2) = ', sum(1, 2))

var minus = require('./vendor/minus')
console.log('minus(1, 2) = ', minus(1, 2))

require(['./vendor/multi'], function(multi) {
  console.log('multi(1, 2) = ', multi(1, 2))
})

$.get(
  '/comments/hotflow',
  {
    id: '4263554020904293',
    mid: '4263554020904293',
    max_id_type: '0'
  },
  function(data) {
    console.log(data)
  }
)

if (module.hot) {
  // 检测是否有模块热更新
  module.hot.accept('./vendor/sum.js', function() {
    // 针对被更新的模块, 进行进一步操作
    console.log('/vendor/sum.js is changed')
  })
}

参考文章: webpack4 系列教程 (十五):开发模式与 webpack-dev-server