-
Notifications
You must be signed in to change notification settings - Fork 428
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): add aliases to mitigate context issues
- Loading branch information
1 parent
9dbb9f6
commit 8c40b74
Showing
5 changed files
with
200 additions
and
25 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
92 changes: 92 additions & 0 deletions
92
packages/sanity/src/_internal/cli/server/__tests__/aliases.test.ts
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,92 @@ | ||
import path from 'node:path' | ||
|
||
import {describe, expect, it, jest} from '@jest/globals' | ||
import resolve from 'resolve.exports' | ||
|
||
import {browserCompatibleSanityPackageSpecifiers, getAliases} from '../aliases' | ||
|
||
const sanityPkgPath = path.resolve(__dirname, '../../../../../package.json') | ||
// eslint-disable-next-line import/no-dynamic-require | ||
const pkg = require(sanityPkgPath) | ||
|
||
describe('browserCompatibleSanityPackageSpecifiers', () => { | ||
it('should have all specifiers listed in the package.json', () => { | ||
const currentSpecifiers = Object.keys(pkg.exports) | ||
.map((subpath) => path.join('sanity', subpath)) | ||
.sort() | ||
|
||
// NOTE: this test is designed to fail if there are any changes to the | ||
// package exports in the sanity package.json so you can stop and consider if that | ||
// new subpath should also go into `browserCompatibleSanityPackageSpecifiers`. | ||
// If there are changes, you may need to update this variable. New subpaths | ||
// should go into `browserCompatibleSanityPackageSpecifiers` if that subpath | ||
// is meant to be imported in the browser (e.g. a new subpath that is only meant | ||
// for the CLI doesn't need to go into `browserCompatibleSanityPackageSpecifiers`). | ||
expect(currentSpecifiers).toEqual([ | ||
'sanity', | ||
'sanity/_internal', | ||
'sanity/_singletons', | ||
'sanity/cli', | ||
'sanity/desk', | ||
'sanity/migrate', | ||
'sanity/package.json', | ||
'sanity/presentation', | ||
'sanity/router', | ||
'sanity/structure', | ||
]) | ||
|
||
expect(browserCompatibleSanityPackageSpecifiers).toHaveLength(7) | ||
|
||
for (const specifier of browserCompatibleSanityPackageSpecifiers) { | ||
expect(currentSpecifiers).toContain(specifier) | ||
} | ||
}) | ||
}) | ||
|
||
describe('getAliases', () => { | ||
it('returns the correct aliases for normal builds', () => { | ||
const aliases = getAliases({ | ||
sanityPkgPath, | ||
conditions: ['import', 'browser'], | ||
browser: true, | ||
}) | ||
|
||
const expectedAliases = browserCompatibleSanityPackageSpecifiers.reduce<Record<string, string>>( | ||
(acc, specifier) => { | ||
const dest = resolve.exports(pkg, specifier, { | ||
browser: true, | ||
conditions: ['import', 'browser'], | ||
})?.[0] | ||
if (dest) { | ||
acc[specifier] = path.resolve(path.dirname(sanityPkgPath), dest) | ||
} | ||
return acc | ||
}, | ||
{}, | ||
) | ||
|
||
expect(aliases).toMatchObject(expectedAliases) | ||
}) | ||
|
||
it('returns the correct aliases for the monorepo', () => { | ||
const monorepoPath = path.resolve(__dirname, '../../../../../monorepo') | ||
const devAliases = { | ||
'sanity/_singletons': 'packages/sanity/src/_singletons.ts', | ||
'sanity/desk': 'packages/sanity/src/desk.ts', | ||
'sanity/presentation': 'packages/sanity/src/presentation.ts', | ||
} | ||
jest.doMock(path.resolve(monorepoPath, 'dev/aliases.cjs'), () => devAliases, {virtual: true}) | ||
|
||
const aliases = getAliases({ | ||
monorepo: {path: monorepoPath}, | ||
}) | ||
|
||
const expectedAliases = Object.fromEntries( | ||
Object.entries(devAliases).map(([key, modulePath]) => { | ||
return [key, path.resolve(monorepoPath, modulePath)] | ||
}), | ||
) | ||
|
||
expect(aliases).toMatchObject(expectedAliases) | ||
}) | ||
}) |
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 |
---|---|---|
@@ -1,32 +1,103 @@ | ||
import path from 'node:path' | ||
|
||
import resolve from 'resolve.exports' | ||
import {type AliasOptions} from 'vite' | ||
|
||
import {type SanityMonorepo} from './sanityMonorepo' | ||
|
||
/** | ||
* Returns an object of aliases for vite to use | ||
* @internal | ||
*/ | ||
export interface GetAliasesOptions { | ||
/** An optional monorepo configuration object. */ | ||
monorepo?: SanityMonorepo | ||
/** The path to the sanity package.json file. */ | ||
sanityPkgPath?: string | ||
/** The list of conditions to resolve package exports. */ | ||
conditions?: string[] | ||
} | ||
|
||
/** | ||
* The following are the specifiers that are expected/allowed to be used within | ||
* a built Sanity studio in the browser. These are used in combination with | ||
* `resolve.exports` to determine the final entry point locations for each allowed specifier. | ||
* | ||
* There is also a corresponding test for this file that expects these to be | ||
* included in the `sanity` package.json. That test is meant to keep this list | ||
* in sync in the event we add another package subpath. | ||
* | ||
* @internal | ||
*/ | ||
export const browserCompatibleSanityPackageSpecifiers = [ | ||
'sanity', | ||
'sanity/_singletons', | ||
'sanity/desk', | ||
'sanity/presentation', | ||
'sanity/router', | ||
'sanity/structure', | ||
'sanity/package.json', | ||
] | ||
|
||
/** | ||
* Returns an object of aliases for Vite to use. | ||
* | ||
* This function is used within our build tooling to prevent multiple context errors | ||
* due to multiple instances of our library. It resolves the appropriate paths for | ||
* modules based on whether the current project is inside the Sanity monorepo or not. | ||
* | ||
* If the project is within the monorepo, it uses the source files directly for a better | ||
* development experience. Otherwise, it uses the `sanityPkgPath` and `conditions` to locate | ||
* the entry points for each subpath the Sanity module exports. | ||
* | ||
* @internal | ||
*/ | ||
export function getAliases(opts: {monorepo?: SanityMonorepo}): Record<string, string> { | ||
const {monorepo} = opts | ||
export function getAliases({monorepo, sanityPkgPath, conditions}: GetAliasesOptions): AliasOptions { | ||
// If the current Studio is located within the Sanity monorepo | ||
if (monorepo?.path) { | ||
// Load monorepo aliases. This ensures that the Vite server uses the source files | ||
// instead of the compiled output, allowing for a better development experience. | ||
const aliasesPath = path.resolve(monorepo.path, 'dev/aliases.cjs') | ||
|
||
if (!monorepo?.path) { | ||
return {} | ||
// Import the development aliases configuration | ||
// eslint-disable-next-line import/no-dynamic-require | ||
const devAliases: Record<string, string> = require(aliasesPath) | ||
|
||
// Resolve each alias path relative to the monorepo path | ||
const monorepoAliases = Object.fromEntries( | ||
Object.entries(devAliases).map(([key, modulePath]) => { | ||
return [key, path.resolve(monorepo.path, modulePath)] | ||
}), | ||
) | ||
|
||
// Return the aliases configuration for monorepo | ||
return monorepoAliases | ||
} | ||
|
||
// Load monorepo aliases (if the current Studio is located within the sanity monorepo) | ||
// This is done in order for the Vite server to use the source files instead of | ||
// the compiled output, allowing for a better dev experience. | ||
const aliasesPath = path.resolve(monorepo.path, 'dev/aliases.cjs') | ||
// If not in the monorepo, use the `sanityPkgPath` and `conditions` | ||
// to locate the entry points for each subpath the Sanity module exports | ||
if (sanityPkgPath && conditions) { | ||
// Load the package.json of the Sanity package | ||
// eslint-disable-next-line import/no-dynamic-require | ||
const pkg = require(sanityPkgPath) | ||
const dirname = path.dirname(sanityPkgPath) | ||
|
||
// eslint-disable-next-line import/no-dynamic-require | ||
const devAliases: Record<string, string> = require(aliasesPath) | ||
// Resolve the entry points for each allowed specifier | ||
const unifiedSanityAliases = browserCompatibleSanityPackageSpecifiers.reduce< | ||
Record<string, string> | ||
>((acc, next) => { | ||
// Resolve the export path for the specifier using resolve.exports | ||
const dest = resolve.exports(pkg, next, {browser: true, conditions})?.[0] | ||
if (!dest) return acc | ||
|
||
const monorepoAliases = Object.fromEntries( | ||
Object.entries(devAliases).map(([key, modulePath]) => { | ||
return [key, path.resolve(monorepo.path, modulePath)] | ||
}), | ||
) | ||
// Map the specifier to its resolved path | ||
acc[next] = path.resolve(dirname, dest) | ||
return acc | ||
}, {}) | ||
|
||
// Return the aliases configuration for external projects | ||
return unifiedSanityAliases | ||
} | ||
|
||
return monorepoAliases | ||
// Return an empty aliases configuration if no conditions are met | ||
return {} | ||
} |
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.