Node.js模块化开发 | 青训营笔记

151 阅读13分钟

这是我参与「第四届青训营 」笔记创作活动的的第13天

Node.js模块化开发

1.1 JavaScript开发弊端

JavaScript在使用时存在两大问题,文件依赖和命名冲突。

1.3 软件中的模块化开发

一个功能就是一个模块,多个模块可以组成完整应用,抽离一个模块不会影响其他功能的运行。

1.4 Node.js中模块化开发规范

Nodejs规定一个JavaScript文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到 模块内部可以使用 exports对象进行成员导出,使用 require方法导入其他模块。

A模块(a.js)
加法函数
减法函数
乘法函数
除法函数 exports.加法 = 加法 exports.减法 = 减法

导出:把要导出的变量、函数 变成 exports 对象的属性值

B模块(b.js) A模块 = require(A模块) A模块.加法() A模块.减法()

导入:使用 require 方法,返回值为导入模块的 exports对象

1.5 模块成员导出

    // a.js
    //在模块内部定义变量
    let version = 1.0 ;
    // 在模块内部定义方法
    const sayHi = name =>'您好,$ {name } ';
    // 向模块外部导出数据
    exports.version = version ;
    exports.sayHi = sayHi ;

1.6 模块成员的导入

    // b.js
    // 在b.js模块中导入模块a
    let a = require (' ./b.js ');
    // 输出b模块中的version变量"
    console.log (a.version) ;
    //调用b模块中的sayHi方法并输出其返回值
    console.log(a.sayHi( '黑马讲师') );
  • 导入模块时后缀(.js)可以省略

1.7 模块成员导出的另一种方式

    module.exports.version = version ;
    module.exports.sayHi = sayHi ;
  • exports 是 module.exports 的别名(地址引用关系 -- 默认情况下指向同一个对象/同一块内存空间),导出对象最终以module.exports为准(如果 exports和module.exports 的地址引用发生了变化)

1.8 模块导出两种方式的联系与区别

  1. 默认情况

exports 和 module.exports 都指向同一个对象{ version: 1.0,sayHi: sayHi }

此时两种写法等价

    exports.version = version;
    module.exports.version = version ;
  1. 这时给 module.exports 重新赋值 则指向新对象{ name:‘lisi’,age: 50 }
    module.exports = {
        name : " zhangsan' 
    }

此时 exports 和 module.exports 指向不同的对象,以 module.exports 为准

系统模块

3.1 什么是系统模块

Node运行环境提供的API. 因为这些API都是以模块化的方式进行开发的,所以我们又称Node运行环境提供的API为系统模块

3.2 系统模块fs 文件操作

f: file文件,s: system系统,文件操作系统。

  • 同步读文件 除了标准的异步读取模式外,fs也提供相应的同步读取函数。同步读取的函数和异步函数相比,多了一个Sync后缀,并且不接收回调函数,函数直接返回结果。
    const fs = require('fs')
    // 1. 读取文件内容
    fs.readFile('文件路径/文件名称' ,['文件编码' (可选] , callback);
    // callback:回调函数,因为硬盘读取文件需要时间,不能通过API的返回值直接拿到文件的读取结果,当文件内容读取完成后,硬盘会通知API文件读取完成,可以调用回调函数,将读取结果通过函数参数传递
    // 第一个参数都是err,所以称nodejs的回调函数为错误优先回调函数

    // 读取文件语法示例
    // 读取上一级css目录下中的base.css
    fs.readFile ( ' ../css/base.css ', 'utf-8' (err,doc) => {
        // 如果文件读取发生错误参数err的值为错误对象,否则err的值为nulll 
        // doc参数为文件内容
        if (err == null) {
            // 在控制台中输出文件内容
            console.log (doc) ;
        }
    });
// 2. 写入文件内容
fs.writerFile ( '文件路径/文件名称''数据', callback) ;

const content = '<h3>正在使用fs.writeFile写入文件内容</h3> ';
fs.writeFile ( ' ../index.html', content,err => {
    if (err != null){
        console.log (err) ;
        return ;
    }
    console.log('文件写入成功');
));

3.3 系统模块path路径操作

针对硬盘的路径进行操作

路径拼接API

  • 为什么要进行路径拼接? 不同操作系统的路径分隔符不统一(路径分隔符:文件夹之间的分隔符) /public/uploads/avatar Windows上是/ Linux 上是/

路径拼接语法

path.join ('路径','路径',...)
// 导入path模块
const path = require( 'path' ) ;
// 路径拼接   因为该操作不属于耗时操作,可以通过返回值接收拼接结果
let finialPath = path.join( 'itcast' , 'a', 'b', 'c.css ' ) ;
// 输出结果 itcast\a\b\c.css
console.log(finialPath);

3.5 相对路径VS绝对路径

  • 大多数情况下使用绝对路径,(除非相对路径是相当于当前文件本身的)因为相对路径有时候相对的是命令行工具的当前工作目录
  • 在读取文件或者设置文件路径时都会选择绝对路径、
  • 使用__dirname(两个下划线)获取当前文件所在的绝对路径
    const fs = require('fs')
    const path = require('path')

    console.log(__dirname)
    console.log(path.join(__dirname,'a.js'))

    fs.readFile(path.join(__dirname,'a.js'),'utf-8',(err,doc) => {
        console.log(err)
        console.log(doc)
    })
  • require:相对路径 相对的就是当前文件,所以可以写相对路径

第三方模块

4.1 什么是第三方模块

别人写好的、具有特定功能的、我们能直接使用的模块即第三方模块,由于第三方模块通常都是由多个文件组成并且被放置在一个文件夹中,所以又名包。

第三方模块有两种存在形式: 以js文件的形式存在,提供实现项目具体功能的API接口。 以命令行工具形式存在,辅助项目开发

4.2 获取第三方模块

npmjs.com:第三方模块的存储和分发仓库

命令行工具:npm (node package manager) : node的第三方模块管理工具

下载: npm install 模块名称 默认下载到命令行工具的当前工作目录

卸载: npm unintall package 模块名称

全局安装与本地安装

  • 本地安装:将模块下载到当前的项目当中,供当前的项目使用

  • 全局安装:将模块下载到公共的目录当中,所有项目都可以使用

    命令行工具:全局安装 库文件:本地安装

4.3 第三方模块nodemon

nodemon是一个命令行工具,用以辅助项目开发。 在Node.js中,每次修改文件都要在命令行工具中重新执行该文件,非常繁琐。

监控文件保存操作,当文件发生保存操作时,重新执行该文件

使用步骤

  1. 使用npm install nodemon -g 下载它 (-g 全局安装)
  2. 在命令行工具中用 nodemon 命令替代 node 命令执行文件

4.4 第三方模块nrm

nrm ( npm registry manager): npm下载地址切换工具 npm默认的下载地址在国外,国内下载速度慢

使用步骤

  1. 使用npm install nrm -g下载它
  2. 查询可用下载地址列表 nrm ls
  3. 切换npm下载地址 nrm use下载地址名称
// 输入 nrm ls  报错
[TypeError: The "path" argument must be of type string. Received undefined
  at new NodeError (node:internal/errors:370:5)
  at validateString (node:internal/validators:119:11)
  at Object.join (node:path:429:7)
  at Object.<anonymous> (C:\Users\wjx\AppData\Roaming\npm\node_modules\nrm\cli.js:17:20)
  at Module._compile (node:internal/modules/cjs/loader:1095:14)
  at Object.Module._extensions..js (node:internal/modules/cjs/loader:1124:10)
  at Module.load (node:internal/modules/cjs/loader:975:32)
  at Function.Module._load (node:internal/modules/cjs/loader:816:12)
  at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)
  at node:internal/main/run_main_module:17:47
] {
  code: 'ERR_INVALID_ARG_TYPE'
}

解决方法:https://blog.csdn.net/S_aitama/article/details/113706339?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

4.5 第三方模块Gulp

基于node平台开发的前端构建工具 将机械化操作编写成任务,想要执行机械化操作时执行一个命令行命令任务就能自动执行了(例如合并压缩css、js) 用机器代替手工,提高开发效率。

Gulp能做什么

项目上线,HTML、CSS、JS文件压缩合并
语法转换(es6、less ...)
公共文件抽离(只需要修改抽取出来的文件)
修改文件浏览器自动刷新

Gulp使用

1.使用npm install gulp下载gulp库文件
2.在项目根目录下建立gulpfile.js文件
3.重构项目的文件夹结构src目录放置源代码文件dist目录放置构建后文件
4.在gulpfile.js文件中编写任务.
5.在命令行工具中执行gulp任务(安装命令行工具:npm install gulp-cli -g)

Gulp中提供的方法

gulp.src():获取任务要处理的文件
gulp.dest():输出文件
gulp.task():建立gulp任务
gulp.watch():监控文件的变化
    // 复制操作
    const gulp = require ('gulp');
    // 使用gulp.task()方法建立任务
    gulp.task ('first', () => {// 当前要建立的任务的名字,callback
        // 获取要处理的文件
        gulp.src ( './src/css/base.css' )
        // 将处理后的文件输出到dist目录
        .pipe(gulp.dest ( './dist/css' ));  // 将要处理的代码放到pipe里面,会自动执行里面的代码
    });
        

使用node执行的是整个文件 执行first任务:gulp first(任务名) 会自动在当前的项目根目录里找gulp-file.js文件,在文件当中找到first任务,执行任务的回调函数

gulp.task('first', () => {
    console.log('第一个gulp任务执行')
    // 使用获取要处理的文件
    gulp.src('./src/css/base.css')
    // 
    .pipe(gulp.dest('dist/css'))
})

// 再powershell里执行
// PS D:\文件\A-前端\Node.js\2.模块化开发\gulp-demo> gulp first
// [20:44:30] Using gulpfile D:\文件\A-前端\Node.js\2.模块化开发\gulp-demo\gulpfile.js
// [20:44:30] Starting 'first'...
// 第一个gulp任务执行
// [20:44:30] The following tasks did not complete: first
// [20:44:30] Did you forget to signal async completion?

// 问题原因:gulp 4.0 的任务函数中,如果任务是同步的,需要使用 done 回调。这样做是为了让 gulp 知道你的任务何时完成。

4.9 Gulp插件

gulp-htmlmin : html文件压缩
gulp-csso : 压缩css
gulp-babel : JavaScript语法转化
gulp-less : less语法转化
gulp-uglify : 压缩混淆JavaScript
gulp-file-include 公共文件包含
browsersync 浏览器实时同步

使用

  1. 下载插件
  2. 在gulpfile.js 里引入插件
  3. 调用插件
    //  html任务
    // 1. html文件中代码的压缩操作
    // 2. 抽取html文件中的公共代码
    // 任务执行有顺序,先抽取公共代码,再压缩,最后输出

    gulp.task('htmlmin',(done) => {
        gulp.src('./src/*.html')    // 获取所有的html文件
        // 处理文件
            .pipe(fileinclude())
            // 压缩html文件中的代码
            .pipe(htmlmin({ collapseWhitespace: true }))
            .pipe(gulp.dest('./dist'))
        done()
    })

抽取代码:将头部信息剪切到header.html文件中 在html里引入头部:@@include('./common/header.html')

    // css任务
    // 1. less语法转换
    // 2. css代码压缩
    gulp.task('cssmin',(done) => {
        // 选择css目录下的所有less文件以及css文件
        gulp.src(['./src/css/*.less','./src/css/*.css'])   // 同时选择:用数组,可写多个路径
        // 将less语法转换为css语法
        .pipe(less())
        // 将css代码进行压缩
        .pipe(csso())
        // 将处理的结果进行输出
        .pipe(gulp.dest('dist/css'))
        done()
    })

    // js任务
    // 1. es6代码转换
    // 2. 代码压缩
    gulp.task('jsmin',(done) => {
        gulp.src('./src/js/*.js')
        .pipe(babel({
            // 它可以判断当前代码的运行环境,将代码转换为当前运行环境所支持的代码
            presets: ['@babel/env']
        }))
        .pipe(uglify())
        .pipe(gulp.dest('dist/js'))
        done()
    })


    // 复制文件夹
    gulp.task('copy',(done) =>{
        gulp.src('./src/images/*')
        .pipe(gulp.dest('./dist/images'))

        gulp.src('./src/lib/*')
        .pipe(gulp.dest('./dist/lib'))
        done()
    })


    // 构建任务
    gulp.task('default',gulp.series('htmlmin','cssmin','jsmin','copy'))
    // ['htmlmin','cssmin','jsmin','copy']是gulp3的方式
    // 在gulp4,你需要使用gulp.series和gulp.parallel,因为gulp任务现在只有两个参数。
    // gulp.series:按照顺序执行
    // gulp.paralle:可以并行计算
    // https://www.jianshu.com/p/c30ff8592421

    // 如果任务名为default,在命令行执行任务时可以只写gulp,它会自动去寻找一个叫default的任务并执行

npm下载多个插件:将插件名字用空格分隔开

package.json文件

6.1 node modules文件夹的问题

1.文件夹以及文件过多过碎,当我们将项目整体拷贝给别人的时候,,传输速度会很慢很慢. 2.复杂的模块依赖关系需要被记录,确保模块的版本和当前保持一致,否则会导致当前项目运行报错

在传输项目的过程中不需要传递node modules文件夹,npm 提供了项目描述文件,在项目描述文件:package.json文件里记录了当前项目依赖了那些第三方模块,别人会根据package.json文件中所记录的依赖项去下载第三方模块,这样项目就可以在别人的电脑上运行了

6.2 package.json文件的作用 -- 解决问题1

项目描述文件,记录了当前项目信息,例如项目名称、版本、作者、github地址、当前项目依赖了哪些第三方模块等。

  • 使用 npm init -y命令生成。(-y:不填写任何信息,全部使用默认值
{
    "name": "description", // 项目名称
    "version": "1.0.0",   // 项目版本
    "description": "",    // 项目描述:当前项目的功能及作用
    "main": "index.js",   // 项目的主入口文件 模块化开发
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },        // 存储命令的别名。当我们要执行的命令比较长的时候,每次在命令行工具中输入比较麻烦,给命令起一个别名,每次要执行命令的时候使用别名就可以
    "keywords": [],   // 关键字,允许用关键字的方式来描述当前的项目
    "author": "",     // 项目的作者
    "license": "ISC"  // 项目遵循的协议 默认ISC:开放源代码的协议
    "dependencies": {
        "mime": "^2.5.2"
    }   // 记录当前项目所依赖的第三方模块 模块名及版本
}

传输项目时,不用传node modules文件夹,直接在命令行工具中输入npm install 命令,npm会自动在项目的根目录中找到 package.json文件,根据dependencies,下载其中记录的第三方模块

别名的使用 在 package.json文件 中的 scripts 里添加别名 格式 --> 别名 : 完整命令 执行时在命令行工具中输入 ---> npm run 别名

6.3项目依赖

  • 在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖
  • 使用npm install包名命令下载的文件会默认被添加到package.json文件的dependencies字段中

6.4开发依赖

在项目的开发阶段需要依赖,线上运营阶段不需要依赖的第三方包,称为开发依赖 使用npm install包名--save-dev命令将包添加到package.json文件的devDependencies字段中

npm install 会下载所有的依赖 npm install --production 下载项目依赖

6.5 package-lock.json文件的作用 -- 解决问题2

记录模块之间的依赖关系

  • 锁定包的版本,确保再次下载时不会因为包版本不同而产生问题
  • 加快下载速度,因为该文件中已经记录了项目所依赖第三方包的树状结构和包的下载地址,重新安装时只需下载即可,不需要做额外的工作

Node.js中模块的加载机制

5.1模块查找规则-当模块拥有路径但没有后缀时

require ( './find.js' ); require( './find' ) ;

  1. require 方法根据模块路径查找模块,如果是完整路径,直接引入模块。
  2. 如果模块后缀省略,先找同名 JS文件 再找 同名JS文件夹
  3. 如果找到了 同名文件夹,找 文件夹 中的 index.js
  4. 如果文件夹中没有 index.js 就会去 当前文件夹中的package.js 文件中查找 main 选项中的入口文件
  5. 如果找指定的入口文件 不存在或者没有指定入口文件 就会报错,模块没有被找到

5.2模块查找规则-当模块没有路径且没有后缀时

require ( 'find' ) ;

  1. Node.js 会假设它是系统模块
  2. Node.js 会去 node_modules 文件夹中
  3. 首先看是否有该名字的 JS文件
  4. 再看是否有该名字的 文件夹
  5. 如果是 文件夹 看里面是否有 index.js
  6. 如果 没有index.js 查看该文件夹中的 package.json 中的 main 选项确定 模块入口文件
  7. 否则找不到 报错