Skip to content

常见问题

为什么 ES 模块比 CommonJS 模块更好?

ES 模块是官方标准,是 JavaScript 代码结构的明确前进道路,而 CommonJS 模块是一种特殊的旧版格式,在 ES 模块被提出之前作为权宜之计。 ES 模块允许静态分析,有助于诸如树摇动和作用域提升等优化,并提供循环引用和实时绑定等高级功能。

ES modules are an official standard and the clear path forward for JavaScript code structure, whereas CommonJS modules are an idiosyncratic legacy format that served as a stopgap solution before ES modules had been proposed. ES modules allow static analysis that helps with optimizations like tree-shaking and scope-hoisting, and provide advanced features like circular references and live bindings.

什么是 "摇树优化?"

Tree-shaking,也称为 "实时代码包含",是 Rollup 消除给定项目中实际未使用的代码的过程。 它是 死代码消除的形式,但在输出大小方面比其他方法更有效。 该名称源自模块的 抽象语法树(不是模块图)。 该算法首先标记所有相关语句,然后 "震动语法树" 删除所有死代码。 它的理念与 标记清除垃圾收集算法 类似。 尽管该算法不限于 ES 模块,但它们使其更加高效,因为它们允许 Rollup 将所有模块一起视为具有共享绑定的大型抽象语法树。

Tree-shaking, also known as "live code inclusion", is Rollup's process of eliminating code that is not actually used in a given project. It is a form of dead code elimination but can be much more efficient than other approaches with regard to output size. The name is derived from the abstract syntax tree of the modules (not the module graph). The algorithm first marks all relevant statements and then "shakes the syntax tree" to remove all dead code. It is similar in idea to the mark-and-sweep garbage collection algorithm. Even though this algorithm is not restricted to ES modules, they make it much more efficient as they allow Rollup to treat all modules together as a big abstract syntax tree with shared bindings.

如何在 Node.js 中将 Rollup 与 CommonJS 模块结合使用?

Rollup 致力于实现 ES 模块的规范,而不一定是 Node.js、NPM、require() 和 CommonJS 的行为。 因此,CommonJS 模块的加载和 Node 模块位置解析逻辑的使用都作为可选插件实现,默认情况下不包含在 Rollup 核心中。 只需 npm install普通 js节点解析 插件,然后使用 rollup.config.js 文件启用它们,你就应该完成所有设置。 如果模块导入 JSON 文件,你还需要 json 插件。

Rollup strives to implement the specification for ES modules, not necessarily the behaviors of Node.js, NPM, require(), and CommonJS. Consequently, loading of CommonJS modules and use of Node's module location resolution logic are both implemented as optional plugins, not included by default in the Rollup core. Just npm install the commonjs and node-resolve plugins and then enable them using a rollup.config.js file and you should be all set. If the modules import JSON files, you will also need the json plugin.

为什么 node-resolve 不是内置功能?

主要原因有两个:

There are two primary reasons:

  1. 从哲学上讲,这是因为 Rollup 本质上是 Node 和浏览器中原生模块加载器的某种 填充材料。 在浏览器中,import foo from 'foo' 不起作用,因为浏览器不使用 Node 的解析算法。

  2. 在实践层面上,如果使用良好的 API 将这些问题巧妙地分开,那么开发软件就会容易得多。 Rollup 的核心相当大,一切阻止它变得更大的事情都是好事。 同时,修复错误和添加功能也更容易。 通过保持 Rollup 的精简,技术债务的可能性很小。

请参阅 这个问题 以获得更详细的解释。

Please see this issue for a more verbose explanation.

为什么在代码分割时我的条目块中会出现额外的导入?

默认情况下,创建多个块时,条目块的依赖的导入将作为空导入添加到条目块本身。 例子

By default, when creating multiple chunks, imports of dependencies of entry chunks will be added as empty imports to the entry chunks themselves. Example:

js
// input
// main.js
import value from './other-entry.js';
console.log(value);

// other-entry.js
import externalValue from 'external';
export default 2 * externalValue;

// output
// main.js
import 'external'; // this import has been hoisted from other-entry.js
import value from './other-entry.js';
console.log(value);

// other-entry.js
import externalValue from 'external';
var value = 2 * externalValue;
export default value;
// input
// main.js
import value from './other-entry.js';
console.log(value);

// other-entry.js
import externalValue from 'external';
export default 2 * externalValue;

// output
// main.js
import 'external'; // this import has been hoisted from other-entry.js
import value from './other-entry.js';
console.log(value);

// other-entry.js
import externalValue from 'external';
var value = 2 * externalValue;
export default value;

这不会影响代码执行顺序或行为,但会加快代码的加载和解析速度。 如果没有这种优化,JavaScript 引擎需要执行以下步骤才能运行 main.js

This does not affect code execution order or behaviour, but it will speed up how your code is loaded and parsed. Without this optimization, a JavaScript engine needs to perform the following steps to run main.js:

  1. 加载并解析 main.js。 最后,将发现对 other-entry.js 的导入。
  2. 加载并解析 other-entry.js。 最后,将发现对 external 的导入。
  3. 加载并解析 external
  4. 执行 main.js

通过这种优化,JavaScript 引擎将在解析入口模块后发现所有传递依赖,从而避免瀑布:

With this optimization, a JavaScript engine will discover all transitive dependencies after parsing an entry module, avoiding the waterfall:

  1. 加载并解析 main.js。 最后,将发现对 other-entry.jsexternal 的导入。
  2. 加载并解析 other-entry.jsexternal。 从 other-entry.js 导入的 external 已经被加载和解析。
  3. 执行 main.js

在某些情况下,可能不需要这种优化,在这种情况下,你可以通过 output.hoistTransitiveImports 选项将其关闭。 使用 output.preserveModules 选项时也不会应用此优化。

There may be situations where this optimization is not desired, in which case you can turn it off via the output.hoistTransitiveImports option. This optimization is also never applied when using the output.preserveModules option.

如何将 Polyfill 添加到 Rollup 包中?

尽管 Rollup 通常会在打包时尝试保持准确的模块执行顺序,但有两种情况并不总是如此: 代码分割和外部依赖。 外部依赖的问题最为明显,请参阅以下 例子

Even though Rollup will usually try to maintain exact module execution order when bundling, there are two situations when this is not always the case: code-splitting and external dependencies. The problem is most obvious with external dependencies, see the following example:

js
// main.js
import './polyfill.js';
import 'external';
console.log('main');

// polyfill.js
console.log('polyfill');
// main.js
import './polyfill.js';
import 'external';
console.log('main');

// polyfill.js
console.log('polyfill');

这里的执行顺序是 polyfill.jsexternalmain.js。 现在,当你打包代码时,你将得到

Here the execution order is polyfill.jsexternalmain.js. Now when you bundle the code, you will get

js
import 'external';
console.log('polyfill');
console.log('main');
import 'external';
console.log('polyfill');
console.log('main');

执行顺序为 externalpolyfill.jsmain.js。 这不是 Rollup 将 import 放在打包包顶部引起的问题—导入始终首先执行,无论它们位于文件中的哪个位置。 这个问题可以通过创建更多块来解决: 如果 polyfill.js 最终位于与 main.js将保留正确的执行顺序 不同的块中。 然而,Rollup 中还没有自动执行此操作的方法。 对于代码分割,情况类似于 Rollup 尝试创建尽可能少的块,同时确保不执行不需要的代码。

with the execution order externalpolyfill.jsmain.js. This is not a problem caused by Rollup putting the import at the top of the bundle—imports are always executed first, no matter where they are located in the file. This problem can be solved by creating more chunks: If polyfill.js ends up in a different chunk than main.js, correct execution order will be preserved. However, there is not yet an automatic way to do this in Rollup. For code-splitting, the situation is similar as Rollup is trying to create as few chunks as possible while making sure no code is executed that is not needed.

对于大多数代码来说这不是问题,因为 Rollup 可以保证:

For most code this is not a problem, because Rollup can guarantee:

如果模块 A 导入模块 B 并且没有循环导入,则 B 将始终在 A 之前执行。

然而,这对于 Polyfill 来说是一个问题,因为它们通常需要首先执行,但通常不希望在每个模块中导入 Polyfill。 幸运的是,这不是必需的:

This is however a problem for polyfills, as those usually need to be executed first but it is usually not desired to place an import of the polyfill in every single module. Luckily, this is not needed:

  1. 如果没有依赖于该 polyfill 的外部依赖,则将 polyfill 的导入作为第一个语句添加到每个静态入口点就足够了。
  2. 否则,另外将 polyfill 设为单独的条目或 手动分块 将始终确保它首先执行。

Rollup 是用于构建库或应用吗?

Rollup 已被许多主要 JavaScript 库使用,也可用于构建绝大多数应用。 但是,如果你想在旧版浏览器中使用代码分割或动态导入,则需要额外的运行时来处理加载丢失的块。 我们建议使用 SystemJS 生产构建,因为它与 Rollup 的系统格式输出完美集成,并且能够正确处理所有 ES 模块实时绑定和重新导出边缘情况。 或者,也可以使用 AMD 加载程序。

Rollup is already used by many major JavaScript libraries, and can also be used to build the vast majority of applications. However, if you want to use code-splitting or dynamic imports with older browsers, you will need an additional runtime to handle loading missing chunks. We recommend using the SystemJS Production Build as it integrates nicely with Rollup's system format output and is capable of properly handling all the ES module live bindings and re-export edge cases. Alternatively, an AMD loader can be used as well.

如何在浏览器中运行 Rollup 本身

虽然常规 Rollup 构建依赖于一些 NodeJS 功能,但还有一个仅使用浏览器 API 的浏览器构建。 你可以通过安装它

While the regular Rollup build relies on some NodeJS features, there is also a browser build available that only uses browser APIs. You can install it via

shell
npm install @rollup/browser
npm install @rollup/browser

并在你的脚本中,通过导入它

and in your script, import it via

js
import { rollup } from '@rollup/browser';
import { rollup } from '@rollup/browser';

或者,你可以从 CDN 导入,例如 用于 ESM 构建

Alternatively, you can import from a CDN, e.g. for the ESM build

js
import * as rollup from 'https://unpkg.com/@rollup/browser/dist/es/rollup.browser.js';
import * as rollup from 'https://unpkg.com/@rollup/browser/dist/es/rollup.browser.js';

对于 UMD 构建

and for the UMD build

html
<script src="https://unpkg.com/@rollup/browser/dist/rollup.browser.js"></script>
<script src="https://unpkg.com/@rollup/browser/dist/rollup.browser.js"></script>

这将创建一个全局变量 window.rollup。 由于浏览器版本无法访问文件系统,因此你需要提供插件来解析和加载你想要打包的所有模块。 这是一个执行此操作的人为示例:

which will create a global variable window.rollup. As the browser build cannot access the file system, you need to provide plugins that resolve and load all modules you want to bundle. Here is a contrived example that does this:

js
const modules = {
	'main.js': "import foo from 'foo.js'; console.log(foo);",
	'foo.js': 'export default 42;'
};

rollup
	.rollup({
		input: 'main.js',
		plugins: [
			{
				name: 'loader',
				resolveId(source) {
					if (modules.hasOwnProperty(source)) {
						return source;
					}
				},
				load(id) {
					if (modules.hasOwnProperty(id)) {
						return modules[id];
					}
				}
			}
		]
	})
	.then(bundle => bundle.generate({ format: 'es' }))
	.then(({ output }) => console.log(output[0].code));
const modules = {
	'main.js': "import foo from 'foo.js'; console.log(foo);",
	'foo.js': 'export default 42;'
};

rollup
	.rollup({
		input: 'main.js',
		plugins: [
			{
				name: 'loader',
				resolveId(source) {
					if (modules.hasOwnProperty(source)) {
						return source;
					}
				},
				load(id) {
					if (modules.hasOwnProperty(id)) {
						return modules[id];
					}
				}
			}
		]
	})
	.then(bundle => bundle.generate({ format: 'es' }))
	.then(({ output }) => console.log(output[0].code));

此示例仅支持两个导入,"main.js""foo.js",并且不支持相对导入。 这是另一个使用绝对 URL 作为入口点并支持相对导入的示例。 在这种情况下,我们只是重新打包 Rollup 本身,但它可以用于公开 ES 模块的任何其他 URL:

This example only supports two imports, "main.js" and "foo.js", and no relative imports. Here is another example that uses absolute URLs as entry points and supports relative imports. In that case, we are just re-bundling Rollup itself, but it could be used on any other URL that exposes an ES module:

js
rollup
	.rollup({
		input: 'https://unpkg.com/rollup/dist/es/rollup.js',
		plugins: [
			{
				name: 'url-resolver',
				resolveId(source, importer) {
					if (source[0] !== '.') {
						try {
							new URL(source);
							// If it is a valid URL, return it
							return source;
						} catch {
							// Otherwise make it external
							return { id: source, external: true };
						}
					}
					return new URL(source, importer).href;
				},
				async load(id) {
					const response = await fetch(id);
					return response.text();
				}
			}
		]
	})
	.then(bundle => bundle.generate({ format: 'es' }))
	.then(({ output }) => console.log(output));
rollup
	.rollup({
		input: 'https://unpkg.com/rollup/dist/es/rollup.js',
		plugins: [
			{
				name: 'url-resolver',
				resolveId(source, importer) {
					if (source[0] !== '.') {
						try {
							new URL(source);
							// If it is a valid URL, return it
							return source;
						} catch {
							// Otherwise make it external
							return { id: source, external: true };
						}
					}
					return new URL(source, importer).href;
				},
				async load(id) {
					const response = await fetch(id);
					return response.text();
				}
			}
		]
	})
	.then(bundle => bundle.generate({ format: 'es' }))
	.then(({ output }) => console.log(output));

Rollup 标志是谁设计的? 很可爱。

朱利安·劳埃德

Julian Lloyd!