likes
comments
collection
share

webpack的诞生,时也势也

作者站长头像
站长
· 阅读数 15

十多年来,随着Web应用变得越来越复杂与庞大,传统的通过直接编写JavaScript、CSS、HTML来开发Web应用的方式已经无法应对了,因此前端社区中就涌现出了许多新的思想与框架。

新的思想与框架带来了程序编码上的便利与规范,但同时也使得这样编写的代码无法直接在浏览器上运行,需要通过一系列转换才可以正常运行,所谓的转换即是【构建】。

在这段时间里,构建工具如雨后春笋般冒了出来,与此同时,一场有关于“谁更好用”的争夺战悄然爆发,刀光剑影过后,Webpack凭借着【开箱即用】、【一站式解决方案】等优势新王登基,一统Web构建工具的江湖近十年。

都说败者食尘,但谁还记得GruntGulpFis3这些值得尊敬的对手呢?

现在,让我们一起回顾那段历史,来看看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 是什么,如下:

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
评论
请登录