首页 小组 问答 话题 好文 素材 用户 唠叨 我的社区

[教程]了解本地 JavaScript 模块

小蜗锅Lv.1普通用户
2024-10-17 16:50:12
0
7

自从 JavaScript 项目开始变得越来越复杂以来,已经有很长一段时间了。因此,将代码分割成易于管理的片段变得至关重要。在 JavaScript 开发的历史上,我们经历了许多不同的将代码拆分成模块的方法。NodeJS 中使用的 CommonJS 就是很好的例子。同样,RequireJS 在过去也被用于最早的 angular 等框架中。

JavaScript 语言在 2015 年进行了一次重大更新,称为 ES6 或 ECMAScript 2015。除其他功能外,它还引入了处理模块的官方语法。这种方法允许我们使用导出关键字来暴露模块中的各种值。然后,我们可以使用 import 语句在其他模块中访问它们。

遗憾的是,在 JavaScript 语言中添加一项功能后,浏览器并不会自动实现它。因此,我们使用 webpack 和 babel 等工具将我们编写的遵循最新标准的代码转换为所有浏览器都能理解的代码。不过,浏览器早在一段时间前就跟上了步伐,推出了对 ES6 模块的本地支持。在本文中,我们将解释它们,以及使用它们与依赖 Webpack 等工具有何不同。

加载 JavaScript 模块

让我们先创建一个简单的 JavaScript 文件。

index.js

const rootElement = document.querySelector('#root');
 
if (rootElement) {
  rootElement.innerhtml = 'Hello world!';
}

它的工作就是找到一个具有根 id 的元素,并在其中渲染一些文本。现在,我们可以将其包含在 HTML 中。

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Native modules</title>
    <script src="index.js" type="module"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

由于使用了 type="module",我们的 JavaScript 文件被作为模块加载。这会影响浏览器的处理方式。

延迟执行

浏览器从上到下读取加载的 HTML 文档,并在 <body> 部分之前解析 <head> 部分。如果我们以常规方式将 JavaScript 文件放入 <head> 部分,它将在浏览器解析 <body> 部分之前被解析和执行。这意味着我们的 JavaScript 代码无法访问 id 为 root 的元素,因为它还不存在。

然而,JavaScript 模块的执行是延迟的。这意味着浏览器会在解析整个文档时执行它们。这意味着,即使我们把它们放在 <head> 部分,它们也会在 <div id="root"> 元素可用时执行。

应用 CORS 策略

对于不带 type="module" 的常规 <script> 标记,即使脚本来自不同的源,浏览器也会获取并执行。但是,当我们加载模块时,浏览器会应用同源策略。它阻止我们的网站访问来自其他来源的模块。我们可以通过 CORS 调整这种行为。跨源资源共享(CORS)是一种禁止或允许从其他源请求资源的机制。

如果您想了解什么是起源以及如何在 Node.js 应用程序中设置 CORS,请查看 API with NestJS #117。CORS - 跨起源资源共享

这也意味着,如果我们直接从文件系统打开 index.html 文件,会出现以下错误:

CORS 策略已阻止从起源 'null' 访问 'file:///home/marcin/Documents/Projects/native-modules/index.js' 处的脚本:仅支持以下协议方案的跨源请求:http、data、isolated-app、chrome-extension、chrome、https、chrome-untrusted。

在本地制作网站时,最直接的处理方法就是使用类似 serve 或 http-server 这样的库,让我们的页面在 localhost 上可用。

导出和导入

让我们创建一个简单的函数,并将其放在一个单独的文件中。

sum.js

export function sum(firstNumber, secondNumber) {
  return firstNumber + secondNumber;
}

使用 export 关键字,我们可以将上述文件导入 index.js。

index.js

import { sum } from './sum.js';

const rootElement = document.querySelector('#root');

if (rootElement) {
  rootElement.innerHTML = sum(1, 2);
}

重要的是,我们不必在 index.html 中包含 sum.js 文件。因为我们将 index.js 标记为模块,所以它可以导入其他模块。

导入Maps

导入时提供的模块路径可以是相对路径,也可以是绝对路径。如果我们正确配置了跨源资源共享,它也可以是不同源的 URL。

Webpack 等工具可以自动解析文件扩展名。但是,在使用本地 JavaScript 模块时,我们需要提供模块的完整路径。这就是我们在导入时包含文件扩展名的原因。

我们可以添加导入映射,指定要导入的模块的 URL。我们还可以为它们命名。

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Native modules</title>
    <script type="importmap">
      {
        "imports": {
          "sum-utilities": "./sum.js"
        }
      }
    </script>
    <script src="index.js" type="module"></script>
  </头>
  <body>
    <div id="root"></div>
  </body>
</html>

上面,我们在导入映射中提供了 sum.js 的路径,并将 id 称为 sum-utilities。有了它,我们在导入该模块时就不必再提供文件的确切路径了。

index.js

import { sum } from 'sum-utilities';
 
const rootElement = document.querySelector('#root');
 
if (rootElement) {
  rootElement.innerHTML = sum(1, 2);
}

动态导入

我们可以在需要时才导入特定模块。例如,通过不预先加载大型模块,我们可以帮助用户节省移动带宽。

为此,我们可以使用返回 promise 的导入函数。

index.js

const rootElement = document.querySelector('#root');
const renderButton = document.querySelector('#render-button');
 
renderButton?.addEventListener('click', async () => {
  const { sum } = await import('sum-utilities');
  if (rootElement) {
    rootElement.innerHTML = sum(1, 2);
  }
})

预加载模块

在我们当前的设置中,浏览器不会自动开始加载 sum.js 文件。而是在执行 main.js 文件时遇到 import 语句时才开始加载。

我们可以通过添加带有 rel="modulepreload" 的 <link /> 元素,提前预加载 sum.js 文件,从而提高性能。

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Native modules</title>
    <script type="importmap">
      {
        "imports": {
          "sum-utilities": "./sum.js"
        }
      }
    </script>
    <link rel="modulepreload" href="sum.js" />
    <script src="index.js" type="module"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

因此,sum.js 和 main.js 文件可以并行下载。

处理旧版浏览器兼容

遗憾的是,有些旧版浏览器不支持本地 JavaScript 模块。如果我们的网站需要在旧版浏览器(如 Internet Explorer)上运行,我们可以使用 nomodule 属性提供一个单独的 JavaScript 文件。

<script src="forOlderBrowsers.js" nomodule></script>

通过添加 nomodule,我们表明该脚本不应在支持本地 JavaScript 模块的浏览器中执行。

原生 JavaScript 模块与 Webpack 和类似工具的比较

使用原生 JavaScript 模块的优势之一是,它们可以直接在所有现代浏览器中运行。这意味着我们不需要任何工具来构建项目。不过,在浏览器原生支持导入和导出功能之前,我们可以使用 Webpack 等工具。它的作用类似,但工作方式不同。

Webpack 是一个捆绑工具。它将我们所有的 JavaScript 文件及其依赖关系整合到一个文件中。在此过程中,Webpack 可以使用各种加载器和插件来转换我们的代码,例如,加入浏览器尚未支持的其他功能。

捆绑包可以分成多个文件,以提高性能。由于 react 或 Redux 等依赖关系不会经常变化,我们可以将它们捆绑到一个单独的文件中,然后由浏览器缓存。

Webpack 最重要的优势在于它提供了许多优化应用程序的方法。它可以在不改变代码功能的情况下,通过删除代码中不必要的代码字符来精简代码,从而减小文件大小。它可以通过移除捆绑包中不需要提供给浏览器的未使用代码来实现摇树。它可以压缩图片等资产,以减少应用程序的加载时间。

总结

浏览器遵循 JavaScript 标准并原生实现模块等功能是件好事。虽然它们肯定有自己的用武之地,但并不会让 Webpack 等工具过时。在捆绑代码的同时,Webpack 有机会以各种方式优化我们的应用程序。有趣的是,Vite 等工具使用的是原生 JavaScript 模块。这样可以在开发过程中快速、高效地加载模块。了解 JavaScript 模块的工作原理很有必要,因为它们可能是前端开发的未来。掌握 Webpack 等传统工具和使用原生模块的方法,可以提高我们的代码效率。

小蜗锅
小蜗锅

5 天前

签名 : 拿人手短,js方面的不懂问我,为了100块钱的赞助豁出去了。   7       0
评论
站长交流