前端工程化:从一个script标签说起

762 阅读4分钟

原始时代

在很久以前,一个前端页面非常简单,由1个html、1个css、1个script组成。

image-20220317092525134.png

后来,随着“前端应用”的复杂度越来越高,linkscript标签的数量也在剧增。举个夸张的例子,如下图:

image-20220317092854868.png

这个html文件引用了1000个js文件,这会引发两个重大的问题:

  1. 访问一个页面需要发起1000次http请求!
  2. 如何有序地管理好这1000个script标签?

针对以上两个问题,我们一般会采取两种策略:

  1. 将1000个script标签合并成一个js文件;
  2. 拿个“小本本”记下每个js文件的顺序。

在原始时代,我们采用纯手工的方式实现上面的策略1:用ctrl + cctrl + v的方式,手动合并所有js文件(task 1);完成合并之后,可能还需要手动地通过Babel进行转译(task2);转译之后,可能还需要手动地进行压缩,删除多余的空格(task3);等等。

这是一个工作量巨大的任务!想想如果我们每次发布一个小版本,都需要手动地“重复一遍”上述所有的任务,那简直是个噩梦!

我们急需一个“自动化”的工具,来帮我们“一键”完成上述的所有任务!

题外话:现在,很多前端工程师觉得要学的东西越来越多了,比如要学Node、Webpack、Vue/React等各种工具。其根本原因就在于此:前端项目复杂度的剧增!前端已经从1个script就能解决的时代,进入了1000个,甚至更多script标签才能解决的时代,也就是“前端工程化”的时代。而工程化是一门极其高深的学问。

gulp/grunt时代

在前一节的背景下,Gulp和Grunt应运而生了。Grunt官网将Grunt定义为 Task Runner,也就是“任务执行器”。我们事先定义好一系列的task,比如第一步为“拼接”、第二步为“转译”、第三步为“压缩”...,最后只需要一行命令,Grunt就能自动地帮我们完成所有任务!

上述定义task的过程是在gruntfile中进行的,下面是一个gruntfile的简单示例:


module.exports = function(grunt) {
​
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    // task1
    concat: {
      // 相关配置
    },
    // task2
    uglify: {
      // 相关配置
    },
    // task3
    qunit: {
      files: ['test/**/*.html']
    },
    // task4
    jshint: {
      // 相关配置
    },
    // task5
    watch: {
      // 相关配置
    }
  });
​
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-qunit');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');
​
  grunt.registerTask('test', ['jshint', 'qunit']);
​
  grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);
​
};

可以看到,gruntfile注册了两个“任务”:test和default。其中test包含了jshint、qunit两个子任务,default包含了jshint、qunit、concat、uglify四个子任务。

这种配置方式,在现在看来,不免显得有些“繁琐”。

webpack时代

Webpack是前端工程化过程中的一个产物,它的核心思想是“自动化”。

提起工程化,怎么能少得了“模块化”开发呢。随着“模块化”的发展,伴之而来的问题是:如何分析模块与模块之间的依赖关系。举个简单的例子:

image-20220317103156993-16474843189213.png

如上图,模块a依赖模块b和模块c。我们如何告诉计算机“在加载a之前需要先加载b和c”这种逻辑呢?由此便诞生了Webpack项目,Webpack会以a.js为入口,递归访问其依赖文件,构建成“依赖关系图”,然后根据这个关系图,生成相应的代码。

Webpack最初的目的是为了分析模块之间的依赖关系,后来人们发现它提供的Loader和Plugin非常好用,为它开发了很多好用的插件,发展至今,Webpack已经成为前端开发必备的一个工具了。

题外话:Webpack的配置方式,相比于gulpfile/gruntfile的配置方式,从设计角度而言,有了很大提升。

现状与展望

之前,网络上流传着“Webpack配置工程师”的一个梗,这反映了Webpack配置繁琐的问题。后来便诞生了很多基于Webpack的构建工具,如Vue/React的Cli工具,它们内置了一些常用的功能,比如Dev Server、HMR等,这样确实省却了前端工程师的配置工作。但是在我看来,这种方式“治标不治本”。

最好的“插件配置”设计体系应该像VS Code那样,插件一键安装,点点鼠标就能完成相关配置。