前端|浅探NPM

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 前端|浅探NPM

包管理器

看了一下NPM,写点口水话。

如果使用过Gradle,一定对下面的配置很容易理解:

//用于构建项目的插件
apply plugin: 'java'
apply plugin: 'spring-boot'
apply plugin: 'idea'
apply plugin: 'war'
buildscript {
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/plugins-release" }
    }
    //指定gradle插件的版本
    dependencies {
      classpath('io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE')
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.2.RELEASE")
    }
}
//构建脚本中所依赖库在jcenter仓库下载
repositories {
    jcenter()
}
//指定当前项目的依赖
dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile('mysql:mysql-connector-java:5.1.38')
    compile('commons-fileupload:commons-fileupload:1.3.1')
    compile('com.alibaba:fastjson:1.2.7')
    compile("org.springframework.boot:spring-boot-starter-velocity")
}
tasks.withType(JavaCompile) {
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'
}

如果使用过Maven,一定对下面的配置很容易理解:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>FruitShop</groupId>
  <artifactId>FruitShop</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>FruitShop Maven Webapp</name>
  <url>http://maven.apache.org</url>
    <properties>
        <shiro.version>1.2.5</shiro.version>
        <spring.version>4.2.5.RELEASE</spring.version>
    </properties>
    <dependencies>
        <!-- Logging API + implementation: -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>
        <!--mybatis的包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!--数据库连接的包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
    </dependencies>
  <build>
    <finalName>FruitShop</finalName>
      <plugins>
          <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <configuration>
                  <source>1.6</source>
                  <target>1.6</target>
              </configuration>
          </plugin>
      </plugins>
      <!--idea能够编译非resources中的xml文件-->
      <resources>
          <resource>
              <directory>src/main/java</directory>
              <includes>
                  <include>**/*.xml</include>
              </includes>
          </resource>
      </resources>
  </build>
</project>

Gradle和Maven都是Java Web项目的构建工具,当然还有Ant,Gradle还作为Android项目的官方构建工具。

所以如果熟悉Gradle和Maven,那么对NPM就非常容易理解。

NPM前世今生

NPM(Node Package Manager)

  • 作为NodeJs的包管理器,伴随着Node的诞生而诞生,内置于Node中,当安装好Node之后,NPM也就对应安装好了。但是具体在哪个版本中内置的我没有找到(请大佬告诉我)。下面是NPM的最初版本:

Yarn与CNPM

  • Yarn可以理解为NPM的远房表亲,同样是包管理器(但我就是不用你)。
  • CNPM是淘宝对NPM做的国内镜像,主要为了解决国内开发者使用NPM下载依赖中超时等问题。但是使用CNPM下载安装各种依赖包时有可能会出现莫名其妙的错误。

NPM项目

初始化项目,命令行输入:npm init,然后一顿回车

D:\dev>cd npm-test
D:\dev\npm-test>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (npm-test)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to D:\dev\npm-test\package.json:
{
  "name": "npm-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
Is this ok? (yes)
D:\dev\npm-test>

此时已经初始化好了项目,并自动生成了package.json文件

{
     "name": "npm-test",    //项目名
     "version": "1.0.0",    //版本号
     "description": "",     //项目描述
     "main": "index.js",    //入口文件
     "scripts": {           //定义脚本命令
       "test": "echo \"Error: no test specified\" && exit 1"
     },
     "author": "",          //作者
     "license": "ISC"       //开放源代码许可证 ISC
}

其中scripts的配置里面有一个test字段,当我们在命令行输入npm run test会输出echo "Error: no test specified" && exit 1

D:\dev\npm-test>npm run test
> npm-test@1.0.0 test D:\dev\npm-test
> echo "Error: no test specified" && exit 1
"Error: no test specified"
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! npm-test@1.0.0 test: `echo "Error: no test specified" && exit 1`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the npm-test@1.0.0 test script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?
npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\wxqdoit\AppData\Roaming\npm-cache\_logs\2019-02-26T06_46_24_718Z-debug.log
D:\dev\npm-test>

scripts下改成:"build": "node index.js",再在项目目录下新建index.js,并编写如下代码

function closure(){
    var a = 1;
    return function(){
        console.log(a)
    }
}
//内部函数引用外部函数的变量,并返回自身,なに、大名鼎鼎的闭包?
closure()()

在命令行输入npm run build,记住是npm run +自定义命令

D:\dev\npm-test>npm run build
> npm-test@1.0.0 build D:\dev\npm-test
> node index.js
1
D:\dev\npm-test>

姿势摆好,准备进入正题了

安装依赖

  • 安装express:<font color=orange>npm install express</font>

很快就安装好了,打开package.json文件发现里面多了如下依赖,同时也自动生成了node_modules文件夹。很多资料说npm install xxx只是安装到node_modules目录中不会添加package.json到中, 而 npm install xxx --save会添加到package.json中,但是我每次执行npm install xxx都修改了package.json

"dependencies": {
    "express": "^4.16.4"
  }

下面我们执行npm install express --save-dev,安装完成后查看package.json, 发现将express转移到了devDependencies里面

"dependencies": {},
  "devDependencies": {
    "express": "^4.16.4"
  }

所以总结如下

  • npm install xxx 在我的环境下等同于npm install xxx --save会安装到dependencies里面
  • npm install xxx --save 项目实际上线后需要依赖的包请使用这条命令安装
  • npm install xxx --save-dev 项目实际上线后【不】需要依赖的包请使用这条命令安装
  • dependencies代表生成环境
  • devDependencies代表开发环境

现在将express转到dependencies里面:npm install express --save

试了几次之后发现npm install express --save根本没有起作用, 所以我npm uninstall expressnpm install express --save

NPM模块安装机制

  • 发出npm install命令
  • 检测package.json依赖
  • 查询node_modules目录之中是否已经存在指定模块
  • npm 向 registry 查询模块压缩包的网址
  • 下载压缩包,存放在根目录下的.npm目录里
  • 解压压缩包到当前项目的node_modules目录
  • 若存在,不再重新安装
  • 若不存在

NPM有哪些命令呢,命令行输入npm,简写命令在Node安装目录\node_modules\npm\lib\config\cmd-list.js可以看到

D:\dev\npm-test>npm
Usage: npm <command>
where <command> is one of:
    access, adduser, bin, bugs, c, cache, completion, config,
    ddp, dedupe, deprecate, dist-tag, docs, doctor, edit,
    explore, get, help, help-search, i, init, install,
    install-test, it, link, list, ln, login, logout, ls,
    outdated, owner, pack, ping, prefix, profile, prune,
    publish, rb, rebuild, repo, restart, root, run, run-script,
    s, se, search, set, shrinkwrap, star, stars, start, stop, t,
    team, test, token, tst, un, uninstall, unpublish, unstar,
    up, update, v, version, view, whoami

打开你的Node安装目录,仔细分析发现NPM其实就是一个Node应用。那我们的npm install究竟干了什么呢,当你安装完Node,就在系统中注册了npm命令。打开Node安装目录\node_modules\npm\bin,有npm.cmd文件与npm-cli.js文件 npm.cmd入口

:: Created by npm, please don't edit manually.
//关闭回显
@ECHO OFF
//本地化
SETLOCAL
//%~dp0代表当前位置
SET "NODE_EXE=%~dp0\node.exe"
IF NOT EXIST "%NODE_EXE%" (
  SET "NODE_EXE=node"
)
//'CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g'这一行在npm.cmd起的作用是
//如果能运行这两个命令并且得到结果的话将NPM_PREFIX_NPM_CLI_JS的值设置
//为"\node_modules\npm\bin\npm-cli.js"
SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js"
FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO (
  SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js"
)
IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" (
  SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%"
)
"%NODE_EXE%" "%NPM_CLI_JS%" %*
//现在实际启动npm并运行命令
  //这是如何以编程方式使用npm:
  conf._exit = true
  npm.load(conf, function (er) {
    if (er) return errorHandler(er)
    npm.commands[npm.command](npm.argv, function (err) {
      // https://www.youtube.com/watch?v=7nfPu8qTiQU
      if (!err && npm.config.get('ham-it-up') && !npm.config.get('json') && !npm.config.get('parseable') && npm.command !== 'completion') {
        output('\n 🎵 I Have the Honour to Be Your Obedient Servant,🎵 ~ npm 📜🖋\n')
      }
      errorHandler.apply(this, arguments)
    })
  })

打开Node安装目录\node_modules\npm\lib,找到install.js文件 (细心的你可能已经发现了基本上每一个js文件对应了上述的一个命令),用编辑器打开install.js

'use strict'
// npm install <pkg> <pkg> <pkg>
//
// See doc/cli/npm-install.md for more description
//
// Managing contexts...
// there's a lot of state associated with an "install" operation, including
// packages that are already installed, parent packages, current shrinkwrap, and
// so on. We maintain this state in a "context" object that gets passed around.
// every time we dive into a deeper node_modules folder, the "family" list that
// gets passed along uses the previous "family" list as its __proto__.  Any
// "resolved precise dependency" things that aren't already on this object get
// added, and then that's passed to the next generation of installation.
module.exports = install
module.exports.Installer = Installer
var usage = require('./utils/usage')
install.usage = usage(
  'install',
  '\nnpm install (with no args, in package dir)' +
  '\nnpm install [<@scope>/]<pkg>' +
  '\nnpm install [<@scope>/]<pkg>@<tag>' +
  '\nnpm install [<@scope>/]<pkg>@<version>' +
  '\nnpm install [<@scope>/]<pkg>@<version range>' +
  '\nnpm install <folder>' +
  '\nnpm install <tarball file>' +
  '\nnpm install <tarball url>' +
  '\nnpm install <git:// url>' +
  '\nnpm install <github username>/<github project>',
  '[--save-prod|--save-dev|--save-optional] [--save-exact] [--no-save]'
)
//175行定义install方法,这个方法进入核心的install逻辑
function install (where, args, cb) {
  if (!cb) {
    cb = args
    args = where
    where = null
  }
  var globalTop = path.resolve(npm.globalDir, '..')
  if (!where) {
    where = npm.config.get('global')
          ? globalTop
          : npm.prefix
  }
  validate('SAF', [where, args, cb])
  // the /path/to/node_modules/..
  var dryrun = !!npm.config.get('dry-run')
  if (npm.config.get('dev')) {
    log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--only=dev` instead.')
  }
  if (where === globalTop && !args.length) {
    args = ['.']
  }
  args = args.filter(function (a) {
    return path.resolve(a) !== npm.prefix
  })
  new Installer(where, dryrun, args).run(cb)
}
//205行定义了Installer类
function Installer (where, dryrun, args, opts) {}
//358行 跟踪器创建
Installer.prototype.newTracker = function (tracker, name, size) {
  validate('OS', [tracker, name])
  if (size) validate('N', [size])
  this.progress[name] = tracker.newGroup(name, size)
  return function (next) {
    process.emit('time', 'stage:' + name)
    next()
  }
}
Installer.prototype.finishTracker = function (name, cb) {
  validate('SF', arguments)
  process.emit('timeEnd', 'stage:' + name)
  cb()
}
Installer.prototype.loadCurrentTree = function (cb) {
  validate('F', arguments)
  log.silly('install', 'loadCurrentTree')
  var todo = []
  if (this.global) {
    todo.push([this, this.readGlobalPackageData])
  } else {
    todo.push([this, this.readLocalPackageData])
  }
  todo.push([this, this.normalizeCurrentTree])
  chain(todo, cb)
}
//387行 创建node
var createNode = require('./install/node.js').create

下面是官方介绍

This command installs a package, and any packages that it depends on. If the package has a package-lock or shrinkwrap file, the installation of dependencies will be driven by that, with an npm-shrinkwrap.json taking precedence if both files exist. See package-lock.json(5) and npm-shrinkwrap(1).

A package is:

  • a) a folder containing a program described by a package.json(5) file
  • b) a gzipped tarball containing (a)
  • c) a url that resolves to (b)
  • d) a <name>@<version> that is published on the registry (see npm-registry(7)) with (c)
  • e) a <name>@<tag> (see npm-dist-tag(1)) that points to (d)
  • f) a <name> that has a "latest" tag satisfying (e)
  • g) a <git remote url> that resolves to (a)

总结下来就是:

  • 执行工程自身
  • 确定首层依赖模块
  • 根据模块信息递归网络获取模块
  • 模块扁平化,解决重复冗余问题
  • 安装模块,更新node_modules
  • 自身生命周期,生成或更新package.json

参考文章

Maven和Gradle对比

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
8月前
|
前端开发 JavaScript API
前端 npm anywhere 与 npm now 网页随时真机测试
前端 npm anywhere 与 npm now 网页随时真机测试
146 0
|
7月前
|
前端开发
windows10 安装node npm 等前端环境 并配置国内源
windows10 安装node npm 等前端环境 并配置国内源
383 3
|
3月前
|
缓存 前端开发 JavaScript
前端架构思考:代码复用带来的隐形耦合,可能让大模型造轮子是更好的选择-从 CDN 依赖包被删导致个站打不开到数年前因11 行代码导致上千项目崩溃谈谈npm黑洞 - 统计下你的项目有多少个依赖吧!
最近,我的个人网站因免费CDN上的Vue.js包路径变更导致无法访问,引发了我对前端依赖管理的深刻反思。文章探讨了NPM依赖陷阱、开源库所有权与维护压力、NPM生态问题,并提出减少不必要的依赖、重视模块设计等建议,以提升前端项目的稳定性和可控性。通过“left_pad”事件及个人经历,强调了依赖管理的重要性和让大模型代替人造轮子的潜在收益
|
3月前
|
缓存 JavaScript 前端开发
拿下奇怪的前端报错(三):npm install卡住了一个钟- 从原理搞定安装的全链路问题
本文详细分析了 `npm install` 过程中可能出现的卡顿问题及解决方法,包括网络问题、Node.js 版本不兼容、缓存问题、权限问题、包冲突、过时的 npm 版本、系统资源不足和脚本问题等,并提供了相应的解决策略。同时,还介绍了开启全部日志、使用替代工具和使用 Docker 提供 Node 环境等其他处理方法。
2335 0
|
3月前
|
前端开发 JavaScript 开发工具
从零开始:构建、打包并上传个人前端组件库至私有npm仓库的完整指南
从零开始:构建、打包并上传个人前端组件库至私有npm仓库的完整指南
601 0
|
3月前
|
资源调度 前端开发 安全
前端实战:基于Verdaccio搭建私有npm仓库,轻松上传与下载自定义npm插件包
前端实战:基于Verdaccio搭建私有npm仓库,轻松上传与下载自定义npm插件包
197 0
|
5月前
|
缓存 前端开发 Linux
哇塞!NPM 缓存竟成开发拦路虎?快来掌握清空秘籍,开启前端开发逆袭之旅!
【8月更文挑战第20天】NPM是前端开发中管理依赖的关键工具。有时需清空其缓存以解决版本不一致或包损坏等问题,确保使用最新依赖。可通过命令`npm cache clean --force`强制清空全部缓存,或手动删除各系统下的缓存文件夹。注意清空缓存可能延长后续安装时间,建议事先备份依赖或确保可重新安装。正确管理缓存有助于提升开发效率。
202 1
|
7月前
|
存储 缓存 资源调度
你真的知道 NPM、Yarn 与 PNPM 这三个前端包管理器之间的区别吗?
【6月更文挑战第9天】NPM、Yarn和PNPM是主流前端包管理器,各有特色。NPM生态庞大,易用但速度慢;Yarn速度快,依赖管理稳定;PNPM性能优异,节省磁盘空间。Yarn和PNPM在速度和确定性上胜出,NPM因广泛使用和丰富资源领先。开发者可根据项目需求和喜好选择,三者共同推动前端开发进步。
166 8
|
8月前
|
前端开发 JavaScript 开发者
探秘npm:解锁前端生态的魔法工具
探秘npm:解锁前端生态的魔法工具
124 0
|
7月前
|
前端开发 JavaScript 开发者
NPM简介与使用指南:打造前端开发的利器
NPM简介与使用指南:打造前端开发的利器
137 0

热门文章

最新文章

推荐镜像

更多