-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: throw if a boundary file is not dynamically imported
- Loading branch information
1 parent
386929f
commit 64b51ed
Showing
6 changed files
with
139 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
"hot-hook": minor | ||
--- | ||
|
||
Now Hot-Hook will throw an error when a file marked as "boundary" is not dynamically imported. | ||
|
||
In AdonisJS, we had a few users complaining about having to restart the server to see the changes applied. Generally, the cause of this was a controller file not dynamically imported: | ||
|
||
```ts | ||
import PostsController from './app/controllers/posts_controller.js' | ||
router.get('/posts', [PostsController, 'index']) | ||
``` | ||
|
||
Before this new version, this code did not throw an error, but it did not work either. You had to reload the server to see the changes. Now Hot-Hook will throw an error for this kind of case. | ||
|
||
I invite you to reread the readme if you want to understand why a dynamic import is necessary for Hot-Hook to work correctly. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { readFile } from 'node:fs/promises' | ||
import { parseImports } from 'parse-imports' | ||
import { relative } from 'node:path' | ||
|
||
/** | ||
* This class is responsible for checking if a given specifier | ||
* is imported dynamically from a given parent file. | ||
* Otherwise we will throw an error since we cannot make the file reloadable | ||
* | ||
* We are caching the results to avoid reading the same file multiple times | ||
*/ | ||
export class DynamicImportChecker { | ||
private cache: Map<string, Map<string, boolean>> = new Map() | ||
private projectRoot: string | ||
|
||
constructor(projectRoot: string) { | ||
this.projectRoot = projectRoot | ||
} | ||
|
||
async ensureFileIsImportedDynamicallyFromParent(parentPath: string, specifier: string) { | ||
const cacheKey = parentPath | ||
if (this.cache.has(cacheKey) && this.cache.get(cacheKey)!.has(specifier)) { | ||
return this.cache.get(cacheKey)!.get(specifier) | ||
} | ||
|
||
const parentCode = await readFile(parentPath, 'utf-8') | ||
const imports = [...(await parseImports(parentCode))] | ||
|
||
const isFileDynamicallyImportedFromParent = imports.some((importStatement) => { | ||
return importStatement.isDynamicImport && importStatement.moduleSpecifier.value === specifier | ||
}) | ||
|
||
const currentCache = this.cache.get(cacheKey) ?? new Map() | ||
this.cache.set(cacheKey, currentCache.set(specifier, isFileDynamicallyImportedFromParent)) | ||
|
||
if (!isFileDynamicallyImportedFromParent) { | ||
throw new Error( | ||
`The import "${specifier}" is not imported dynamically from ${relative(this.projectRoot, parentPath)}.\nYou must use dynamic import to make it reloadable (HMR) with hot-hook.` | ||
) | ||
} | ||
|
||
return isFileDynamicallyImportedFromParent | ||
} | ||
|
||
invalidateCache(key: string) { | ||
this.cache.delete(key) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { test } from '@japa/runner' | ||
import { DynamicImportChecker } from '../src/dynamic_import_checker.js' | ||
import { join } from 'node:path' | ||
|
||
test.group('Dynamic Import Checker', () => { | ||
test('Throw if given specifier is not dynamically importedf', async ({ assert, fs }) => { | ||
await fs.create( | ||
'app.ts', | ||
` | ||
import './foo' | ||
await import('./bla') | ||
import '#app/aliases' | ||
await import('#app/aliases-bla') | ||
` | ||
) | ||
|
||
const checker = new DynamicImportChecker(fs.basePath) | ||
|
||
const path = join(fs.basePath, 'app.ts') | ||
|
||
await assert.rejects(async () => { | ||
await checker.ensureFileIsImportedDynamicallyFromParent(path, './foo') | ||
}) | ||
|
||
await assert.rejects(async () => { | ||
await checker.ensureFileIsImportedDynamicallyFromParent(path, '#app/aliases') | ||
}) | ||
|
||
assert.isTrue(await checker.ensureFileIsImportedDynamicallyFromParent(path, './bla')) | ||
assert.isTrue(await checker.ensureFileIsImportedDynamicallyFromParent(path, '#app/aliases-bla')) | ||
}) | ||
}) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.