Skip to content

Commit

Permalink
fix(build): address @ionic/angular bundle size issue (#5705)
Browse files Browse the repository at this point in the history
This fixes an issue where when @ionic/angular was built using Stencil
and then used in an angular project it would produce much larger bundle
sizes than it had previously. This issue started appearing in Stencil
v4.17.x, after we switched to using esbuild for our production builds.

The issue occurred because of how bundles in `internal/` were depending
on code in `mock-doc/`. In particular, in `src/runtime/dom-extras.ts` we
had an import from `@stencil/core/mock-doc`. In our bundles this was
being correctly externalized, so that `internal/client/index.js` for
instance would import from `'../../mock-doc/index.cjs'`, but the problem
is that when an Angular project then tried to bundle this file later it
would not tree-shake the import properly and the whole contents of
mock-doc would be injected into the bundle, bloating it by 400kb or so.

This is because esbuild, which `ng build` uses under the hood, does not
support tree-shaking in commonjs modules. See here for details:

https://esbuild.github.io/api/#tree-shaking

The simplest fix for this is to add the `NODE_TYPES` enum to
`src/utils/constants.ts` so that the runtime does not need to have any
dependency on `mock-doc` at all.
  • Loading branch information
alicewriteswrongs committed Apr 26, 2024
1 parent aea6275 commit 0a7becc
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 13 deletions.
5 changes: 1 addition & 4 deletions scripts/esbuild/internal-platform-client.ts
Expand Up @@ -17,7 +17,7 @@ import { externalAlias, getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExte
* @param opts build options
* @returns an array of ESBuild option objects
*/
export async function getInternalClientBundle(opts: BuildOptions): Promise<ESBuildOptions[]> {
export async function getInternalClientBundles(opts: BuildOptions): Promise<ESBuildOptions[]> {
const inputClientDir = join(opts.srcDir, 'client');
const outputInternalClientDir = join(opts.output.internalDir, 'client');
const outputInternalClientPolyfillsDir = join(outputInternalClientDir, 'polyfills');
Expand Down Expand Up @@ -59,9 +59,6 @@ export async function getInternalClientBundle(opts: BuildOptions): Promise<ESBui
replace(createReplaceData(opts)),
externalAlias('@app-data', '@stencil/core/internal/app-data'),
externalAlias('@utils/shadow-css', './shadow-css.js'),
// we want to get the esm, not the cjs, since we're creating an esm
// bundle here
externalAlias('@stencil/core/mock-doc', '../../mock-doc/index.js'),
findAndReplaceLoadModule(),
],
};
Expand Down
4 changes: 1 addition & 3 deletions scripts/esbuild/internal-platform-hydrate.ts
Expand Up @@ -35,7 +35,7 @@ export async function getInternalPlatformHydrateBundles(opts: BuildOptions): Pro

const hydratePlatformInput = join(hydrateSrcDir, 'platform', 'index.js');

const external = [...getEsbuildExternalModules(opts, outputInternalHydrateDir), '@stencil/core/mock-doc'];
const external = getEsbuildExternalModules(opts, outputInternalHydrateDir);

const internalHydrateAliases = getEsbuildAliases();
internalHydrateAliases['@platform'] = hydratePlatformInput;
Expand All @@ -54,8 +54,6 @@ export async function getInternalPlatformHydrateBundles(opts: BuildOptions): Pro
plugins: [
externalAlias('@utils/shadow-css', '../client/shadow-css.js'),
externalAlias('@app-data', '@stencil/core/internal/app-data'),
// this needs to be externalized and also pointed at the esm version
externalAlias('@stencil/core/mock-doc', '../../mock-doc/index.js'),
],
};

Expand Down
2 changes: 1 addition & 1 deletion scripts/esbuild/internal-platform-testing.ts
Expand Up @@ -33,7 +33,7 @@ export async function getInternalTestingBundle(opts: BuildOptions): Promise<ESBu
'@platform': inputTestingPlatform,
};

const external = [...getEsbuildExternalModules(opts, opts.output.internalDir), '@stencil/core/mock-doc'];
const external = getEsbuildExternalModules(opts, opts.output.internalDir);

const internalTestingBuildOptions: ESBuildOptions = {
...getBaseEsbuildOptions(),
Expand Down
6 changes: 3 additions & 3 deletions scripts/esbuild/internal.ts
Expand Up @@ -6,7 +6,7 @@ import { copyStencilInternalDts } from '../bundles/internal';
import type { BuildOptions } from '../utils/options';
import { writePkgJson } from '../utils/write-pkg-json';
import { getInternalAppDataBundles } from './internal-app-data';
import { getInternalClientBundle } from './internal-platform-client';
import { getInternalClientBundles } from './internal-platform-client';
import { getInternalPlatformHydrateBundles } from './internal-platform-hydrate';
import { getInternalTestingBundle } from './internal-platform-testing';
import { getBaseEsbuildOptions, runBuilds } from './util';
Expand Down Expand Up @@ -53,13 +53,13 @@ export async function buildInternal(opts: BuildOptions) {
platform: 'node',
};

const clientPlatformBundle = await getInternalClientBundle(opts);
const clientPlatformBundles = await getInternalClientBundles(opts);
const hydratePlatformBundles = await getInternalPlatformHydrateBundles(opts);
const appDataBundles = await getInternalAppDataBundles(opts);
const internalTestingBundle = await getInternalTestingBundle(opts);

return runBuilds(
[shadowCSSBundle, ...clientPlatformBundle, ...hydratePlatformBundles, internalTestingBundle, ...appDataBundles],
[shadowCSSBundle, ...clientPlatformBundles, ...hydratePlatformBundles, internalTestingBundle, ...appDataBundles],
opts,
);
}
Expand Down
3 changes: 1 addition & 2 deletions src/runtime/dom-extras.ts
@@ -1,7 +1,6 @@
import { BUILD } from '@app-data';
import { getHostRef, plt, supportsShadow } from '@platform';
import { NODE_TYPES } from '@stencil/core/mock-doc';
import { CMP_FLAGS, HOST_FLAGS } from '@utils';
import { CMP_FLAGS, HOST_FLAGS, NODE_TYPES } from '@utils/constants';

import type * as d from '../declarations';
import { PLATFORM_FLAGS } from './runtime-constants';
Expand Down
25 changes: 25 additions & 0 deletions src/utils/constants.ts
Expand Up @@ -231,3 +231,28 @@ export const VALID_CONFIG_OUTPUT_TARGETS = [
] as const;

export const GENERATED_DTS = 'components.d.ts';

/**
* DOM Node types
*
* See https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
*
* Note: this is a duplicate of the `NODE_TYPES` enum in mock-doc, it's
* copied over here so that we do not need to introduce a dependency on the
* mock-doc bundle in the runtime. See
* https://github.com/ionic-team/stencil/pull/5705 for more details.
*/
export const enum NODE_TYPES {
ELEMENT_NODE = 1,
ATTRIBUTE_NODE = 2,
TEXT_NODE = 3,
CDATA_SECTION_NODE = 4,
ENTITY_REFERENCE_NODE = 5,
ENTITY_NODE = 6,
PROCESSING_INSTRUCTION_NODE = 7,
COMMENT_NODE = 8,
DOCUMENT_NODE = 9,
DOCUMENT_TYPE_NODE = 10,
DOCUMENT_FRAGMENT_NODE = 11,
NOTATION_NODE = 12,
}
1 change: 1 addition & 0 deletions tsconfig.json
Expand Up @@ -40,6 +40,7 @@
"@stencil/core/cli": ["src/cli/index.ts"],
"@sys-api-node": ["src/sys/node/index.ts"],
"@utils": ["src/utils/index.ts"],
"@utils/*": ["src/utils/*"],
"@utils/shadow-css": ["src/utils/shadow-css"]
},
"pretty": true,
Expand Down

0 comments on commit 0a7becc

Please sign in to comment.