我喜欢在没有构建系统的情况下编写 Javascript,昨天我第一百万次遇到了一个问题,我需要弄清楚如何在不使用构建系统的情况下在我的代码中导入 Javascript 库,并且花了很长时间才弄清楚如何导入这是因为库的安装说明假设您正在使用构建系统。
幸运的是,此时我已经基本学会了如何应对这种情况,要么成功使用该库,要么认为它太困难并切换到不同的库,所以这里是我希望我几年前必须导入 Javascript 库的指南。
我只会讨论在前端使用 Javacript 库,以及如何在无构建系统设置中使用它们。
在这篇文章中我将讨论:
- 库可能提供的三种主要类型的 Javascript 文件(ES 模块、“经典”全局变量类型和 CommonJS)
- 如何确定 Javascript 库在其构建中包含哪些类型的文件
- 在代码中导入每种类型文件的方法
三种Javascript文件
库可以提供 3 种基本类型的 Javascript 文件:
- 定义全局变量的“经典”类型的文件。这种文件只需
<script src>
即可运行。如果您能获得但并不总是可用,那就太好了 - 一个 ES 模块(可能依赖于其他文件,也可能不依赖于其他文件,我们稍后会讨论)
- 一个“CommonJS”模块。这是针对 Node 的,如果不使用构建系统,你根本无法在浏览器中使用它。
我不确定“经典”类型是否有更好的名称,但我只是将其称为“经典”。还有一种称为“AMD”的类型,但我不确定它在 2024 年的相关性如何。
现在我们已经知道了 3 种类型的文件,接下来我们来谈谈如何确定库实际上提供了哪一种!
在哪里可以找到文件:NPM 构建
每个 Javascript 库都有一个上传到 NPM 的构建版本。你可能会想(就像我最初那样)——朱莉娅!重点是我们没有使用 Node 来构建我们的库!我们为什么要谈论 NPM?
但是,如果您使用来自 CDN 的链接(例如https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js ) ,则您仍在使用 NPM 构建! CDN 上的所有文件最初都来自 NPM。
因此,我有时喜欢npm install
库,即使我根本不打算使用 Node 来构建我的库 – 我只会创建一个新的临时文件夹,在那里npm install
,然后在我需要时将其删除。我完成了。我喜欢能够在我的文件系统上的 NPM 构建中浏览文件,因为这样我就可以 100% 确定我看到了库在其构建中提供的所有内容,并且 CDN 没有隐藏某些内容从我这里。
因此,让我们npm install
一些库,并尝试找出它们在构建中提供的 Javascript 文件类型!
示例库 1:chart.js
首先让我们看看Chart.js ,一个绘图库。
$ cd /tmp/whatever $ npm install chart.js $ cd node_modules/chart.js/dist $ ls *.*js chart.cjs chart.js chart.umd.js helpers.cjs helpers.js
这个库似乎有 3 个基本选项:
选项 1: chart.cjs
。 .cjs
后缀告诉我这是一个CommonJS 文件,用于在 Node 中使用。这意味着如果没有某种构建步骤,就不可能直接在浏览器中使用它。
选项2: chart.js
。 .js
后缀本身并不能告诉我们它是什么类型的文件,但如果我打开它,我会看到import '@kurkle/color';
这是一个直接的标志,表明这是一个 ES 模块 – import ...
语法是 ES 模块语法。
选项 3: chart.umd.js
。 “UMD”代表“通用模块定义”,我认为这意味着您可以将此文件与基本的<script src>
、 CommonJS 或我不理解的称为 AMD 的第三个东西一起使用。
如何使用 UMD 文件
当我使用 Chart.js 时,我选择了选项 3。我只需要将其添加到我的代码中:
<script src="./chart.umd.js"> </script>
然后我可以将库与全局Chart
环境变量一起使用。再简单不过了。我只是将chart.umd.js
复制到我的 Git 存储库中,这样我就不必担心使用 NPM 或 CDN 出现故障等问题。
构建文件并不总是位于dist
目录中
许多库会将其构建放在dist
目录中,但并非总是如此!构建文件的位置在库的package.json
中指定。
例如,这是 Chart.js 的package.json
的摘录。
"jsdelivr": "./dist/chart.umd.js", "unpkg": "./dist/chart.umd.js", "main": "./dist/chart.cjs", "module": "./dist/chart.js",
我认为这意味着如果你想使用 ES 模块( module
),你应该使用dist/chart.js
,但是 jsDelivr 和 unpkg CDN 应该使用./dist/chart.umd.js
。我猜main
是针对 Node 的。
chart.js
的package.json
还显示"type": "module"
,根据本文档,它告诉 Node 默认将文件视为 ES 模块。我认为它并没有具体告诉我们哪些文件是 ES 模块,哪些不是,但它确实告诉我们其中有一个 ES 模块。
示例库 2: @atcute/oauth-browser-client
@atcute/oauth-browser-client
是一个用于在浏览器中使用 OAuth 登录 Bluesky 的库。
让我们看看它在其构建中提供了哪些类型的 Javascript 文件!
$ npm install @atcute/oauth-browser-client $ cd node_modules/@atcute/oauth-browser-client/dist $ ls *js constants.js dpop.js environment.js errors.js index.js resolvers.js
似乎这里唯一合理的根文件是index.js
,它看起来像这样:
export { configureOAuth } from './environment.js'; export * from './errors.js'; export * from './resolvers.js';
这个export
语法意味着它是一个ES 模块。这意味着我们可以在浏览器中使用它,而无需构建步骤!让我们看看如何做到这一点。
如何将 ES 模块与 importmaps 一起使用
使用 ES 模块并不像添加<script src="whatever.js">
那样简单。相反,如果 ES 模块具有依赖项(如@atcute/oauth-browser-client
那样),则步骤如下:
- 在 HTML 中设置导入映射
- 放置导入语句,例如
import { configureOAuth } from '@atcute/oauth-browser-client';
在你的 JS 代码中 - 在 HTML 中包含 JS 代码:
<script type="module" src="YOURSCRIPT.js"></script>
我们需要导入映射的原因是模块内部有导入语句,例如import {something} from @atcute/client
,我们需要告诉浏览器在哪里获取@atcute/client
的代码。
这是我使用的@atcute/oauth-browser-client
的 importmap 的样子:
<script type="importmap"> { "imports": { "nanoid": "./node_modules/nanoid/bin/dist/index.js", "nanoid/non-secure": "./node_modules/nanoid/non-secure/index.js", "nanoid/url-alphabet": "./node_modules/nanoid/url-alphabet/dist/index.js", "@atcute/oauth-browser-client": "./node_modules/@atcute/oauth-browser-client/dist/index.js", "@atcute/client": "./node_modules/@atcute/client/dist/index.js", "@atcute/client/utils/did": "./node_modules/@atcute/client/dist/utils/did.js" } } </script>
让这些导入映射工作起来非常繁琐,我觉得必须有一个工具可以自动生成它们,但我还没有找到。绝对可以编写一个使用esbuild 的图元文件自动生成导入映射的脚本,但我还没有这样做,也许有更好的方法。
我昨天需要设置 importmaps 才能使github.com/jvns/bsky-oauth-example正常工作,因此该存储库中有一些示例代码。
还有人向我推荐了 Simon Willison 的download-esm ,它将下载 ES 模块并重写导入以直接指向 JS 文件,这样您就不需要导入映射。我还没有尝试过,但这似乎是个好主意。
如何在没有导入映射的情况下使用 ES 模块
如果 ES 模块没有依赖项,那就更容易了——你不需要导入映射!你可以:
- 将
<script type="module" src="YOURCODE.js"></script>
放入 HTML 中。type="module"
很重要。 - 将
import {whatever} from "https://example.com/whatever.js"
放入YOURCODE.js
中
替代方案:使用 esbuild
如果您不想使用 importmaps,您也可以使用像esbuild这样的构建系统。我在有关使用 esbuild 的一些注意事项中讨论了如何做到这一点,但这篇博文是关于完全避免构建系统的方法,因此我不会在这里讨论该选项。不过我仍然喜欢 esbuild,并且我认为在这种情况下这是一个不错的选择。
浏览器对导入映射的支持是什么?
CanIUse说导入映射位于“2023 年基线:跨主要浏览器新可用”,所以我的感觉是,到 2024 年,这可能仍然有点太新了?我想我会使用 importmaps 来编写一些有趣的实验代码,我只想像我自己和 12 个人一样使用这些代码,但如果我希望我的代码更广泛可用,我会改用esbuild
。
示例库 3: @atproto/oauth-client-browser
让我们看一下最后一个示例库!这是与@atcute/oauth-browser-client
不同的 Bluesky 身份验证库。
$ npm install @atproto/oauth-client-browser $ cd node_modules/@atproto/oauth-client-browser/dist $ ls *js browser-oauth-client.js browser-oauth-database.js browser-runtime-implementation.js errors.js index.js indexed-db-store.js util.js
同样,这里似乎唯一真正的候选文件是index.js
。但这和之前的示例库是不同的情况!让我们看一下index.js
:
index.js
中有很多这样的东西:
__exportStar(require("@atproto/oauth-client"), exports); __exportStar(require("./browser-oauth-client.js"), exports); __exportStar(require("./errors.js"), exports); var util_js_1 = require("./util.js");
这个require()
语法是 CommonJS 语法,这意味着我们根本无法在浏览器中使用这个文件,我们需要使用某种构建步骤,并且 ESBuild 也不起作用。
另外,在该库的package.json
中,它显示"type": "commonjs"
这是表明它是 CommonJS 的另一种方式。
如何将 CommonJS 模块与esm.sh一起使用
最初我认为如果不学习构建系统就不可能使用 CommonJS 模块,但后来有人 Bluesky 告诉我关于esm.sh !它是一个 CDN,可以将任何内容转换为 ES 模块。
对于@atproto/oauth-client-browser
使用它看起来非常简单,我只需要把它放在我的 HTML 中:
<script type="module" src="script.js"> </script>
然后将其放入script.js
中。
import { BrowserOAuthClient } from "https://esm.sh/@atproto/[email protected]"
看起来只是工作,这很酷!当然,这仍然是使用构建系统 – 只是 esm.sh 代替我运行构建。我对这种方法的主要担忧是,我并不真正相信 CDN 能够永远工作——通常我喜欢将依赖项复制到我的存储库中,这样它们将来就不会因为某种原因消失。我还听说 CDN 存在一些安全问题,这让我感到害怕。
我觉得必须有一种方法可以自己将 CommonJS 模块构建到 ES 模块中,然后自己托管文件,但我还不确定它是什么,如果我学会了,我会更新它。
三类文件总结
以下总结了您可能遇到的三种类型的 JS 文件、如何使用它们的选项以及如何识别它们。
毫无帮助的是, .js
或.min.js
文件扩展名可能是这 3 个选项中的任何一个,因此,如果文件是something.js
您需要做更多的侦探工作来找出您正在处理的内容。
- “经典”JS 文件
- 使用方法::
<script src="whatever.js"></script>
- 鉴别方法:
- 该网站的设置说明中有一个友好的大横幅,上面写着“将此与 CDN 一起使用!”或者什么
.umd.js
扩展名- 只需尝试将其放入
<script src=...
标记中,看看它是否有效
- 使用方法::
- ES模块
- 使用方法:
- 如果没有依赖项,只需直接在代码中
import {whatever} from "./my-module.js"
- 如果存在依赖项,则创建导入映射并
import {whatever} from "my-module"
- 或使用download-esm消除对导入映射的需要
- 使用esbuild或任何 ES 模块捆绑器
- 如果没有依赖项,只需直接在代码中
- 鉴别方法:
- 寻找
import
或export
声明。 (不是module.exports = ...
,这是 CommonJS ) -
.mjs
扩展名 - 也许是
package.json
中的"type": "module"
(尽管我不清楚这到底指的是哪个文件)
- 寻找
- 使用方法:
- CommonJS 模块
- 使用方法:
- 使用https://esm.sh将其转换为 ES 模块,如
https://esm.sh/@atproto/[email protected]
- 以某种方式使用构建(?)
- 使用https://esm.sh将其转换为 ES 模块,如
- 鉴别方法:
- 在代码中查找
require()
或module.exports = ...
-
.cjs
扩展名 - 也许
package.json
中的"type": "commonjs"
(尽管我不清楚这到底指的是哪个文件)
- 在代码中查找
- 使用方法:
ES 模块标准化真是太好了
从我的角度来看,CommonJS 模块和 ES 模块之间的主要区别在于 ES 模块实际上是一个标准。这让我对使用它们更有信心,因为浏览器致力于永远向后兼容 Web 标准——如果我今天使用 ES 模块编写一些代码,我可以确信 15 年后它仍然会以同样的方式工作。
这也让我对使用像esbuild
这样的工具感觉更好,因为即使 esbuild 项目消亡了,因为它正在实现一个标准,我感觉将来可能会有另一个类似的工具可以取代它。
JS 社区已经构建了很多非常酷的工具
很多时候,当我谈论这些东西时,我会得到诸如“我讨厌 javascript!!!”之类的回答。这是最糟糕的!!!”。但我的经验是,有很多很棒的 Javascript 工具(我昨天刚刚了解了https://esm.sh ,这看起来很棒!我喜欢 esbuild!),如果我花时间学习它是如何工作的,我会可以利用其中一些工具,让我的生活变得更加轻松。
因此,这篇文章的目标绝对不是抱怨 Javascript,而是了解情况,以便我可以以一种让我感觉良好的方式使用该工具。
我还有疑问
这是我仍然有的一些问题,如果我知道答案,我会将答案添加到帖子中。
- 是否有一个工具可以自动为我在本地设置的 ES 模块生成导入映射?
- 如何在我的计算机上将 CommonJS 模块转换为 ES 模块,就像https://esm.sh那样?
所有的工具
以下是我们在本文中讨论的每个工具的列表:
- 西蒙·威利森 (Simon Willison) 的download-esm将会
- https://esm.sh/
- esbuild
写这篇文章让我想到,尽管我通常不希望每次更新项目时都运行一个构建,但我可能愿意运行一个构建步骤(使用download-esm
或其他东西)仅在设置项目时运行一次,并且不再运行,除非我正在更新我的依赖项版本。
就这样!
感谢马可·罗杰斯(Marco Rogers)在这篇文章中教会了我很多东西。我可能在这篇文章中犯了一些错误,我很想知道它们是什么 – 让我知道 Bluesky 或 Mastodon!
原文: https://jvns.ca/blog/2024/11/18/how-to-import-a-javascript-library/