前端模块化与包管理器

  1. 什么是模块化?

    将一个复杂的程序依据一定的规则封装成几个块文件,并进行组合

  2. 模块化特点

    模块中数据与实现是私有的,只向外部暴露一些接口方法与其他模块通信


模块化历程

全局function

function util1(){
    //...
}
function util2(){
    //...
}

引入js文件,这样会污染到全局当时使用命名空间优化这个问题,引入一个工具对象(不私有)

闭包

向window对象添加全局属性

(function (win){
    function aFn(){}
    function bFn(){}
    win.myMethod = {aFn,bFn}
})(window)

传入依赖模块,必须要有引入顺序

(function (win,$){
    function aFn(){}
    function bFn(){}
    //...
    win.myMethod = {aFn,bFn}
})(window,jQuery)

问题

  1. 请求过多

    首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多

  2. 依赖模糊

    我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。

  3. 难以维护

    以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。 模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上这些问题。

模块化规范


1. 浏览器模块化规范

  1. AMD

    非同步加载,允许回调函数

    1. RequireJs用于客户端模块管理。通过define定义,require实现加载
  2. cmd

    模块的加载是异步的,模块使用时才会加载执行。

    1. sea.js
|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
  |-main.js
|-index.html

2. 服务端模块化规范(CommoJs)

Node 应用由模块组成,采用 CommonJS 模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

1. 特点
  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。
  • CommonJS 模块就是对象
  • 运行时加载
2. 基本语法
//暴露方式
module.exports = value;
exports.xxx = value;
//导入方式
const value = require('./test.js');//自定义模块就是路径,第三方模块就是名称
3. 加载机制

CommonJS模块的加载机制是,输入的是被输出的值的拷贝(a=b)。也就是说,一旦输出一个值(基础类型),模块内部的变化就影响不到这个值。

//test.js
let data = {a:1};
module.exports.add = function(){
    data.a++;
}
module.exports.data=data;
//main.js
const data = require('./test.js');
console.log("main1",data)
//main2.js
const main = require('./main.js');
const data = require('./test.js');
data.add();
console.log("main2",data)

3. Es6模块化规范(通用)

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依’’

赖关系,以及输入和输出的变量。

1. 特点
  • 非默认导出加载时需要知道变量,函数名

  • ES6 模块是编译时加载,使得静态分析成为可能ES6 模块是编译时加载,使得静态分析成为可能

  • 由于静态分析,导入时不能有语句结构、变量
2. 基础语法

一个模块中内部变量和接口一一对应

export default xxx;//默认导出一次
import xx from ''
//按需导出
import {b as c} from ''
export let b = 1;
//直接运行
import ''
//整体加载
import * as MODULE from ''
//转发
export xxx from ’’//当前模块访问不了只做转发
//报错
let m =    1;
export  m;//m是一个值1不是接口
3. 运行机制

ES6 模块输出的是值的引用

//test.js
export let data = 1;
export function add(){
    data++;
}
//main.js
import {data} from './test.js';
console.log("main1",data)
//main2.js
import './main.js'
import {data,add} from './test.js';
add();
console.log("main2",data)

4. CommoJs与Es6模块的区别

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
    • JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

PS:Node.js v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。 Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块。或者package.json中将type改为module

包管理器


NPM

NPM是随同NodeJS一起安装的包管理工具,它可以用来辅助构建NodeJs项目。利用构建工具,使得其能在前端项目中用到(下一节课)

1. 基础命令
生成
npm init -y  -y默认生成
安装
npm install xxx
npm install xxx --save或者-S添加到dependencies环境(默认也是)
npm install xxx --sace-dev或者-D将模块添加到devDependencies属性
安装依赖
npm install
移除
npm uninstall xxx
更新
npm update xxx
npm配置
npm config list
运行脚本
npm run xxx scripts属性中的内容

初始化一下npm的配置文件

2. package.json字段

项目配置文件

{
  "name": "node", 
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
  • name: 必须字段,模块的名称,不能以“._[A-Z]”开头。

  • version: 必须字段,当前版本号,默认为1.0.0

  • description: 可选字段,描述信息,有助于别人搜索到你的包

  • main: 可选字段,指定了项目的加载入口文件

  • author: 可选字段,开发者

  • license: 可选字段,证书,包的权限

  • private: 可选字段,true则不发布到npm

  • dependencies: 生产环境依赖

    当我们安装包后,以express为例

      //安装
      npm install express --save
    
      "dependencies": {
          "express": "^4.17.1" 对应模块名称和版本号
      }
    
  • devDependencies: 开发环境依赖

    当我们安装webpack后

    npm install webpack webpack-cli --save-dev
    
    "devDependencies": {
        "webpack": "^5.28.0",
        "webpack-cli": "^4.5.0"
    }
    
    • 在一个项目中我们最好区分开模块使用的环境,以此来减小项目体积。
      • 单元测试,语法转化,css处理器,代码规范等适合开发环境依赖
  • script: 接收一个对象,它的值作为npm run运行的脚本,通常是终端命令。

    "scripts": {
        "test": "node index.js",
        "build": "webpack --mode=development",
        "dev": "webpack-dev-server --mode=development --contentBase=./dist"
    }
    

    npm run test === node index.js

    一般结合一些构建工具进行使用,比如webpack

3. 版本相关

版本号一般由四个部分构成如3.2.1遵照大版本,次要版本,小版本构成,一般大版本不向后兼容,次要版本一般更新特性,小版本修bug。

第四部分为日期版本号加希腊字母版本号,希腊字母版本号共有五种,分别为base、alpha、beta 、RC 、 release

"dependencies": {
        "bar": ">=1.0.2 <2.1.2",        必须大于等于1.0.2版本且小于2.1.2版本
        "baz": ">1.0.2 <=2.3.4",        必须大于1.0.2版本且小于等于2.3.4版本
        "boo": "2.3.1",                 必须匹配这个版本
        "boo": "~2.3.1",                约等于2.3.1,只更新最小版本,相当于2.3.X,即>=2.3.1 <2.4.0
        "thr": "2.3.x",
        "boo": "^2.3.1",                与2.3.1版本兼容,相当于2.X.X, 即>=2.3.1 < 3.0.0,不改变大版本号。
        "qux": "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0",
        "asd": "http://asdf.com/asdf.tar.gz",   在版本上指定一个压缩包的url,当执行npm install 时这个压缩包会被下载并安装到本地。
        "lat": "latest",                安装最新版本
}
4. package-lock.json

包锁定文件,随着npm命令自动生成

因为包的版本一直都在发布,我们在package.json中允许版本存在着差异,如^~。而package-lock.json将会为我们锁定这些版本.

在有package-lock.json的情况下,npm install会优先根据package-lock.json安装依赖。这使得我们在协同开发,或者下载别人的项目时能使用完全一直的版本,从而避免因为版本更新带来的bug。

CNPM

cnpm是中国npm镜像的客户端。因为npm是国外的网站,可能会有网络不稳定的情况,cnpm是淘宝做的国内镜像,

使用与npm一致,只是把npm换成cnpm,即cnpm xxx

安装
npm install cnpm -g --registry=https://registry.npm.taobao.org

或者直接把npm设置成淘宝的源

npm config set registry https://registry.npm.taobao.org

yarn

安装npm install -g yarn

yarn相较于npm的提升:

  • 速度快,所有包同步下载
  • 有缓存,下载过的包自动从缓存中提取
  • 输出信息简洁
生成
yarn init
安装依赖
yarn
安装
yarn add xxx
yarn add xxx --dev
卸载
yarn remove xxx

yarn.lock 对应 package-lock.json