Node.js 折腾日记:开发自用的 npm 包(下)
使用 moranutils
紧接上篇文章,接下来我们再创建一个项目来模拟如何使用开发的 npm 包。
# 创建项目目录
mkdir Mor_Test
# 进入项目目录
cd Mor_Test
# 初始化项目,直接使用默认设置
yarn init -y
# 安装 TypeScript
yarn add typescript --dev
# 初始化 TypeScript 配置文件
npx tsc --init
多的不说,直接上修改后的 tsconfig.json 文件内容:
{
"compilerOptions": {
// 指定 ECMAScript 目标版本,这里是 ES2017
"target": "ES2017",
// 使用最新的 ES 模块标准
"module": "ESNext",
// 允许导入符合 ES6 模块规范的模块,确保与 CommonJS 模块兼容
"esModuleInterop": true,
// 强制文件名的大小写必须一致,防止在大小写敏感的操作系统中出现问题
"forceConsistentCasingInFileNames": true,
// 启用所有严格类型检查选项
"strict": true,
// 跳过检查 '.d.ts' 声明文件
"skipLibCheck": true,
"moduleResolution": "node",
},
"include": [
"src/**/*"
]
}
基本内容都有注释,特意说明一下 moduleResolution
,因为我们会通过软链接(下文会说)的方式使用的 moranutils 包,在 package.json 中没有包记录,设置 moduleResolution
的是告诉解析器需要查找 node_modules 目录。
接着上修改后的 package.json 文件内容:
{
"name": "mor_test",
"version": "1.0.0",
"type": "module",
"license": "MIT",
"devDependencies": {
"typescript": "^5.4.4"
},
"scripts": {
"build": "tsc --outDir dist/"
}
}
这里我删除了默认配置中的 main
,因为这是一个正式项目,并不会作为包对其他项目开放调用,所以不需要这个配置。type
设置同上篇文章讲解的那样,这里不过多介绍。build
中添加了构建的输出目录,这个没有放到 tsconfig.json 文件中,后面会说。
为了保证流程的完整性,这次先放出目录结构,然后再做说明:
.
├── node_modules
├── public
│ └── index.html
├── src
│ └── index.ts
├── package.json
├── tsconfig.json
└── yarn.lock
public 和 src 这里不多说,问就是约定。
接下来我们要在 index.ts 中使用 moranutils 包的函数,打印当前时间字符串。
正常我们要使用包的时候,会使用 npm install [package_name]
或者 yarn add [package_name]
安装,然后使用。
那么对于本地包,其实也可以,如下命令:
yarn add file:D:\Mor_Npm
npm 命令同理,这里就等于是安装了本地包。但是这种安装方式有一个缺点,就是每次修改了 moranutils 包的代码,都需要再次安装更新,偶尔疏忽还会被缓存坑。
这里推荐使用软链接的方式,具体操作步骤如下:
- 在 Mor_Npm 包项目中执行
yarn link
命令 - 接着在 Mor_Test 项目中执行
yarn link moranutils
命令
这样一来就通过软链接安装了本地包,下图:
我们更新 Mor_Test 的 index.ts 文件内容,如下:
// /src/index.ts
import { currentTime } from "moranutils";
console.log(currentTime.getDateTime());
注:如果使用的 VSCode 在使用包的时候出现错误提示,可以重启一下 VSCode,其他编辑器不知道。
再更新 index.html 文件内容,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
Hello World!
<script src="../dist/index.js"></script>
</body>
</html>
这里为什么是 ../dist/index.js
?因为浏览器只能运行 js 文件,编译后的 js 文件在 dist 目录。接着我们使用 yarn build
编译一下,在浏览器中打开 index.html。
浏览器打开后,打开控制台,面对的结果不是输出了日期,是报错:
Uncaught SyntaxError: Cannot use import statement outside a module
必然,为了更好的引出构建的概念,这里故意出错。我们细看 index.js 文件会发现,依然使用的 import { currentTime } from "moranutils";
,如下:
import { currentTime } from "moranutils";
console.log(currentTime.getDateTime());
这里其实完全可以想到,dist 目录下没有任何与 moranutils 相关的代码文件,这个调用完全没有来由。并且 js 文件中也不支持 import 导入的形式,更多的是在 html 页面中使用类似 <script type="module" src="path/to/your/script.js"></script>
的方式导入模块,这可太复杂了。
构建代码
script 标签导入的方式不是本文主题,所以不深入说明。其实做这个我才看明白了,之前导入外部模块的时候,老让我用 <script type="module" src="path/to/your/script.js"></script>
,真的看着就头疼,也是写这篇文章前弄明白了,顺带明白了构建的意义。
常用的构建工具一般就是 Webpack、Rollup、Vite 等,这里使用 Vite。之前使用 Vue 的时候,每次都是使用 Vite 初始化项目,对于我这种浅层玩家,略过了很多细节,就迷糊。
这次反过来了,先把项目创建好了,Vite 对我来说就是一个开发依赖工具,更能加深对构建工具的理解。在 Mor_Test 中安装 Vite,如下:
yarn add vite --dev
这里不是全局安装,并且只是开发依赖。安装完成后,在项目中创建 vite.config.ts 配置文件,编辑如下内容:
import { defineConfig } from "vite";
export default defineConfig({
build: {
// 指定输出目录,默认是 'dist'
outDir: "dist",
// 每次构建时清空输出目录
emptyOutDir: true,
rollupOptions: {
// 构建时候的入口文件,这里就是构建我们的 index.ts 文件
input: {
index: "src/index.ts",
},
// 输出配置
output: {
// 输出文件的模块格式
format: "esm",
// 输出文件不使用 hash,直接以其原始名称命名
entryFileNames: `assets/[name].js`,
},
},
},
});
基本注释我都写的很详细了,其实构建的目的简单理解就是:不管用的是 JavaScript 还是 TypeScript 开发,项目随着开发进度,会有越来越多的 js/ts 文件,同时也会引入外部模块,管理起来很不方便。通过构建,能将这些模块打包成一个或多个 js 文件,同时还能处理代码优化、混淆、解决兼容性问题等,具体的只需要对比构建后的 index.js 代码就能明白了。
我们还需要修改一下 package.json 的脚本内容,如下:
{
"name": "mor_test",
"version": "1.0.0",
"type": "module",
"license": "MIT",
"devDependencies": {
"typescript": "^5.4.4",
"vite": "^5.2.8"
},
"scripts": {
"build": "vite build"
}
}
这里我们修改了 build 中的内容,直接使用 Vite 构建,这也是为什么 tsconfig.json 文件中的配置内容少了一些的原因。
还需要修改一下 index.html 的内容,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
Hello World!
<script src="assets/index.js"></script>
</body>
</html>
这里修改了 script 的 src 内容,因为我们上面 Vite 的设置是将编译后的 js 文件放在 dist/assets 中的,index.html 会在构建的时候从 public 目录复制到 dist 目录(问就是默认约定)。
执行 yarn build
开始构建,构建完成后的最终目录结构如下:
.
├── dist
│ ├── assets
│ │ └── index.js
│ └── index.html
├── node_modules
├── package.json
├── public
│ └── index.html
├── src
│ └── index.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
我们再看看 index.js 的代码,如下:
const g={getDateTime:()=>{const t=new Date,e=t.getFullYear(),n=t.getMonth()+1,o=t.getDate(),r=t.getHours(),a=t.getMinutes(),s=t.getSeconds();return`${e}-${n.toString().padStart(2,"0")}-${o.toString().padStart(2,"0")} ${r.toString().padStart(2,"0")}:${a.toString().padStart(2,"0")}:${s.toString().padStart(2,"0")}`}};console.log(g.getDateTime());
完全不一样了,已经将我们要使用的函数混淆后放到了一起,这就是构建的意义。运行 dist 目录下的 index.html,就能在控制台中看到打印的内容了。
以上,其实通过这个过程,我更多理清的是构建的意义和整体逻辑,非常有价值,也希望对看到本文的你有价值。
整个过程的源码可以通过 https://wqmoran.com/software-download-guide-tips/ 获取,“源码”目录下的“Moran_Study.zip”就是,记得使用 yarn install
命令安装依赖后,创建软链接才能使用。