Skip to content
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

[eslint-plugin] Proposal: "Packlets" model for lightweight package-like folders within a project #2254

Closed
octogonz opened this issue Oct 2, 2020 · 6 comments · Fixed by #2256

Comments

@octogonz
Copy link
Collaborator

octogonz commented Oct 2, 2020

Motivation

@bartvandenende-wm and @victor-wm came to our tools team with an cool idea for encouraging modularization of a large Rush project, without actually splitting the code into separate library projects. Why would we want to do that? In our scenario, creating a new Rush project brings some overhead:

  • Creating a new project requires discussion with other teams, to choose a name, clarify the charter, determine the catgeory folder
  • CODEOWNERS policy needs to be set up
  • We by default enable API Extractor for libraries, which means their APIs get tracked centrally
  • The Jest/Webpack watch scenarios aren't that great (yet!) across multiple projects

Often you set out with only a rough idea of how new code should be organized. You want to experiment with it a bit before proposing a Rush project. We will probably make proper library packages later, but can we start with something more lightweight?

One idea is to simply organize the code into folders (src/controls/, src/styles/, etc). But how do you clarify the public/private relationships? How do you prevent circular references?

Proposed Design

Let's formalize the import relationships for these folders a bit. Then we can use ESLint to enforce the API contracts.

We're calling this formalization "packlets": They behave like NPM packages, but without the overhead of a real package.json file.

The lint rules and specification will go in a new NPM package @rushstack/eslint-plugin-packlets.

It won't be tied specifically to Rush or Heft, though. The packlet system can be usefully adopted by any TypeScript project, even in a standalone Git repo. Just enable the ESLint rules.

5 rules for packlets

  1. A "packlet" is defined to be a folder path ./src/packlets/<packlet-name>/index.ts. The index.ts file will have the exported APIs. The <packlet-name> name must follow NPM package naming rules (lower case words separated by hyphens).

    Example file paths:

    src/packlets/controls
    src/packlets/logger
    src/packlets/my-long-name
    

    NOTE: The packlets cannot be nested deeper in the tree. Like with NPM packages, src/packlets is a flat namespace.

  2. Files outside the packlet folder can only import the packlet root index.ts:

    src/example.ts

    // Okay
    import { Button } from "../packlets/controls";
    
    // Not okay
    import { Button } from "../packlets/controls/index";
    import { Button } from "../packlets/controls/Button";
  3. Files inside a packlet folder should import their siblings directly, not via their own index.ts (which might create a circular reference):

    src/packlets/controls/CheckBox.ts

    // Okay
    import { Button } from "./Button";
    
    // Not okay
    import { Button } from ".";
    import { Button } from "./index";
  4. Packlets may reference other packlets, but NEVER in a way that would create a circular reference:

    src/packlets/controls/Button.ts

    // Okay
    import { ConsoleLogger } from "../packlets/logger";

    src/packlets/logger/LogLevels.ts

    // Not okay, because Button.ts already references this packlet
    import { Button } from "../packlets/controls";
  5. Other source files are allowed outside the src/packlets folder. They may import a packlet, but packlets must only import from other packlets or NPM packages.

    src/index.ts

    // Okay
    import { ConsoleLogger } from "../packlets/logger";

    src/packlets/controls/Button.ts

    // NOT OKAY because src/Thing.ts is not a packlet
    import { Thing } from "../../../Thing.ts";
@victor-wm
Copy link

❤️

@jasonswearingen
Copy link
Contributor

sounds reasonable!

tangential: curious about referencing packlets: is there something special about naming the entrypoint index.ts? does that imply that node/webpack will automatically load it when given a import "path/to/folder"?

@KevinTCoughlin
Copy link
Member

Sounds like a useful feature for staging library code without requiring the full NPM package overhead.

@octogonz
Copy link
Collaborator Author

octogonz commented Oct 2, 2020

tangential: curious about referencing packlets: is there something special about naming the entrypoint index.ts? does that imply that node/webpack will automatically load it when given a import "path/to/folder"?

Yes, index.ts is a special standard filename recognized by module resolvers. That's why we can write:

import { ConsoleLogger } from "../packlets/logger";

instead of:

import { ConsoleLogger } from "../packlets/logger/index";

@octogonz
Copy link
Collaborator Author

octogonz commented Oct 4, 2020

Here's a PR: #2256

@dmichon-msft
Copy link
Contributor

I should point out that resolution will, in general, be faster and more robust if you always reference path/to/index, even if you can refer to it by folder in the node resolution algorithm. Folder imports cause the resolver to look for a package.json to try to ascertain the main file.

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

Successfully merging a pull request may close this issue.

5 participants