Update[09/12/2017 16:58 EST]
Added reason why I hesitate to use the natively-supported non-relative import with my own modules to the bottom of this question.
Update[09/12/2017 12:58 EST]:
Per request, I made the file structure below reflect my actual use case. Which is a nested view module requesting a utility module somewhere up the directory tree.
Per request
In both TypeScript and ES6, one can import custom module by
// Relative
import { numberUtil } from './number_util';
// OR
// Non-relative
import { numberUtil } from 'number_util';
And according to TypeScript docs (link), one should:
... use
relative imports for your own modules that are guaranteed to maintain
their relative location at runtime.
... Use non-relative paths when importing any
of your external dependencies.
My problem here is that I have a project structure like this:
project/
|--utils
| |--number_util.ts
|--views
| |--article_page
| |-- editor_view
| |--editor_text_area.ts
And when I include utils/number_util inside my editor_text_area module, the import statement looks like:
import { numberUtil } from './../../../utils/number_util';
Which is long and not readable and, worst of all, difficult to maintain: whenever I need to move editor_text_area, I will have to update each these relative paths, when in the meantime I can just use the non-relative way of
import { numberUtil } from 'utils/number_util';
Does anyone have any suggestions on how best to do module imports to achieve the highest readability and maintainability?
But using the non-relative way poses a problem (other than that it is not recommended by official docs): what if I installed an npm module that has the same name with the module I'm importing? On that note, it is not as safe as the uglier alternative mentioned above.
Depending on your project tooling and structure, you have some options.
A) You could publish part of your dependencies as stand-alone modules, perhaps in a private registry. You can then install them with npm and require them like any other external dependency.
B) Many module systems support some sort of path mapping. The vue-js webpack template uses webpack's alias feature to set # to the source code root. TypeScript supports path mapping too. After you introduce that mapping you can use
import { numberUtil } from '#/utils/number_util';
This approach basically introduces a private namespace for your modules.
It is safe, in that you could only ever shadow an npm module with the name # which is an invalid name and therefore cannot exist.
For your example, you would have to have these entries in your compilerOptions:
"baseUrl": ".",
"paths": {
"#/*": ["*"]
}
Or if you only want to import modules from utils, you could change the mapping to "#/*": ["utils/*"] and import using '#/number_util'.
C) Another thing to consider is to improve your project structure. Depending on your actual project, it might make sense to apply the facade pattern at one point or another. Maybe inject the dependency into editor_text_area instead of letting it import it itself.
you can add "baseUrl": "./src", to you tsconfig.json,
then you can import * as utils from 'utils' to import ./src/utils/index.ts
Related
I have a problem with imports inside typescript package.
I have class A and class B inside models directory
so i'm importing B into A
import B from "models/B"
interface A {
b: B
}
export default A;
but after build A.d.ts cannot see B interface.
I was trying to set exports inside package.json but it's still not working.
How to import interface into interface. I need to use these interfaces directry after install for example:
import A from "#space/my-package/models/A"
You very probably have some paths alias in your tsconfig.json to convert the absolute path models/B into an actual path on your system.
As described in TS doc about module and paths resolution: (emphasis mine)
The TypeScript compiler has a set of additional flags to inform the compiler of transformations that are expected to happen to the sources to generate the final output.
It is important to note that the compiler will not perform any of these transformations; it just uses these pieces of information to guide the process of resolving a module import to its definition file.
So it is like having 2 compilation systems, each with their own path resolution:
TypeScript compilation, which uses flags in tsconfig.json, but compiles the paths without changing them in its output
your project compilation (bundling, consumption, or even a new TypeScript compilation), which uses its own configuration (possibly its own tsconfig.json in case of TS again)
As explained in the doc, the TS flags in tsconfig.json are supposed to reflect what is done in the final compilation.
But if you configure some path alias in the module tsconfig.json, without having the corresponding counterpart in the consuming project, the latter will not be able to correctly resolve those paths.
As a general rule, avoid absolute paths in libraries/modules (that must be compiled and consumed by other projects), unless they reference packages that you know will be in a node_modules folder (and refer to them by their package name, do not attempt esoteric alias).
Context
I am creating a library with 2 ways of initialization:
Automatic - I download some stuff for you asynchronously, then initialize.
Manual - You already downloaded the stuff before, then I initialize the library immediately (sync).
I have successfully implemented tree-shakable libraries in the past. What we would normally do, is separate the code into two modules, and let the app developer choose which one to import, thus allowing tree-shaking the other part. Similarly to this:
import { LibraryAsyncModule } from 'my-library'; // 🔁 Or LibrarySyncModule
#NgModule({
imports: [LibraryAsyncModule] // 🔁 Or LibrarySyncModule
})
export class AppModule { }
What I want to accomplish ✔
To reduce the learning curve of using my library, I'm trying to design is a single imported module which includes the relevant module and allows tree shaking the other. The following diagram shows the desired structure.
What I want to avoid 🚫
I could create factory providers that will detect the config passed to forRoot() and load the corresponding module at runtime. However, importing the modules at runtime turns initialization to async, and will also prevent angular from bundling the used module with the app.
I need a build time solution. My prototypes show that simply including both sync and async modules in the core module result in both being bundled.
How would that single module look like? Any ideas / suggestions? 🤔
Your approach should work, but you will need to enable it.
Since Tree-shaking works only with es6 modules you need to publish your lib with it & specify module property in your package.json.
// package.json
{
...
"main": "dist/index.js",
"module": "dist/es/index.js", // <- this file should contain es modules not commonjs
...
}
This setup will tell bundlers (webpack in your case) to use the es modules export and allow them to enable tree-shaking feature.
I recommend for package devs to use tsdx cli tool which does this automatically and supports many other features such as TypeScript support etc.
npx tsdx create mylib
I'm developing a module that doesn't have a build that the user imports. Instead, he imports individual components and then bundles them along with his code. However, those components share utilities and I want to import them without going through relative path hell.
I know that's a pretty common question and I did some research. Suppose I have module/components/foo/bar/baz/index.js that wants to import module/utils/helper.js
Option 1
Just use relative paths and do:
import helper from '../../../../utils/helper'
Option 2
Use the module-alias package and have:
import helper from '#utils/helper'
This would work in Node.js because modules are resolved at runtime. However, let's say the module user has Webpack and imports the module:
import component from 'module/components/foo/bar/baz'
Webpack wouldn't be able to resolve #utils unless the user specifies that alias in his own Webpack configuration. That would be pretty annoying.
Option 3
Use Webpack aliases in webpack.config.js:
module.exports = {
resolve: {
alias: {
'#utils': path.join(__dirname, 'utils')
}
}
}
This would work fine if the module was pre-bundled. But as I've previously mentioned, I want the library to be usable with ES6 imports so that users can bundle only the parts they need.
Option 4
I could use the module name in the module's own source code:
import helper from 'module/utils/helper'
This appears to solve the problem, but I think it's a pretty bad solution. For development, you'd have to create a symlink node_modules/module -> module. I'm sure this hides many potential issues and collaborators would have to manually do it as well.
Is there a way to avoid relative paths while allowing the library to be used with ES6 imports?
I am working on an NPM package written in Typescript, and I am having trouble wrapping my head around module resolution when compiling the library to publish.
Throughout the project, I have been using non-relative imports to avoid the annoyance of ../../../. However, I read in the typescript documentation that relative imports should be used within a project.
A relative import is resolved relative to the importing file and cannot resolve to an ambient module declaration. You should use relative imports for your own modules that are guaranteed to maintain their relative location at runtime.
A non-relative import can be resolved relative to baseUrl, or through path mapping, which we’ll cover below. They can also resolve to ambient module declarations. Use non-relative paths when importing any of your external dependencies.
I would like to not have to sacrifice the nice, neat imports in favor of relative imports, but I am not sure how to set up the compiler settings in order to get this to work. When running tests, I specify NODE_PATH in order to resolve the modules, but this isn't working for post-compilation.
I would like to be able to write files using non-relative imports, but have them transformed in some way so that the dist/ files can resolve the imports.
The project is hosted on github here.
The relevant issue is that I end up with an index.d.ts file in my dist/ folder that looks like this:
import { Emitter } from 'emitter';
import { Schema } from 'migrations';
import { Model, model, relation } from 'model';
import { Builder } from 'query';
export { Builder, Emitter, Model, model, relation, Schema };
But all the modules have errors that the module cannot be resolved. How can I keep these imports in their current form, but transform them in some way when building so that when I publish the npm package, the modules can be correctly resolved.
I would follow the advice in the official Typescript docs:
https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html
Basically, the suggestion is to build your library just before publishing to npm. You will have two files, in output; let's call them main.js and main.d.ts.
The critical point, here, is that by tsc-ing your source files you resolve the dependencies before npm is involved at all, so you can keep your references as you wish.
In your package.json, include two lines (or change them accordingly, if you have them already):
{
...
"main": "./lib/main.js",
"types": "./lib/main.d.ts"
...
}
In this way, any consuming project doesn't need to know about the internals of your library: they can just use the compiled output, referencing the generated typings file.
I know I have to remove all deep imports before upgrading to Angular 4, but I have no idea what is a deep import. Literally nobody mentions it. What is it? How does it look like?
Taken from How to deal with losing deep imports in Angular 4, an example of a deep import:
import { VALID } from '#angular/forms/src/model'
meaning 3 levels deep, whereas now you can only go 1 level:
import { VALID } from '#angular/forms'
which is invalid if "VALID" is in model, 3 levels deep. that's all there is to it. If you need something that's in "deep", it should be exported to in the first level now, or you need to open a ticket to angular to export it.
Not trying to advertise my own gist article, but I explain them here
Description
A "deep import" is simply an ESM import that goes deeper than the package root:
import thingA from 'my-package-name/src/components/thingA'
import thingB from '#my-namespace/my-package-name/src/components/thingA'
A namespaced package does not necessitate a deep import if the package name (package.json:name key) contains a slash:
import thingA from '#my-namespace/my-package-name'
Relative imports (./path/to/module) are also not considered deep imports, in fact it might be considered bad practice to "shallow import" an exported module from one's own package by it's package.json:main key location (i.e. src/index.js) as this can often cause circular dependencies.
Why deep imports can be a problem
Package bundling
Package bundlers are often used to transpile and condense a project to a distributive, single-file version (i.e. dist/bundle.js). Other packages that try to deep import from such packages experience errors, as the directory structure from src may often not be maintained or even included in the published package.
Unintended imports and directory changes
A developer might deep import a module from a package when it was not intended to be used outside of the scope of that package. Your changing of the name of an internally-used export or your package's directory structure should not require a major version (breaking change) update for a module that was only intended to be consumed internally.