javascript模块化发展历程

  • 什么是模块化 ?
  • 为何要作Javascript模块化?
  • JavaScript 模块化发展历程

什么是模块化 ?

模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它能够把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。javascript

一个模块就是实现特定功能的文件, 逻辑上相关的代码组织到同一个包内,包内是一个相对独立的王国,不用担忧命名冲突什么的,那么外部使用的话直接引入对应的package便可.css

就好像做家会把他的书分章节和段落;程序员会把他的代码分红模块。html

就好像书籍的一章,模块仅仅是一坨代码而已。前端

好的代码模块分割的内容必定是很合理的,便于你增长减小或者修改功能,同时又不会影响整个系统。java

为何要作Javascript模块化?

早期前端只是为了实现简单的页面交互逻辑,随着Ajax技术的普遍应用,前端库的层出不穷,前端代码日益膨胀,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,这时候JavaScript极其简单的代码组织规范不足以驾驭如此庞大规模的代码.node

模块化可使你的代码低耦合,功能模块直接不相互影响。jquery

  1. 可维护性:根据定义,每一个模块都是独立的。良好设计的模块会尽可能与外部的代码撇清关系,以便于独立对其进行改进和维护。维护一个独立的模块比起一团凌乱的代码来讲要轻松不少。webpack

  2. 命名空间:在JavaScript中,最高级别的函数外定义的变量都是全局变量(这意味着全部人均可以访问到它们)。也正因如此,当一些无关的代码碰巧使用到同名变量的时候,咱们就会遇到“命名空间污染”的问题。git

  3. 可复用性:现实来说,在平常工做中咱们常常会复制本身以前写过的代码到新项目中, 有了模块, 想复用的时候直接引用进来就行。程序员

JavaScript 模块化发展历程

前端的先驱在刀耕火种的阶段开始,作了不少努力,在现有的运行环境中,实现"模块"的效果。

函数封装

模块就是实现特定功能的一组方法。在JavaScript中,函数是建立做用域的惟一方式, 因此把函数做为模块化的第一步是很天然的事情.

function foo(){
    //...
}

function bar(){
    //...
}
复制代码

上面的,组成一个模块。使用的时候,直接调用就好了。

这种作法的缺点很明显:全局变量被污染,很容易命名冲突, 并且模块成员之间看不出直接关系。

对象写法

为了解决上面的缺点,能够把模块写成一个对象,全部的模块成员都放到这个对象里面。

var MYAPP = {
    count: 0,
    foo: function(){},
    bar: function(){}
}

MYAPP.foo();
复制代码

上面的代码中,函数foobar, 都封装在MYAPP对象里。使用的时候,就是调用这个对象的属性。 可是,这样的写法会暴露全部模块成员,内部状态能够被外部改写.

当即执行函数(IIFE)写法

使用当即执行函数(Immediately-Invoked Function Expression,IIFE),能够达到不暴露私有成员的目的。

var Module = (function(){
    var _private = "safe now";
    var foo = function(){
        console.log(_private)
    }

    return {
        foo: foo
    }
})()

Module.foo();
Module._private; // undefined
复制代码

这种方法的好处在于,你能够在函数内部使用局部变量,而不会意外覆盖同名全局变量,但仍然可以访问到全局变量, 在模块外部没法修改咱们没有暴露出来的变量、函数.

引入依赖

将全局变量当成一个参数传入到匿名函数而后使用

var Module = (function($){
    var _$body = $("body");     // we can use jQuery now!
    var foo = function(){
        console.log(_$body);    // 特权方法
    }

    // Revelation Pattern
    return {
        foo: foo
    }
})(jQuery)

Module.foo();
复制代码

jQuery的封装风格曾经被不少框架模仿,经过匿名函数包装代码,所依赖的外部变量传给这个函数,在函数内部可使用这些依赖,而后在函数的最后把模块自身暴漏给window

若是须要添加扩展,则能够做为jQuery的插件,把它挂载到$上。 这种风格虽然灵活了些,但并未解决根本问题:所需依赖仍是得外部提早提供、仍是增长了全局变量。

模块化面临什么问题

从以上的尝试中,能够概括出js模块化须要解决那些问题:

  1. 如何安全的包装一个模块的代码?(不污染模块外的任何代码)
  2. 如何惟一标识一个模块?
  3. 如何优雅的把模块的API暴漏出去?(不能增长全局变量)
  4. 如何方便的使用所依赖的模块?

围绕着这些问题,js模块化开始了一段艰苦而曲折的征途。

JavaScript 模块规范

上述的全部解决方案都有一个共同点:使用单个全局变量来把全部的代码包含在一个函数内,由此来建立私有的命名空间和闭包做用域。

你必须清楚地了解引入依赖文件的正确顺序。就拿Backbone.js来举个例子,想要使用Backbone就必须在你的页面里引入Backbone的源文件。

然而Backbone又依赖 Underscore.js,因此Backbone的引入必须在其以后。

而在工做中,这些依赖管理常常会成为让人头疼的问题。

另一点,这些方法也有可能引发命名空间冲突。举个例子,要是你碰巧写了俩重名的模块怎么办?或者你同时须要一个模块的两个版本时该怎么办?

还有就是协同开发的时候, 你们编写模块的方式各不相同,你有你的写法,我有个人写法, 那就乱了套.

接下来介绍几种广受欢迎的解决方案

  • CommonJS
  • AMD
  • CMD
  • ES6模块

CommonJS

2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。

这标志Javascript模块化编程正式诞生。由于老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;可是在服务器端,必定要有模块,与操做系统和其余应用程序互动,不然根本无法编程。

node.js的模块系统,就是参照CommonJS规范实现的。

CommonJS定义的模块分为:

  1. 定义模块: 根据CommonJS规范,一个单独的文件就是一个模块。每个模块都是一个单独的做用域,也就是说,在该模块内部定义的变量,没法被其余模块读取,除非定义为global对象的属性。

  2. 模块输出: 模块只有一个出口,module.exports对象,咱们须要把模块但愿输出的内容放入该对象。module对象就表明模块自己。

  3. 加载模块: 加载模块使用require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象。

// math.js
exports.add = function(a, b){
    return a + b;
}
复制代码
// main.js
var math = require('math')      // ./math in node
console.log(math.add(1, 2));    // 3
复制代码

这种实现模式有两点好处:

  • 避免全局命名空间污染
  • 明确代码之间的依赖关系

可是, 因为一个重大的局限,使得CommonJS规范不适用于浏览器环境。

看上面的main.js代码, 第二行的math.add(1, 2),在第一行require('math')以后运行,所以必须等math.js加载完成。也就是说,若是加载的依赖不少, 时间很长,整个应用就会停在那里等。

咱们分析一下浏览器端的js和服务器端js都主要作了哪些事,有什么不一样:

服务器端JS 浏览器端JS
相同的代码须要屡次执行 代码须要从一个服务器端分发到多个客户端执行
CPU和内存资源是瓶颈 带宽是瓶颈
加载时从磁盘中加载 加载时须要经过网络加载

这对服务器端不是一个问题,由于全部的模块都存放在本地硬盘,能够同步加载完成,等待时间就是硬盘的读取时间。可是,对于浏览器,这倒是一个大问题,由于模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

所以,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

AMD

AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。全部依赖这个模块的语句,都定义在一个回调函数中,等到加载完成以后,这个回调函数才会运行。

// main.js
  require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // some code here
  });
复制代码
  1. 用全局函数define来定义模块,用法为:define(id?, dependencies?, factory);
  2. id为模块标识,听从CommonJS Module Identifiers规范
  3. dependencies为依赖的模块数组,在factory中需传入形参与之一一对应
  4. 若是dependencies的值中有"require"、"exports""module",则与commonjs中的实现保持一致
  5. 若是dependencies省略不写,则默认为["require", "exports", "module"]factory中也会默认传入require,exports,module.
  6. 若是factory为函数,模块对外暴漏API的方法有三种:return任意类型的数据、exports.xxx=xxx、module.exports=xxx.
  7. 若是factory为对象,则该对象即为模块的返回值

大名鼎鼎的require.js就是AMD规范的实现.

require.js要求,每一个模块是一个单独的js文件。这样的话,若是加载多个模块,就会发出屡次HTTP请求,会影响网页的加载速度。所以,require.js提供了一个优化工具(Optimizerr.js,当模块部署完毕之后,能够用这个工具将多个模块合并在一个文件中,实现前端文件的压缩与合并, 减小HTTP请求数。

咱们来看一个require.js的例子

//a.js
define(function(){
     console.log('a.js执行');
     return {
          hello: function(){
               console.log('hello, a.js');
          }
     }
});
复制代码
//b.js
define(function(){
     console.log('b.js执行');
     return {
          hello: function(){
               console.log('hello, b.js');
          }
     }
});
复制代码
//main.js
require.config({
  paths: {
      "jquery": "../js/jquery.min"
  },
});

require(['jquery','a', 'b'], function($, a, b){
  console.log('main.js执行');
  a.hello();
  $('#btn').click(function(){
       b.hello();
  });
})
复制代码

上面的main.js被执行的时候,会有以下的输出: a.js执行 b.js执行 main.js执行 hello, a.js

在点击按钮后,会输出: hello, b.js

可是若是细细来看,b.js被预先加载而且预先执行了,(第二行输出),b.hello这个方法是在点击了按钮以后才会执行,若是用户压根就没点,那么b.js中的代码应不该该执行呢?

这其实也是AMD/RequireJs被吐槽的一点,因为浏览器的环境特色,被依赖的模块确定要预先下载的。问题在于,是否须要预先执行?若是一个模块依赖了十个其余模块,那么在本模块的代码执行以前,要先把其余十个模块的代码都执行一遍,无论这些模块是否是立刻会被用到。这个性能消耗是不容忽视的。

另外一点被吐槽的是,在定义模块的时候,要把全部依赖模块都罗列一遍,并且还要在factory中做为形参传进去,要写两遍很大一串模块名称,像这样:

define(['a', 'b', 'c', 'd', 'e', 'f', 'g'], function(a, b, c, d, e, f, g){  ..... })
复制代码

CMD

CMD 即Common Module Definition, CMD是sea.js的做者在推广sea.js时提出的一种规范.

CMD 规范中,一个模块就是一个文件。代码的书写格式以下:

define(function(require, exports, module) {
    // 模块代码
    // 使用require获取依赖模块的接口
    // 使用exports或者module或者return来暴露该模块的对外接口
})
复制代码
  1. 也是用全局的define函数定义模块, 无需罗列依赖数组,在factory函数中需传入形参require,exports,module.
  2. require 用来加载一个 js 文件模块,require 用来获取指定模块的接口对象 module.exports
//a.js
define(function(require, exports, module){
     console.log('a.js执行');
     return {
          hello: function(){
               console.log('hello, a.js');
          }
     }
});
复制代码
//b.js
define(function(require, exports, module){
     console.log('b.js执行');
     return {
          hello: function(){
               console.log('hello, b.js');
          }
     }
});
复制代码
//main.js
define(function(require, exports, module){
     console.log('main.js执行');

     var a = require('a');
     a.hello();    

     $('#b').click(function(){
          var b = require('b');
          b.hello();
     });
    
});
复制代码

上面的main.js执行会输出以下: main.js执行 a.js执行 hello, a.js

a.js和b.js都会预先下载,可是b.js中的代码却没有执行,由于尚未点击按钮。当点击按钮的时候,会输出以下: b.js执行 hello, b.js

Sea.js加载依赖的方式

  • 加载期:即在执行一个模块以前,将其直接或间接依赖的模块从服务器端同步到浏览器端;
  • 执行期:在确认该模块直接或间接依赖的模块都加载完毕以后,执行该模块。

AMD vs CMD

  • AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块,
  • CMD推崇就近依赖,只有在用到某个模块的时候再去require,
  • AMD和CMD最大的区别是对依赖模块的执行时机处理不一样

一样都是异步加载模块,AMD在加载模块完成后就会执行改模块,全部模块都加载执行完后会进入require的回调函数,执行主逻辑.

CMD加载完某个依赖模块后并不执行,只是下载而已,在全部依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是彻底一致的。

这也是不少人说AMD用户体验好,由于没有延迟,依赖模块提早执行了,CMD性能好,由于只有用户须要的时候才执行的缘由。

ES6模块

上述的这几种方法都不是JS原生支持的, 在ECMAScript 6 (ES6)中,引入了模块功能, ES6 的模块功能汲取了CommonJS 和 AMD 的优势,拥有简洁的语法并支持异步加载,而且还有其余诸多更好的支持。

简单来讲,ES6 模块的设计思想就是:一个 JS 文件就表明一个 JS 模块。在模块中你可使用 import 和 export 关键字来导入或导出模块中的东西。

ES6 模块主要具有如下几个基本特色:

  • 自动开启严格模式,即便你没有写 use strict
  • 每一个模块都有本身的上下文,每个模块内声明的变量都是局部变量,不会污染全局做用域
  • 模块中能够导入和导出各类类型的变量,如函数,对象,字符串,数字,布尔值,类等
  • 每个模块只加载一次,每个 JS 只执行一次, 若是下次再去加载同目录下同文件,直接从内存中读取。

补充: Typescript 识别模块的模式

通常来说,组织声明文件的方式取决于库是如何被使用的。 在JavaScript中一个库有不少使用方式,这就须要你书写声明文件去匹配它们.

经过库的使用方法及其源码来识别库的类型。

  1. 全局库

全局库是指能在全局命名空间下访问的,许多库都是简单的暴露出一个或多个全局变量。 好比jQuery.

当你查看全局库的源代码时,你一般会看到:

  • 顶级的var语句或function声明
  • 一个或多个赋值语句到window.someName
  1. 模块化库

一些库只能工做在模块加载器的环境下。 好比,像 express只能在Node.js 里工做因此必须使用CommonJSrequire函数加载。

模块库至少会包含下列具备表明性的条目之一:

  • 无条件的调用requiredefine
  • import * as a from 'b'; or export c;这样的声明
  • 赋值给exportsmodule.exports
  1. UMD (Universal Module Definition)

    UMD创造了一种同时使用两种规范的方法,而且也支持全局变量定义。因此UMD的模块能够同时在客户端和服务端使用。

    本质上,UMD 是一套用来识别当前环境支持的模块风格的 if/else 语句。下面是一个解释其功能的例子:

(function (root, factory) {
  if (typeof define === "function" && define.amd) {
      define(["libName"], factory);
  } else if (typeof module === "object" && module.exports) {
      module.exports = factory(require("libName"));
  } else {
      root.returnExports = factory(root.libName);
  }
}(this, function (b) {})
复制代码

前端自动化构建工具

  • Grunt
  • Gulp
  • Webpack
  • Browserify
  • ......

简单的说,Grunt / Gulp 和 browserify / webpack不是一回事。

  1. Gulp / Grunt Gulp / Grunt 是一种工具,可以优化前端工做流程。好比自动刷新页面、combo、压缩css、js、编译less等等。简单来讲,就是使用Gulp/Grunt,而后配置你须要的插件,就能够把之前须要手工作的事情让它帮你作了。

  2. 说到 browserify / webpack ,那还要说到 seajs / requirejs 。这四个都是JS模块化的方案。其中seajs / require 是一种类型,browserify / webpack 是另外一种类型。seajs / require : 是一种在线"编译" 模块的方案,至关于在页面上加载一个 CMD/AMD 解释器。这样浏览器就认识了 define、exports、module这些东西。也就实现了模块化。

  3. browserify / webpack : 是一个预编译模块的方案,相比于上面 ,这个方案更加智能, 首先,它是预编译的,不须要在浏览器中加载解释器。另外,你在本地直接写JS,无论是 AMD / CMD / ES6风格的模块化,它都能认识,而且编译成浏览器认识的JS。这样就知道,Gulp是一个工具,而webpack等等是模块化方案。Gulp也能够配置seajs、requirejs甚至webpack的插件。

Grunt

每次运行grunt 时,他就利用node提供的require()系统查找本地安装的 Grunt

若是找到一份本地安装的 Gruntgrunt-CLI就将其加载,并传递Gruntfile中的配置信息,而后执行你所指定的任务。

  • 安装grunt-cli
npm install -g grunt-cli
复制代码
  • 配置gruntfile.js文件
module.exports = function (grunt) {
  // 项目配置.
  grunt.initConfig({
    // 定义Grunt任务
  });

  // 加载可以提供"uglify"任务的插件。
  grunt.loadNpmTasks('grunt插件');

  // Default task(s).
  grunt.registerTask('default', ['任务名']);
}
复制代码

gulp

gulp是基于Nodejs的自动化任务运行器,它能自动化地完成javascript/sass/less/html/image/css 等文件的的测试、检查、合并、压缩、格式化、浏览器自动刷新、部署文件生成,并监听文件在改动后重复指定的这些步骤。

使用Gulp的优点就是利用流的方式进行文件的处理,使用管道(pipe)思想,前一级的输出,直接变成后一级的输入,经过管道将多个任务和操做链接起来,所以只有一次I/O的过程,流程更清晰,更纯粹。Gulp去除了中间文件,只将最后的输出写入磁盘,整个过程所以变得更快。

使用Gulp,能够避免浏览器缓存机制,性能优化(文件合并,减小http请求;文件压缩)以及效率提高(自动添加CSS3前缀;代码分析检查)

browserify

Browserify 是一个模块打包器,它遍历代码的依赖树,将依赖树中的全部模块打包成一个文件。有了 Browserify,咱们就能够在浏览器应用程序中使用 CommonJS 模块。

browserify模块化的用法和node是同样的,因此npm上那些本来仅仅用于node环境的包,在浏览器环境里也同样能用.

webpack官网有对两者的使用方法进行对比,能够看一下:webpack for browserify users

browserify main.js -o bundle.js
复制代码

Compare Webpack vs Browserify vs RequireJS

webpack

官网对webpack的定义是MODULE BUNDLER(模块打包器),他的目的就是把有依赖关系的各类文件打包成一系列的静态资源。 请看下图

Webpack的工做方式是:把你的项目当作一个总体,经过一个给定的主文件(如:main.js),Webpack将从这个文件开始找到你的项目的全部依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

webpack核心概念

1. 入口(entry):

webpack将建立全部应用程序的依赖关系图表(dependency graph)。

entry配置项告诉Webpack应用的根模块或起始点在哪里, 入口起点告诉 webpack 从哪里开始,并遵循着依赖关系图表知道要打包什么。能够将应用程序的入口起点认为是根上下文或 app 第一个启动文件。它的值能够是字符串、数组或对象.

//webpack.config.js
const config = {
 entry: {
   app: './src/app.js',
   vendors: './src/vendors.js'
 }
};
复制代码

2. 出口(output)

将全部的资源(assets)合并在一块儿后,咱们还须要告诉 webpack 在哪里打包咱们的应用程序。output 选项控制 webpack 如何向硬盘写入编译文件。注意,即便能够存在多个入口起点,但只指定一个输出配置。

output: {
    path: helpers.root('dist/nonghe'),
    publicPath: '/',
    filename: 'js/[name].[chunkhash].bundle.js',
    chunkFilename: 'js/[name].[chunkhash].bundle.js'
 }
复制代码

3. 加载器(loader)

在webpack的世界里, 一切皆模块, 经过 loader 的转换,任何形式的资源均可以视做模块,好比 CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。并且 webpack 只理解 JavaScript。

对比 Node.js 模块,webpack 模块可以以各类方式表达它们的依赖关系:

  • ES2015 import 语句
  • CommonJS require() 语句
  • AMD define 和 require 语句
  • css/sass/less 文件中的 @import 语句。
  • 样式(url(...))或 HTML 文件(<img src=...>)中的图片连接

webpack compiler在碰到上面那些语句的时候, 经过与其相对应的loader将这些文件进行转换,而转换后的文件会被添加到依赖图表中。

module: {
        loaders: [{
            test: /\.scss$/,
            loaders: 'style!css!sass'
        }, {
            test: /\.(png|jpg|svg)$/,
            loader: 'url?limit=20480' //20k
        }]
    }}
复制代码

4. 插件(plugin)

plugin 插件,用于扩展webpack的功能,在webpack构建生命周期的节点上加入扩展hookwebpack加入功能。

LoadersPlugins经常被弄混,可是他们实际上是彻底不一样的东西,能够这么来讲,loaders是在打包构建过程当中用来处理源文件的(js,ts, Scss,Less..),一次处理一个,一般做用于包生成以前或生成的过程当中。

插件并不直接操做单个文件,它直接对整个构建过程其做用。

几款经常使用的插件

  • HtmlWebpackPlugin : 这个插件的做用是依据一个简单的html模板,生成一个自动引用打包后的JS文件的新index.html

  • Hot Module Replacement: 它容许你在修改组件代码后,自动刷新实时预览修改后的效果。

  • CommonsChunkPlugin: 对于有多个入口文件的, 能够抽取公共的模块,最终合成的文件可以在最开始的时候加载一次,便存起来到缓存中供后续使用。

  • DefinePlugin: 容许你建立一个在编译时能够配置的全局常量。这可能会对开发模式和发布模式的构建容许不一样的行为很是有用。

  • ExtractTextWebpackPlugin: 它会将打包在js代码中的样式文件抽离出来, 放到一个单独的 css 包文件 (styles.css)当中, 这样js代码就能够和css并行加载.

  • UglifyjsWebpackPlugin: 这个插件使用 UglifyJS 去压缩你的JavaScript代码。

webpack构建流程

从启动webpack构建到输出结果经历了一系列过程,它们是:

  1. 解析webpack配置参数,合并从shell传入和webpack.config.js文件里配置的参数,生产最后的配置结果。
  2. 注册全部配置的插件,让插件监听webpack构建生命周期的事件节点,以作出对应的反应。
  3. 从配置的entry入口文件开始解析文件构建依赖图谱,找出每一个文件所依赖的文件,递归下去。
  4. 在解析文件递归的过程当中根据文件类型和loader配置找出合适的loader用来对文件进行转换。
  5. 递归完后获得每一个文件的最终结果,根据entry配置生成代码块chunk
  6. 输出全部chunk到文件系统。

代码拆分(Code Splitting)

代码拆分是 webpack 中最引人注目的特性之一。你能够把代码分离到不一样的 bundle 中,而后就能够去按需加载这些文件.

  1. 分离资源,实现缓存资源
  • 分离第三方库(vendor) CommonsChunkPlugin
  • 分离 CSS
  1. 传统的模块打包工具(module bundlers)最终将全部的模块编译生成一个庞大的bundle.js文件。所以Webpack使用许多特性来分割代码而后生成多个“bundle”文件,并且异步加载部分代码以实现按需加载
  • 使用 require.ensure() 按需分离代码

    require.ensure(dependencies: String[], callback: function(require), chunkName: String)
    复制代码

模块热替换(Hot Module Replacement)

模块热替换功能会在应用程序运行过程当中替换、添加或删除模块,而无需从新加载页面。这使得你能够在独立模块变动后,无需刷新整个页面,就能够更新这些模块.

webpack-dev-server 支持热模式,在试图从新加载整个页面以前,热模式会尝试使用 HMR 来更新。

webpack-dev-server 主要是启动了一个使用 express 的 Http服务器 。它的做用 主要是用来伺服资源文件 。此外这个 Http服务器 和 client 使用了 websocket 通信协议,原始文件做出改动后, webpack-dev-server 会实时的编译,可是最后的编译的文件并无输出到目标文件夹, 实时编译后的文件都保存到了内存当中。

"server": "webpack-dev-server --inline --progress --hot",
复制代码

webpack-dev-server 支持2种自动刷新的方式:

  • Iframe mode

  • Iframe mode 是在网页中嵌入了一个 iframe ,将咱们本身的应用注入到这个 iframe 当中去,所以每次你修改的文件后,都是这个 iframe 进行了 reload 。

  • inline mode

  • 而 Inline-mode ,是 webpack-dev-server 会在你的 webpack.config.js 的入口配置文件中再添加一个入口,

module.exports = {
        entry: {
            app: [
                'webpack-dev-server/client?http://localhost:8080/',
                './src/js/index.js'
            ]
        },
        output: {
            path: './dist/js',
            filename: 'bundle.js'
        }
    }
复制代码

这样就完成了将 inlinedJS打包进 bundle.js 里的功能,同时 inlinedJS里面也包含了 socket.ioclient 代码,能够和 webpack-dev-server 进行 websocket 通信。

其余配置选项

  • --hot 开启 Hot Module Replacement功能
  • --quiet 控制台中不输出打包的信息
  • --compress 开启gzip压缩
  • --progress 显示打包的进度
相关文章
相关标签/搜索