模块化与包管理工具

225 阅读18分钟

模块化

  • 远古时代: 立即执行函数表达式(IIFE:Immediately Invoked Function Expressions)---自调用。

    // a.js
    (function() {
     	var obj = xxxxx // 你自己的模块
     	
     	window.obj = obj // 挂到window暴露出来
     })(window)
    
    // a1.js
    (function() {
     	var obj1 = xxxxx // 你自己的模块
     	
     	window.obj.新功能 = obj1 // 挂到window暴露出来
     })(window)
    
  • 第三方的库 - sea.js , require.js

    曲线救图 --- 借助第三方库,来实现模块化

    a.js
    定义模块
    
    
    
    b.js
    根据sea.js的语法去引入a.js的模块
    
  • 工具: webpack----在node.js的环境中运行,它可以支持nodejs的模块化(或者是es6模块化),再把代码打包成es5的代码

  • es6中直接有模块化/ nodejs中也有

模块化: 对前端的意义,如果没有模块化,前端就不可能做大!

模块化-理解模块化

提问:在浏览器中,我们写代码时,

- index.html # 主页的页面
- index.js # 主页需要用到的js代码
---- getData()  
- tool.js # 为整个项目提供公共方法
---- doSomething(){ }

对于如上的代码结构,如何让index.js中的getData去使用tool.js中的doSomething()函数?

  1. 上面的代码写法,会有什么问题?
  2. 为什么不能直接让index.js来直接使用tool.js的函数,而要通过index.html来统一管理一下呢?

原因很简单:es5中不支持模块化(不能直接在一个js文件中去引用另一个js文件的方法,必须要通过第三个文件.html来引入)

模块化的定义

一个js文件可以引入其他的js文件,能使用引入的js文件的中的变量、数据,这种特性就称为模块化。

使用模块化开发可以很好的解决变量、函数名冲突问题,也能灵活的解决文件依赖问题。

模块化的发展

  • 以前

    es5不支持模块化,让前端人员很为难。为了让支持模块化,我们一般会借用第三方库来实现:

  • 现在

    • es6原生语法也支持模块化(并不表示浏览器也直接支持模块化 --- 需要单独设置一下)
    • Nodejs内部也支持模块化(与es6的模块化有些不同之处),具体的语法在后面来介绍。

模块化-体验模块化

演示性的学习,让大家了解几种模块化的写法

  • nodejs的模块化

  • es6的模块化

  • sea.js

  • require.js

www.zhihu.com/question/20…

nodejs中的模块分类-复习

每个模块都是一个独立的js文件。每个模块都可以完成特定的功能,我们需要时就去引入它们,并调用。不需要时也不需要管它。(理解于浏览器的js中的Math对象)

nodejs模块的分类

  • 核心模块
    • 就是nodejs自带的模块,在安装完nodejs之后,就可以随意使用啦。相当于学习js时使用的Array对象。
    • 例:fs, http, querystring, path
    • 全部模块的源代码 github.com/nodejs/node…
  • 自定义模块
    • 程序员自己写的模块。就相当于我们在学习js时的自定义函数。
  • 第三方模块
    • 其他程序员写好的模块。nodejs生态提供了一个专门的工具npm来管理第三方模块,后面我们会专门讲到。
    • 相当于别人写好的函数或者库。例如我们前面学习的JQuery库,arttemplate等。

自定义模块

程序员自己写的模块

我们对代码的封装是以模块(一个独立的.js文件)为单位进行的。一般的做法是实现好某一个功能之后,封装成一个模块,然后在其它文件中使用这个模块。

类比于js自定义函数,自定义模块的使用场景是:

  • 代码需要在项目重用
  • 代码需要提供给他人使用
  • 代码虽然不需要重用,但封装成模块有利于优化代码结构,方便后期维护与扩展

一共有两步:

  1. 定义模块。就是创建一个js文件,有导出。

  2. 使用模块。在需要使用的地方去导入模块文件。

定义模块

所谓定义模块,就是新建一个js文件。文件取名时,要注意一下:

  • 一般会用模块名给它命名。类比于核心模块,例如,你的模块叫myModule,则这个js文件最好叫myModule.js
  • 不要与核心模块的名字重复了。就像我们定义变量不要与核心关键字重名,你自己定义的模块也不要叫fs.js,因为nodejs有一个核心模块就叫fs.js。
  • 要记得导出模块

示例:我们定义一个模块,文件名是tools.js。 在js文件内我们定义一些函数,变量,它们会根据我们的业务要求做一些不同的工作。最后根据情况导出这些函数,变量。

//tools.js
const relativeTime = (oldTime) => {
  const t = new Date(oldTime)
  // Date.now():现在的时间戳(毫秒)
  // t.getTime():旧时间的时间戳(毫秒)

  const diff = Date.now() - t.getTime() // 相隔多少毫秒
  // Math.floor 向下取整: 1.7年 ---> 1年前
  const year = Math.floor(diff / (1000 * 3600 * 24 * 365))
  if (year) {
    return `${year}年前`
  }
  const month = Math.floor(diff / (1000 * 3600 * 24 * 30))
  if (month) {
    return `${month}月前`
  }
  const day = Math.floor(diff / (1000 * 3600 * 24))
  if (day) {
    return `${day}天前`
  }
  const hour = Math.floor(diff / (1000 * 3600))
  if (hour) {
    return `${hour}小时前`
  }
  const minute = Math.floor(diff / (1000 * 60))
  if (minute) {
    return `${minute}分钟前`
  } else {
    return '刚刚'
  }
}

const formatDate = (dateTime) => {
  // console.log(date)
  // date = new Date();
  const date = new Date(dateTime) // 转换成Data();
  console.log(date)
  var y = date.getFullYear()
  console.log(y)
  var m = date.getMonth() + 1
  m = m < 10 ? '0' + m : m
  var d = date.getDate()
  d = d < 10 ? ('0' + d) : d
  return y + '-' + m + '-' + d
}
// 通过module.exports来导出模块
module.exports = {
  formatDate,
  relativeTime
};

导出模块

在文件尾部,使用module.exports来导出模块。

// 格式
module.exports = 要导出的内容

注意:

  • module.exports 是固定写法,一般放在文件的最末尾,也只用一次。
  • module.exports表示当前模块要暴露给其它模块的功能。
    • 它可以导出对象,数组,函数等等类型。为了方便组织代码,导出对象的情况比较多。
    • 不必要导出所有函数,对象,数组等。那些没有导出的部分就相当于这个模块的内部变量了。

1575035153257.png

导入模块

完成了模块定义之后,我们就可以在另一个文件中使用这个模块了。

基本步骤是

1. 导入模块;
2. 先打出来看看;

格式:

const 模块名 = require('./模块路径')

当一个模块被成功引入之后,就可以类比使用核心模块的过程一样去使用它们了。

// index.js
// 1. 导入模块
// 注意这里使用的是相对路径。可省略.js.
const myMath = require('./myMath');

// 在使用之前请先打印出来看看
console.log(myMath);

// 2. 使用模块中的方法
let rs = myMath.add(23,45);
console.log(rs)

注意:

  • 使用require语句引入你定义好的模块
  • 这里必须使用相对路径的格式去引入自定义模块。"./" 也不能省略。

导出模块的两种方式

导出模块有两种方式

参考

  • exports
  • module.exports
// 定义方法,常量
const myPI = 3.14
function add(a,b) {return a + b ;}

// 导出,两种方法任意都可以
// 方法一:
exports.myPI = myPI
exports.add = add

// 方法二:
module.exports.myPI = myPI
module.exports.add = add

// 方法二(变形)
module.exports  = {
    myPI,
    add
}

在阅读其它人的代码时,可能会遇到这两种不同的写法。所以我们还是有必要了解一下的。

cookie模块,body-parser模块,arry-flatten模块中的导出均采用不同的方式。

两个对象的关系

  • 初始exports和module.exports是指向同一块内存区域,其内容都是一个空对象。(exports是module.exports的别名)即:
exports === module.exports // 输出是 true

所以下面两种写法的效果是一样的:

//1 mymodule.js
exports.f = function(){ }
exports.pi = 3.1415926
//2 mymodule.js
module.exports.f = function(){ }
module.exports.pi = 3.1415926
  • 在定义模块时:

    如果直接给exports对象赋值(例如:exports={a:1,b:2}),此时,exports就不会再指向module.exports,而转而指向这个新对象,此时,exports与module.exports不是同一个对象。

在引入某模块时:以该模块代码中module.exports指向的内容为准。

图示

image-20210324142039663.png

image-20210324142213492.png

image-20210324142532461.png

结论

在导出模块过程中,建议只用一种方式(建议直接使用module.exports)

了解npm

npm

  • npm 全称 Node Package Manager(node 包管理器),它的诞生是为了解决 Node 中第三方包共享的问题。
  • npm 不需要单独安装。在安装Node的时候,会连带一起安装npm
  • npm -v检查安装的情况。
  • 官网

当我们谈到npm时,我们在说两个东西:

  • 命令行工具。这个工具在安装node时,已经自动安装过了。
  • npm网站。这是一个第三方模块的"不花钱的超市",我们可以自由地下载,上传模块。

包(package)与模块关系

npm网站收集了前端的各种工具.

之前学习过:

​ jQuery, bootStrap, flexible.js, arttemplate.js, layui.js, echarts.js........

你是如何下载的?

官网下载


有没有一个想法:在一个地方下载所有的库(模块.....)

npm网站上去下载我们的需要的代码时,它们是以""这种结构放在npm网站上的。先来了解下包和模块的关系。

模块与包的关系.png

  • nodejs中一个模块就是一个单独的js文件
  • 包是多个模块的集合。一个模块的功能比较单一,所以一个包一般会包含多个模块。
  • npm 管理的单位是包。类似于网站和网页的区别:一个网站一般会包含多个网页。

npm下载使用包

分成三步:

  • 初始化项目。npm init 如果之前已经初始化,则可以省略。
  • 安装包。 npm install 包名。
  • 引入模块,使用。

保持联网的状态哈

第一步:初始化项目

这里提到的项目并不是某个具体的功能,只是要创建一个空文件夹即可(注意,不要起中文名字哈)。

进入到项目所在的根目录下,启动小黑窗(按下shift键,点击右键,在弹出的菜单中选择 “在此处打开命令行”)

输入如下命令:

npm init --yes
// --与yes之间没有空格, -- 与init之间有空格
// 或者是 npm init -y

init命令用来在根目录下生成一个package.json文件,这个文件中记录了我们当前项目的基本信息,它是一切工作的开始。

第二步:安装包

npm 这个超市上有好的代码,我们想下载来用 ------ 安装包

生成了package.json文件之后,我们就可以来安装第三方包了。在npm官网中,有上百万个包,供我们使用(你需要在npm网上注册帐号,登陆上去,才可以看到如下的数据,如果只是下载安装包,则并不需要注册)。

1562724829170-1575039940084.png 根据我们遇到的实际问题,我们来引入相应的包来解决它们。例如,我们在开发一个项目,其中涉及一些对日期时间的处理可以安装dayjs包。

安装day.js包

day.js是一个专门用来处理日期时间的一个包

dayjs.fenxianglu.cn/

npm install dayjs

第三包:使用包

当我们已经下载好一个包之后,就可以像使用核心模块一样去使用它。

格式是:const 常量名 = require('包名') 这个格式与引入核心模块的格式是一样的。

// 从npm下载 别人写的好代码,在本地引入,并使用
const dayjs = require('dayjs')

console.log( dayjs()
.startOf('month')
.add(1, 'day')
.set('year', 2018)
.format('YYYY-MM-DD HH:mm:ss') );

console.log(dayjs);

npm init 命令

在某个目录下开启小黑窗,输入如下命令:

# init 初始化
npm init 

它会启动一个交互式的程序,让你填入一些关于本项目的信息,最后生成一个package.json文件

如果你希望直接采用默认信息,可以使用:

npm init --yes
// 或者是 npm init -y

说明:

  • 这个命令只需要运行一次,它的目的仅仅是生成一个package.json文件。
  • 如果项目根目录下已经有了package.json文件,就不需要再去运行这个命令了。
  • 这个package.json文件后期是可以手动修改的。

package.json文件

它一般是由npm init命令创建出来的,它的整体内容是一个json字符串,用来对当前项目进行整体描述。其中最外层可以看作是一个js的对象(每一个属性名都加了"",这就是一个典型的json标记)。这个文件中有非常多的内容,我们目前学习如下几个:

  • name: 表示这个项目的名字。如是它是一个第三方包的话,它就决定了我们在require()时应该要写什么内容。

  • version:版本号

  • keywords:关键字

  • author: 作者

  • descrption: 描述

其它可参考

1.javascript.ruanyifeng.com/nodejs/pack…

2.caibaojian.com/npm/files/p…

node_modules文件夹

作用

在使用npm install 命令时,会从npm网站下载对应的包到这个文件夹中,这个文件夹中保存着我们从npm中下载来的第三方包。

node_modules.jpg

执行逻辑

当键入npm install XXX之后(这里假设这个XXX包是存在的,也没有出现任何的网络错误):

  1. 如果有package.json

    (1) 修改package.json文件。根据开发依赖和生产依赖的不同,决定把这句记录在加在devDependencies或者是dependencies列表中。

    (2) 修改node_modules文件夹

    1. 如果有node_modules文件夹,则直接在下面新建名为XXX的文件夹,并从npm中下来这个包。如果这个包还有其它的依赖,则也会下载下来。
    2. 如果没有node_modules,则先创建这个文件夹,再去下载相应的包
  2. 如果没有package.json。会给一个警告信息。

说明:

  • 建议先用init命令创建package.json之后,再去install

  • 在分享代码时,我们一般不需要把node_modules也给别人(就像你不需要把jquery.js给别人,因为他们可以自己去下载)。对方拿到我们的代码之后,先运行npm install(后面不接任何的包名),自己去安装这些个依赖包。

全局安装包和本地安装包

我们通过npm install 命令来安装包,简单说就是把包从npm的官网(或者是指定的镜像源)下载到我们自己的电脑中。那具体这个包下载到哪里了,还是有一点讲究的。

分成两类:

  • 全局安装: 包被安装到了系统目录(一般在系统盘的node_modules中)。

    • 命令:npm install -g 包名 或者 npm install 包名 -g

    • 辅助提示:

      • npm root -g // 查看全局包的安装目录
        npm list -g --depth 0 //查看全局安装过的包
        
  • 局部安装(或者叫本地安装),包安装在当前项目的根目录下(与package.json同级)的node_modules中。

    • ​ 命令:npm install 包名

全局包与本地包的区别

  • 全局安装的包一般可提供直接执行的命令。我们通过对一些工具类的包采用这种方式安装,如:

    gulp, nodemon, live-server,nrm等。

  • 本地安装的包是与具体的项目有关的, 我们需要在开发过程中使用这些具体的功能。

一个经验法则:

  • 要用到该包的命令执行任务的就需要全局安装
  • 要通过require引入使用的就需要本地安装。

全局安装nrm包

作用

nrm 这个工具是帮助我们切换安装包的来源的。因为下载包时,默认是从npm官网(国外的网站)下载,速度可能会比较慢,我们可以手动去切换****安装来源

不应该只限于某个具体的项目,所以我们采用全局安装的方式来安装它。

nrm包的地址:www.npmjs.com/package/nrm

nrm的使用方法

操作步骤

共三步

// 第一步: 全局安装 
npm install nrm -g

// 第二步:列出所有的源信息
// (*)标注的就是当前使用的源
nrm ls

// 第三步:根据需要切换源 
// 例如:指定使用taobao源
nrm use taotao

// 接下来,正常安装你需要的包
  • nrm ls 的查看讲解

image-20210604100453553.png

如果nrm 命令不能用,请看这里:

blog.csdn.net/LQCV587/art…

const NRMRC = path.join(process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'], '.nrmrc');

全局安装nodemon包

作用

它能帮我们自动检测到我们的代码的修改,并自动重新运行我们的代码

我们每次修改了代码,要想代码生效都需要重启http服务器:

  1. 进入小黑窗
  2. 按下ctrl+c,停止已有http服务器。
  3. 手动运行:node index.js 来重启服务器。

这有点麻烦哈。

有没有一个工具会自动检测到我们的修改并自动重新运行我们的代码呢?有,它叫nodemon。地址

安装 nodemon

通过npm包管理工具来进行安装。

步骤:

在任意位置 打开一个小黑窗,输入如下命令

npm install -g nodemon

回车。

此操作需要联网,根据网络速度所耗时间不同。如果这个命令执行完成并没有报错,就是说明安装成功了。

对上面的命令说明如下:

  • npm是一个工具,用来管理node代码中要使用的第三方模块。它是随着node的安装而自动安装的:如果你安装node,则npm也已经安装过了,你可以直接使用。

  • -g 表示全局安装。它也可以写在nodemon后面。即npm install nodemon -g

使用nodemon

等待安装成功之后,使用方法也非常简单:在命令中,使用nodemon来代替node

例如,原来是:

node server.js  

现在是:

// 改成 nodemon server.js
nodemon server.js

它的好处在于会自动监听server.js这个文件的变化,如果变化了,就会重新自动再去运行。相当于是:

while(server.js 变化了){
  node server.js
}

说明:

  • 它是一个第三方的包(其它程序员写的工具)

  • 之前的node server.js还是可以用的。

注意:出现系统禁止执行 nodemon.ps1文件的解决办法

image-20210604095140121.png 解决办法:

https://blog.csdn.net/weixin_38289787/article/details/108352666

1. 以管理员身份运行PowerShell
2. 执行:get-ExecutionPolicy,回复Restricted,表示状态是禁止的
3. 执行:set-ExecutionPolicy RemoteSigned
4. 输入Y

卸载包

当一个包不使用的时候,我们要将其卸载

卸载包分为两种:本地包和全局包的卸载

  • 本地包卸载:

      1. 进入到你想要卸载的包所在的文件夹,到package.json这一层即可
      2. 打开cmd小黑窗
      3. 在小黑窗中执行 npm uninstall 包名
  • 全局包的卸载:

    • 1.在任意地方打开小黑窗
      1. 输入 npm uninstall 全局包名 -g

开发依赖和生产依赖(了解)

在本地安装包时,表示我们这个项目要用到这个包,换句话说,我们这个项目要想成功运行,要依赖于这些个包。

但在,具体在项目进行的哪一阶段依赖于这些包呢?也有具体的差异。

理解

举个生活中建房子的场景:

在建房子时,我们依赖:

  • 辅助工具:尺子,水平仪,脚手架
  • 原材料:钢筋,水泥

在住房子时,我们依赖:

  • 原材料:钢筋,水泥

在房子进入到了使用阶段时,我们就不再需要尺子,水平仪,脚手架等这些个辅助工具了。我们买一所房子时,也不应该支付巨型脚手架的费用。 在开发前端项目的过程中也存在类似的问题:我们的开发过程和使用过程是分开的,开发项目时需要用到的包可能在使用项目时就不需要用到了。

假设有这么两个包:

  • gulp-htmlmin。这个工具是用来把html代码进行压缩的(去掉空格,换行等),我们需要在开发时使用它,而项目一旦上线,我们就不再需要它了。,因此将它归类为"开发依赖"。
  • jquery。在开发时参与源码编写,在发布上线的生产环境中也是需要它的。不仅在开发环境编写代码时要依赖它、线上环境也要依赖它,因此将它归类为"生产依赖"。

1562728491126.png

这个差异就表现在,我们在打包项目时,就不需要打包“开发依赖”的包,这样减少成本。

---webpack

操作

这两种依赖关系,在具体操作的过程中,有如下区别

  1. 保存到开发依赖(devDependencies)
npm install 包名 --save-dev
// 或者 npm install 包名 -D

通过这种方式安装的包出会现在package.json文件中的devDependencies字段中。

  1. 保存到生产依赖(dependencies):
npm install 包名
// 或者 npm install 包名
// 或者 npm install 包名 -S

技巧

  • 加了-D : 开发依赖,这就表示这个工具包只在开发项目时候要用,项目开发完成就不需要
  • 不加-D: 生产依赖,这就表示这个工具包在项目做完了之后也要用。

什么包加上-D,什么包不要加?------- 看文档

npm包的问题

如何去下载包

命令:npm i 包名

  1. 在另一个项目中去下载包

  2. 为了提升下载速度,我们会切换镜像到taobao。

虽然我们上传是传到npm官网,但是,它会自动同步(例如:每隔15分钟就会通过其它镜像最新的包的信息)给其它的镜像 --- taobao, cnpm.....

require的加载机制

在我们使用一个模块时,我们会使用require命令来加载这个模块。以加载一个自定义模块为例,require(文件名)的效果是:

  1. 执行这个文件中的代码
  2. 把这个文件中的module.exports对象中的内容返回出来。

以如下代码为例:

// moudule1.js
var a = 1;
var b = 2;
console.log(a+b);
var c = a+b;
module.exports = {
	data: c
}

在index.js中使用模块

// index.js
const obj = require('./moudule1.js');
console.log(obj);

//这里的obj对象就是moudule1.js中的module.exports对象

require加载规则:

  • require 优先加载缓存中的模块。同一个模块第一次require之后,就会缓存一份,下一次require时就直接从缓存中去取。

  • 如果是加载核心模块,直接从内存中加载,并缓存

    • 加载核心模块的格式是 const xxx = require("模块名") 。不能写相对路径!
  • 如果是相对路径,则根据路径加载自定义模块,并缓存

    • require('./main')为例( 省略扩展名的情况)
    • 先加载 main.js,如果没有再加载 main.json,如果没有再加载 main.node(c/c++编写的模块),找不到就报错。
  • 如果不是自定义模块,也不是核心模块,则加载第三方模块

    require('XXX')为例:

    • node 会去本级 node_modules 目录下的xxx文件夹中找xxx.js ----> xxx.json ----> xxx.node(找到一个即返回),找到并缓存。
    • 如果找不到,node 则取上一级目录中的node_modules下找 , node_modules/xxx 目录,规则同上
    • 如果一直找到代码文件的文件系统的根目录还找不到,则报错:模块没有找到。

在module.paths命令中可以看到搜索路径中包含node_modules这个文件夹

附:npm 常用命令

  • 查看

    npm -v  			    // 查看npm 版本
    where node   			// 查看node的安装目录
    where npm   			// 查看npm的安装目录
    npm root -g 			// 查看全局包的安装目录
    npm list -g --depth 0   // 查看全局安装过的包
    
  • 升级 npm

    npm install npm --global  // 简写成 -g
    npm install npm -g
    
  • 初始化 package.json

    npm init -y // 或者是npm init --yes
    
  • 安装第三方包

    // 安装当前目录下package.json中列出的所有的包
    // 如果之前安装了包,又在package.json中手动删除依赖
    // 它相当是删除包
    npm install
    
    // 全局安装
    npm install 包名 -g  // npm install -g 包名 
    
    // 本地安装,没有指定版本,默认安装最新的版本
    npm install 包名
    
    // 一次安装多个包,空格隔开
    npm install 包名1 包名2 包名3
    
    // 安装指定版本的包
    npm install 包名@版本号
    
    // 简写。把install简写成 i
    npm i 包名
    
  • 删除已安装的包

    npm uninstall 本地安装的包名
    npm uninstall 全局安装的包名 -g
    
    uninstall  可简写为  un
    
  • 设置npm的register

    如果你不想用 nrm ,下面这个原生的命令也可以切换镜像源(从哪里下载包)。

    npm config set registry https://registry.npm.taobao.org
    
    ## 所有npm i 包 都会从taobao的镜像去下载。
    ## 配置后可通过下面方式来验证是否成功
    npm config get registry 
    

参考