Repository navigation
Suggestion: making exports visible only to the same directory with JSDoc @package tag #41425
Description
Activity
I'd recommend configurating these package entries in tsconfig.
Reacted by kahirokunn, Nguyễn Trọng Vĩnh, Son Nguyen, Jayatubi and Davide Pedron- addedAwaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScriptAn idea for TypeScript
on Nov 12, 2020 Just FYI, I created a userland solution on this as a combination of ESLint plugin and TypeScript Language Service Plugin.
https://github.com/uhyo/eslint-plugin-import-access
However, the implementation of Language Service Plugin is kind of barbarous, so support from TypeScript is still wanted.
Reacted by OGURA Daiki, fsubal, Shinobu Hayashi, shohei ueta, kasugaiasuka, Anton Fedchenko, Hideaki Noshiro, dorarep, Katsuma Ito, ysgk and 12 moreI have another suggestion to this: use modifier before
exportto set the visibility. By default,exportispublic exportthat every file can see this.For example, if you want to export some function only inside of the directory (package), use
inpackage export:For file
src/sort/sort.ts:export function sort(/* some args here */) { } inpackage export function quickSortPartition(/* */) { }
For file
src/sort/ordered_stat.ts:import { quickSortPartition } from "./sort.ts" function findNthSmallest(/* */) { // Can use partition function here quickSortPartition() }
But for other file
src/test.ts:// Cannot import here import { quickSortPartition } from "./sort/sort.ts"
Reacted by Artyom Chuykov, Jesse Docken, Ori Shalom, LemonNeko and Thomas GuzmanReacted by uhyoI've been thinking about this quite a bit recently.
I like the idea of having a prefix for
exportdeclarations but:inpackage- I find it a bit too cumbersomepackage- unlike Java, where we explicitly declare packages, we don't have packages in JS. Actually, we do, but those are npm packages, so the termpackagemight be confusing for pure JS developers.- I was thinking about
private- personally I like the idea ofprivate export const .... Private class members are only accessible within the same class, while private exports could only be accessible within their directory. But I'm sure some people will be against usingprivatefor exports. The other problem - what if we want to make the export available one dir or two dirs above the current one? We could have atypesfolder which should be accessible by the parent and all children. Something likeprivate(../..) exportdefinitely doesn't look right.
All of this led me to create a plugin (inspired by uhyo (@uhyo)'s plugin) that adds support for
@scope ../..comments that define scopes for exports.I wanted to create a TS decorator, that would make something like this possible:
@scope("../..") export default "";
but, unfortunately, TS doesn't support export decorators. If TS allows it then at least we would be able to create ESLint/TS plugins that would read the decorators instead of comments (which look quite a bit uglier)
Reacted by Ori Shalom and Thomas GuzmanReacted by uhyo, Nguyễn Trọng Vĩnh, Saad, Codrin Socol, Ahmed Abdelbaset and Ori ShalomSuggestion : can use dir named "internal" , this dir can be import same level dir file. same as golang internal dir rule !
Alex (@A-Shleifman) I think TS does support decorators, doesn't it? https://www.typescriptlang.org/docs/handbook/decorators.html Unless the terminology is wrong and you mean
@annotations? Either way, I'm pretty sure both are supported in TS at this point, no?Bow Archer (@decoded4620), I was talking about
export decoratorsmeaning decorators that go beforeexportstatements, which are not supported by TS.Reacted by Bow ArcherI was thinking about idea similar to one of HQ6968. Here is list of my ideas/opinions. I've left questions that probably can be answered by somebody with more experience
The idea
- Code can be grouped into folders (for the lack of better term I will literally call these "folders"), which have two parts: private and public. As an initial idea, let's say all the private code goes into
private/subfolder, and the rest of code is public:
src/ foo/ private/ foo-private.ts foo-public.ts bar/ foo-bar-public.ts bar bar.tsSo code in module
bar.tscan importfoo/foo-public.ts,foo/bar/foo-bar-public.ts, but notfoo/private/foo-private.ts- Modules inside
private/folders should be excluded from auto complete, unless we are typing code inside their folder.
- When typing inside
bar/bar.ts, we will never see anything fromfoo/private/* - However, when typing inside
foo/*, we will see everything fromfoo/private/*
Scope is given to the entire module, not to individual identifiers
- Comparing to this proposal and
@scope()suggested above, this approach makes everything within a module (i.e. file) public or private
In my opinion, giving scope to individual functions/variables is harder to reason about: to see the entire public API of a component, or entire private API within the component, you would have to browse each individual file, since each file might contain a mix of both
Is there ever a good case to mix public and private things in one module? Can we just split the code in multiple modules for such cases?
Dependencies between folders
It might be useful to declare dependencies between folders, instead of allowing every folder to import any other folder
- In my opinion, this should be done explicitly at the top of the folder, so you can see what folders does the folder depend on, without having to browse its code
I like how TS Project references do that: each folder has tsconfig.json at its top, with
referencesproperties declaring dependencies on other folder. What if TS simply allowed imports from folders listed inreferences, but not from theirprivate/subfolders?NPM dependencies are shared for all folders
I think it's convenient that all such folder would be able to import any NPM packages from the root "package.json". If you want different components of code not to share each other's NPM dependencies, you can use a monorepo (e.g. Rush.js)
Nested folders
What if a folder contains another folder? Can the parent folder access
private/of the child folder? Can the child access theprivate/folder of parent? We will need to think about itChild accessing things of parent
I like idea of #41316, which introduces
protectedaccess level - things in parent visible to child. We could haveprotected/folder for itParent accessing things of child
@scope(../..)allows to declare function/variable accessible from parent folder inside a child folder. In my opinion, folders should be "encapsulated": they can access the surrounding code (e.g.protectedthings of parent), but the surrounding code should not be able to access internals of component insideNo barrel files
- Comparing to packlets, we don't have to maintain index.ts files
IMHO, barrel files are tiresome to maintain. It might be a good idea to have all public API declared in one file, but is there practical problem of browsing all folders except
private/to understand the public API of a component?- Code can be grouped into folders (for the lack of better term I will literally call these "folders"), which have two parts: private and public. As an initial idea, let's say all the private code goes into
Is there ever a good case to mix public and private things in one module?
Yes! This might not be the best example, but let's say we have a file defining a React Context for an encapsulated Component:
const context = createContext({}); export const ContextProvider = context.Provider; // 👆 should only be accessible by the internal component files export const useComponentContext = () => useContext(context); // 👆 should be accessible by any component anywhere wrapped in the Component providing the context
but the surrounding code should not be able to access internals of component inside
Generally, that's true. That's why we're having this conversation, but there are exceptions. The most basic would be an index file re-exporting an export from a child folder.
utils ├── schema-utils/ │ ├── parsers.ts │ └── validators.ts └── index.ts
In this case, it should be possible for the
index.tsfile to import fromschema-utils/validators.tsfor example. I have a big project with a few of those exceptions even outside index files.I would love a solution to this problem to be integrated into TS, but while we wait, here's a v2 of eslint-plugin-export-scope
Reacted by Alex, Aakash Meshram and azerumPersonally, I would use this kind of export primarly for testing purpose: Exporting a "private" function that could be seen only by the associated test file.
Reacted by Mikkel ALMONTE--RINGAUD and Erik BongersThis would be a great addition to TS. For me, it would allow me to better break it down my file structure ensuring that modules are self-contained. For e.g., in my React projects, I would have my folder structure like:
src/ components/ button/ index.tsx // contains the TSX style.ts // styles are only exported internally, so no other modules outside button/ can access these styles validation.ts // same here, can't be access outside interface.ts // same here, can't be access outsideIf we had something like the
internal exportthat was presented here, this would ensure other components wouldn't use thestylesorvalidationthat are only for the button component. In that way I don't need to keep everything within the same file.This would also allow exporting two modules with same name in different scopes.
Reacted by Mateus Antonio Franceschina and davidAtInleague
Search Terms
JSDoc package directory package-private
Suggestion
If an export is annotated with
@package, that export is visible only to files in the same directory.I can think of two levels of
@packagesupport: a soft one would remove these exports from auto completion if not visible. A hard one would also emit a compile error.See also: Use JSDoc: @package
Use Cases
We organize source code using directory structure. We often make exports that are meant to be referenced only by sibling modules. However, JavaScript/TypeScript has no idea of scoping based on file system, so we are free to import such "local" exports everywhere. TypeScript could help us do file system-based scoping.
Examples
Checklist
My suggestion meets these guidelines: