JavaScript

Parcel 提供对 JavaScript 的一流支持,包括 ES 模块和 CommonJS、多种类型的依赖、针对浏览器目标的自动转译、JSX 和 TypeScript 支持等等。

模块

#

模块允许您将代码拆分为不同的文件,并通过导入和导出值来在它们之间共享功能。这可以帮助您将代码结构化为独立的部分,并为它们之间的通信定义明确的接口。

Parcel 同时支持 ES 模块和 CommonJS 语法。模块指定符按照依赖解析中描述的方式解析。

ES 模块

#

ES 模块语法是在 JavaScript 中在文件之间导入和导出值的标准方式。对于新代码,应优先使用它而不是 CommonJS。import 语句可用于引用另一个文件中通过 export 语句暴露的值。

此示例从 math.js 导入 multiply 函数,并使用它实现 square 函数。

square.js:
import { multiply } from "./math.js";

export function square(x) {
return multiply(x, x);
}
math.js:
export function multiply(a, b) {
return a * b;
}

要了解更多关于 ES 模块的信息,请参见 MDN 上的文档。

CommonJS

#

CommonJS 是一个在 Node 中支持的传统模块系统,并被 npm 上的库广泛使用。如果您正在编写新代码,通常应优先使用上面描述的 ES 模块语法。CommonJS 提供了一个 require 函数,可用于访问另一个文件暴露的 exports 对象。

此示例使用 require 加载 math.js 模块,并使用其导出对象上的 multiply 函数实现 square 函数。

square.js:
let math = require("./math");

exports.square = function (x) {
return math.multiply(x, x);
};
math.js:
exports.multiply = function (a, b) {
return a * b;
};

除了 requireexports,Parcel 还支持 module.exports,以及模块全局变量 __dirname__filename,以及用于访问环境变量的 process.env。有关更多详细信息,请参见 Node 模拟文档。

要了解更多关于 CommonJS 的信息,请参见 Node.js 文档

动态导入

#

ES 模块 import 语句和 CommonJS require 函数都是同步加载依赖:即模块可以立即引用,无需等待网络请求。通常,同步导入的模块会与其父模块捆绑在同一个文件中,或在另一个已加载的捆绑包中引用。

动态 import() 函数可用于异步加载依赖。这允许按需延迟加载代码,是减少应用初始页面加载文件大小的好技术。import() 返回一个 Promise,在依赖加载完成时通知您。

import("./pages/about").then(function (page) {
page.render();
});

有关如何使用动态导入的更多详细信息,请参见代码拆分文档。

经典脚本

#

模块(无论是 ES 模块还是 CommonJS)的优点之一是它们隔离功能。这意味着在顶层作用域中声明的变量除非导出,否则无法在模块外部访问。这通常是一个很好的特征,但 JavaScript 中的模块相对较新,许多传统库和脚本并不期望被隔离。

经典脚本是通过 HTML 中的传统 <script> 标签(不带 type="module")或其他方式(如 Workers)加载的 JavaScript 文件。经典脚本将顶层作用域中的变量视为全局变量,即使在不同的脚本之间也可以访问。

例如,在加载 jQuery 等库时,$ 变量可以全局地被页面上的其他脚本访问。如果 jQuery 作为模块加载,则除非手动将其分配为 window 对象的属性,否则 $ 将不可访问。

<script src="jquery.js"></script>
<script>
// $ 变量现在可以全局访问。
$(".banner").show();
</script>

此外,经典脚本不支持通过 ES 模块或 CommonJS 进行同步导入或导出,并且禁用了 Node 模拟。但是,动态 import() 支持的,可以从经典脚本中加载模块。

Parcel 匹配浏览器对经典脚本和模块的行为。如果您希望在代码中使用导入或导出,则需要使用 <script type="module"> 从 HTML 文件引用 JavaScript。对于 workers,使用 {type: 'module'} 选项(见下文)。如果缺少这一点,您将看到如下诊断信息。

显示"浏览器脚本不能有导入或导出。将 type='module' 属性添加到脚本标签中。"的错误消息屏幕截图

import.meta

#

import.meta 对象包含有关引用它的模块的信息。Parcel 目前支持一个属性 import.meta.url,它包含文件系统上模块的 file:// URL。此 URL 相对于您的项目根目录(例如,Git 初始化的目录),以避免暴露任何构建系统细节。

console.log(import.meta);
// => {url: 'file:///src/App.js'}

console.log(import.meta.url);
// => 'file:///src/App.js'

URL 依赖

#

您可以使用 URL 构造函数从 JavaScript 文件引用非 JavaScript 资源,如图像或视频。这些是通过使用 import.meta.url 作为基本 URL 参数相对于模块解析的。第一个参数必须是字符串字面量才能被识别(不能是变量或表达式)。

此示例引用与 JavaScript 文件相同目录中名为 hero.jpg 的图像,并从中创建 <img> 元素。

let img = document.createElement('img');
img.src = new URL('hero.jpg', import.meta.url);
document.body.appendChild(img);

Parcel 将处理 URL 依赖引用的任何文件,就像处理任何其他依赖一样。例如,图像将由图像转换器处理,您可以使用查询参数指定调整大小和转换的选项。如果没有为特定文件类型配置转换器,则文件将未经修改地复制到 dist 目录。生成的文件名还将包含内容哈希,以便在浏览器中长期缓存。

Workers

#

Parcel 内置支持 Web Workers、Service Workers 和 Worklets,允许将工作移动到不同的线程。

Web Workers

#

Web Workers 是最通用的 Worker 类型。它们允许您在后台线程中运行任意 CPU 密集型工作,以避免阻塞用户界面。Workers 是使用 Worker 构造函数创建的,并使用 URL 构造函数引用另一个 JavaScript 文件,如上所述。

要在 Worker 中使用 ES 模块或 CommonJS 语法,请使用上面经典脚本中描述的 type: 'module' 选项。根据您的目标和当前浏览器支持,Parcel 将在必要时将您的 Worker 编译为非模块 Worker。

new Worker(new URL("worker.js", import.meta.url), { type: "module" });

Parcel 还支持 SharedWorker 构造函数,它创建一个可以在不同浏览器窗口或 iframe 中访问的 Worker。它支持与上述 Worker 相同的选项。

要了解更多关于 Web Workers 的信息,请参见 MDN 上的文档。

Service Workers

#

Service Workers 在后台运行,提供离线缓存、后台同步和推送通知等功能。它们是使用 navigator.serviceWorker.register 函数创建的,并使用 URL 构造函数引用另一个 JavaScript 文件。

要在 Service Worker 中使用 ES 模块或 CommonJS 语法,请使用上面经典脚本中描述的 type: 'module' 选项。根据您的目标和当前浏览器支持,Parcel 将在必要时将您的 Service Worker 编译为非模块 Worker。

navigator.serviceWorker.register(
new URL("service-worker.js", import.meta.url),
{ type: "module" }
);

note:Service Workers 中不支持动态 import()

Service Workers 通常用于预缓存静态资源,如 JavaScript、CSS 和图像。@parcel/service-worker 可用于从 Service Worker 中访问捆绑包 URL 列表。它还提供一个 version 哈希,每次清单更改时都会变化。您可以使用它来生成缓存名称。

首先,将其作为依赖项安装到您的项目中。

yarn add @parcel/service-worker

此示例展示了如何在安装 Service Worker 时预缓存所有捆绑包,并在激活时清理旧版本。

service-worker.js:
import { manifest, version } from "@parcel/service-worker";

async function install() {
const cache = await caches.open(version);
await cache.addAll(manifest);
}
addEventListener("install", (e) => e.waitUntil(install()));

async function activate() {
const keys = await caches.keys();
await Promise.all(keys.map((key) => key !== version && caches.delete(key)));
}
addEventListener("activate", (e) => e.waitUntil(activate()));

要了解更多关于 Service Workers 的信息,请参见 MDN 上的文档,以及 web.dev 上的离线手册

经典脚本 Workers

#

type: 'module' 选项可能被省略,以将 Workers 和 Service Workers 视为经典脚本而不是模块。可以使用 importScripts 函数在经典脚本 Workers 中加载其他代码,但是,代码将在运行时加载,并且不会由 Parcel 处理。这是因为 importScripts 相对于页面解析 URL,而不是 Worker 脚本。因此,importScripts 的参数必须是完全限定的绝对 URL,而不是相对路径。

以下示例显示了支持和不支持的模式。

// ✅ 绝对 URL
importScripts("http://some-cdn.com/worker.js");

// ✅ 计算的 URL
importScripts(location.origin + "/worker.js");

// 🚫 相对路径
importScripts("worker.js");

// 🚫 绝对路径
importScripts("/worker.js");

Worklets

#

Parcel 还支持 Worklets,包括 CSS Houdini 绘制 Worklets 以及 Web 音频 Worklets。这些允许您挂接浏览器中渲染过程或音频处理管道的低级方面。

绘制 Worklets 使用以下语法自动检测:

CSS.paintWorklet.addModule(new URL("worklet.js", import.meta.url));

Web 音频 Worklets 是不可静态分析的,因此对于这些 Worklets,您可以使用 worklet: 方案导入 Worklet 文件的 URL,该 URL 已编译为正确的环境。

import workletUrl from "worklet:./worklet.js";

context.audioWorklet.addModule(workletUrl);

Worklets 始终是模块 - 没有经典脚本 Worklets。这意味着 type: 'module' 选项对于 Worklets 是不必要的,并且 importScripts 是不受支持的。

此外,Worklets 中不支持动态 import()

转置

#

Parcel 支持开箱即用地转换 JSX、TypeScript 和 Flow,以及转换现代 JavaScript 语法以支持旧浏览器。此外,Babel 还支持实验性或自定义 JavaScript 转换。

JSX

#

Parcel 内置支持 JSX,这是一种在 JavaScript 中编写类似 HTML 的语法,通常与 React 一起使用。要使用 JSX,只需在文件中编写 JSX 代码,Parcel 将自动处理转换。

App.jsx:
import React from "react";

export default function App() {
return (
<div>
<h1>Hello, world!</h1>
<p>This is a JSX example.</p>
</div>
);
}

默认情况下,Parcel 使用 Babel 将 JSX 转换为标准 JavaScript。您可以通过在 package.json 中配置 Babel 来自定义 JSX 转换。

React 运行时

#

对于 React 17 及更高版本,Parcel 支持新的 JSX 转换,这意味着您不再需要在每个文件中导入 React。

// 不再需要导入 React
export default function App() {
return <div>Hello, world!</div>;
}

Flow

#

有关 Flow 的详细信息,请参见 TypeScript 文档。

TypeScript

#

Parcel 内置支持 TypeScript,这是 JavaScript 的超集,增加了静态类型检查。要使用 TypeScript,只需创建带有 .ts.tsx 扩展名的文件。

greeter.ts:
function greet(name: string) {
return `Hello, ${name}!`;
}

console.log(greet("World"));

Parcel 使用 esbuild 快速转译 TypeScript,这比传统的 tsc 编译器快得多。

类型检查

#

默认情况下,Parcel 不执行类型检查。如果您想要类型检查,可以使用 --typecheck 标志:

parcel build src/index.ts --typecheck

转译

#

Parcel 使用 esbuild 自动转译 JavaScript,以支持旧版浏览器。转译过程包括:

浏览器目标

#

您可以在 package.json 中使用 browserslist 字段指定目标浏览器:

{
"browserslist": ["> 0.5%", "last 2 versions", "not dead"]
}

Babel

#

尽管 Parcel 使用 esbuild 进行快速转译,但仍然支持 Babel 进行更复杂的转换。要使用 Babel,请创建 .babelrcbabel.config.json

{
"presets": ["@babel/preset-env"]
}

代码拆分

#

Parcel 通过动态 import() 支持自动代码拆分。这允许您按需加载代码:

import("./pages/about").then(function (page) {
page.render();
});

生产构建

#

在生产模式下,Parcel 包含优化以减少代码文件大小。有关详细信息,请参见生产文档。

压缩

#

Parcel 使用 esbuild 自动压缩 JavaScript,以减小生产构建的文件大小。

作用域提升

#

Parcel 支持作用域提升,这是一种通过将模块合并到单个作用域中来减少代码大小和改善运行时性能的技术。默认情况下,在生产构建中启用作用域提升。

内容哈希

#

在生产构建中,Parcel 会为输出文件名添加内容哈希,以实现长期缓存。这意味着只有在文件内容发生变化时,文件名才会更改。

性能

#

Parcel 使用 esbuild 提供极快的 JavaScript 处理速度,显著减少构建时间。

总结

#

Parcel 提供了对现代 JavaScript 生态系统的全面支持,包括:

通过其零配置方法,Parcel 使 JavaScript 开发变得简单而高效。

Minification

#

Parcel 使用 esbuild 自动压缩 JavaScript,以减小生产构建的文件大小。

Tree shaking

#

Parcel 支持树摇(Tree Shaking),这是一种通过删除未使用的代码来减小最终捆绑包大小的技术。这有助于优化应用程序的性能和加载时间。