8.Webpack学习
什么是Webpack
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle.
Webpack 是当下最热门的前端资源模块化管理和打包工具,它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分离,等到实际需要时再异步加载。通过 loader 转换,任何形式的资源都可以当做模块,比如 CommonsJS、AMD、ES6、CSS、JSON、CoffeeScript、LESS 等;
伴随着移动互联网的大潮,当今越来越多的网站已经从网页模式进化到了 WebApp 模式。它们运行在现代浏览器里,使用 HTML5、CSS3、ES6 等新的技术来开发丰富的功能,网页已经不仅仅是完成浏览器的基本需求;WebApp 通常是一个 SPA (单页面应用),每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的 JS 代码,这给前端的开发流程和资源组织带来了巨大挑战。
前端开发和其他开发工作的主要区别,首先是前端基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器的,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。
模块化的演进
Script 标签
1 | <script src="module1.js"> |
这是最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中,不同模块的调用都是一个作用域。
这种原始的加载方式暴露了一些显而易见的弊端:
- 全局作用域下容易造成变量冲突(不同js文件相互引用,变量名冲突可能性大)
- 文件只能按照
<script>
的书写顺序进行加载 - 开发人员必须主观解决模块和代码库的依赖关系
- 在大型项目中各种资源难以管理,长期积累的问题导致代码库混乱不堪
CommonsJS
服务器端的 NodeJS 遵循 CommonsJS 规范,该规范允许模块通过 require 方法来同步加载所需依赖的其它模块,然后通过 exports 或 module.exports 来导出需要暴露的接口。
1 | require("module"); |
优点:
- 服务器端模块便于重用
- NPM 中已经有超过 45 万个可以使用的模块包
- 简单易用
缺点:
- 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的
- 不能非阻塞的并行加载多个模块
实现:
- 服务端的 NodeJS
- Browserify,浏览器端的 CommonsJS 实现,可以使用 NPM 的模块,但是编译打包后的文件体积较大
- modules-webmake,类似 Browserify,但不如 Browserify 灵活
- wreq,Browserify 的前身
AMD
Asynchronous Module Definition 规范其实主要一个主要接口 define(id?, dependencies?, factory); 它要在声明模块的时候指定所有的依赖 dependencies,并且还要当做形参传到 factory 中,对于依赖的模块提前执行。
1 | define("module", ["dep1", "dep2"], function(d1, d2) { |
优点
- 适合在浏览器环境中异步加载模块
- 可以并行加载多个模块
缺点
- 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不畅
- 不符合通用的模块化思维方式,是一种妥协的实现
实现
- RequireJS
- curl
CMD
Commons Module Definition 规范和 AMD 很相似,尽量保持简单,并与 CommonsJS 和 NodeJS 的 Modules 规范保持了很大的兼容性。
1 | define(function(require, exports, module) { |
优点:
- 依赖就近,延迟执行
- 可以很容易在 NodeJS 中运行
缺点
- 依赖 SPM 打包,模块的加载逻辑偏重
实现
- Sea.js
- coolie
ES6 模块
EcmaScript6 标准增加了 JavaScript 语言层面的模块体系定义。 ES6 模块的设计思想,是尽量静态化,使编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonsJS 和 AMD 模块,都只能在运行时确定这些东西。
1 | import "jquery"; //导入 |
优点
- 容易进行静态分析
- 面向未来的 EcmaScript 标准
缺点
- 原生浏览器端还没有实现该标准
- 全新的命令,新版的 NodeJS 才支持
实现
- Babel
安装Webpack
WebPack 是一款模块加载器兼打包工具,它能把各种资源,如 JS、JSX、ES6、SASS、LESS、图片等都作为模块来处理和使用。
安装:
1 | #cmd尽量在管理员模式下 |
测试安装成功:
webpack -v
webpack-cli -v
配置
创建 webpack.config.js
配置文件
- entry:入口文件,指定 WebPack 用哪个文件作为项目的入口
- output:输出,指定 WebPack 把处理完成的文件放置到指定路径
- module:模块,用于处理各种类型的文件
- plugins:插件,如:热更新、代码重用等
- resolve:设置路径指向
- watch:监听,用于设置文件改动后直接打包
1 | module.exports = { |
使用webpack
创建项目,项目名webpack-study
项目下创建个名为 modules 的目录,用于放置 JS 模块等资源文件
在modules下创建模块文件``hello.js`,内容如下。
1
2
3
4
5
6
7
8//暴露一个方法:sayHi
exports.sayHi = function () {
document.write("<div>Hello WebPack</div>");
};
exports.sayHi2 = function () {
document.write("<div>Hello WebPack2</div>");
};在modules下创建名为 main.js 的入口文件,用于打包时设置 entry 属性,内容如下。
1
2
3
4//require 导入一个模块,就可以调用这个模块中的方法了
var hello = require("./hello");
hello.sayHi();
hello.sayHi2();在项目目录下创建 webpack.config.js 配置文件。
1
2
3
4
5
6
7
8
9//exports:导出
module.exports = {
//entry:程序的入口
entry: "./modules/main.js",
//输出位置
output: {
filename: "./js/bundle.js"
}
};直接运行
webpack
命令打包。(IDEA控制台中直接执行webpack;若失败,改用管理员权限运行!)在项目目录下创建 HTML 页面,如 index.html,导入 WebPack 打包后的 JS 文件。
1
2
3
4<body>
<!--导入打包后的 JS 文件-->
<script src="dist/js/bundle.js"></script>
</body>运行 HTML ,效果如下
Hello WebPack
Hello WebPack2
目录结构
说明:
1 | # 参数 --watch 用于监听变化(项目有变化就重新打包) |