从 npm 本地导入 ES 模块依赖项,无需捆绑/转译第一方源

Posted

技术标签:

【中文标题】从 npm 本地导入 ES 模块依赖项,无需捆绑/转译第一方源【英文标题】:Natively import ES module dependencies from npm without bundling/transpiling first-party source 【发布时间】:2021-05-05 00:24:30 【问题描述】:

背景

我正在尝试创建一个“buildless”javascript 应用程序,我不需要在每次保存任何源文件时运行watch 任务来转换 JSX、重新捆绑代码等。

仅使用第一方代码就可以正常工作,但是当我尝试从 npm 获取 import 依赖项时,我被卡住了。

目标

我想实现这样的工作流程:

    npm install foo(假设它是一个 ES 模块,而不是 CommonJS) 编辑source/index.js并添加import bar from 'foo' npm run build。某些东西(webpack、rollup、自定义脚本等)运行,并将foo 及其依赖项捆绑到./build/vendor.js 中(没有来自source/ 的任何东西)。 编辑index.html 以添加<script src="build/vendor.js" type="module"... 我可以在浏览器中重新加载source/index.jsbar 将可用。在下一次添加/删除依赖项之前,我不必运行 npm run build

我已经让 webpack 将依赖项拆分到一个单独的文件中,但是在无构建上下文中从该文件到 import,我必须 import bar from './build/vendor.js。届时 webpack 将不再捆绑 bar,因为它不是相对导入。

我也尝试过Snowpack,在概念上更接近我想要的,但我仍然无法配置它来实现上述工作流程。

我可以编写一个简单的脚本来将文件从node_modules 复制到build/,但我想使用捆绑软件来实现摇树等。很难找到支持这种工作流程的东西,不过。

【问题讨论】:

合乎逻辑的问题是,如果您不想“构建”您的项目,为什么还要使用 webpack。如果你使用 webpack,你将不得不构建。这就是它的工作原理。我已经构建了几十个应用程序,但没有一个使用 webpack 或任何“打包”工具。如果您选择使用 webpack 来提供它提供的功能,那么您选择必须构建您的应用程序。 这是一种妥协。理想情况下,它根本不需要,但似乎 production 需要它,而不是在 dev 工作流程期间。如果我可以在没有 webpack 的情况下完成这一切,那就更好了。您如何处理应用程序中的依赖项? 呃,澄清一下,有两个原因:1)在开发工作流程中,我使用的是现代浏览器,但对于生产我需要支持 IE10+,所以我需要转译为 ES5 等。我还想转译 HTM 等以提高性能。 2) 依赖关系。假设我要导入eff-diceware-passphrase,它只提供了一个CommonJS 模块,并且有自己的依赖项。构建步骤似乎是必要的,即使只是在开发工作流程中导入它。 好吧,如果你在编译,你总是在构建。如果我正在为生产进行编译,我通常也会在我的开发环境中进行编译,所以我正在测试/运行将在生产中运行的相同代码。似乎您可以将外部模块及其依赖项分别构建到它们自己单独导入的包中,只要它们不更新,您就不必重新构建它们。对于生产来说可能效率不高,因为如果你不让捆绑器一起分析所有内容,一些代码可能会重复,但对于开发环境来说可能没问题。 例如,您可以将 eff-diceware-passphrase 及其依赖项构建到您导入的一个捆绑脚本中,并且该构建将是一次性的,直到您更新到该模块的较新版本。这类似于在 C++ 中构建一次 DLL,如果没有任何更改,则每次执行新构建时都不会重新构建它。 【参考方案1】:

我想出了如何使用 Import Maps 和 Snowpack 来做到这一点。

高级解释

我使用Import Maps 将诸如import v4 from 'uuid' 之类的裸模块说明符转换为URL。它们是 currently just a drafted standard,但在 Chrome 中支持在实验标志后面,并且有 a shim。

这样,您可以在代码中使用纯import 语句,以便捆绑程序能够理解它们并可以正常工作、进行摇树等操作。不过,当浏览器解析导入时,它会看到它import v4 from 'http://example.org/vendor/uuid.js',然后像普通的 ES 模块一样下载它。

设置完成后,您可以使用任何捆绑程序来安装软件包,但需要将其配置为构建单独的捆绑包,而不是将所有软件包合并为一个。 Snowpack 在这方面做得非常好,因为它是为非捆绑式开发工作流设计的。它在底层使用esbuild,比 Webpack 快 10 倍,因为它避免了不必要地重新构建未更改的包。它仍然会摇树等等。

实现 - 最小示例

index.html

<!doctype html>
<!-- either use "defer" or load this polyfill after the scripts below-->
<script defer src="es-module-shims.js"></script>
<script type="importmap-shim">

  "imports": 
    "uuid": "https://example.org/build/uuid.js"
  

</script>

<script type="module-shim">
  import  v4  from "uuid";

  console.log(v4);
</script>

snowpack.config.js

module.exports = 
    packageOptions: 
        source: 'remote',
    ,
;

packageOptions.source = remote 告诉 Snowpack 自己处理依赖项,而不是期望 npm 来处理。 运行npx snowpack add module slug - e.g., 'uuid',在snowpack.deps.json文件中注册一个依赖,并安装到build文件夹中。

package.json

"scripts": 
    "build":  "snowpack build"

每当您添加/删除/更新依赖项时调用此脚本。不需要watch 脚本。

实现 - 完整示例

查看iandunn/no-build-tools-no-problems/f1bb3052。以下是相关行的直接链接:

snowpack.config.js snowpack.deps.json package.json core.php 输出垫片 plugin.php - 输出导入映射 passphrase-generator.js - 导入模块。 (在此示例中它们已被注释掉,出于此答案范围之外的原因,只需取消注释它们,运行 bundle 脚本,它们就会起作用)。

【讨论】:

【参考方案2】:

如果您愿意使用在线服务,Skypack CDN 似乎可以很好地解决这个问题。例如,我想使用 sample-player NPM 模块,我选择为我的项目使用无捆绑工作流,只使用 ES6 模块,因为我的目标是嵌入式 Chromium 最新版本,所以不需要担心旧版浏览器支持,所以我需要做的就是:

import SamplePlayer from "https://cdn.skypack.dev/sample-player@^0.5.5";

// init() once the page has finished loading.
window.onload = init;

function init() 
  console.log('hello sampler', SamplePlayer)

在我的 html 中:

  <script src="./src/sampler/sampler.js" type="module"></script>

当然,您可以查看 CDN 在上述 url 生成的 JS 文件并下载它指向的生成的一体化 js 文件,以便在需要时也可以离线使用。

【讨论】:

以上是关于从 npm 本地导入 ES 模块依赖项,无需捆绑/转译第一方源的主要内容,如果未能解决你的问题,请参考以下文章

如何将 typescript npm 模块导入语法转换为 ECMA2015 模块导入语法

是否可以使用webpack单独导入捆绑的webpack和库?

d3 4.x 的 es6 模块导入失败

捆绑为 UMD 的 React 组件在通过另一个 React 应用程序中的脚本标签导入时无法加载依赖项

Firebase 部署 - 找不到本地依赖项的模块

git被阻止,如何安装npm模块