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 模块,并使用其 exports 对象上的 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 中的模块是比较新的,并且有许多传统库和脚本不希望被隔离。

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

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

<script src="jquery.js"></script>
<script>
// The $ variable is now available globally.
$('.banner').show();
</script>

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

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

Screenshot of an error message showing "Browser scripts cannot have imports or exports. Add the type='module' attribute to the script tag."

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 worker、service worker 和 worklet,它们允许将工作移到不同的线程。

Web Workers

#

Web worker 是最通用的 worker 类型。它们允许您在后台线程中运行任意 CPU 密集型工作,以避免阻塞用户界面。Worker 是使用 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 worker 的更多信息,请参阅 MDN 上的文档。

Service Workers

#

Service worker 在后台运行,并提供离线缓存、后台同步和推送通知等功能。它们是使用 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'}
);

注意:动态 import() 在 service worker 中不受支持。

Service worker 通常用于预缓存静态资产,如 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 worker 的更多信息,请参阅 MDN 上的文档,以及 web.dev 上的 离线食谱

传统脚本 worker

#

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

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

// ✅ absolute URL
importScripts('http://some-cdn.com/worker.js');

// ✅ computed URL
importScripts(location.origin + '/worker.js');

// 🚫 relative path
importScripts('worker.js');

// 🚫 absolute path
importScripts('/worker.js');

Worklets

#

Parcel 还支持 worklet,包括 CSS Houdini paint worklet 以及 web 音频 worklet。这些允许您在浏览器中挂钩到渲染过程或音频处理管道的低级方面。

Paint worklet 使用以下语法自动检测

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

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

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

context.audioWorklet.addModule(workletUrl);

Worklet 始终是模块 - 没有传统脚本 worklet。这意味着 type: 'module' 选项对于 worklet 不是必需的,并且 importScripts 不受支持。

此外,动态import()在 worklet 中不受支持。

转译

#

Parcel 默认支持 JSX、TypeScript 和 Flow 的转译,以及将现代 JavaScript 语法转译为支持旧版浏览器的语法。此外,还支持 Babel 来启用实验性或自定义 JavaScript 转换。

JSX

#

Parcel 默认支持 JSX。JSX 在 .jsx.tsx 文件中自动启用,或者当以下库之一在您的 package.json 中定义为依赖项时。

正确的 JSX pragma 也会根据您使用的库自动推断。Parcel 还会自动检测已安装的 React 或 Preact 版本,并在支持的情况下启用现代 JSX 转换

JSX 编译也可以使用 tsconfig.jsonjsconfig.json 文件进行配置。这允许覆盖运行时、pragma 和其他选项。有关更多信息,请参阅TSConfig 参考

tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}

Flow

#

flow-bin 在您的项目根目录 package.json 中列为依赖项时,Flow 支持会自动启用。您还必须在您希望编译的文件中使用 @flow 指令。

Parcel 目前使用 Babel 来去除 Flow 类型。如果您有自定义 Babel 配置,则需要自己添加 Flow 插件。有关更多详细信息,请参阅Babel

TypeScript

#

请参阅TypeScript

浏览器兼容性

#

默认情况下,Parcel 不会对旧版浏览器执行任何 JavaScript 语法转译。这意味着如果您使用现代语言功能编写代码,Parcel 将输出的就是这些代码。您可以使用 package.json 中的 browserslist 字段声明应用程序支持的浏览器。当声明此字段时,Parcel 将相应地转译您的代码,以确保与您支持的浏览器兼容。

package.json
{
"browserslist": "> 0.5%, last 2 versions, not dead"
}

有关如何配置此功能以及 Parcel 对自动差异化捆绑的支持的更多详细信息,请参阅目标 文档。

默认情况下,Parcel 支持所有标准 JavaScript 功能,以及已在一种或多种浏览器中发布的提案。您还可以使用 tsconfig.jsonjsconfig.json 文件启用对未来提案的支持。目前,唯一支持的提案是装饰器,您可能通过 TypeScript 使用它。

tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true
}
}

可以使用Babel 启用更多实验性功能。

Babel

#

Babel 是一个流行的 JavaScript 转译器,拥有庞大的插件生态系统。在 Parcel 中使用 Babel 的方式与独立使用或与其他构建工具一起使用的方式相同。创建一个 Babel 配置文件,例如 .babelrc,Parcel 将自动将其拾取。

Parcel 支持项目范围的配置文件,例如 babel.config.json,以及文件相关的配置文件,例如 .babelrc。有关配置的更多详细信息,请参阅Babel 文档

注意:应避免使用 JavaScript Babel 配置(例如 babel.config.js)。这些会导致 Parcel 的缓存效率降低,这意味着每次重新启动 Parcel 时,所有 JS 文件都将重新编译。为了避免这种情况,请改用基于 JSON 的配置格式(例如 babel.config.json)。

默认预设

#

Parcel 默认包含对浏览器目标(等效于 @babel/preset-env)、JSX(等效于 @babel/preset-react)、TypeScript(等效于 @babel/preset-typescript)和 Flow(等效于 @babel/preset-flow)的转译。如果这些是您项目中唯一需要的转换,那么您可能根本不需要 Babel。

如果您有一个现有项目,其 Babel 配置仅包含上述预设,那么您可能可以将其删除。由于 Parcel 的内置转译器比 Babel 快得多,因此这可以显着提高构建性能。

自定义插件

#

如果您有除了上面列出的预设之外的自定义 Babel 预设或插件,您可以配置 Babel 仅包含这些自定义插件并省略标准预设。这将通过让 Babel 做更少的工作并让 Parcel 处理默认转换来提高构建性能。

babel.config.json
{
"plugins": ["@emotion/babel-plugin"]
}

由于 Parcel 使用 Babel 来转译 Flow,因此您需要在 Babel 配置中保留 @babel/preset-flow 以及任何自定义插件。否则,您的 Babel 配置将覆盖 Parcel 的默认值。可以删除上面列出的其他 Babel 预设。

@babel/preset-env@babel/plugin-transform-runtime 不了解 Parcel 的目标,这意味着差异化捆绑 将无法正常工作。这很可能会导致不必要的转译和更大的捆绑包大小。此外,@babel/preset-env 默认情况下会转译 ES 模块,这可能会导致作用域提升 出现问题。

@babel/preset-env@babel/plugin-transform-runtime 不是必需的,因为 Parcel 会自动处理对浏览器目标的转译。但是,如果您出于某种原因需要它们,则可以使用 Parcel 的包装器,这些包装器了解 Parcel 的目标。

babel.config.json
{
"presets": ["@parcel/babel-preset-env"],
"plugins": ["@parcel/babel-plugin-transform-runtime"]
}

与其他工具一起使用

#

虽然 Parcel 默认包含转译,但您可能仍然需要将 Babel 与其他工具一起使用,例如测试运行器,如Jest,以及 linter,如ESLint。如果是这种情况,您可能无法完全删除 Babel 配置。您可以让 Parcel 忽略您的 Babel 配置,这将带来性能优势并防止上述其他问题。

要禁用 Parcel 中的 Babel 转译,请覆盖 JavaScript 的默认 Parcel 配置以排除 @parcel/transformer-babel

.parcelrc
{
"extends": "@parcel/config-default",
"transformers": {
"*.{js,mjs,jsx,cjs,ts,tsx}": [
"@parcel/transformer-js",
"@parcel/transformer-react-refresh-wrap"
]
}
}

这将允许其他工具继续使用您的 Babel 配置,但禁用 Parcel 中的 Babel 转译。

生产

#

在生产模式下,Parcel 包含优化措施以减小代码的文件大小。有关其工作原理的更多详细信息,请参阅生产

缩小

#

在生产模式下,Parcel 会自动缩小您的代码以减小捆绑包的文件大小。默认情况下,Parcel 使用SWC 来执行缩小。为了向后兼容,这支持Terser 配置文件。要配置缩小器,您可以在项目根目录中创建一个 .terserrc 文件。有关可用选项的信息,请参阅SWC 文档

Parcel 的先前版本使用 Terser 作为默认的 JavaScript 缩小器。要继续使用 Terser 而不是 SWC,您可以在 .parcelrc 中配置 Parcel 以使用 @parcel/optimizer-terser 插件。有关更多信息,请参阅插件

树摇

#

在生产构建中,Parcel 会静态分析每个模块的导入和导出,并删除所有未使用的内容。这称为“树摇”或“死代码消除”。树摇支持静态和动态 import()、CommonJS 和 ES 模块,甚至跨语言支持 CSS 模块。

Parcel 还会在可能的情况下将模块连接到单个作用域中,而不是将每个模块包装在单独的函数中。这称为“作用域提升”。这有助于使缩小更有效,并且还通过使模块之间的引用成为静态引用而不是动态对象查找来提高运行时性能。

有关使树摇更有效的技巧,请参阅作用域提升 文档。