Module Bundling
Stencil uses Rollup under the hood to bundle your components. This guide will explain and recommend certain workarounds for some of the most common bundling issues you might encounter.
One Component Per Module
Code-splitting in Rollup happens at the module level, which means that it's not possible to code-split logic within the same module (.ts
file). For Stencil to bundle your components most efficiently, you must declare a single component (class decorated with
@Component
) per TypeScript file, and the component itself
must be unique
export
.
Modules that contain a component are entry-points, which means that no other module should import anything from them, ie. they cannot contain any shared logic imported by other components (modules).
The following example is NOT valid:
src/components/my-cmp.tsx:
// This module has a component, you cannot export anything else
export function someUtilFunction() {
console.log('do stuff');
}
@Component({
tag: 'my-cmp'
})
export class MyCmp {}
In this case, the compiler will emit an error that looks like this:
[ ERROR ] src/components/my-cmp.tsx:4:1
To allow efficient bundling, modules using @Component() can only have a single export which is the component
class itself. Any other exports should be moved to a separate file. For further information check out:
https://stenciljs.com/docs/module-bundling
L4: export function someUtilFunction() {
L5: console.log('do stuff');
The solution is to move any shared functions or classes to a different
.ts
file, like this:
src/utils.ts:
export function someUtilFunction() {
console.log('do stuff');
}
src/components/my-cmp.tsx:
import { someUtilFunction } from '../utils.ts';
@Component({
tag: 'my-cmp'
})
export class MyCmp {}
src/components/my-cmp-two.tsx:
import { someUtilFunction } from '../utils.ts';
@Component({
tag: 'my-cmp-two'
})
export class MyCmpTwo {}
CommonJS Dependencies
Rollup depends on ES modules (ESM) to properly tree-shake the module graph, unfortunately, some third-party libraries ship their code using the CommonJS module system, which is not ideal.
Since CommonJS libraries are still common today, Stencil comes with
rollup-plugin-commonjs
already installed and configured.
At compiler-time, the rollup-plugin-commonjs
plugin does a best-effort to
transform CommonJS into ESM, but this is not always an easy task. CommonJS is dynamic by nature, while ESM is static by design.
Stencil's config exposes a commonjs
property that is passed down to the Rollup CommonJS plugin, you can use this setting to work around certain bundling issues.
NamedModules: X is not exported by X
Sometimes, Rollup is unable to properly static analyze CommonJS modules, and it misses some named exports. Fortunately, there is a workaround we can use.
As we already know, stencil.config.ts
exposes a
commonjs
property, in this case, we can take advantage of
the namedExports
property.
Let's say, Rollup fails, when trying to use the hello
named export of the
commonjs-dep
module:
// NamedModules: hello is not exported by commonjs-dep
import { hello } from 'commonjs-dep';
We can use the config.commonjs.namedExports
setting in the
stencil.config.ts
file to work around the issue:
export const config = {
commonjs: {
namedExports: {
// commonjs-dep has a "hello" export
'commonjs-dep': ['hello']
}
}
}
We can set a map of
namedExports
for problematic dependencies, in this case, we are explicitally defining the namedhello
export in thecommonjs-dep
module.
For further information, check out the rollup-plugin-commonjs docs.
Custom Rollup plugins
Stencil provides an API to pass custom rollup plugins to the bundling process in
stencil.config.ts
. Under the hood, stencil ships with some built-in plugins including
node-resolve
and
commonjs
, since the execution order of some rollup plugins is important, stencil provides an API to inject custom plugin
before node-resolve and after commonjs transform:
export const config = {
rollupPlugins: {
before: [
// Plugins injected before rollupNodeResolve()
resolvePlugin()
],
after: [
// Plugins injected after commonjs()
nodePolyfills()
]
}
}
As a rule of thumb, plugins that need to resolve modules, should be place in
before
, while code transform plugins like:
node-polyfills
,
replace
... should be placed in after
. Follow the plugin's documentation to make sure it's executed in the right order.
Node Polyfills
Depending on which libraries a project is dependent on, the rollup-plugin-node-polyfills plugin may be required. In such cases, an error message similar to the following will be displayed at build time.
[ ERROR ] Bundling Node Builtin
For the import "crypto" to be bundled from 'problematic-dep',
ensure the "rollup-plugin-node-polyfills" plugin is installed
and added to the stencil config plugins.
This is caused by some third-party dependencies that use
Node APIs that are not available in the browser, the
rollup-plugin-node-polyfills
plugin works by transparently polyfilling this missing APIs in the browser.
1. Install rollup-plugin-node-polyfills
:
npm install rollup-plugin-node-polyfills --save-dev
2. Update the
stencil.config.ts
file including the plugin:
import { Config } from '@stencil/core';
import nodePolyfills from 'rollup-plugin-node-polyfills';
export const config: Config = {
namespace: 'mycomponents',
rollupPlugins: {
after: [
nodePolyfills(),
]
}
};
NOTE:
rollup-plugin-node-polyfills
is a code-transform plugin, so it needs to run AFTER the commonjs transform plugin, that's the reason it's placed in the "after" array of plugins.
Strict Mode
ES modules are always parsed in strict mode. That means that certain non-strict constructs (like octal literals) will be treated as syntax errors when Rollup parses modules that use them. Some older CommonJS modules depend on those constructs, and if you depend on them your bundle will blow up. There's nothing we can do about that.
Luckily, there is absolutely no good reason not to use strict mode for everything — so the solution to this problem is to lobby the authors of those modules to update them.
Source: https://github.com/rollup/rollup-plugin-commonjs#strict-mode