模块化及自动化构建工具学习总结

我们从模块化的发展历程开始

自调用函数

JavaScript诞生之初,网络设备性能还很差,网速很慢,将所有交互都放在后端的话用户体验很差,因此急需一门语言来处理简单的前端交互,如表单非空校验之类的,js就因此诞生

一开始js能做的事情并不多,代码量很少,将代码直接写到script标签或者一个单独的js文件里即可

// index.html


// index.html

到后来ajax诞生,前端能做的事情越来越多,代码量大增,单个js文件会导致文件过大;开发者们便根据功能划分了不同的js文件,以便复用和维护

// index.html



但是早期的js是只有函数作用域的,文件里声明的变量都是在全局生效的,这就导致了全局变量的污染,多人开发维护变得困难

于是大家就想到了利用函数作用域和闭包的特性,既保留了私有变量,又暴露出了功能接口

var module1 = (function(){
    var foo = 1;
  
    function getFoo(){
      return foo;
    };
    function changeFoo(param){
      foo = param
    };
  
    return {
        getFoo: getFoo,
        changeFoo: changeFoo
    };
})();

这就是模块化的雏形,但是用这种方式存在很多问题

  • 需要手动排列模块的加载顺序来保证模块间的相互引用
  • 依赖关系不清晰,应用复杂之后难以维护
  • 仍然有全局变量污染问题

Commonjs / AMD / CMD

到2009年,Nodejs发布,将js带到服务端,并将服务端js的模块化规范Commonjs发扬光大

var foo = require('./foo');
module.exports = {
  bar:1
}

开发者们想把这种模块化迁移到浏览器端,但浏览器不能直接识别commonjs的语法,且commonjs是同步加载文件的,用于服务器时,依靠磁盘读取模块,影响不大,而用于浏览器时则需要靠请求获取模块,会造成阻塞的问题,因此出现了两个分支AMD(Asynchronous Module Definition)和CMD(Common Module Definition)规范,还有他们各自的实现方案RequirejsSeajs

// AMD
define(['Module1'], function (module1) {
    var result1 = module1.exec();
    return {
      result1: result1,
    }
}); 

// CMD
define(function (requie, exports, module) {
    var module1 = require('Module1');
    var result1 = module1.exec();
    module.exports = {
      result1: result1,
    }
});

这两种实现都是通过动态创建script标签来实现异步加载js模块,最大的不同在于AMD推崇依赖前置,模块加载完就执行依赖包,CMD推崇依赖就近,下载后不执行,require调用时才执行依赖包

虽然AMD和CMD解决了前端模块化的问题,但这类方案都是通过“在线编译”的方式来组织模块的,当用户访问页面后开始下载依赖包,下载好后再进行模块的依赖分析确定加载和执行顺序,这种方式存在以下问题

  • 在线组织依赖包会延长页面加载的时间
  • 加载过程中还会发出大量http请求,而http1.x协议的队头阻塞和浏览器并行请求限制问题会导致页面性能降低

bundle 类的构建工具

为了解决这些问题,出现了各种打包工具,最有代表性的是2011年推出的broswerify和2012年发布的webpack,他们可以在代码部署上线前就将模块依赖组织好,并将大量依赖包合并成少数几个,以此减少http请求的数量,提升页面性能;

当代码量很多的时候,会出单个打包产物过大的问题,webpack提供了代码拆分(Code Splitting)的功能,可以将产物包分成多个,比如将不常更新的第三方库和常更新的业务代码分开打包,利用浏览器缓存第三方库依赖包,以此提高访问速度;通过代码拆分还可以实现按需加载,提高首屏访问速度

在打包工具还在发展的过程中,开发者们逐渐不满足于仅仅打包,希望将代码压缩等重复性的工作都交给工具解决,这就是自动化构建工具的产生,代表工具依旧有webpack,还有2012年发布的grunt,及2013年发布的gulp

gulp是编程式的,链式调用,写配置像是写业务代码一样

// gulpfile.js
const { src, dest } = require('gulp');
const less = require('gulp-less');
const minifyCSS = require('gulp-csso');

function css() {
  return src('client/templates/*.less')
    .pipe(less())
    .pipe(minifyCSS())
    .pipe(dest('build/css'))
}  

webpack不止于打包,还加入到了自动化构建的行列里;它是配置式的,除了主流程的配置,最主要的就是loaderplugin;loader用于解析非js模块,而plugin用于实现压缩优化,代码拆分等loader无法实现的内容;

// webpack.config.js
module.exports = {
  mode: "production",
  /** webpack打包入口 */
  entry: path.join(__dirname, "../src/app.tsx"),
  output: {},
  resolve: {},
  /** 配置如何处理项目中的不同类型的模块 */
  module: {
    rules: [
      {
        test: /\.(j|t)sx?$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
    ],
  },
  /** 插件配置 */
  plugins: [],
  /** 开发服务器配置 */
  devServer: {},
};

最后webpack在自动化构建工具的竞争中胜出,是现在的主流,但它也有一些问题,比如

  • 配置复杂
  • 随项目内容增多,构建逐渐变慢

大型项目构建慢,这也是bundle类构建工具的通病,因为这些工具的思想都是先递归循环依赖包,组建依赖树,优化依赖树后生成可运行的部署包,这一打包的步骤在开发阶段也要不断重复运行,随项目复杂导致开发效率降低

Es module

2015年 es6 正式发布,带来了官方的模块化规范 es module;


import {} from '/foo.js'
const bar = {}
export default bar 

esm有以下几个特点

  • 异步加载,等同于打开了

你可能感兴趣的:(模块化及自动化构建工具学习总结)