Skip to content

chore(build): Package modules according to Angular Package Format #1088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Aug 4, 2017

Conversation

davideast
Copy link
Collaborator

@davideast davideast commented Aug 1, 2017

WIP, do not merge

Checklist

Houston, we have a packaging problem

AngularFire2 is designed to work with tree shaking. If you only need database, you import the core AngularFireModule and the AngularFireDatabaseModule.

import { NgModule } from '@angular/core';
import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';

@NgModule({
  imports: [
    AngularFireModule.initializeApp({ }),
    AngularFireDatabaseModule
  ]
})
export MyModule { }

While these two modules are installed through the single angularfire2 module, they should be thought of as two separate modules.

However, there is a problem with the way we packaging the library. To understand the problem, look at the import lines for angularfire2 and angularfire2/database.

Deep imports vs. Packages

What happens when you import from a module? In 99.99% of Angular cases you're using Webpack. I'm not a Webpack expert, so I'm making some educated(ish) guesses at what is going on. Webpack is going to look at the node_modules for that module name and look at the package.json to know which file to resolve. In the case of angularfire2, it will find the module of index.js and start there. Webpack sees this is as a package.

Now, what happens when you import from angularfire2/database? Webpack sees the src/database.ts file and treats it as a deep import. This is not a package. A deep import means we are packaging this as one big module.

We want Webpack and other build systems to see angularfire2/database as its own module. Webpack is clever and lets us get away with this mistake. However, Systemjs and commonjs builds for node break.

Each feature needs to be treated as it's own hermetic module. To do this we need a proper folder structure, an ESM build, a UMD build, and a package.json that guides each build systems to the correct files.

Project Structure

node_modules/angularfire2
└── bundles
│       ├── core.umd.js
│       ├── auth.umd.js
│       ├── database.umd.js
│       ├── test.umd.js
└── es2015 (ES2015 sources for dev)
├── package.json 
|      typings: ./index.d.ts
|         main: ./bundles/core.umd.js
|       module: ./index.js
|       es2015: ./es2015/index.js
├── index.js (ESM)
├── public_api.js (ESM)
├── (other ESM files)
└── database (angularfire2/database)
│       ├── index.js (ESM)
│       ├── public_api.js (ESM)
│       └── es2015 (ES2015 sources for dev)
│       ├── package.json 
│        typings: ./index.d.ts
|        main: ../bundles/database.umd.js
|        module: ./index.js
|        es2015: ./es2015/index.js

This is a rough diagram of the final build. The key thing to notice is that the main angularfire2 folder has a package.json that manifests a main, module, and es2015 build. The main build is a UMD bundle, which is used by SystemJS and CommonJS. The module build is ESM, and it is used by module bundlers like Webpack. The es2015 build is pure ES2015 syntax and is used for dev sources.

Notice that the angularfire2/database follows the same structure. It has a package.json with the main, module, and es2015 builds. When Webpack looks for angularfire2/database it will see the dedicated package.json and not a deep import. If you look closer into the package.json of angularfire2/database you'll see that angularfire2 is a dependency.

ESM Build

ESM is a term for JavaScript code that uses ES5 syntax but with ES2015 modules. While this is strange, this is used by module bundlers like Webpack and Rollup for better tree shaking. The ES2015 module syntax is just used to build a dependency graph and not used in the production build.

The ESM build is starts with the index.js file in each module.

Fixing the UMD Build

AngularFire2 currently packages on UMD bundle for the core AngularFireModule, but not for the database and auth modules. This build generates a UMD bundle for each feature, and they are stored in the bundles folder.

Angular Universal

Angular Universal requires a UMD bundle to run against CommonJS. This currently breaks due to the angularfire2/database deep import. However, due to the new structure and package.json Angular Universal will use the UMD bundle instead of the deep import.

@Kuziwa
Copy link

Kuziwa commented Aug 7, 2017

Hi David, how can the packaging problem be resolved when working on stackblitz.com?
I get the error below when trying to import the AngularFireModule and AngularFireDatabaseModule

image

@davideast
Copy link
Collaborator Author

@kamshak This hasn't been released outside of the next tag yet and AFAIK, StackBlitz doesn't support pinning versions. I'm still working on a bug and once that is fixed I'll cut the release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants