---
# System prepended metadata

title: Build convergence decisions
tags: [build]

---

---
robots: nofollow, noindex
tags: build
---

# Build convergence decisions

[TOC]

For each of these, we need to:
- Decide
- Update create-package
- Update v0 to match (this month if feasible)
- Update v7 to match? (in some cases; probably not this month)

([Build convergence project plan](/NqFK4pMpRguDGZYQHwykDw))

✅❓❌❗️🛠

# Build orchestration

## Proposal: everyone will use `just` not `gulp` ✅

Target completion this month.

🛠 Work items:
- [ ] use standard just tasks where possible
- [ ] convert stuff just doesn't currently do into just tasks
  - [ ] including custom gulp plugins
- [ ] make most tasks work on an individual package
- [ ] possibly add root-level just config for whole-repo tasks
- [ ] track build perf and ensure it doesn't regress too much (adding caching back if needed)

# What must work before building

"Building" means running TS and/or babel. (Ignoring the scss step.)

Areas (current status):
- intellisense (mixed)
  - for root-level imports
  - for lib imports
- running tests (working in v0 but not v7)
- running webpack (working?)

Elements/options of making things work:
- path mapping (in tsconfig, jest config, webpack config)
- project references + main: src/index?
  - causes its own problems
- disallowing lib imports would make it easier

Considerations:
- Project references have been a huge time sink in the past
- As of Jan/Feb neither jest nor webpack supported project references, so you still need path mappings and possibly other magic

## Proposal ✅

- We would like to have project references working in the future, especially as we transition from large packages to smaller ones.
- It's not feasible this month
- Whoever gets this task in the future needs to work with the TS team to understand how project references interact with other settings (paths, etc) and how to get them working properly in our setup. Otherwise it's likely to just continue as a huge time sink.

🛠 Next steps:

- [ ] Get the TypeScript team's help (who?)

# Compilation, output, and importing

(these are all interrelated, so there may be overlap between the sections)

## How to import our code

A consumer wants to import Avatar. Where are they allowed to import from, and how do they determine which module type they get?

Current:
- v0:
  - root imports only (no path imports)
  - module types handled by package.json `main` (cjs) /`module` (esm) fields
- v7:
  - root imports allowed
  - first-level path imports allowed (some consumers are importing deeper paths, but this technically is unsupported)
  - module types handled manually

Notes:

- Reasons for path imports:
  - Easier for consumers to limit the parsed tree and potential side effects
  - There are also some cases where...?
- https://material-ui.com/guides/minimizing-bundle-size/

### Proposal ❓

- Small packages only allow importing from root
  - may only have a single types file? (depends on build tooling)
- May need limited path-based support in suite packages (only), and we'd need .d.ts everywhere in that case
  - ❓ Shift wanted to know why we need this (should discuss directly with Shift and David)
  - structure:
  ```
  @fluentui/react (suite package)
    index.js
      export { .. } from './Avatar';
    Avatar.js
      export { .. } from '@fluentui/react-avatar';
      
    @fluentui/react/dist/esm/Avatar
    @fluentui/react/dist/cjs/Avatar
  ```

## Output file structure

Current:

- v0:
  - `dist/es`: ESM via babel, no types
  - `dist/commonjs`: CJS via babel, no types
  - `dist/dts`: types only via TS
  - `dist/umd`: UMD via webpack
- v7:
  - `lib`: ESM via TS, types
  - `lib-commonjs`: CJS via TS, types
  - `lib-amd`: AMD via TS, types
  - `dist`: UMD via webpack, types rollup via API Extractor, sass?
- Alternatives:
  - like v0 but everything is under `lib`
  - Material does this (would require pre-publish step) https://unpkg.com/browse/@material-ui/core@4.9.10/
    - `/<each component>`
    - `/esm/<each component>`
    - `/umd`
    - `/index.js`, `/index.d.ts`
    - import { Button } from 'package/Button' // esm

Considerations:

- look at other projects
- `dist/*` (or `lib/*`) makes cleaning, ignoring, and publishing simpler
- moving v7 to `dist/*` would require more updates (and a habit shift) by consumers since we allow importing from `oufr/lib/*`
- v7 has types side-by-side with all files because we've allowed non-root imports

### Proposal ✅

For new packages (and eventually convert all packages):
- `dist` folder contains all output content
- Sub-folders for flavor-specific content:
  - `dist/cjs` for commonjs (types side-by-side)
  - `dist/esm` for ES modules (types side-by-side)
  - `dist/amd` for AMD modules (types side-by-side)
  - `dist/umd` for UMD bundle and types rollup
  - Types will be side-by-side 

For existing packages:
- v0 mostly retains existing output structure including separate `dist/dts` folder (simplest)
  - If we decide on `dist/esm` rather than `dist/es`, this should be renamed in v0
- v7 retains existing output structure; start adding shims
  - Add shims under `dist/*` for index and top-level files only
    - We will NOT mirror the entire file structure to avoid bloating package size. If someone is doing deep imports, they need to switch. (If we've forgotten to export something which should be exported, we'll fix it.)
  - Will switch in v8 to only `dist/*`

Migration path:
```
from: "package/lib/Button"
to:   "package"
  or  "package/dist/esm/Button"
  
from: "package/lib-commonjs/Button"
to:   "package/dist/cjs/index"
  or  "package/dist/cjs/Button"
```
(this will be included in the `update-fluentui` script, using string replace or ts-morph)

🛠 Work items:
- [ ] Update create-package to use new structure (and proper main/module fields)
- [ ] Update existing packages
  - [ ] v0: change `dist/es` to `dist/esm`?
  - [ ] v7: add `dist/*` as shims (in v8 will switch to these being primary)
- [ ] (later) Add path updater (using string replace or ts-morph) to `update-fluentui` script

## Transpilation tooling, output target, and helper libraries

Current transpilation tooling:
- v0: babel primary, TS for .d.ts only
- v7: TS only
- Other option: TS primary (to `target: esnext`), babel pre-publish for downleveling (and module types?)

Current output target and helper libs:
- v0:
  - specific targeting to IE11 using `preset-env`
  - non-es5 built-ins allowed
  - dep on babel-runtime
- v7:
  - target es5
  - no es5+ built-ins allowed
  - dep on tslib

Considerations/discussion:

- I think TS primary, babel pre-publish should be our future goal but maybe is not feasible now (certainly not feasible to convert existing projects this month)
  - Details:
    - for TS, use `target: esnext, module: esnext, declaration: true`
    - run babel later (maybe not until pre-publish) with `preset-env` for downleveling
  - Advantages:
    - faster initial build (not running TS multiple times like v7)
    - ability to use all JS/TS constructs and only do the specific downleveling needed for our browser support set (*might* improve bundle size)
  - Questions/risks:
    - What should we use to transpile for tests? (note that jest stillll doesn't support ESM)
    - If we don't run babel before/during testing (which would make testing slower), we'd be testing different code than we ship (which 99.999% of the time would be fine, but is highly likely to cause issues at some point)
    - Size/performance implications?
      - babel version may be more performant, partly due to `useBuiltins` options ([example](https://babeljs.io/docs/en/babel-plugin-proposal-object-rest-spread))
        - https://github.com/microsoft/fluent-ui-react/pull/1895
      - babel-runtime: https://babeljs.io/docs/en/babel-runtime
    - Do we want to bundle or depend on polyfills beyond tslib/babel-runtime?
  - Also look at this for comparison/ideas https://www.npmjs.com/package/tsdx#optimizations

### Proposal: TS+tslib, ES5 without polyfills ❓ (or as-is) short-term, TBD long-term ✅

Short-term, emphasis on simplicity:
- New packages:
  - Use TS for transpilation (no babel)
  - Depend on tslib
  - Target ES5 (no polyfills; IE11-supported collections are allowed) ❓
    - this means you can use ES6+ syntax (anything TS can handle) but **not** ES6+ APIs, e.g. newer array methods, Object.assign, creation of Promises
    - **need to confirm this is acceptable or find a viable (i.e. very little work required) alternative**
- v0 packages continue as-is
- v7 packages continue as-is

Longer-term, we should research the feasibility and pros/cons of the TS/babel approach, and reconsider which helper libraries and/or polyfills we use. It has potential benefits but would be too complex to set up this month.

🛠 Next steps:
- [ ] Implement short-term approach outlined in create-package
- [ ] (later?) Research TS/babel approach: options, pros/cons, risks, perf


## TypeScript options

Questions

- Remind me why v0 has `isolatedModules`? Do we really need this?
  - required for babel
  - effect: disallows using named type exports: `export { SomeType } from './foo'`
- What about `esModuleInterop`? Some packages which become impossible to import with types without this. `module.exports = foo`
  - esModuleInterop has cascading effect forcing consumers to use it

### Proposal ✅

- New browser packages will use these options (including `strict`).
  - New and existing node packages may use slightly different options
- Existing packages will be migrated to stricter options as time allows.

```jsonc
{
  "strict": true, // noImplicitAny, noImplicitThis, alwaysStrict, strictBindCallApply,
                  // strictNullChecks, strictFunctionTypes, strictPropertyInitialization
  "noUnusedLocals": true,
  "forceConsistentCasingInFileNames": true,
  "experimentalDecorators": true,
  "importHelpers": true, // for now
  "preserveConstEnums": true,
  "declaration": true,
  "declarationMap": true,
  "sourceMap": true,
  "jsx": "react",
  "moduleResolution": "node",
  "baseUrl": ".",
  // plus partial collection types (custom-global) and es2015.promise as needed
  "lib": ["es5", "dom"], 
  "target": "es5", // for now
  "module": "???", // not actually used
  "outDir": "???", // not actually used
  // variable: types, typeRoots, paths
}
```

🛠 Work items:
- [ ] Update create-package to apply these rules to new projects
- [ ] For existing projects, enable `noUnusedLocals` and `forceConsistentCasingInFileNames` (should require minimal changes)
- [ ] For existing projects, move towards `strict` as time allows, likely by enabling options individually:
    - [ ] noImplicitAny
    - [ ] noImplicitThis
    - [ ] alwaysStrict
    - [ ] strictBindCallApply
    - [ ] strictNullChecks
    - [ ] strictFunctionTypes
    - [ ] strictPropertyInitialization

# Test file structure

Current:
- Location:
  - v0: tests live in `<pkg>/test`
  - v7: tests live side by side with thing being tested
  - Other options:
    - `<pkg>/src/__tests__` (or similar)
    - `<pkg>/src/components/Foo/__tests__` (or similar)
- Naming:
  - v0: `*-test.ts*`
  - v7: `*.test.ts*`

Considerations:
- I do think we should move away from `<pkg>/test` because having multiple roots seems to cause weirdness with TS
  - If people strongly prefer to have tests separate, `<pkg>/src/__tests__` would fix the multiple roots issue
- side-by-side tests:
  - make it clearer what does and doesn't have tests
  - make import paths in tests simpler
- which structure is most common in open source projects (especially TS ones)?
- With side-by-side tests, where do test utilities go?
  - separate package?
  - OUFR: `pkg/src/common/testUtilities.ts`
  - `pkg/src/testUtilities/<stuff>`
  - be sure to exclude test files and test utilities from publishing!

## Proposal ✅

- Tests live side-by-side with files being tested
- Naming: `*.test.ts*`
- Test utilities live in their own package or under `pkg/src/testUtilities` (or similar; TBD)
- Be sure to exclude test files and test utilities from publishing

🛠 Work items:
- [ ] Update create-package
- [ ] Move v0 tests
- [ ] Move v7 test utilities?
- [ ] Ensure tests and test utilities are excluded from publish everywhere
  - [ ] new packages
  - [ ] v0 packages
  - [ ] v7 packages

# Linter and lint rules

## Lint rules 

[See dedicated note for more details](/KZ1tKWUVQMaXE11RYSI_Jw)

## Lint tooling

Current:
- Linter:
  - v0: eslint (and tslint but this is removable now)
    - does not currently run on precommit
  - v7: tslint (would like to move to eslint)
    - runs on precommit
  - both projects would like to move fully to eslint
- Run tslint in precommit

Considerations:
- ~~what tslint rules does v0 still need? can we move them to eslint?~~ (discovered all remaining tslint rules in v0 are redundant and can be removed!)
- move v7 to eslint?
- do we want to continue to run lint in precommit?
  - if so, we need to fix it to also run eslint
- should everyone start inheriting from airbnb config (or similar)?
- probably out of scope for now
  - update all existing packages to use any new lint rules (if we do switch the overall lint config, we'll add temporary overrides in existing packages for rules that would be nontrivial to fix)

## Proposal ❓

[See dedicated note for more details](/KZ1tKWUVQMaXE11RYSI_Jw)


🛠 Work items:
- [ ] Remove tslint from v0! :)
- [ ] Add converged shared eslint config
  - [ ] Add separate v0 eslint config (and update inheritance) if needed
- [ ] Add conditional eslint step to just build
- [ ] Use eslint in create-package
- [ ] Update precommit to run eslint too (if we decide to keep precommit)
- [ ] (later? depends on difficulty) Migrate v7 to eslint
  - This may not be hard because the set of rules we use is relatively small
- [ ] (later!) Remove per-package lint overrides in v0 and v7

## Other coding practices

- Why does v0 generally prefer `type` over `interface`? Importance of carrying this forward?
- Default vs named exports
  - v0: mostly just default exports
  - v7: mostly named exports
- Other?

### Proposal ❓

# Inner loop and example structure

Current:
- v0:
  - Doc site for inner loop
  - All examples live in docs package
- v7:
  - Storybook or legacy doc site for inner loop
  - Examples live under `pkg/src/components/<component>/examples` but we want to move them to a separate package (to eliminate circular dep issues)

Questions
- Where should the examples/stories live?
- Should we separate stories from public examples?

## Proposal ❓

- New packages use storybook for inner loop
- Example/story location??

🛠 Work items:
- [ ] Finish moving v7 examples to separate package?
- [ ] Add storybook support in v0 (much faster startup)

# (later) API rollup/review

(don't need to go deep into this or decide now, just calling it out)

Do we want to continue using API Extractor?
Research longer-term but stick with it for now.

# (later) Component doc generation

(don't need to go deep into this or decide now, just calling it out)

Short-term we'll stick with separate approaches.
For new packages, it would be fairly easy to hook up either approach.