如何使用导入映射动态导入JavaScript
作为 Write for DOnations 计划的一部分,作者选择了 Creative Commons 来接受捐赠。
介绍
外部库可能会增加新 JavaScript 项目的复杂性。 为了能够安装和使用外部代码库,您需要一个可以解析代码并将您导入的库捆绑为最终格式的构建工具。 构建完成后,您可以轻松地添加和集成新代码,但仍然存在一些问题。
例如,您可能只需要在应用程序的一部分中使用库,这是大多数用户可能永远不需要的应用程序的一部分,例如管理页面。 但默认情况下,大多数构建系统会将所有代码捆绑到一个大文件中。 用户将加载代码,无论他们是否需要执行它。 构建系统足够灵活,可以根据需要配置它们以加载代码,但该过程需要一些工作。
构建工具是开发体验的重要组成部分,但是一个名为 import maps 的规范将允许您在没有构建工具的情况下将外部代码导入项目,并且它只会在需要时加载代码运行。 导入映射不会完全取代执行许多其他有价值操作(如构建样式表和处理图像)的构建工具,但它们将允许您仅使用本机浏览器功能快速轻松地引导新的 JavaScript 应用程序。
在本教程中,您将使用导入映射和 JavaScript 模块来导入代码,而无需构建工具。 您将创建一个显示消息的基本应用程序,您将创建一个导入映射,它会告诉您的浏览器在哪里找到外部代码。 接下来,您将把导入的代码集成到您的 JavaScript 中,并使用第三方代码,而无需在本地下载代码或通过构建步骤运行它。 最后,您将了解当前实现导入映射的许多方面并适用于所有现代浏览器的工具。
先决条件
- 您将需要一个运行 Node.js 的开发环境。 本教程在 Node.js 版本 14.17.1 和 npm 版本 6.14.23 上进行了测试。 要在 macOS 或 Ubuntu 18.04 上安装它,请按照 如何在 macOS 上安装 Node.js 和创建本地开发环境中的步骤或 的 使用 PPA 部分安装如何在 Ubuntu 18.04 上安装 Node.js。
- 您还需要 JavaScript 的基本知识,可以在 如何在 JavaScript 中找到这些知识,以及 HTML 和 CSS 的基本知识。 HTML 和 CSS 的一个很好的资源是 Mozilla 开发者网络 。
第 1 步 — 创建 HTML 页面并插入 JavaScript
在这一步中,您将创建一个 HTML 页面,使用 JavaScript 进行动态活动,并启动一个本地开发服务器来跟踪您的更改。
首先,在一个新目录中,创建一个空白 HTML 文档。
在文本编辑器中打开一个名为 index.html
的文件:
nano index.html
在文件内部,添加一个简短的空白 HTML 页面:
索引.html
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Hello World</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> </body> </html>
该文件有一些标准[1]
标签和一个空的<body>
元素。
接下来添加一个 <script>
标签。 脚本标签的 src
属性将是您即将创建的名为 hello.js
的新 JavaScript 文件:
索引.html
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Hello World</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script defer src="./hello.js"></script> </head> <body> </body>
请注意,您将 defer 属性添加到 <script>
标记。 这将延迟脚本标签的执行,直到文档被解析之后。 如果您不推迟,您可能会在尝试添加到元素时收到一条错误消息,指出 body
未找到。
接下来,在与 index.html
相同的目录中创建一个名为 hello.js
的 JavaScript 文件:
nano hello.js
在文件内部,编写一些 JavaScript 来创建一个带有文本 "Hello, World"
的新文本元素:
你好.js
const el = document.createElement('h1'); const words = "Hello, World!" const text = document.createTextNode(words); el.appendChild(text); document.body.appendChild(el);
现在您已经有了脚本,您可以在浏览器中打开 index.html
文件。 在与 index.html
文件相同的目录中,运行 npx serve
。 这将在本地运行 serve 包,而无需下载到您的 node_modules
。 serve
包运行一个简单的网络服务器,它将在本地为您的代码提供服务。
npx serve
该命令将询问您是否要安装软件包。 输入 y
同意:
Need to install the following packages: serve Ok to proceed? (y) y
运行命令时,您将看到如下输出:
npx: installed 88 in 15.187s ┌────────────────────────────────────────┐ │ │ │ Serving! │ │ │ │ Local: http://localhost:5000 │ │ │ │ Copied local address to clipboard! │ │ │ └────────────────────────────────────────┘
当您打开 Web 浏览器到 http://localhost:5000
时,您将看到您的代码。 您可以让服务器在单独的选项卡或窗口中运行,也可以在预览代码后使用 CTRL+C
将其关闭。
现在您正在浏览器中显示一个基本页面,但您还不能利用第三方代码和 JavaScript 包。 在下一步中,您将在没有构建工具的情况下动态导入代码并将函数导入脚本。
第 2 步 — 使用 ES6 模块编写 Hello World 脚本
在此步骤中,您将编写使用外部包的代码。 您将修改代码以使用 ES 导入来导入 JavaScript 代码。 最后,您将使用 module
类型在浏览器中加载代码,以便浏览器知道动态加载代码。
首先,打开 hello.js
:
nano hello.js
您将从 lodash 导入一些代码来动态更改您的文本。
在文件内部,将文本从 Hello World
更改为全部小写:hello world
。 然后在文件顶部,使用标准 ES6 import
语法从 lodash
导入 startCase 函数:
你好.js
import startCase from '@lodash/startCase'; const el = document.createElement('h1'); const words = "hello, world"; const text = document.createTextNode(words); el.appendChild(text); document.body.appendChild(el);
最后,使用 words
变量作为 document.createTextNode
内部的参数调用 startCase
:
你好.js
import startCase from '@lodash/startCase'; const el = document.createElement('h1'); const words = "hello, world"; const text = document.createTextNode(startCase(words)); el.appendChild(text); document.body.appendChild(el);
如果您关闭了网络服务器,请打开一个新的终端窗口或选项卡并运行 npx serve
以重新启动服务器。 然后在 Web 浏览器中导航到 http://localhost:5000
以查看更改。
在浏览器中预览代码时,打开开发者控制台。 当你这样做时,你会看到一个错误:
OutputUncaught SyntaxError: Cannot use import statement outside a module
由于代码使用 import
语句,您需要修改 index.html
内部的 <script>
标记来处理现在在多个文件之间拆分的 JavaScript。 一个文件是您编写的原始代码。 另一个文件是从 lodash 导入的代码。 导入其他代码的 JavaScript 代码称为 modules。
关闭 hello.js
并打开 index.html
:
nano index.html
要将脚本作为模块运行,请将 <script>
标记上的 type
属性的值更改为 module
:
你好.js
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Hello World</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="module" src="./hello.js"></script> </head> <body> </body> </html>
请注意,您还删除了 defer
属性。 JavaScript 模块在页面被解析之前不会执行。
打开你的浏览器,你仍然会看到一个错误`:
OutputUncaught TypeError: Failed to resolve module specifier "@lodash/startCase". Relative references must start with either "/", "./", or "../"
现在问题不在于 import
语句。 问题是浏览器不知道 import
语句的含义。 它期望在网络服务器上找到代码,因此它会查找与当前文件相关的文件。 要解决这个问题,您需要一个名为 import maps
的新工具。
在这一步中,您学习了如何修改 JavaScript 代码以使用 ES 导入加载外部库。 您还修改了 HTML script
标记来处理 JavaScript 模块。
在下一步中,您将告诉浏览器如何使用 import maps
查找代码。
第 3 步 — 使用导入映射加载外部代码
在这一步中,您将学习如何创建导入映射来告诉您的浏览器在哪里可以找到外部代码。 您还将学习如何导入模块代码并查看代码是如何在浏览器中延迟加载的。
import map
是一个 JavaScript 对象,其中键是导入的名称 (@lodash/startCase
),值是代码的位置。
在 index.html
内部添加一个新的 script
标签,其 type
为 importmap
。 在 script
标记内,创建一个键为 imports
的 JavaScript 对象。 该值将是另一个对象:
你好.js
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Hello World</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="importmap"> { "imports": { } } </script> <script type="module" src="./hello.js"></script> </head> <body> </body> </html>
确保不要在对象中添加任何尾随逗号。 浏览器将不知道如何处理它们。
现在您已经有了基本对象,您可以添加代码的位置。 导入映射的结构将如下所示:
{ "imports": { "nameOfImport": "locationOfCode", "nameOfSecondImport": "secondLocation" } }
您已经知道导入的名称 @lodash/startCase
,但现在您需要找到将导入映射指向的位置。
一个很好的资源是 unpkg。 Unpkg 是 npm
中任何包的内容分发网络 (CDN)。 如果你可以 npm install
一个包,你应该可以通过 unpkg 加载它。 Unpkg 还包括一个浏览选项,可以帮助您找到所需的特定文件。
要查找 startCase
代码,请在浏览器中打开 https://unpkg.com/browse/lodash-es@4.17.21/。 请注意 URL 中的单词 browse
。 这为您提供了一种查看目录的图形方式,但您不应将路径添加到导入映射,因为它提供的是 HTML 页面而不是原始 JavaScript 文件。
另外,请注意您浏览的是 lodash-es
而不是 lodash
。 这是导出为 ES 模块的 lodash
库,这是您在这种情况下需要的。
浏览代码,您会注意到有一个名为 startCase.js 的文件。 此代码导入其他函数并使用它们将每个单词的首字母转换为大写:
import createCompounder from './_createCompounder.js'; import upperFirst from './upperFirst.js'; /** * Converts `string` to * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). * * @static * @memberOf _ * @since 3.1.0 * @category String * @param {string} [string=''] The string to convert. * @returns {string} Returns the start cased string. * @example * * _.startCase('--foo-bar--'); * // => 'Foo Bar' * * _.startCase('fooBar'); * // => 'Foo Bar' * * _.startCase('__FOO_BAR__'); * // => 'FOO BAR' */ var startCase = createCompounder(function(result, word, index) { return result + (index ? ' ' : '') + upperFirst(word); }); export default startCase;
浏览器将遵循 import
语句并导入每个必要的文件。
现在您有了导入映射的位置,使用新 URL 更新文件导入映射。 在 index.html
中,添加 @lodash/startCase
和 URL。 再次确保删除 browse
:
索引.html
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Hello World</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="importmap"> { "imports": { "@lodash/startCase": "https://unpkg.com/lodash-es@4.17.21/startCase.js" } } </script> <script type="module" src="./hello.js"></script> </head> <body> </body> </html>
保存文件。 刷新浏览器,您将看到 Hello World。
注意: 导入地图尚未得到广泛支持。 在最新版本的 Edge 或 Chrome 中打开代码或查看最新的 支持的浏览器 。
您的浏览器显示“Hello World”,但现在发生了更有趣的事情。 在浏览器中打开浏览器控制台,选择Inspect Elements,然后切换到Network选项卡。
打开 Network 选项卡后,刷新页面,您会看到浏览器正在动态加载代码。 每当找到新的 import
语句时,它都会导入相关代码:
更重要的是,请注意所有代码都是延迟加载的。 这意味着浏览器在特别需要之前不会导入任何代码。 例如,即使 startCase
在导入映射中并且导入映射是在 hello.js
的脚本之前定义的,但直到 hello.js
加载并导入代码之后才会加载它.
如果您要在导入映射中添加其他条目,浏览器根本不会加载它们,因为它们从未导入到代码中。 导入地图是位置地图,本身不导入任何代码。
一个主要问题是所有浏览器尚未完全支持导入地图。 即使它们受支持,某些用户也可能不使用受支持的浏览器。 幸运的是,有不同的项目在添加完整的浏览器支持的同时使用导入映射语法。
在此步骤中,您创建了一个导入映射。 您还学习了如何导入模块代码以及如何在浏览器中延迟加载代码。 在下一步中,您将使用 SystemJS 导入代码。
第 4 步 — 使用 SystemJS 构建跨浏览器支持
在这一步中,您将使用 SystemJS 在所有浏览器中使用导入映射。 您将导出代码作为 SystemJS 构建以及如何将导入映射类型设置为使用 SystemJS 格式。 在这一步结束时,您将能够在任何浏览器中利用导入地图,并为构建更复杂的应用程序和微前端奠定基础。
导入地图将从应用程序中删除许多复杂的构建步骤,但它们尚未得到广泛支持。 此外,并非所有库都构建为 ES 模块,因此在代码中使用它们的方式存在一些限制。
幸运的是,有一个名为 SystemJS 的项目可以使用创建导入映射来支持跨浏览器并使用各种包构建。
lodash 库很方便,因为它是以 ES 格式编译的,但对于大多数库来说并非如此。 许多库以其他格式导出。 最常见的一种是通用模块定义或 UMD。 这种格式适用于浏览器和节点模块。
主要区别在于,与 ES 导入不同,UMD 构建通常将所有代码组合到一个文件中。 该文件会大很多,你最终会得到更多的代码,然后你可能会执行。
要更新您的项目以使用 SystemJS 和 lodash 的 UMD 构建,首先打开 hello.js
:
nano hello.js
更改import
语句,直接从lodash
导入startCase
函数。
你好.js
import { startCase } from 'lodash'; const el = document.createElement('h1'); const words = "hello, world"; const text = document.createTextNode(startCase(words)); el.appendChild(text); document.body.appendChild(el);
保存并关闭文件。
接下来,要将文件构建为 SystemJS 构建,您将需要一个简单的构建步骤。 您可以使用其他构建工具,例如 webpack,但在此示例中,您将使用 rollup。
首先,初始化工程,创建一个package.json
文件。 添加 -y
标志以接受所有默认值:
npm init -y
命令运行后,您将看到成功输出:
{ "name": "hello", "version": "1.0.0", "description": "", "main": "index.js", "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "homepage": "" }
注意: 根据您使用的 npm
版本,您的输出可能会略有不同。
接下来,将 rollup
安装为 devDepenceny
:
npm install --save-dev rollup
片刻之后,您将看到一条成功消息:
+ rollup@2.56.2 added 1 package from 1 contributor and audited 2 packages in 6.85s found 0 vulnerabilities
接下来,创建一个简单的构建配置。 打开一个名为 rollup.config.js
的新文件:
nano rollup.config.js
然后添加一个将以 SystemJS 格式输出代码的配置:
rollup.config.js
export default { external: ["lodash"], input: ["hello.js"], output: [ { dir: "public", format: "system", sourcemap: true } ] };
external
键告诉汇总不要在最终构建中包含任何 lodash 代码。 SystemJS 将在导入时动态加载该代码。
input
是根文件的位置。 output
告诉汇总将最终代码放在哪里以及它应该使用的格式,在这种情况下是 system
。
保存并关闭文件。
现在您有了构建步骤,您需要添加一个任务来运行它。 打开package.json
:
nano package.json
在 scripts
对象中,添加一个名为 build
的脚本,该脚本将运行 rollup -c
。 将 main
键更改为 hello.js
:
包.json
{ "name": "hello", "version": "1.0.0", "description": "", "main": "hello.js", "devDependencies": { "rollup": "^2.56.2" }, "scripts": { "build": "rollup -c" }, "keywords": [], "author": "", "license": "ISC", "homepage": "" }
保存并关闭文件,然后运行 build
命令:
npm run build
该命令将短暂运行,然后您将看到一条成功消息:
> rollup -c hello.js → public... created public in 21ms
您还将看到一个名为 public
的新目录,其中包含构建的文件。 如果你打开 public/hello.js
你会看到你的项目以系统格式编译。
nano public/hello.js
该文件将如下所示。 它类似于 hello.js
带有环绕 System.register
的方法。 此外,lodash
在一个数组中。 这将告诉 SystemJS 在运行时加载外部库。 其中一位维护者创建了 视频 ,进一步解释了模块格式。
公共/hello.js
System.register(['lodash'], function () { 'use strict'; var startCase; return { setters: [function (module) { startCase = module.startCase; }], execute: function () { const el = document.createElement('h1'); const words = "hello, world"; const text = document.createTextNode(startCase(words)); el.appendChild(text); document.body.appendChild(el); } }; }); //# sourceMappingURL=hello.js.map
保存并关闭文件。
最后一步是更新您的 index.html
以处理新文件:
打开index.html
nano index.html
首先,您需要导入 SystemJS 代码。 使用带有指向 CDN 分发的 src
属性的常规 <script>
标记。
将 <script>
标签放在导入映射的正下方:
索引.html
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Hello World</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="importmap"> { "imports": { "@lodash/startCase": "https://unpkg.com/lodash-es@4.17.21/startCase.js } } </script> <script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.js"></script> <script type="module" src="./hello.js"></script> </head> <body> </body> </html>
接下来,您需要更新导入映射。 格式类似于您在第 3 步中完成的格式,但有三处更改:
- 更新导入映射
type
。 - 更新对
lodash
的引用。 - 添加对
hello.js
脚本的引用。
首先,更新type
。 由于这不是导入地图的原生浏览器版本,而是 systemjs
版本,因此将类型更改为 systemjs-importmap
:
索引.html
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Hello World</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="systemjs-importmap"> { "imports": { "@lodash/startCase": "https://unpkg.com/lodash-es@4.17.21/startCase.js } } </script> <script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.js"></script> <script type="module" src="./hello.js"></script> </head> <body> </body> </html>
接下来,更新引用。 将 @lodash/startCase
更改为 lodash
。 您将导入完整的库。 然后将位置更改为 unpkg 处的 UMD 构建。
然后为 hello
添加一个新条目并将其指向 public
目录中的编译版本:
索引.html
... <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="systemjs-importmap"> { "imports": { "hello": "./public/hello.js", "lodash": "https://unpkg.com/lodash@4.17.21/lodash.js" } } </script> ...
现在您正在导入 systemJS
并更新了导入映射,剩下的就是加载模块。
将模块的 script
标签上的 type
属性更改为 systemjs-module
。 然后将 src
更改为 import:hello
。 这将告诉 systemjs
加载 hello
脚本并执行:
你好.js
... <script type="systemjs-importmap"> { "imports": { "hello": "./public/hello.js", "lodash": "https://unpkg.com/lodash@4.17.21/lodash.js" } } </script> <script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.js"></script> <script type="systemjs-module" src="import:hello"></script> </head> ...
保存并关闭文件。
当你这样做时,浏览器会刷新,你会看到 Hello World。
与原生导入地图不同,这适用于任何浏览器。 这是 FireFox 中的结果:
如果您查看 网络 选项卡。 您会看到,与导入映射一样,代码会根据需要延迟加载:
在这一步中,您使用 SystemJS 跨浏览器导入映射。 您更改了脚本以使用 lodash 的 UMD 构建,创建了一个汇总构建以以 system
格式输出代码,并更改了导入映射和模块类型以使用 SystemJS
结论
在本教程中,您使用导入映射来动态加载 JavaScript 代码。 您呈现了一个动态加载外部库的应用程序,而无需任何构建步骤。 然后,您创建了一个构建过程来生成 SystemJS 格式的代码,以便您可以在所有浏览器中使用导入映射。
导入地图让您有机会开始将大型项目分解成更小的独立部分,称为 microfrontends。 您也不需要像本教程中所学习的那样将自己限制在静态定义的地图上; 您还可以创建可以从其他脚本加载的 动态导入地图。 您还可以通过使用 scopes 为导入它们的不同脚本定义不同版本的依赖项来为多个项目使用单个导入映射。
有新功能正在进行中,您可以在 官方规范 上关注它们。