webpack的诞生,时也势也
十多年来,随着Web应用变得越来越复杂与庞大,传统的通过直接编写JavaScript、CSS、HTML来开发Web应用的方式已经无法应对了,因此前端社区中就涌现出了许多新的思想与框架。
新的思想与框架带来了程序编码上的便利与规范,但同时也使得这样编写的代码无法直接在浏览器上运行,需要通过一系列转换才可以正常运行,所谓的转换即是【构建】。
在这段时间里,构建工具如雨后春笋般冒了出来,与此同时,一场有关于“谁更好用”的争夺战悄然爆发,刀光剑影过后,Webpack凭借着【开箱即用】、【一站式解决方案】等优势新王登基,一统Web构建工具的江湖近十年。
都说败者食尘,但谁还记得Grunt
、Gulp
、Fis3
这些值得尊敬的对手呢?
现在,让我们一起回顾那段历史,来看看Webpack成为目前主流构建工具的前因后果吧!
模块化
模块化是指把一个复杂的系统分解到多个模块以方便编码。
很久以前,开发网页要通过命名空间的方式来组织代码,例如 jQuery 库把它的API都放在了 window.$
下,在加载完 jQuery 后其他模块再通过 window.$
去使用 jQuery。 这样做有很多问题,其中包括:
- 命名冲突,两个库可能会使用同一个名称,例如 Zepto 也被放在
window.$
下,会发生变量覆盖问题; - 文件依赖,js文件之间无法相互引用;
- 无法合理地管理项目的依赖和版本。
模块化就是把单独的一个功能封装到一个模块(文件)中,模块之间相互隔离,但是可以通过特定的接口公开内部成员,也可以依赖别的模块。因此,模块化也方便代码的重用,从而提升开发效率,并且方便后期的维护。
CommonJS
CommonJS 是一种使用广泛的 JavaScript 模块化规范,核心思想是通过 require
方法来同步地加载依赖的其他模块,通过 module.exports
导出需要暴露的接口。 CommonJS 规范的流行得益于 Node.js 采用了这种方式,后来这种方式被引入到了网页开发中。
采用 CommonJS 导入及导出时的代码如下:
// 导入
const moduleA = require('./moduleA');
// 导出
module.exports = moduleA.someFunc;
CommonJS 的优点在于:
- 代码可复用于 Node.js 环境下并运行,例如做同构应用;
- 通过 NPM 发布的很多第三方模块都采用了 CommonJS 规范。
CommonJS 的缺点在于这样的代码无法直接运行在浏览器环境下,必须通过工具转换成标准的 ES5。
CommonJS 还可以细分为 CommonJS1 和 CommonJS2,区别在于 CommonJS1 只能通过
exports.XX = XX
的方式导出,CommonJS2 在 CommonJS1 的基础上加入了module.exports = XX
的导出方式。 CommonJS 通常指 CommonJS2。
AMD
AMD 也是一种 JavaScript 模块化规范,与 CommonJS 最大的不同在于它采用异步的方式去加载依赖的模块。 AMD 规范主要是为了解决针对浏览器环境的模块化问题,最具代表性的实现是 requirejs。
采用 AMD 导入及导出时的代码如下:
// 定义一个模块
define('module', ['dep'], function(dep) {
return exports;
});
// 导入和使用
require(['module'], function(module) {
});
AMD 的优点在于:
- 可在不转换代码的情况下直接在浏览器中运行;
- 可异步加载依赖;
- 可并行加载多个依赖;
- 代码可运行在浏览器环境和 Node.js 环境下。
AMD 的缺点在于JavaScript 运行环境没有原生支持 AMD,需要先导入实现了 AMD 的库后才能正常使用。
CMD
与AMD相似,唯一不同的是,AMD推崇依赖前置,即在定义模块的时候就要声明其依赖的模块,CMD推崇就近依赖,只有在用到某个模块的时候再去require。
其代表实现是 Sea.js
AMD 与 CMD 已经被社区抛弃了。
ES6 模块化
CommonJS、AMD、CMD 这些社区提出的模块化标准,还是存在一定的差异性与局限性,并不是浏览器与服务器通用的模块化标准,例如:
- AMD 和 CMD 适用于浏览器端的JavaScript 模块化
- CommonJS 适用于服务器端的JavaScript模块化
因此,ES6语法规范中,在语言层面上定义了ES6模块化规范,是浏览器端与服务器端通用的模块化开发规范。
ES6模块化规范中定义:
- 每个js文件都是一个独立的模块
- 导入模块成员使用 import 关键字
- 暴露模块成员使用 export 关键字
采用 ES6 模块化导入及导出时的代码如下:
// 导入
import { readFile } from 'fs';
import React from 'react';
// 导出
export function hello() {};
export default {
// ...
};
ES6模块虽然是终极模块化方案,但它的缺点在于目前无法直接运行在大部分 JavaScript 运行环境下,必须通过工具转换成标准的 ES5 后才能正常运行。
样式文件的模块化
除了 JavaScript 开始模块化改造,前端开发里的样式文件也支持模块化。比如 SCSS,就可以把公共的可复用的样式抽离出来成一个文件,然后通过@import
语句去导入和使用这些公共的样式片段。
// common.scss 文件
@mixin center {
display: flex;
justify-content: center;
align-items: center;
}
// main.scss 文件
// 导入和使用 common.scss 中定义的样式片段
@import "common.scss";
#box{
@include center;
}
框架
随着Web应用的繁复及庞大,直接操作Dom不仅会使得浏览器性能消耗过大,我们编写的代码也会变得复杂而难以维护,因此,许多新的框架诞生了。
Angular
Angular是一款来自谷歌开源的web前端框架,诞生于2009年,由Misko Hevery等人创建,后为Google所收购。它率先采用了模板语法,是一个【大而全】的框架。
在 Angular2 中,推崇采用 TypeScript 语言去开发应用,并且可以通过注解的语法描述组件的各种属性。
@Component({
selector: 'my-app',
template: `<h1>{{title}}</h1>`
})
export class AppComponent {
title = 'Tour of Heroes';
}
如果你写过 Vue2 class-component,看到这里我想你应该会跟我当初看到一样有种回家的感觉(狗头)
React
紧随其后的是React,它诞生于2013年,由FaceBook开源并维护。它开创了 JSX 的先河,得益于 JS 的完全编程能力,其灵活性是无可比拟的。
let bool = true
return <div>{ bool && <div>展示按钮</div> }</div>
Vue
祖师爷在2014年开源的渐进式框架,在 Angular 和 React 的【大小职责范围】中取中庸之道,易上手的同时还可以很轻量,对 Diff 和 VNode 也做了不少优化。当然,这并不是说Vue最好,在某些情况下还是需要权衡利弊。
语言
JavaScript 最初被设计用于完成一些简单的工作,在用它开发大型应用时一些语言缺陷会暴露出来。 CSS 只能用静态的语法描述元素的样式,无法像写 JavaScript 那样增加逻辑判断与共享变量。 为了解决这些问题,许多新语言诞生了。
Typescript
TypeScript 是 JavaScript 的一个超集,由微软开发并开源,它提供了静态类型检查。
TypeScript 常常用于大型项目的开发,其优秀的静态类型检测往往能在代码编写过程中规避许多问题,也能很方便后期的维护。但其缺点也不言而喻,它的语法相对于 JavaScript 更加啰嗦,并且无法直接运行在浏览器或 Node.js 环境下。
// 静态类型检查机制会检查传给 hello 函数的数据类型
function hello(content: string) {
return `Hello, ${content}`;
}
let content = 'word';
hello(content);
Flow
Flow 也是 JavaScript 的一个超集,是由FaceBook开发并开源。
它的主要特点是为 JavaScript 提供静态类型检查,和 TypeScript 相似但更灵活,可以让你只在需要的地方加上类型检查@flow
。
// @flow
let monthsAYear: 12 = 12;
monthsAYear = 13; // Flow 会在这里报错
构建工具
上面提到的都是无论是框架还是新的语言,都无法直接在浏览器上运行,且又由于各家浏览器对新特性的支持存在差异,这就需要一些工具来对它们进行转换,构建成浏览器兼容的代码,以保证程序能在不同厂商的浏览器上正常运行。
Grunt
Grunt是一个任务执行者,有大量现成的插件封装了常见的任务,也能管理任务之间的依赖关系,自动化执行依赖的任务。
它的优点是灵活,只负责执行我们定义的任务,且有量的可复用插件封装好了常见的构建任务。
缺点是集成度不高,要写很多配置后才可以用,无法做到开箱即用。
Grunt 相当于进化版的 npm run xxx
,它的诞生其实是为了弥补 npm run xxx
的不足。
Gulp
Gulp 可以看作是 Grunt 的增强版,它基于函数式编程的理念,构建出了流式的工作流,增加了监听文件、读写文件,同时提供了一系列常用的插件去处理流,流可以在插件之间传递,大致使用如下:
// 引入 Gulp
var gulp = require('gulp');
// 引入插件
var jshint = require('gulp-jshint');
var sass = require('gulp-sass');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
// 编译 SCSS 任务
gulp.task('sass', function() {
// 读取文件通过管道喂给插件
gulp.src('./scss/*.scss')
// SCSS 插件把 scss 文件编译成 CSS 文件
.pipe(sass())
// 输出文件
.pipe(gulp.dest('./css'));
});
// 合并压缩 JS
gulp.task('scripts', function() {
gulp.src('./js/*.js')
.pipe(concat('all.js'))
.pipe(uglify())
.pipe(gulp.dest('./dist'));
});
// 监听文件变化
gulp.task('watch', function(){
// 当 scss 文件被编辑时执行 SCSS 任务
gulp.watch('./scss/*.scss', ['sass']);
gulp.watch('./js/*.js', ['scripts']);
});
但即使 Gulp 灵活优雅,但它仍然面临着集成度不高,无法开箱即用,要写很多配置才能使用的尴尬处境。
Fis3
由百度开发的优秀的国产构建工具,作为早期的【大而全】的构建工具,Fis3不仅有 Grunt、Gulp 的基本功能,还集成了Web 开发中的常用构建功能,比如 读写文件、资源定位、文件编译、Hash文件、压缩资源和图片合并。
它非常强大,且开箱即用,很可惜的是目前官方已经不再更新维护了,且新的Node版本也不再兼容。
Webpack
Webpack 是一个打包模块化 JavaScript 的工具,在 Webpack 里一切文件皆模块,通过 Loader 转换文件,通过 Plugin 注入钩子,最后输出由多个模块组合成的文件。Webpack 专注于构建模块化项目。
其官网的首页图很形象的画出了 Webpack 是什么,如下:
一切文件:JavaScript、CSS、SCSS、图片、模板,在 Webpack 眼中都是一个个模块,这样的好处是能清晰的描述出各个模块之间的依赖关系,以方便 Webpack 对模块进行组合和打包。 经过 Webpack 的处理,最终会输出浏览器能使用的静态资源。
Webpack 具有很大的灵活性,能配置如何处理文件,大致使用如下:
module.exports = {
// 所有模块的入口,Webpack 从入口开始递归解析出所有依赖的模块
entry: './app.js',
output: {
// 把入口所依赖的所有模块打包成一个文件 bundle.js 输出
filename: 'bundle.js'
}
}
Webpack的优点是:
- 专注于处理模块化的项目,能做到开箱即用一步到位;
- 通过 Plugin 扩展,完整好用又不失灵活;
- 使用场景不仅限于 Web 开发;
- 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展;
- 良好的开发体验。
Webpack的缺点是只能用于采用模块化开发的项目。
Rollup
Rollup 是一个和 Webpack 很类似但专注于 ES6 的模块打包工具。与Webpack偏向于应用打包的定位不同,Rollup 更专注于 Javscript 类库打包。我们熟知的Vue、React等诸多知名框架或类库都是通过 Rollup 进行打包的。
一开始 Rollup 凭借着 Tree-shaking 和 对 ES6 模块有着算法上的支持而坐到与 Webpack 平起平坐的位置上,但很快 Webpack2 就实现了上述功能。实际上,Rollup已经在渐渐地失去了当初的优势了。但是它并没有被抛弃,反而因其简单的API与使用方式仍被许多库开发者青睐。
Webpack为何成为目前主流的构建工具
在 npm run xxx
和 Grunt 时代,Web 开发要做的事情变多,流程复杂,自动化思想被引入,用于简化流程;
在 Gulp 时代开始出现一些新语言用于提高开发效率,流式处理思想的出现是为了简化文件转换的流程,例如将 ES6 转换成 ES5。
在 Webpack 时代由于单页应用的流行,一个网页的功能和实现代码变得庞大,Web 开发向模块化改进。
经过多年的发展, Webpack 已经成为构建工具中的首选,这是有原因的:
- 大多数团队在开发新项目时会采用紧跟时代的技术,这些技术几乎都会采用“模块化+新语言+新框架”,Webpack 可以为这些新项目提供一站式的解决方案;
- Webpack 有良好的生态链和维护团队,能提供良好的开发体验和保证质量;
- Webpack 被全世界的大量 Web 开发者使用和验证,能找到各个层面所需的教程和经验分享。
转载自:https://juejin.cn/post/7241080797024616503