社区新闻

webpack 与 WordPress 包如何交互

查看官方原文 ↗ 发布于

对于您的用户而言,在前端使用块让一切看起来都很简单。但在幕后,您越了解并使用将 React 和 JSX 转换为任何浏览器都能读取的纯 JavaScript 的构建过程,您的工作就会变得越轻松——这是真的。

在前端开发中,转译器的概念并不新鲜。(这是一种将源代码从一种语言翻译成另一种语言的程序。)您可能听说过 SASS 和 CoffeeScript;它们的输出必须经过转译器处理,浏览器才能呈现。

我们可以使用转译器来减轻我们繁琐、重复的任务负担,比如浏览器前缀。像 BabelPostCSS 这样的插件让我们可以专注于代码及其功能,而插件则添加前缀以及我们的代码在每个浏览器中工作所需的其他内容。

除了转译器,还有打包器——而且有很多!——来管理构建过程。像 GruntBower 这样的工具是最早出现的,现在已有近十年历史。在很大程度上,Web 开发已经转向更现代的工具,如 webpackVite 等。

WordPress 使用 webpack

WordPress 在其构建过程中使用 webpack。您可以在 @wordpress/scripts 包中找到它,该包与来自 @wordpress/create-block 包的块一起输出。

一个有趣的事实:这正是 Gutenberg 项目用来构建 Gutenberg 插件的包!

该包的意图是为您配置 webpack,以便您可以立即开始您的项目。因为自行配置 webpack 可能非常困难。

“不存在”的导入

如果您有 JavaScript 背景,可能之前使用过包。您首先使用 npm install 安装包,然后从这些包中导入项目以在您的项目中使用。

您也会熟悉从包中导入项目如何影响您的包大小(webpack 输出的 JavaScript 文件)。通常,您导入的越多,文件就越大,您可以通过代码分割摇树优化等技术来解决这个问题。

当您使用 WordPress 包时,您可能注意到了两件事:

  • 您从不使用 npm install 安装这些包。
  • 无论您从 @wordpress/* 包中导入多少项目,包大小几乎不会改变(如果有的话)。

难道 WordPress 找到了一种无需导入且无论导入多少东西都不会增加包大小的 JavaScript 工作方式吗?

简短的回答是:是的。但仅限于 WordPress 内置的包。因为 WordPress 会自动将这些包加入队列并添加到全局的 wp 对象中。

显示 wp 全局对象内容的浏览器控制台。

因为这些包已经可用,所以使用它们比将它们与您的自定义脚本重新打包更有意义——而这正是幕后发生的事情。

例如,这段代码片段生成了一个非常简单的 Button 组件,从 @wordpress/components 包导入。

import { Button } from '@wordpress/components';

const MyComponent = () => {
   return <Button>{__('Click me!')}</Button>;
}
export default MyComponent;

如果您查看 webpack 将其转译成的文件,您会看到一些看起来像这样的行。这是 webpack 将我们正在使用的项目包的一些路径导出到 wp 全局对象上。

/***/ "@wordpress/components":

/*!************************************!*

 !*** external ["wp","components"] ***!

 ************************************/

/***/ ((module) => {

module.exports = window["wp"]["components"];

/***/ }),

/***/ "@wordpress/element":

/*!*********************************!*

 !*** external ["wp","element"] ***!

 *********************************/

/***/ ((module) => {

module.exports = window["wp"]["element"];

然后,在文件的后面,我们有这个。它相当简洁,但这是 webpack 创建引用上面定义的导出的变量。这些变量包含了该包的所有内容。

var _wordpress_element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @wordpress/element */ "@wordpress/element");

var _wordpress_element__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__);

var _wordpress_components__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @wordpress/components */ "@wordpress/components");

var _wordpress_components__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_wordpress_components__WEBPACK_IMPORTED_MODULE_1__);

最后,实际的组件引用这些变量来检索您需要的项目。

const MyComponent = () => {
     return (0,_wordpress_element__WEBPACK_IMPORTED_MODULE_0__.createElement)(_wordpress_components__WEBPACK_IMPORTED_MODULE_1__.Button, null, "'Click me!");
};

const __WEBPACK_DEFAULT_EXPORT__ = (MyComponent);

这里发生了很多事情,所以如果您此时感到不知所措,请不要担心。您实际上不需要阅读(或理解)这个文件。

如果您简化代码,用实际路径替换变量,它看起来像这样。这样就更容易看出发生了什么!

const MyComponent = () => {
 return (0,wp.element.createElement)(wp.components.Button, null, "'Click me!");
};

const __WEBPACK_DEFAULT_EXPORT__ = (MyComponent);

如您所见,您的代码已经改变:它查看的是 wp 全局对象,而不是被导入的包。

您可能已经注意到,在上面的示例中使用了 <strong>wp.element.createElement</strong>,但在 myComponent 示例中,根本没有引用它。这个函数是由构建过程添加的。

它是 React 的一部分,用于在不使用 JSX 的情况下创建 React 组件。有关更多详细信息,请参阅官方文档

所以最大的问题是:Webpack 是如何做到这一点的?

DependencyExtractionWebPackPlugin

作为 @wordpress/scripts 包中 webpack 配置的一部分,有一个名为 DependencyExtractionWebPackPlugin 的自定义 webpack 插件,它做了两件非常重要的事情来实现这一切:

  • 转换导入
  • 自动化依赖管理

任务一:转换导入

它的第一个任务是检测任何以 wordpress 开头(以及其他一些)的 import 语句,并使它们访问 wp 全局对象。

本质上,它将这段代码

import { Button } from '@wordpress/components';

转换成这样:

const { Button } = wp.components;

项目中安装的任何第三方包都不会被转换,并且仍然会增加包大小。

任务二:自动化依赖管理

当您在 WordPress 中将脚本加入队列时,通常需要定义一个依赖项数组,这些依赖项将在您的脚本之前加载。

但是当您使用本文中的 webpack 工作流时,这就是 DependencyExtractionWebPackPlugin 的第二个任务。

它将生成一个名为 index.asset.php 的 PHP 文件,其中列出了它刚刚从 import 语句转换而来的依赖项。然后,它会根据文件最后一次构建的时间为每个依赖项添加版本号。

<?php return array('dependencies' => array('wp-components', 'wp-element'), 'version' => '1d75a9e186898f1d6300');

index.asset.php 是默认名称,但它源自 webpack 中定义的入口点名称。如果我们将入口点名称定义为 test,则生成的文件将称为 test.asset.php。这仅在您扩展 webpack 配置时才重要。

如果您正在构建块,块注册过程会自动加载此文件。但如果您想在代码中使用它,可以执行类似以下的操作:

add_action( 'enqueue_block_editor_assets', 'enqueue_my_file' );

function enqueue_my_file() {
	// 查找路径。
	$dependencies_file_path = plugin_dir_path( __FILE__ ) . 'build/index.asset.php';
	// 如果文件存在,则将其加入队列。
	if ( file_exists( $dependencies_file_path ) ) {
		$dependencies = require $dependencies_file_path;
		wp_enqueue_script(
			'my-script',
			plugin_dir_url( __FILE__ ) . 'build/index.js',
			$dependencies['dependencies'],
			$dependencies['version']
		);
	}
}

这个文件非常方便。一旦您引用了它,就再也不用担心更新依赖项了!

绕过构建过程

您可能想知道,我为什么需要这个?它所做的只是将代码转换为我同样可以轻松编写的东西

嗯,简单的答案是,您不需要。您可以完全绕过构建过程,编写标准的 JavaScript;它也能正常工作。

例如:

const MyComponent = function () {
   return wp.element.createElement( wp.components.Button, null, "'Click me!" );
}

这段代码完全有效,并且在 Block Development Examples 存储库中有一些完全不使用构建过程的示例。

使用构建过程的好处

再次强调,您并非必须使用构建过程。这完全是可选的。

但是看看构建过程能给您带来什么:

  • 自动块检测
  • 自动化依赖管理
  • 使用 JSX
  • 导入语法
  • 静态代码分析
  • 可用命令

所有这些都无需额外费用!(开个玩笑……)但让我们看看为什么您可能想要这些好处。

自动块检测

如果您正在构建自定义块,构建过程可以自动检测和构建添加到您项目中的任何块。

自动化依赖管理

我们已经讨论过自动化依赖管理,但问题是:如果您不为自定义块使用构建过程,您仍然需要创建 index.asset.php 文件。块注册需要它。然后您需要手动更新依赖项。

所以就像那些俗气的警察节目里说的:您可以选择困难的方式,也可以选择简单的方式。

您可以使用 JSX

当您使用构建过程时,您可以使用 JSX 语法。这是许多与 React.js(Gutenberg 构建于其上的框架)一起使用的开发人员使用的 JavaScript 语法扩展。它的语法很像 HTML,这使得它比原生 JS 更易于阅读(也更易于维护!)。

JSX

return (
   <p { ...useBlockProps() }>
       { __(
           'My First Block – hello from the editor!',
           'my-first-block'
       ) }
   </p>
);

原生 JavaScript

return el(
   'p',
   useBlockProps(),
   __( 'My First Block – hello from the editor!', 'my-first-block' )
);

导入语法

利用构建过程可以让您使用 JavaScript 模块来导入和导出文件和组件。这使得您的代码更易于组织,也更容易重用。

静态代码分析

构建过程为您提供了许多工具,可以检查代码格式并自动修复代码规范问题。

以及更多

上面的好处仅仅是个开始。还有更多的命令和其他工具可以让您的生活更轻松,项目更好。请在 @wordpress/scripts 的官方文档中查看所有内容。

{
   "scripts": {
       "build": "wp-scripts build",
       "check-engines": "wp-scripts check-engines",
       "check-licenses": "wp-scripts check-licenses",
       "format": "wp-scripts format",
       "lint:css": "wp-scripts lint-style",
       "lint:js": "wp-scripts lint-js",
       "lint:md:docs": "wp-scripts lint-md-docs",
       "lint:md:js": "wp-scripts lint-md-js",
       "lint:pkg-json": "wp-scripts lint-pkg-json",
       "packages-update": "wp-scripts packages-update",
       "plugin-zip": "wp-scripts plugin-zip",
       "start": "wp-scripts start",
       "test:e2e": "wp-scripts test-e2e",
       "test:unit": "wp-scripts test-unit-js"
   }
}

自定义 Webpack 配置

正如官方文档所指出的,您可以根据需要自定义 webpack。怎么做?添加一个 webpack.config.js,然后扩展默认配置。我们可以花一周时间讨论扩展配置的所有方法,但这远远超出了本文的范围。可以说,webpack 文档中有很多内容需要解读。

但这里有一个常见的情况值得介绍。

如果您想在项目中创建自定义块,但还需要为另一个用例生成一个单独的文件,该怎么办?例如,如果您想生成 SlotFills 怎么办?

要开始,您需要添加一个新的入口点

// 从 @wordpress/scripts 包导入原始配置。
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );


// 导入辅助函数以在 src 目录中查找和生成入口点
const { getWebpackEntryPoints } = require( '@wordpress/scripts/utils/config' );


// 通过扩展 Webpack 配置来添加新的入口点。
module.exports = {
   ...defaultConfig,
   entry: {
       ...getWebpackEntryPoints(),
       custom: './path/to/index.js',
   },
};

这里的注意事项是,您还需要保持动态检测和构建块的功能——这通常由 getWebpackEntryPoints 函数处理。