Issue
I'm using babel-plugin-content-transformer to load a directory of YAML documents in React Native/Expo.
My babel plugin is configured as follows:
['content-transformer', {
transformers: [{
file: /\.ya?ml$/,
format: 'string'
}],
content: [
// Import statements ending with "content" are converted into
// an array of imports. The array will ony include ".y(a)ml" files
{
dir: /texts$/,
filter: /\.ya?ml$/
}
]
}]
This is working, and I can both load individual YAMLs as strings, and the whole texts
directory as an object.
My question is how do I declare a typed module for texts
?
Within my types.d.ts
I have this:
declare module '*.yaml' {
const data: string
export default data
}
Which works for loading in individual YAMLs. But when I try to import the whole directory (which I've verified works if I ignore the compiler) this doesn't work:
declare module 'texts' {
const data: Record<string, string>
export default data
}
// results in Cannot find module '../../texts' or its corresponding type declarations.
// when I try to load from "../../texts"
Is this possible, and if so what's the right way to approach this? Thanks!
Solution
Reading the Docs
In the following snippet, declare module '*.yaml' {...}
is an ambient module declaration named *.yaml
. The *
part of the name *.yaml
is a wildcard character. Wildcards in module names was a feature introduced in TypeScript v2.0:
TypeScript 2.0 supports the use of the wildcard character (*) to declare a “family” of module names; this way, a declaration is only required once for an extension, and not for every resource.
The docs on TypeScript's module resolution logic states:
First, the compiler will try to locate a file that represents the imported module. To do so the compiler follows one of two different strategies: Classic or Node. These strategies tell the compiler where to look for moduleA.
If that didn’t work and if the module name is non-relative (and in the case of "moduleA", it is), then the compiler will attempt to locate an ambient module declaration. We’ll cover non-relative imports next.
Applying the Docs to Context
Here's where things get surprising. In that "later", it states:
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.
(emphasis added). If that's the case, then how did the compiler resolve your import singleFile from "../../texts/myFile.yaml"
? The path there clearly contains a relative path, and resolves to an ambient module declaration.
Comparing the Docs with Actual Behaviour
The reason is because the wildcard functionality doesn't do anything special with path resolution. It doesn't care that ../
means "relative parent" in the filesystem world. It just sees that ../../texts/myFile
can be resolved with the wildcard, and that the remaining .yaml
in the import matches the remaining .yaml
in the ambient module declaration name.
Try doing:
import singleFile from "../../../../AAA/texts/myFile.yaml"
import singleFile from "/texts/myFile.yaml"
import singleFile from "C:\\//\\\\/myFile.yaml"
They should resolve to the ambient module declaration just the same even though resolving that relative path would actually fail on the filesystem.
Denouemont
So why is your attempt to do import allFiles from "../../texts"
failing? It's because there's no wildcard in the name of your ambient module declaration for the text folder, so the ../../
before text
means it doesn't resolve to the ambient module that you have declared with the name text
. If you name your ambient module declaration *text
, it should then resolve how you want. Or, you can change your import to import allFiles from "texts"
. Since you use a build-system (WebPack) which also has its own module resolution machinery, you might want to use the first approach, where the relative path can be interpreted and used by the build-system machinery in a way that prevents it from being as "leaky" (resolvable by other imports in unintended and surprising ways).
Closing Thoughts
What I find troubling is that the current documentation (2022/10/05) seems contradictory to actual behaviour:
If that didn’t work and if the module name is non-relative [...], then the compiler will attempt to locate an ambient module declaration.
[...]
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.
I'm not sure if my experiential understanding of the TS compiler behaviour is 100% right, but perhaps the docs should be saying something like:
If that didn’t work, then the compiler will attempt to locate an ambient module declaration.
[...]
A relative import is resolved relative to the importing file and if that resolution fails, an attempt at resolving to an ambient module declaration is made. You should use relative imports for your own modules that are guaranteed to maintain their relative location at runtime or when parsed by build systems calling the TS compilier.
Perhaps those clauses of the docs predate the addition of wildcards and need an update to clarify behaviour in the presence of wildcards. I've raised an issue ticket (#2559) on the TS Website repo for discussion on this matter.
Answered By - rainbow.gekota Answer Checked By - Senaida (PHPFixing Volunteer)
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.