<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: inomjon Ismaylov</title>
    <description>The latest articles on DEV Community by inomjon Ismaylov (@inomjon_ismaylov_0f8a1171).</description>
    <link>https://dev.to/inomjon_ismaylov_0f8a1171</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3411824%2F34527505-bbd1-4845-a334-8da9f9e161c8.jpg</url>
      <title>DEV Community: inomjon Ismaylov</title>
      <link>https://dev.to/inomjon_ismaylov_0f8a1171</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/inomjon_ismaylov_0f8a1171"/>
    <language>en</language>
    <item>
      <title>One secondary entry point per component: splitting an Angular library with ng-packagr</title>
      <dc:creator>inomjon Ismaylov</dc:creator>
      <pubDate>Thu, 12 Nov 2026 06:44:10 +0000</pubDate>
      <link>https://dev.to/inomjon_ismaylov_0f8a1171/one-secondary-entry-point-per-component-splitting-an-angular-library-with-ng-packagr-24hi</link>
      <guid>https://dev.to/inomjon_ismaylov_0f8a1171/one-secondary-entry-point-per-component-splitting-an-angular-library-with-ng-packagr-24hi</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/InomjonIsmaylov/kometa-ui-kit" rel="noopener noreferrer"&gt;Kometa UI&lt;/a&gt; is an Angular 22 component kit. Up to 0.1.x it was published the way the Angular CLI scaffolds a library: one &lt;code&gt;public-api.ts&lt;/code&gt; that re-exports everything, built by ng-packagr into one package entry. In 0.2.0 every source file became its own secondary entry point, 85 of them. This post covers why, what the layout looks like, and the one rule that makes it build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: lazy routes paying for the initial chunk
&lt;/h2&gt;

&lt;p&gt;The app I extracted the kit from has an admin area with lazy-loaded routes. Most kit components are only used inside those routes. You would expect each component's code to load with its route.&lt;/p&gt;

&lt;p&gt;It did not. ng-packagr builds each entry point into a single flat ES module. With one entry, that is one module containing every component. Tree-shaking removes the components nobody imports, but it does not split the rest: a bundler places a module's code in one chunk. As soon as eagerly loaded code (the shell, the root component) imported anything from &lt;code&gt;kometa-ui&lt;/code&gt;, the module belonged to the initial chunk, and with it every kit component that any lazy route used.&lt;/p&gt;

&lt;p&gt;In that admin app, this put 78 kB (raw, not compressed) into the initial chunk that belonged to lazy routes.&lt;/p&gt;

&lt;p&gt;The fix is to give the bundler module boundaries that match component boundaries. If &lt;code&gt;KometaDrawer&lt;/code&gt; lives in its own module, a route that is the only user of the drawer gets the drawer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layout
&lt;/h2&gt;

&lt;p&gt;Each component folder under &lt;code&gt;projects/kometa-ui/&lt;/code&gt; is now an entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;projects/kometa-ui/
  ng-package.json          # primary entry: src/public-api.ts, assets, inlineStyleLanguage
  src/public-api.ts        # re-exports every entry
  button/
    kometa-button.ts
    kometa-button.spec.ts
    index.ts
    ng-package.json
  drawer/
    kometa-drawer.ts
    kometa-drawer.spec.ts
    index.ts
    ng-package.json
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two extra files are as small as they can be. &lt;code&gt;button/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'./kometa-button'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;button/ng-package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"entryFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"index.ts"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ng-packagr discovers secondary entry points by finding &lt;code&gt;ng-package.json&lt;/code&gt; files below the primary one, so nothing else has to list them. Each folder publishes as &lt;code&gt;kometa-ui/&amp;lt;folder&amp;gt;&lt;/code&gt;, where the folder is the file name without the &lt;code&gt;kometa-&lt;/code&gt; prefix: &lt;code&gt;kometa-ui/button&lt;/code&gt;, &lt;code&gt;kometa-ui/paging&lt;/code&gt;, &lt;code&gt;kometa-ui/provide-kometa-ui&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The primary entry still exists and re-exports everything, by package path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;provideKometaUi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;KOMETA_LAYER_ORDER&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'kometa-ui/provide-kometa-ui'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'kometa-ui/accordion'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'kometa-ui/button'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;import { KometaButton } from 'kometa-ui'&lt;/code&gt; from 0.1 still compiles. It just brings back the original problem, so the docs and the agent guide recommend the entry imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;KometaButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'kometa-ui/button'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;KometaDrawer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'kometa-ui/drawer'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The rule: import other entries by package path, never relatively
&lt;/h2&gt;

&lt;p&gt;Components use each other. The accordion uses the fold's body; the drawer uses the motion directive. Before the split these were relative imports between files in &lt;code&gt;src/lib/&lt;/code&gt;. After it, they cross entry-point boundaries, and ng-packagr refuses a relative import that reaches into another entry point: each entry is compiled as its own unit and must reference the others the way a consumer would.&lt;/p&gt;

&lt;p&gt;So inside the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// projects/kometa-ui/accordion/kometa-accordion.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;KometaFoldBody&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'kometa-ui/fold'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// projects/kometa-ui/drawer/kometa-drawer.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;KometaMotion&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'kometa-ui/motion'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ng-packagr works out the build order from these imports. In the published package, &lt;code&gt;kometa-ui/accordion&lt;/code&gt; imports &lt;code&gt;kometa-ui/fold&lt;/code&gt; like any other dependency, and a bundler sees two modules that can go to different chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the workspace agree
&lt;/h2&gt;

&lt;p&gt;Two places outside the library need to know the entry points exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript paths.&lt;/strong&gt; The demo app imports the library from source, so there is no rebuild while developing. The root &lt;code&gt;tsconfig.json&lt;/code&gt; maps both the primary and the secondary entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"kometa-ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./projects/kometa-ui/src/public-api.ts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nl"&gt;"kometa-ui/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./projects/kometa-ui/*/index.ts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second line also resolves the library's own &lt;code&gt;from 'kometa-ui/fold'&lt;/code&gt; imports during development and in tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test include.&lt;/strong&gt; Specs moved next to their components, out of &lt;code&gt;src/&lt;/code&gt;, so the library's test target in &lt;code&gt;angular.json&lt;/code&gt; picks them up from the sibling folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"**/*.spec.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"../*/*.spec.ts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;src/lib/&lt;/code&gt; now holds only the test setup and two specs that cover several components at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a component now
&lt;/h2&gt;

&lt;p&gt;A folder with four files (the component, its spec, &lt;code&gt;index.ts&lt;/code&gt;, &lt;code&gt;ng-package.json&lt;/code&gt;) and one &lt;code&gt;export *&lt;/code&gt; line in &lt;code&gt;src/public-api.ts&lt;/code&gt;. The docs generator reads the folder name too, so each docs page prints the import line for the right entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth it for your library?
&lt;/h2&gt;

&lt;p&gt;If your library is a handful of components that every page uses, probably not: everything ends up in the initial chunk anyway. If it is a kit where most components appear on one or two screens, and apps lazy-load those screens, a single barrel quietly moves code into the initial load, and the build output will not warn you about it. Comparing the initial chunk before and after is a quick way to find out.&lt;/p&gt;

&lt;p&gt;Kometa UI is MIT, on npm as &lt;code&gt;kometa-ui&lt;/code&gt; (now 0.3.0, which also adds &lt;code&gt;ng add kometa-ui&lt;/code&gt;). Docs: &lt;a href="https://inomjonismaylov.github.io/kometa-ui-kit/" rel="noopener noreferrer"&gt;https://inomjonismaylov.github.io/kometa-ui-kit/&lt;/a&gt; · Try it on StackBlitz: &lt;a href="https://stackblitz.com/github/InomjonIsmaylov/kometa-ui-kit/tree/main/examples/starter?file=src/app/app.ts" rel="noopener noreferrer"&gt;https://stackblitz.com/github/InomjonIsmaylov/kometa-ui-kit/tree/main/examples/starter?file=src/app/app.ts&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>performance</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
