javascript模块化及webpack基本介绍
JavaScript 模块化发展历程
- 什么是模块化 ?
- 为什么要做
Javascript
模块化? - JavaScript 模块化发展历程
什么是模块化 ?
模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。
一个模块就是实现特定功能的文件, 逻辑上相关的代码组织到同一个包内,包内是一个相对独立的王国,不用担心命名冲突什么的,那么外部使用的话直接引入对应的package
即可.
就好像作家会把他的书分章节和段落;程序员会把他的代码分成模块。
就好像书籍的一章,模块仅仅是一坨代码而已。
好的代码模块分割的内容一定是很合理的,便于你增加减少或者修改功能,同时又不会影响整个系统。
为什么要做Javascript
模块化?
早期前端只是为了实现简单的页面交互逻辑,随着Ajax
技术的广泛应用,前端库的层出不穷,前端代码日益膨胀,JavaScript
却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module
)了,这时候JavaScript
极其简单的代码组织规范不足以驾驭如此庞大规模的代码.
模块化可以使你的代码低耦合,功能模块直接不相互影响。
- 可维护性:根据定义,每个模块都是独立的。良好设计的模块会尽量与外部的代码撇清关系,以便于独立对其进行改进和维护。维护一个独立的模块比起一团凌乱的代码来说要轻松很多。
- 命名空间:在JavaScript中,最高级别的函数外定义的变量都是全局变量(这意味着所有人都可以访问到它们)。也正因如此,当一些无关的代码碰巧使用到同名变量的时候,我们就会遇到“命名空间污染”的问题。
- 可复用性:现实来讲,在日常工作中我们经常会复制自己之前写过的代码到新项目中, 有了模块, 想复用的时候直接引用进来就行。
JavaScript 模块化发展历程
前端的先驱在刀耕火种的阶段开始,做了很多努力,在现有的运行环境中,实现"模块"的效果。
函数封装
模块就是实现特定功能的一组方法。在JavaScript中,函数是创建作用域的唯一方式, 所以把函数作为模块化的第一步是很自然的事情.
function foo(){
//...
}
function bar(){
//...
}
上面的,组成一个模块。使用的时候,直接调用就行了。
这种做法的缺点很明显:全局变量被污染,很容易命名冲突, 而且模块成员之间看不出直接关系。
对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var MYAPP = {
count: 0,
foo: function(){},
bar: function(){}
}
MYAPP.foo();
上面的代码中,函数foo
和bar
, 都封装在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模块化需要解决那些问题:
- 如何安全的包装一个模块的代码?(不污染模块外的任何代码)
- 如何唯一标识一个模块?
- 如何优雅的把模块的API暴漏出去?(不能增加全局变量)
- 如何方便的使用所依赖的模块?
围绕着这些问题,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
定义的模块分为:
- 定义模块:
根据CommonJS
规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global
对象的属性。
- 模块输出:
模块只有一个出口,module.exports
对象,我们需要把模块希望输出的内容放入该对象。module
对象就代表模块本身。
- 加载模块:
加载模块使用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
});
- 用全局函数
define
来定义模块,用法为:define(id?, dependencies?, factory)
; -
id
为模块标识,遵从CommonJS Module Identifiers
规范 -
dependencies
为依赖的模块数组,在factory
中需传入形参与之一一对应 - 如果
dependencies
的值中有"require"、"exports"
或"module"
,则与commonjs
中的实现保持一致 - 如果
dependencies
省略不写,则默认为["require", "exports", "module"]
,factory
中也会默认传入require,exports,module
. - 如果
factory
为函数,模块对外暴漏API
的方法有三种:return
任意类型的数据、exports.xxx=xxx、module.exports=xxx
. - 如果
factory
为对象,则该对象即为模块的返回值
大名鼎鼎的require.js
就是AMD规范的实现.
require.js
要求,每个模块是一个单独的js
文件。这样的话,如果加载多个模块,就会发出多次HTTP
请求,会影响网页的加载速度。因此,require.js
提供了一个优化工具(Optimizer
)r.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来暴露该模块的对外接口
})
- 也是用全局的
define
函数定义模块, 无需罗列依赖数组,在factory
函数中需传入形参require,exports,module
. -
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中一个库有很多使用方式,这就需要你书写声明文件去匹配它们.
通过库的使用方法及其源码来识别库的类型。
-
全局库
全局库是指能在全局命名空间下访问的,许多库都是简单的暴露出一个或多个全局变量。 比如jQuery.
当你查看全局库的源代码时,你通常会看到:
- 顶级的var语句或function声明
- 一个或多个赋值语句到window.someName
-
模块化库
一些库只能工作在模块加载器的环境下。 比如,像
express
只能在Node.js
里工作所以必须使用CommonJS
的require
函数加载。模块库至少会包含下列具有代表性的条目之一:
- 无条件的调用
require
或define
- 像
import * as a from 'b'; or export c
;这样的声明 - 赋值给
exports
或module.exports
- 无条件的调用
- 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
不是一回事。
-
Gulp / Grunt
Gulp / Grunt 是一种工具,能够优化前端工作流程。比如自动刷新页面、combo、压缩css、js、编译less等等。简单来说,就是使用Gulp/Grunt,然后配置你需要的插件,就可以把以前需要手工做的事情让它帮你做了。 - 说到
browserify / webpack
,那还要说到seajs / requirejs
。这四个都是JS模块化的方案。其中seajs / require
是一种类型,browserify / webpack
是另一种类型。seajs / require
: 是一种在线"编译" 模块的方案,相当于在页面上加载一个CMD/AMD
解释器。这样浏览器就认识了define、exports、module
这些东西。也就实现了模块化。 -
browserify / webpack
: 是一个预编译模块的方案,相比于上面 ,这个方案更加智能, 首先,它是预编译的,不需要在浏览器中加载解释器。另外,你在本地直接写JS,不管是 AMD / CMD / ES6
风格的模块化,它都能认识,并且编译成浏览器认识的JS。这样就知道,Gulp
是一个工具,而webpack
等等是模块化方案。Gulp
也可以配置seajs、requirejs
甚至webpack
的插件。
Grunt
每次运行grunt
时,他就利用node
提供的require()
系统查找本地安装的 Grunt
。
如果找到一份本地安装的 Grunt
,grunt-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
](http://webpack.github.io/docs...
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
构建生命周期的节点上加入扩展hook
为webpack
加入功能。
Loaders
和Plugins
常常被弄混,但是他们其实是完全不同的东西,可以这么来说,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构建到输出结果经历了一系列过程,它们是:
- 解析
webpack
配置参数,合并从shell
传入和webpack.config.js
文件里配置的参数,生产最后的配置结果。 - 注册所有配置的插件,让插件监听webpack构建生命周期的事件节点,以做出对应的反应。
- 从配置的
entry
入口文件开始解析文件构建依赖图谱,找出每个文件所依赖的文件,递归下去。 - 在解析文件递归的过程中根据文件类型和
loader
配置找出合适的loader
用来对文件进行转换。 - 递归完后得到每个文件的最终结果,根据
entry
配置生成代码块chunk
。 - 输出所有
chunk
到文件系统。
代码拆分(Code Splitting)
代码拆分是 webpack
中最引人注目的特性之一。你可以把代码分离到不同的 bundle
中,然后就可以去按需加载这些文件.
-
分离资源,实现缓存资源
- 分离第三方库(vendor)
CommonsChunkPlugin
- 分离 CSS
- 分离第三方库(vendor)
-
传统的模块打包工具(
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.io
的 client
代码,可以和 webpack-dev-server
进行 websocket
通讯。
其他配置选项
- --hot 开启
Hot Module Replacement
功能 - --quiet 控制台中不输出打包的信息
- --compress 开启gzip压缩
- --progress 显示打包的进度
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。