From f8a9e8562ab28d107caaed31235cc6e359365fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ovidiu=20Chereche=C8=99?= Date: Thu, 27 Apr 2023 15:40:55 +0300 Subject: [PATCH] Generate TypeScript imports module (#1465) * Add TypeScript to userdeps templates * Convert user deps import extensions to .js * Update native.ts * Make it optional to import JS extension in userdeps * Make static imports more robust * Update native.ts * Add typeScript option * Only generate TS userdeps on disk * Refactor * Clean up paths * Refactor userdeps flag into new exposeModules option * Specify native port in test workflow * Improve ignore files * Rename server plugin * Improve user deps formatting * Rename user deps to user imports * s/exposeModules/exposeImports * s/modules/imports * Update exposeImports.ts * Update .prettierignore * Update reactNative.md * Update exposeImports.ts * Update exposeImports.ts * Update types.ts * Tweak stuff * Rename user deps to user imports in webpack package * Rename tests --- .github/workflows/test.yml | 2 +- .prettierignore | 9 +- cypress/tests/native.ts | 26 ++-- docs/README.md | 2 +- docs/customBundlerSetup.md | 6 +- docs/reactNative.md | 20 +-- examples/vite/.gitignore | 2 +- examples/vite/package.json | 2 +- examples/webpack/.gitignore | 2 +- examples/webpack/package.json | 2 +- .../src/createViteRendererIndex.ts | 4 +- .../src/reactCosmosViteRollupPlugin.ts | 17 ++- .../src/viteDevServerPlugin.ts | 8 +- .../src/client/index.ts | 4 +- .../client/{userDeps.ts => userImports.ts} | 0 .../server/testHelpers/mockEsmLoaderPath.ts | 2 +- .../__tests__/customDevConfig.ts | 6 +- .../__tests__/customExportConfig.ts | 6 +- .../__tests__/defaultDevConfig.ts | 6 +- .../__tests__/defaultExportConfig.ts | 6 +- .../webpackConfig/getWebpackConfigModule.ts | 6 +- .../webpackConfig/resolveWebpackLoaderPath.ts | 2 +- ...erDepsLoader.cjs => userImportsLoader.cjs} | 5 +- packages/react-cosmos/config.schema.json | 7 +- .../src/corePlugins/exposeImports.ts | 67 +++++++++ .../react-cosmos/src/corePlugins/index.ts | 4 +- .../src/corePlugins/userDepsFile.ts | 52 ------- .../cosmosConfig/__tests__/exposeImports.ts | 36 +++++ .../src/cosmosConfig/__tests__/lazy.ts | 21 +++ .../__tests__/userDepsFilePath.ts | 17 --- .../src/cosmosConfig/createCosmosConfig.ts | 23 ++- .../react-cosmos/src/cosmosConfig/types.ts | 10 +- .../src/getFixtures/getFixtures.ts | 4 +- packages/react-cosmos/src/index.ts | 4 +- .../react-cosmos/src/shared/playgroundHtml.ts | 6 +- .../userDepsLazyTemplate.test.ts.snap | 61 -------- .../userDepsTemplate.test.ts.snap | 71 --------- packages/react-cosmos/src/userDeps/shared.ts | 32 ---- .../src/userDeps/userDepsLazyTemplate.test.ts | 44 ------ .../src/userDeps/userDepsLazyTemplate.ts | 51 ------- .../src/userDeps/userDepsShared.ts | 41 ------ .../src/userDeps/userDepsTemplate.test.ts | 44 ------ .../src/userDeps/userDepsTemplate.ts | 59 -------- .../userImportsLazyTemplate.test.ts.snap | 117 +++++++++++++++ .../userImportsTemplate.test.ts.snap | 137 ++++++++++++++++++ .../findUserModulePaths.ts | 0 .../fixtureWatcher.ts | 0 .../generateUserImports.ts} | 15 +- .../importUserModules.ts} | 2 +- .../react-cosmos/src/userModules/shared.ts | 63 ++++++++ .../userImportsLazyTemplate.test.ts | 74 ++++++++++ .../userModules/userImportsLazyTemplate.ts | 83 +++++++++++ .../userModules/userImportsTemplate.test.ts | 74 ++++++++++ .../src/userModules/userImportsTemplate.ts | 89 ++++++++++++ packages/react-cosmos/src/utils/json.ts | 7 + scripts/build.ts | 4 +- website/.gitignore | 2 +- 57 files changed, 885 insertions(+), 581 deletions(-) rename packages/react-cosmos-plugin-webpack/src/client/{userDeps.ts => userImports.ts} (100%) rename packages/react-cosmos-plugin-webpack/src/server/webpackConfig/{userDepsLoader.cjs => userImportsLoader.cjs} (91%) create mode 100644 packages/react-cosmos/src/corePlugins/exposeImports.ts delete mode 100644 packages/react-cosmos/src/corePlugins/userDepsFile.ts create mode 100644 packages/react-cosmos/src/cosmosConfig/__tests__/exposeImports.ts create mode 100644 packages/react-cosmos/src/cosmosConfig/__tests__/lazy.ts delete mode 100644 packages/react-cosmos/src/cosmosConfig/__tests__/userDepsFilePath.ts delete mode 100644 packages/react-cosmos/src/userDeps/__snapshots__/userDepsLazyTemplate.test.ts.snap delete mode 100644 packages/react-cosmos/src/userDeps/__snapshots__/userDepsTemplate.test.ts.snap delete mode 100644 packages/react-cosmos/src/userDeps/shared.ts delete mode 100644 packages/react-cosmos/src/userDeps/userDepsLazyTemplate.test.ts delete mode 100644 packages/react-cosmos/src/userDeps/userDepsLazyTemplate.ts delete mode 100644 packages/react-cosmos/src/userDeps/userDepsShared.ts delete mode 100644 packages/react-cosmos/src/userDeps/userDepsTemplate.test.ts delete mode 100644 packages/react-cosmos/src/userDeps/userDepsTemplate.ts create mode 100644 packages/react-cosmos/src/userModules/__snapshots__/userImportsLazyTemplate.test.ts.snap create mode 100644 packages/react-cosmos/src/userModules/__snapshots__/userImportsTemplate.test.ts.snap rename packages/react-cosmos/src/{userDeps => userModules}/findUserModulePaths.ts (100%) rename packages/react-cosmos/src/{userDeps => userModules}/fixtureWatcher.ts (100%) rename packages/react-cosmos/src/{userDeps/generateUserDepsModule.ts => userModules/generateUserImports.ts} (64%) rename packages/react-cosmos/src/{userDeps/getUserModules.ts => userModules/importUserModules.ts} (97%) create mode 100644 packages/react-cosmos/src/userModules/shared.ts create mode 100644 packages/react-cosmos/src/userModules/userImportsLazyTemplate.test.ts create mode 100644 packages/react-cosmos/src/userModules/userImportsLazyTemplate.ts create mode 100644 packages/react-cosmos/src/userModules/userImportsTemplate.test.ts create mode 100644 packages/react-cosmos/src/userModules/userImportsTemplate.ts create mode 100644 packages/react-cosmos/src/utils/json.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2548f5ac9e..27d39c993b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,7 +79,7 @@ jobs: start: | yarn workspace example-${{ matrix.example }} start --lazy=${{ matrix.lazy == true }}, yarn workspace example-${{ matrix.example }} serve, - yarn workspace example-${{ matrix.example }} native --lazy=${{ matrix.lazy == true }} + yarn workspace example-${{ matrix.example }} native --lazy=${{ matrix.lazy == true }} --port 5002 wait-on: 'http://localhost:5000,http://localhost:5001,http://localhost:5002' env: CYPRESS_EXAMPLE_NAME: ${{ matrix.example }} diff --git a/.prettierignore b/.prettierignore index 5e27815d40..359d1d5efa 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,11 +1,10 @@ .vscode +cosmos-export +cosmos.imports.js +cosmos.imports.ts coverage -examples/*/cosmos-export -examples/*/cosmos.userdeps.js -examples/*/package.json lerna.json package.json packages/*/dist -packages/*/package.json -website/dist testMocks +website/dist diff --git a/cypress/tests/native.ts b/cypress/tests/native.ts index 6b16a48f9e..f102585f8c 100644 --- a/cypress/tests/native.ts +++ b/cypress/tests/native.ts @@ -32,40 +32,40 @@ describe('Native', () => { }); }); - context('user deps file', () => { + context('imports file', () => { it('has port option', () => { - getUserDepsFile().should( + getImportsFile().should( 'contain', `"playgroundUrl": "http://localhost:5002"` ); }); it('has fixture paths', () => { - containsImport('src/__fixtures__/HelloWorld.ts'); - containsImport('src/Counter.fixture.tsx'); - containsImport('src/WelcomeMessage/WelcomeMessage.fixture.tsx'); + containsImport('src/__fixtures__/HelloWorld.js'); + containsImport('src/Counter.fixture.js'); + containsImport('src/WelcomeMessage/WelcomeMessage.fixture.js'); }); it('has decorator paths', () => { - containsImport('src/WelcomeMessage/cosmos.decorator.tsx'); + containsImport('src/WelcomeMessage/cosmos.decorator.js'); }); }); }); -function getUserDepsFile() { - return cy.readFile(`examples/${exampleName()}/cosmos.userdeps.js`); +function getImportsFile() { + return cy.readFile(`examples/${exampleName()}/cosmos.imports.ts`); } -function containsImport(modulePath: string) { +function containsImport(importPath: string) { if (lazy()) { - getUserDepsFile().should( + getImportsFile().should( 'match', - new RegExp(`import\\('./${modulePath}'\\)`) + new RegExp(`import\\('./${importPath}'\\)`) ); } else { - getUserDepsFile().should( + getImportsFile().should( 'match', - new RegExp(`import [a-z0-9]+ from './${modulePath}'`) + new RegExp(`import \\* as [a-z0-9]+ from './${importPath}'`) ); } } diff --git a/docs/README.md b/docs/README.md index 81a782ecb5..1a7cdb4a74 100644 --- a/docs/README.md +++ b/docs/README.md @@ -613,7 +613,7 @@ This is a `.babelrc` example for Next.js: #### Fixtures not detected? -- Run `cosmos` with the `--external-userdeps` flag. This should generate `cosmos.userdeps.js`. Check that file to see if your fixtures are being picked up by Cosmos. +- Run `cosmos` with the `--expose-imports` flag. This should generate `cosmos.imports.js`. Check that file to see if your fixtures are being picked up by Cosmos. - Check your directory structure. If you are using a Cosmos config file, Cosmos will use the directory of the config file as the root of your project. If your Cosmos config file is nested in a directory that isn't an ancestor of your fixture files, Cosmos will not find your fixtures. To solve this add a [`rootDir`](https://github.com/react-cosmos/react-cosmos/blob/d800a31b39d82c810f37a2ad0d25eed5308b830a/packages/react-cosmos/config.schema.json#L10-L14) entry to your Cosmos config pointing to your root directory. --- diff --git a/docs/customBundlerSetup.md b/docs/customBundlerSetup.md index 7ec20981a5..e330e17663 100644 --- a/docs/customBundlerSetup.md +++ b/docs/customBundlerSetup.md @@ -23,18 +23,18 @@ Next, choose a port for the renderer other than the main Cosmos port, say `5050` } ``` -Next, start Cosmos with the `--external-userdeps` CLI flag. This will generate a `cosmos.userdeps.js` module that contains maps of user module imports (fixtures and decorators) as well the renderer config. Feel free to add this file to .gitignore. +Next, start Cosmos with the `--expose-imports` CLI flag. This will generate a `cosmos.imports.js` module that contains maps of user module imports (fixtures and decorators) as well the renderer config. Feel free to add this file to .gitignore. Finally, create a web server using your bundler of choice that serves an `index.html`, which loads a JS module with the following code: ```js import { mountDomRenderer } from 'react-cosmos-dom'; -import * as mountArgs from './cosmos.userdeps.js'; +import * as mountArgs from './cosmos.imports.js'; mountDomRenderer(mountArgs); ``` -That's it, really. The Cosmos Server will automatically update `cosmos.userdeps.js` when fixture/decorator files are added, changed or removed. +That's it, really. The Cosmos Server will automatically update `cosmos.imports.js` when fixture/decorator files are added, changed or removed. To learn how turn your setup into a Cosmos plugin check out the [Vite plugin](../packages/react-cosmos-plugin-vite). diff --git a/docs/reactNative.md b/docs/reactNative.md index da95c7b69a..ad54029924 100644 --- a/docs/reactNative.md +++ b/docs/reactNative.md @@ -82,7 +82,7 @@ yarn cosmos 🚀 Open **[localhost:5000](http://localhost:5000)** in your browser. -> You'll notice Cosmos generated a `cosmos.userdeps.js` module, which becomes relevant in step 5. You can add this file to .gitignore. +> You'll notice Cosmos generated a `cosmos.imports.js` module, which becomes relevant in step 5. You can add this file to .gitignore. The `Hello` fixture will show up in your React Cosmos UI. @@ -110,7 +110,7 @@ Then add the Cosmos renderer under `App.cosmos.js`: // App.cosmos.js import React, { Component } from 'react'; import { NativeFixtureLoader } from 'react-cosmos-native'; -import { rendererConfig, moduleWrappers } from './cosmos.userdeps.js'; +import { rendererConfig, moduleWrappers } from './cosmos.imports.js'; export default class CosmosApp extends Component { render() { @@ -124,16 +124,6 @@ export default class CosmosApp extends Component { } ``` -> When using TypeScript you'll notice an error related to `cosmos.userdeps.js`, which is a plain JS module. We're working on providing an option to generate `cosmos.userdeps.ts` soon. Meanwhile you can ignore this error by slapping a naughty `@ts-ignore` comment: -> -> ```diff -> rendererConfig={rendererConfig} -> + // @ts-ignore -> moduleWrappers={moduleWrappers} -> /> -> ``` - Finally, create a new `App.js` that routes between your main and Cosmos entry points based on enviromnent: ```js @@ -170,10 +160,12 @@ You can configure the Cosmos Native renderer to auto load a fixture on init. ``` +`initialFixtureId` expects a fixture path relative to the project root. You'll find the exact path in `cosmos.imports.js` as a key in the `fixtures` object. + ## Troubleshooting - React Native blacklists `__fixtures__` dirs by default (at least it used to). Unless you configure Cosmos to use a different directory pattern, you need to [override `getBlacklistRE` in the React Native CLI config](https://github.com/skidding/jobs-done/blob/585b1c472a123c9221dfec9018c9fa1e976d715e/rn-cli.config.js). @@ -184,6 +176,6 @@ You can get Cosmos to [mirror your fixtures on both DOM and Native renderers](ht 1. Set up Cosmos for Native using the steps above. 2. Set up the react-cosmos-webpack-plugin as described [here](README.md#getting-started). -3. Start Cosmos with the `cosmos --external-userdeps` command. +3. Start Cosmos with the `cosmos --expose-imports` command. [Join us on Discord](https://discord.gg/3X95VgfnW5) for feedback, questions and ideas. diff --git a/examples/vite/.gitignore b/examples/vite/.gitignore index adda7e047f..0aa11dde26 100644 --- a/examples/vite/.gitignore +++ b/examples/vite/.gitignore @@ -1,2 +1,2 @@ cosmos-export -cosmos.userdeps.js +cosmos.imports.ts diff --git a/examples/vite/package.json b/examples/vite/package.json index 420199c66e..03dc62a207 100644 --- a/examples/vite/package.json +++ b/examples/vite/package.json @@ -9,7 +9,7 @@ "start": "cosmos", "export": "cosmos-export", "serve": "http-server -p 5001 ./cosmos-export", - "native": "cosmos-native --port 5002" + "native": "cosmos-native" }, "dependencies": { "@vitejs/plugin-react": "^4.0.0", diff --git a/examples/webpack/.gitignore b/examples/webpack/.gitignore index adda7e047f..0aa11dde26 100644 --- a/examples/webpack/.gitignore +++ b/examples/webpack/.gitignore @@ -1,2 +1,2 @@ cosmos-export -cosmos.userdeps.js +cosmos.imports.ts diff --git a/examples/webpack/package.json b/examples/webpack/package.json index e35386ede9..ac3b4f1475 100644 --- a/examples/webpack/package.json +++ b/examples/webpack/package.json @@ -9,7 +9,7 @@ "start": "cosmos", "export": "cosmos-export", "serve": "http-server -p 5001 ./cosmos-export", - "native": "cosmos-native --port 5002" + "native": "cosmos-native" }, "dependencies": { "@babel/core": "^7.21.4", diff --git a/packages/react-cosmos-plugin-vite/src/createViteRendererIndex.ts b/packages/react-cosmos-plugin-vite/src/createViteRendererIndex.ts index 1979a1ba24..7a6ee69354 100644 --- a/packages/react-cosmos-plugin-vite/src/createViteRendererIndex.ts +++ b/packages/react-cosmos-plugin-vite/src/createViteRendererIndex.ts @@ -1,4 +1,4 @@ -export function createViteRendererIndex(userDepsModuleId: string) { +export function createViteRendererIndex(userImportsModuleId: string) { return ` import { mountDomRenderer } from 'react-cosmos-dom'; @@ -6,7 +6,7 @@ mount(); async function mount() { // Use dynamic import to reload updated modules upon hot reloading - const args = await import('${userDepsModuleId}'); + const args = await import('${userImportsModuleId}'); mountDomRenderer(args); } diff --git a/packages/react-cosmos-plugin-vite/src/reactCosmosViteRollupPlugin.ts b/packages/react-cosmos-plugin-vite/src/reactCosmosViteRollupPlugin.ts index 40e36fba17..de7489507b 100644 --- a/packages/react-cosmos-plugin-vite/src/reactCosmosViteRollupPlugin.ts +++ b/packages/react-cosmos-plugin-vite/src/reactCosmosViteRollupPlugin.ts @@ -1,15 +1,15 @@ import path from 'node:path'; import { CosmosConfig, - generateUserDepsModule, + generateUserImports, getPlaygroundUrl, } from 'react-cosmos'; import { Plugin } from 'rollup'; import { CosmosViteConfig } from './createCosmosViteConfig.js'; import { createViteRendererIndex } from './createViteRendererIndex.js'; -export const userDepsVirtualModuleId = 'virtual:cosmos-userdeps'; -export const userDepsResolvedModuleId = '\0' + userDepsVirtualModuleId; +export const userImportsVirtualModuleId = 'virtual:cosmos-imports'; +export const userImportsResolvedModuleId = '\0' + userImportsVirtualModuleId; const defaultIndexPattern = new RegExp( `^(src\\${path.sep})?(index|main)\.(js|ts)x?$` @@ -23,22 +23,23 @@ export function reactCosmosViteRollupPlugin( name: 'react-cosmos-vite-renderer', resolveId(id) { - if (id === userDepsVirtualModuleId) { - return userDepsResolvedModuleId; + if (id === userImportsVirtualModuleId) { + return userImportsResolvedModuleId; } else { return null; } }, load(id: string) { - if (id == userDepsResolvedModuleId) { - return generateUserDepsModule({ + if (id == userImportsResolvedModuleId) { + return generateUserImports({ cosmosConfig, rendererConfig: { playgroundUrl: getPlaygroundUrl(cosmosConfig), containerQuerySelector: cosmosConfig.dom.containerQuerySelector, }, relativeToDir: null, + typeScript: false, }); } else { return null; @@ -55,7 +56,7 @@ export function reactCosmosViteRollupPlugin( console.log(`[Cosmos] Replacing vite index module at ${relPath}`); return { - code: createViteRendererIndex(userDepsVirtualModuleId), + code: createViteRendererIndex(userImportsVirtualModuleId), map: null, }; } else { diff --git a/packages/react-cosmos-plugin-vite/src/viteDevServerPlugin.ts b/packages/react-cosmos-plugin-vite/src/viteDevServerPlugin.ts index 0568dfcfdf..3511b2c48d 100644 --- a/packages/react-cosmos-plugin-vite/src/viteDevServerPlugin.ts +++ b/packages/react-cosmos-plugin-vite/src/viteDevServerPlugin.ts @@ -3,7 +3,7 @@ import { createServer } from 'vite'; import { createCosmosViteConfig } from './createCosmosViteConfig.js'; import { reactCosmosViteRollupPlugin, - userDepsResolvedModuleId, + userImportsResolvedModuleId, } from './reactCosmosViteRollupPlugin.js'; export async function viteDevServerPlugin({ @@ -37,10 +37,12 @@ export async function viteDevServerPlugin({ await server.listen(); const watcher = await startFixtureWatcher(cosmosConfig, 'add', () => { - const module = server.moduleGraph.getModuleById(userDepsResolvedModuleId); + const module = server.moduleGraph.getModuleById( + userImportsResolvedModuleId + ); if (!module) { throw new Error( - `Vite module graph doesn't contain module with id ${userDepsResolvedModuleId}` + `Vite module graph doesn't contain module with id ${userImportsResolvedModuleId}` ); } server.moduleGraph.invalidateModule(module); diff --git a/packages/react-cosmos-plugin-webpack/src/client/index.ts b/packages/react-cosmos-plugin-webpack/src/client/index.ts index 4ae0bb8312..07435a91f1 100644 --- a/packages/react-cosmos-plugin-webpack/src/client/index.ts +++ b/packages/react-cosmos-plugin-webpack/src/client/index.ts @@ -5,7 +5,7 @@ mount(); async function mount() { // Use dynamic import to reload updated modules upon hot reloading - const { rendererConfig, moduleWrappers } = await import('./userDeps.js'); + const { rendererConfig, moduleWrappers } = await import('./userImports.js'); mountDomRenderer({ rendererConfig, moduleWrappers, @@ -14,7 +14,7 @@ async function mount() { } if ((import.meta as any).webpackHot) { - (import.meta as any).webpackHot.accept('./userDeps.js', () => { + (import.meta as any).webpackHot.accept('./userImports.js', () => { // If a previous error has been solved, the error overlay auto-closes nicely. // If the error persists, however, the overlay will pop up again on its own dismissErrorOverlay(); diff --git a/packages/react-cosmos-plugin-webpack/src/client/userDeps.ts b/packages/react-cosmos-plugin-webpack/src/client/userImports.ts similarity index 100% rename from packages/react-cosmos-plugin-webpack/src/client/userDeps.ts rename to packages/react-cosmos-plugin-webpack/src/client/userImports.ts diff --git a/packages/react-cosmos-plugin-webpack/src/server/testHelpers/mockEsmLoaderPath.ts b/packages/react-cosmos-plugin-webpack/src/server/testHelpers/mockEsmLoaderPath.ts index ee2b1efc90..7002a1c42a 100644 --- a/packages/react-cosmos-plugin-webpack/src/server/testHelpers/mockEsmLoaderPath.ts +++ b/packages/react-cosmos-plugin-webpack/src/server/testHelpers/mockEsmLoaderPath.ts @@ -1,6 +1,6 @@ jest.mock('../webpackConfig/resolveWebpackLoaderPath.js', () => { function resolveWebpackLoaderPath() { - return require.resolve('../webpackConfig/userDepsLoader.cjs'); + return require.resolve('../webpackConfig/userImportsLoader.cjs'); } return { resolveWebpackLoaderPath }; diff --git a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/customDevConfig.ts b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/customDevConfig.ts index 8c40c32f48..27fe6e23ce 100644 --- a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/customDevConfig.ts +++ b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/customDevConfig.ts @@ -111,11 +111,11 @@ it('create output', async () => { ); }); -it('includes user deps loader', async () => { +it('includes user imports loader', async () => { const { module } = await getCustomDevWebpackConfig(); expect(module!.rules).toContainEqual({ - loader: require.resolve('../userDepsLoader'), - include: require.resolve('../../../client/userDeps'), + loader: require.resolve('../userImportsLoader'), + include: require.resolve('../../../client/userImports'), options: { cosmosConfig }, }); }); diff --git a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/customExportConfig.ts b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/customExportConfig.ts index bd4ae99995..8396d751e7 100644 --- a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/customExportConfig.ts +++ b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/customExportConfig.ts @@ -88,11 +88,11 @@ it('create output', async () => { ); }); -it('includes user deps loader', async () => { +it('includes user imports loader', async () => { const { module } = await getCustomExportWebpackConfig(); expect(module!.rules).toContainEqual({ - loader: require.resolve('../userDepsLoader'), - include: require.resolve('../../../client/userDeps'), + loader: require.resolve('../userImportsLoader'), + include: require.resolve('../../../client/userImports'), options: { cosmosConfig }, }); }); diff --git a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/defaultDevConfig.ts b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/defaultDevConfig.ts index ff59681d84..c9f25516d8 100644 --- a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/defaultDevConfig.ts +++ b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/defaultDevConfig.ts @@ -55,11 +55,11 @@ it('create output', async () => { ); }); -it('includes user deps loader', async () => { +it('includes user imports loader', async () => { const { module } = await getDefaultDevWebpackConfig(); expect(module!.rules).toContainEqual({ - loader: require.resolve('../userDepsLoader'), - include: require.resolve('../../../client/userDeps'), + loader: require.resolve('../userImportsLoader'), + include: require.resolve('../../../client/userImports'), options: { cosmosConfig }, }); }); diff --git a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/defaultExportConfig.ts b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/defaultExportConfig.ts index d7f347d34f..f91f41a141 100644 --- a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/defaultExportConfig.ts +++ b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/__tests__/defaultExportConfig.ts @@ -60,11 +60,11 @@ it('create output', async () => { ); }); -it('includes user deps loader', async () => { +it('includes user imports loader', async () => { const { module } = await getDefaultExportWebpackConfig(); expect(module!.rules).toContainEqual({ - loader: require.resolve('../userDepsLoader'), - include: require.resolve('../../../client/userDeps'), + loader: require.resolve('../userImportsLoader'), + include: require.resolve('../../../client/userImports'), options: { cosmosConfig }, }); }); diff --git a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/getWebpackConfigModule.ts b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/getWebpackConfigModule.ts index b9caf50d51..7200074814 100644 --- a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/getWebpackConfigModule.ts +++ b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/getWebpackConfigModule.ts @@ -18,15 +18,15 @@ function getRules( { module }: webpack.Configuration ) { const existingRules = (module && module.rules) || []; - return [...existingRules, getUserDepsLoaderRule(cosmosConfig)]; + return [...existingRules, getUserImportsLoaderRule(cosmosConfig)]; } -function getUserDepsLoaderRule( +function getUserImportsLoaderRule( cosmosConfig: CosmosConfig ): webpack.RuleSetRule { return { loader: resolveWebpackLoaderPath(), - include: resolveWebpackClientPath('userDeps'), + include: resolveWebpackClientPath('userImports'), options: { cosmosConfig }, }; } diff --git a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/resolveWebpackLoaderPath.ts b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/resolveWebpackLoaderPath.ts index 7d7e31e516..6148a6ecb9 100644 --- a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/resolveWebpackLoaderPath.ts +++ b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/resolveWebpackLoaderPath.ts @@ -5,5 +5,5 @@ import { createRequire } from 'node:module'; export function resolveWebpackLoaderPath() { const require = createRequire(import.meta.url); - return require.resolve('./userDepsLoader.cjs'); + return require.resolve('./userImportsLoader.cjs'); } diff --git a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/userDepsLoader.cjs b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/userImportsLoader.cjs similarity index 91% rename from packages/react-cosmos-plugin-webpack/src/server/webpackConfig/userDepsLoader.cjs rename to packages/react-cosmos-plugin-webpack/src/server/webpackConfig/userImportsLoader.cjs index 73a43d13fa..3a26b043d6 100644 --- a/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/userDepsLoader.cjs +++ b/packages/react-cosmos-plugin-webpack/src/server/webpackConfig/userImportsLoader.cjs @@ -2,7 +2,7 @@ // https://github.com/webpack/webpack/issues/13233 // To circumvent this we use this CJS source file that's manually copied in the // dist folder as part of the build process -module.exports = async function injectUserDeps() { +module.exports = async function injectUserImports() { const cosmos = await import('react-cosmos'); const { cosmosConfig } = this.getOptions(); @@ -21,9 +21,10 @@ module.exports = async function injectUserDeps() { playgroundUrl: cosmos.getPlaygroundUrl(cosmosConfig), containerQuerySelector, }; - return cosmos.generateUserDepsModule({ + return cosmos.generateUserImports({ cosmosConfig, rendererConfig, relativeToDir: null, + typeScript: false, }); }; diff --git a/packages/react-cosmos/config.schema.json b/packages/react-cosmos/config.schema.json index 54576504bf..195f443de5 100644 --- a/packages/react-cosmos/config.schema.json +++ b/packages/react-cosmos/config.schema.json @@ -69,10 +69,9 @@ "description": "Dynamically import fixture and decorator modules as they are loaded. When false all fixture and decorator modules are imported statically and bundled together. [default: false]", "type": "boolean" }, - "userDepsFilePath": { - "description": "Where to generate a file that contains a map to all fixtures and other Cosmos-related user modules. Only used in setups where Cosmos can't piggyback on the user's build (eg. React Native setups). [default: \"cosmos.userdeps.js\"]", - "type": "string", - "minLength": 1 + "exposeImports": { + "description": "Expose user imports and config required for the Cosmos renderer. Used with React Native and in custom integrations. When a path is specified it requires a file extension (eg. \"src/cosmos.imports.ts\"). [default: false]", + "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "boolean" }] }, "hostname": { "description": "Dev server hostname. Set to null to accept connections with any hostname. [default: null]", diff --git a/packages/react-cosmos/src/corePlugins/exposeImports.ts b/packages/react-cosmos/src/corePlugins/exposeImports.ts new file mode 100644 index 0000000000..05de0d9f37 --- /dev/null +++ b/packages/react-cosmos/src/corePlugins/exposeImports.ts @@ -0,0 +1,67 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { RendererConfig } from 'react-cosmos-core'; +import { CosmosConfig } from '../cosmosConfig/types.js'; +import { CosmosServerPlugin, PlatformType } from '../cosmosPlugin/types.js'; +import { getPlaygroundUrl } from '../shared/playgroundUrl.js'; +import { startFixtureWatcher } from '../userModules/fixtureWatcher.js'; +import { generateUserImports } from '../userModules/generateUserImports.js'; +import { moduleExists } from '../utils/fs.js'; + +export const exposeImportsServerPlugin: CosmosServerPlugin = { + name: 'exposeImports', + + async devServer({ cosmosConfig, platformType }) { + if (!shouldExposeImports(platformType, cosmosConfig)) return; + + await generateImportsFile(cosmosConfig); + const watcher = await startFixtureWatcher(cosmosConfig, 'all', () => { + generateImportsFile(cosmosConfig); + }); + + return () => { + watcher.close(); + }; + }, +}; + +function shouldExposeImports( + platformType: PlatformType, + cosmosConfig: CosmosConfig +) { + return platformType === 'native' || Boolean(cosmosConfig.exposeImports); +} + +async function generateImportsFile(cosmosConfig: CosmosConfig) { + const { exposeImports } = cosmosConfig; + + const filePath = + typeof exposeImports === 'string' + ? exposeImports + : getDefaultFilePath(cosmosConfig.rootDir); + + const typeScript = /\.tsx?$/.test(filePath); + + const rendererConfig: RendererConfig = { + playgroundUrl: getPlaygroundUrl(cosmosConfig), + }; + const fileSource = generateUserImports({ + cosmosConfig, + rendererConfig, + relativeToDir: path.dirname(filePath), + typeScript, + }); + await fs.writeFile(filePath, fileSource, 'utf8'); + + const relModulesPath = path.relative(process.cwd(), filePath); + if (typeScript && typeof exposeImports === 'boolean') { + console.log(`[Cosmos] Generated ${relModulesPath} (TypeScript detected)`); + } else { + console.log(`[Cosmos] Generated ${relModulesPath}`); + } +} + +function getDefaultFilePath(rootDir: string) { + const ext = moduleExists('typescript') ? 'ts' : 'js'; + return path.resolve(rootDir, `cosmos.imports.${ext}`); +} diff --git a/packages/react-cosmos/src/corePlugins/index.ts b/packages/react-cosmos/src/corePlugins/index.ts index 98116e5b9b..fb0748f950 100644 --- a/packages/react-cosmos/src/corePlugins/index.ts +++ b/packages/react-cosmos/src/corePlugins/index.ts @@ -1,13 +1,13 @@ import { CosmosServerPlugin } from '../cosmosPlugin/types.js'; +import { exposeImportsServerPlugin } from './exposeImports.js'; import { httpProxyServerPlugin } from './httpProxy.js'; import { openFileServerPlugin } from './openFile.js'; import { pluginEndpointServerPlugin } from './pluginEndpoint.js'; import { portRetryServerPlugin } from './portRetry.js'; -import { userDepsFileServerPlugin } from './userDepsFile.js'; export const coreServerPlugins: CosmosServerPlugin[] = [ portRetryServerPlugin, - userDepsFileServerPlugin, + exposeImportsServerPlugin, httpProxyServerPlugin, openFileServerPlugin, pluginEndpointServerPlugin, diff --git a/packages/react-cosmos/src/corePlugins/userDepsFile.ts b/packages/react-cosmos/src/corePlugins/userDepsFile.ts deleted file mode 100644 index 6cb789a73a..0000000000 --- a/packages/react-cosmos/src/corePlugins/userDepsFile.ts +++ /dev/null @@ -1,52 +0,0 @@ -import fs from 'fs/promises'; -import path from 'path'; -import { RendererConfig } from 'react-cosmos-core'; -import { CosmosConfig } from '../cosmosConfig/types.js'; -import { CosmosServerPlugin, PlatformType } from '../cosmosPlugin/types.js'; -import { getPlaygroundUrl } from '../shared/playgroundUrl.js'; -import { startFixtureWatcher } from '../userDeps/fixtureWatcher.js'; -import { generateUserDepsModule } from '../userDeps/generateUserDepsModule.js'; -import { getCliArgs } from '../utils/cli.js'; - -export const userDepsFileServerPlugin: CosmosServerPlugin = { - name: 'userDepsFile', - - async devServer(args) { - if (!shouldGenerateUserDepsFile(args.platformType)) return; - - const { cosmosConfig } = args; - await generateUserDepsFile(cosmosConfig); - const watcher = await startFixtureWatcher(cosmosConfig, 'all', () => { - generateUserDepsFile(cosmosConfig); - }); - - return () => { - watcher.close(); - }; - }, -}; - -function shouldGenerateUserDepsFile(platformType: PlatformType): boolean { - return ( - platformType === 'native' || - // CLI support for --external-userdeps flag (useful with react-native-web) - Boolean(getCliArgs().externalUserdeps) - ); -} - -async function generateUserDepsFile(cosmosConfig: CosmosConfig) { - const { userDepsFilePath } = cosmosConfig; - - const rendererConfig: RendererConfig = { - playgroundUrl: getPlaygroundUrl(cosmosConfig), - }; - const userDepsModule = generateUserDepsModule({ - cosmosConfig, - rendererConfig, - relativeToDir: path.dirname(userDepsFilePath), - }); - await fs.writeFile(userDepsFilePath, userDepsModule, 'utf8'); - - const relUserDepsFilePath = path.relative(process.cwd(), userDepsFilePath); - console.log(`[Cosmos] Generated ${relUserDepsFilePath}`); -} diff --git a/packages/react-cosmos/src/cosmosConfig/__tests__/exposeImports.ts b/packages/react-cosmos/src/cosmosConfig/__tests__/exposeImports.ts new file mode 100644 index 0000000000..d409eaa801 --- /dev/null +++ b/packages/react-cosmos/src/cosmosConfig/__tests__/exposeImports.ts @@ -0,0 +1,36 @@ +// Import mocks first +import '../../testHelpers/mockEsmResolve.js'; +import { mockCliArgs, unmockCliArgs } from '../../testHelpers/mockYargs.js'; + +import { getCwdPath } from '../../testHelpers/cwd.js'; +import { createCosmosConfig } from '../createCosmosConfig.js'; + +afterEach(() => { + unmockCliArgs(); +}); + +it('does not expose imports by default', () => { + const cosmosConfig = createCosmosConfig(process.cwd()); + expect(cosmosConfig.exposeImports).toBe(false); +}); + +it('returns resolved user imports path', () => { + const cosmosConfig = createCosmosConfig(process.cwd(), { + exposeImports: 'src/myImports.ts', + }); + expect(cosmosConfig.exposeImports).toBe(getCwdPath('src/myImports.ts')); +}); + +it('uses --exportImports CLI arg', () => { + mockCliArgs({ exposeImports: true }); + + const cosmosConfig = createCosmosConfig(process.cwd()); + expect(cosmosConfig.exposeImports).toBe(true); +}); + +it('resolves --exportImports CLI arg path', () => { + mockCliArgs({ exposeImports: 'src/myImports.ts' }); + + const cosmosConfig = createCosmosConfig(process.cwd()); + expect(cosmosConfig.exposeImports).toBe(getCwdPath('src/myImports.ts')); +}); diff --git a/packages/react-cosmos/src/cosmosConfig/__tests__/lazy.ts b/packages/react-cosmos/src/cosmosConfig/__tests__/lazy.ts new file mode 100644 index 0000000000..70bbca79d1 --- /dev/null +++ b/packages/react-cosmos/src/cosmosConfig/__tests__/lazy.ts @@ -0,0 +1,21 @@ +// Import mocks first +import '../../testHelpers/mockEsmResolve.js'; +import { mockCliArgs, unmockCliArgs } from '../../testHelpers/mockYargs.js'; + +import { createCosmosConfig } from '../createCosmosConfig.js'; + +afterEach(() => { + unmockCliArgs(); +}); + +it('defaults lazy to false', () => { + const cosmosConfig = createCosmosConfig(process.cwd()); + expect(cosmosConfig.lazy).toBe(false); +}); + +it('uses --lazy CLI arg', () => { + mockCliArgs({ lazy: true }); + + const cosmosConfig = createCosmosConfig(process.cwd()); + expect(cosmosConfig.lazy).toBe(true); +}); diff --git a/packages/react-cosmos/src/cosmosConfig/__tests__/userDepsFilePath.ts b/packages/react-cosmos/src/cosmosConfig/__tests__/userDepsFilePath.ts deleted file mode 100644 index ac86bc7f38..0000000000 --- a/packages/react-cosmos/src/cosmosConfig/__tests__/userDepsFilePath.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Import mocks first -import '../../testHelpers/mockEsmResolve.js'; - -import { getCwdPath } from '../../testHelpers/cwd.js'; -import { createCosmosConfig } from '../createCosmosConfig.js'; - -it('returns resolved default getUserDepsFilePath', () => { - const cosmosConfig = createCosmosConfig(process.cwd()); - expect(cosmosConfig.userDepsFilePath).toBe(getCwdPath('cosmos.userdeps.js')); -}); - -it('returns resolved custom getUserDepsFilePath', () => { - const cosmosConfig = createCosmosConfig(process.cwd(), { - userDepsFilePath: 'heremydeps.js', - }); - expect(cosmosConfig.userDepsFilePath).toBe(getCwdPath('heremydeps.js')); -}); diff --git a/packages/react-cosmos/src/cosmosConfig/createCosmosConfig.ts b/packages/react-cosmos/src/cosmosConfig/createCosmosConfig.ts index 695136b75e..b92c8d3cf2 100644 --- a/packages/react-cosmos/src/cosmosConfig/createCosmosConfig.ts +++ b/packages/react-cosmos/src/cosmosConfig/createCosmosConfig.ts @@ -14,9 +14,10 @@ export function createCosmosConfig( return { ...cosmosConfigInput, rootDir, - exportPath: getExportPath(cosmosConfigInput, rootDir), detectLocalPlugins: cosmosConfigInput.detectLocalPlugins ?? true, disablePlugins: cosmosConfigInput.disablePlugins ?? false, + exposeImports: getExposeImports(cosmosConfigInput, rootDir), + exportPath: getExportPath(cosmosConfigInput, rootDir), fixtureFileSuffix: getFixtureFileSuffix(cosmosConfigInput), fixturesDir: getFixturesDir(cosmosConfigInput), globalImports: getGlobalImports(cosmosConfigInput, rootDir), @@ -25,14 +26,13 @@ export function createCosmosConfig( httpsOptions: getHttpsOptions(cosmosConfigInput, rootDir), ignore: getIgnore(cosmosConfigInput), lazy: getLazy(cosmosConfigInput), + plugins: getPlugins(cosmosConfigInput, rootDir), port: getPort(cosmosConfigInput), portRetries: getPortRetries(cosmosConfigInput), - plugins: getPlugins(cosmosConfigInput, rootDir), publicUrl: getPublicUrl(cosmosConfigInput), + rendererUrl: cosmosConfigInput.rendererUrl ?? null, staticPath: getStaticPath(cosmosConfigInput, rootDir), - userDepsFilePath: getUserDepsFilePath(cosmosConfigInput, rootDir), watchDirs: getWatchDirs(cosmosConfigInput, rootDir), - rendererUrl: cosmosConfigInput.rendererUrl ?? null, dom: getDomConfig(cosmosConfigInput.dom || {}), ui: cosmosConfigInput.ui || {}, }; @@ -92,12 +92,21 @@ function getWatchDirs(cosmosConfigInput: CosmosConfigInput, rootDir: string) { return watchDirs.map(watchDir => path.resolve(rootDir, watchDir)); } -function getUserDepsFilePath( +function getExposeImports( cosmosConfigInput: CosmosConfigInput, rootDir: string ) { - const { userDepsFilePath = 'cosmos.userdeps.js' } = cosmosConfigInput; - return path.resolve(rootDir, userDepsFilePath); + const cliArgs = getCliArgs(); + if (typeof cliArgs.exposeImports === 'boolean') { + return cliArgs.exposeImports; + } else if (typeof cliArgs.exposeImports === 'string') { + return path.resolve(rootDir, cliArgs.exposeImports); + } + + const { exposeImports = false } = cosmosConfigInput; + return typeof exposeImports === 'string' + ? path.resolve(rootDir, exposeImports) + : exposeImports; } function getHostname({ hostname = null }: CosmosConfigInput) { diff --git a/packages/react-cosmos/src/cosmosConfig/types.ts b/packages/react-cosmos/src/cosmosConfig/types.ts index 596bcec45e..93d49c2fb0 100644 --- a/packages/react-cosmos/src/cosmosConfig/types.ts +++ b/packages/react-cosmos/src/cosmosConfig/types.ts @@ -12,6 +12,11 @@ export type CosmosConfig = { detectLocalPlugins: boolean; disablePlugins: boolean; dom: CosmosDomConfig; + // Used with React Native and in custom integrations, exposeImports specifies + // whether (and where when passed a string) to generate a file that exposes + // the user imports and config required for the Cosmos renderer (fixtures, + // decorators, etc.) When a path is specified it requires a file extension. + exposeImports: boolean | string; fixtureFileSuffix: string; fixturesDir: string; globalImports: string[]; @@ -32,11 +37,6 @@ export type CosmosConfig = { rendererUrl: string | null; rootDir: string; staticPath: null | string; - // Only used by the React Native server, userDepsFilePath specifies where to - // generate the file with global imports, fixtures and decorators. - // Whereas most of the other paths are used to import modules, userDepsFilePath - // is used as an output file path and it requires a file extension. - userDepsFilePath: string; watchDirs: string[]; // Plugin configs [option: string]: unknown; diff --git a/packages/react-cosmos/src/getFixtures/getFixtures.ts b/packages/react-cosmos/src/getFixtures/getFixtures.ts index 504ea88073..236326beeb 100644 --- a/packages/react-cosmos/src/getFixtures/getFixtures.ts +++ b/packages/react-cosmos/src/getFixtures/getFixtures.ts @@ -17,7 +17,7 @@ import { import { CosmosConfig } from '../cosmosConfig/types.js'; import { RENDERER_FILENAME } from '../shared/playgroundHtml.js'; import { resolveRendererUrl } from '../shared/resolveRendererUrl.js'; -import { getUserModules } from '../userDeps/getUserModules.js'; +import { importUserModules } from '../userModules/importUserModules.js'; export type FixtureApi = { absoluteFilePath: string; @@ -35,7 +35,7 @@ export function getFixtures(cosmosConfig: CosmosConfig) { const { fixturesDir, fixtureFileSuffix, rootDir } = cosmosConfig; const fixtureInfo: FixtureApi[] = []; - const { fixtures, decorators } = getUserModules(cosmosConfig); + const { fixtures, decorators } = importUserModules(cosmosConfig); const fixtureList = getFixtureListFromExports(fixtures); const fixtureTree = createFixtureTree({ fixtures: fixtureList, diff --git a/packages/react-cosmos/src/index.ts b/packages/react-cosmos/src/index.ts index 96c54b441e..887edfe596 100644 --- a/packages/react-cosmos/src/index.ts +++ b/packages/react-cosmos/src/index.ts @@ -11,8 +11,8 @@ export { RENDERER_FILENAME } from './shared/playgroundHtml.js'; export * from './shared/playgroundUrl.js'; export * from './shared/staticServer.js'; export * from './testHelpers/cwd.js'; -export * from './userDeps/fixtureWatcher.js'; -export * from './userDeps/generateUserDepsModule.js'; +export * from './userModules/fixtureWatcher.js'; +export * from './userModules/generateUserImports.js'; export * from './utils/cli.js'; export * from './utils/fs.js'; export * from './utils/requireSilent.js'; diff --git a/packages/react-cosmos/src/shared/playgroundHtml.ts b/packages/react-cosmos/src/shared/playgroundHtml.ts index 2fac79e38f..798317b2a2 100644 --- a/packages/react-cosmos/src/shared/playgroundHtml.ts +++ b/packages/react-cosmos/src/shared/playgroundHtml.ts @@ -10,8 +10,8 @@ import { import { PlaygroundConfig, PlaygroundMountArgs } from 'react-cosmos-ui'; import { CosmosConfig } from '../cosmosConfig/types.js'; import { PlatformType } from '../cosmosPlugin/types.js'; -import { findUserModulePaths } from '../userDeps/findUserModulePaths.js'; -import { userDepsKeyPath } from '../userDeps/userDepsShared.js'; +import { findUserModulePaths } from '../userModules/findUserModulePaths.js'; +import { importKeyPath } from '../userModules/shared.js'; import { resolveRendererUrl } from './resolveRendererUrl.js'; import { getStaticPath } from './staticPath.js'; @@ -125,7 +125,7 @@ function getFixtureList(cosmosConfig: CosmosConfig) { return fixturePaths.reduce( (acc, fixturePath) => ({ ...acc, - [userDepsKeyPath(fixturePath, cosmosConfig.rootDir)]: { type: 'single' }, + [importKeyPath(fixturePath, cosmosConfig.rootDir)]: { type: 'single' }, }), {} ); diff --git a/packages/react-cosmos/src/userDeps/__snapshots__/userDepsLazyTemplate.test.ts.snap b/packages/react-cosmos/src/userDeps/__snapshots__/userDepsLazyTemplate.test.ts.snap deleted file mode 100644 index 2010efa808..0000000000 --- a/packages/react-cosmos/src/userDeps/__snapshots__/userDepsLazyTemplate.test.ts.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should generate user deps module with absolute paths 1`] = ` -"// This file is automatically generated by Cosmos. Add it to .gitignore and -// only edit if you know what you're doing. - -// Keeping global imports here is superior to making them bundle entry points -// because this way they become hot-reloadable. -import '/Users/ovidiu/cosmos/src/polyfills.ts'; -import '/Users/ovidiu/cosmos/src/global.css'; - -export const rendererConfig = { - "playgroundUrl": "http://localhost:5002" -}; - -const fixtures = { - 'src/Counter/Counter.fixture.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/Counter/Counter.fixture.tsx') }, - 'src/CounterButton/CounterButton.fixture.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.tsx') } -}; - -const decorators = { - 'src/cosmos.decorator.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/cosmos.decorator.tsx') } -}; - -export const moduleWrappers = { - lazy: true, - fixtures, - decorators, -}; -" -`; - -exports[`should generate user deps module with relative paths 1`] = ` -"// This file is automatically generated by Cosmos. Add it to .gitignore and -// only edit if you know what you're doing. - -// Keeping global imports here is superior to making them bundle entry points -// because this way they become hot-reloadable. -import './polyfills.ts'; -import './global.css'; - -export const rendererConfig = { - "playgroundUrl": "http://localhost:5002" -}; - -const fixtures = { - 'src/Counter/Counter.fixture.tsx': { getModule: () => import('./Counter/Counter.fixture.tsx') }, - 'src/CounterButton/CounterButton.fixture.tsx': { getModule: () => import('./CounterButton/CounterButton.fixture.tsx') } -}; - -const decorators = { - 'src/cosmos.decorator.tsx': { getModule: () => import('./cosmos.decorator.tsx') } -}; - -export const moduleWrappers = { - lazy: true, - fixtures, - decorators, -}; -" -`; diff --git a/packages/react-cosmos/src/userDeps/__snapshots__/userDepsTemplate.test.ts.snap b/packages/react-cosmos/src/userDeps/__snapshots__/userDepsTemplate.test.ts.snap deleted file mode 100644 index 4429c2783e..0000000000 --- a/packages/react-cosmos/src/userDeps/__snapshots__/userDepsTemplate.test.ts.snap +++ /dev/null @@ -1,71 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should generate user deps module with absolute paths 1`] = ` -"// This file is automatically generated by Cosmos. Add it to .gitignore and -// only edit if you know what you're doing. - -// Keeping global imports here is superior to making them bundle entry points -// because this way they become hot-reloadable. -import '/Users/ovidiu/cosmos/src/polyfills.ts'; -import '/Users/ovidiu/cosmos/src/global.css'; - -import fixture0 from '/Users/ovidiu/cosmos/src/Counter/Counter.fixture.tsx'; -import fixture1 from '/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.tsx'; - -import decorator0 from '/Users/ovidiu/cosmos/src/cosmos.decorator.tsx'; - -export const rendererConfig = { - "playgroundUrl": "http://localhost:5002" -}; - -const fixtures = { - 'src/Counter/Counter.fixture.tsx': { module: { default: fixture0 } }, - 'src/CounterButton/CounterButton.fixture.tsx': { module: { default: fixture1 } } -}; - -const decorators = { - 'src/cosmos.decorator.tsx': { module: { default: decorator0 } } -}; - -export const moduleWrappers = { - lazy: false, - fixtures, - decorators, -}; -" -`; - -exports[`should generate user deps module with relative paths 1`] = ` -"// This file is automatically generated by Cosmos. Add it to .gitignore and -// only edit if you know what you're doing. - -// Keeping global imports here is superior to making them bundle entry points -// because this way they become hot-reloadable. -import './polyfills.ts'; -import './global.css'; - -import fixture0 from './Counter/Counter.fixture.tsx'; -import fixture1 from './CounterButton/CounterButton.fixture.tsx'; - -import decorator0 from './cosmos.decorator.tsx'; - -export const rendererConfig = { - "playgroundUrl": "http://localhost:5002" -}; - -const fixtures = { - 'src/Counter/Counter.fixture.tsx': { module: { default: fixture0 } }, - 'src/CounterButton/CounterButton.fixture.tsx': { module: { default: fixture1 } } -}; - -const decorators = { - 'src/cosmos.decorator.tsx': { module: { default: decorator0 } } -}; - -export const moduleWrappers = { - lazy: false, - fixtures, - decorators, -}; -" -`; diff --git a/packages/react-cosmos/src/userDeps/shared.ts b/packages/react-cosmos/src/userDeps/shared.ts deleted file mode 100644 index 19114293b6..0000000000 --- a/packages/react-cosmos/src/userDeps/shared.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { replaceKeys } from 'react-cosmos-core'; - -export type Json = - | string - | number - | boolean - | null - | { [property: string]: Json } - | Json[]; - -// NODE: These can be made configurable if a proper need arises -const FIXTURE_PATTERNS = [ - '**//**/*.{js,jsx,ts,tsx}', - '**/*..{js,jsx,ts,tsx}', -]; -const DECORATOR_PATTERNS = ['**/cosmos.decorator.{js,jsx,ts,tsx}']; - -export function getFixturePatterns( - fixturesDir: string, - fixtureFileSuffix: string -): string[] { - return FIXTURE_PATTERNS.map(pattern => - replaceKeys(pattern, { - '': fixturesDir, - '': fixtureFileSuffix, - }) - ); -} - -export function getDecoratorPatterns() { - return DECORATOR_PATTERNS; -} diff --git a/packages/react-cosmos/src/userDeps/userDepsLazyTemplate.test.ts b/packages/react-cosmos/src/userDeps/userDepsLazyTemplate.test.ts deleted file mode 100644 index cbaa840646..0000000000 --- a/packages/react-cosmos/src/userDeps/userDepsLazyTemplate.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { RendererConfig } from 'react-cosmos-core'; -import { userDepsLazyTemplate } from './userDepsLazyTemplate.js'; - -const globalImports = [ - '/Users/ovidiu/cosmos/src/polyfills.ts', - '/Users/ovidiu/cosmos/src/global.css', -]; - -const fixturePaths = [ - '/Users/ovidiu/cosmos/src/Counter/Counter.fixture.tsx', - '/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.tsx', -]; - -const decoratorPaths = ['/Users/ovidiu/cosmos/src/cosmos.decorator.tsx']; - -const rendererConfig: RendererConfig = { - playgroundUrl: 'http://localhost:5002', -}; - -it('should generate user deps module with absolute paths', () => { - expect( - userDepsLazyTemplate({ - globalImports, - fixturePaths, - decoratorPaths, - rendererConfig, - rootDir: '/Users/ovidiu/cosmos/', - relativeToDir: null, - }) - ).toMatchSnapshot(); -}); - -it('should generate user deps module with relative paths', () => { - expect( - userDepsLazyTemplate({ - globalImports, - fixturePaths, - decoratorPaths, - rendererConfig, - rootDir: '/Users/ovidiu/cosmos', - relativeToDir: '/Users/ovidiu/cosmos/src', - }) - ).toMatchSnapshot(); -}); diff --git a/packages/react-cosmos/src/userDeps/userDepsLazyTemplate.ts b/packages/react-cosmos/src/userDeps/userDepsLazyTemplate.ts deleted file mode 100644 index b6c71a61fc..0000000000 --- a/packages/react-cosmos/src/userDeps/userDepsLazyTemplate.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - userDepsImportMap, - userDepsImportPath, - UserDepsTemplateArgs, -} from './userDepsShared.js'; - -export function userDepsLazyTemplate({ - globalImports, - fixturePaths, - decoratorPaths, - rendererConfig, - rootDir, - relativeToDir, -}: UserDepsTemplateArgs) { - const fixtures = userDepsImportMap(fixturePaths, rootDir, relativeToDir); - const fixtureKeys = Object.keys(fixtures); - - const decorators = userDepsImportMap(decoratorPaths, rootDir, relativeToDir); - const decoratorKeys = Object.keys(decorators); - - return ` -// This file is automatically generated by Cosmos. Add it to .gitignore and -// only edit if you know what you're doing. - -// Keeping global imports here is superior to making them bundle entry points -// because this way they become hot-reloadable. -${globalImports - .map(p => `import '${userDepsImportPath(p, relativeToDir)}';`) - .join(`\n`)} - -export const rendererConfig = ${JSON.stringify(rendererConfig, null, 2)}; - -const fixtures = { -${fixtureKeys - .map(k => ` '${k}': { getModule: () => import('${fixtures[k]}') }`) - .join(`,\n`)} -}; - -const decorators = { -${decoratorKeys - .map(k => ` '${k}': { getModule: () => import('${decorators[k]}') }`) - .join(`,\n`)} -}; - -export const moduleWrappers = { - lazy: true, - fixtures, - decorators, -}; -`.trimStart(); -} diff --git a/packages/react-cosmos/src/userDeps/userDepsShared.ts b/packages/react-cosmos/src/userDeps/userDepsShared.ts deleted file mode 100644 index 1f575f8973..0000000000 --- a/packages/react-cosmos/src/userDeps/userDepsShared.ts +++ /dev/null @@ -1,41 +0,0 @@ -import path from 'path'; -import { slash } from '../utils/slash.js'; -import { Json } from './shared.js'; - -export type UserDepsTemplateArgs = { - globalImports: string[]; - fixturePaths: string[]; - decoratorPaths: string[]; - rendererConfig: Json; - rootDir: string; - relativeToDir: string | null; -}; - -export function userDepsImportMap( - paths: string[], - rootDir: string, - relativeToDir: string | null -): Record { - return paths.reduce( - (acc, p) => ({ - ...acc, - [userDepsKeyPath(p, rootDir)]: userDepsImportPath(p, relativeToDir), - }), - {} - ); -} - -export function userDepsKeyPath(filePath: string, rootDir: string) { - return slash(path.relative(rootDir, filePath)); -} - -export function userDepsImportPath( - filePath: string, - relativeToDir: string | null -) { - return slash( - relativeToDir - ? `.${path.sep}${path.relative(relativeToDir, filePath)}` - : filePath - ); -} diff --git a/packages/react-cosmos/src/userDeps/userDepsTemplate.test.ts b/packages/react-cosmos/src/userDeps/userDepsTemplate.test.ts deleted file mode 100644 index a9a578d981..0000000000 --- a/packages/react-cosmos/src/userDeps/userDepsTemplate.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { RendererConfig } from 'react-cosmos-core'; -import { userDepsTemplate } from './userDepsTemplate.js'; - -const globalImports = [ - '/Users/ovidiu/cosmos/src/polyfills.ts', - '/Users/ovidiu/cosmos/src/global.css', -]; - -const fixturePaths = [ - '/Users/ovidiu/cosmos/src/Counter/Counter.fixture.tsx', - '/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.tsx', -]; - -const decoratorPaths = ['/Users/ovidiu/cosmos/src/cosmos.decorator.tsx']; - -const rendererConfig: RendererConfig = { - playgroundUrl: 'http://localhost:5002', -}; - -it('should generate user deps module with absolute paths', () => { - expect( - userDepsTemplate({ - globalImports, - fixturePaths, - decoratorPaths, - rendererConfig, - rootDir: '/Users/ovidiu/cosmos/', - relativeToDir: null, - }) - ).toMatchSnapshot(); -}); - -it('should generate user deps module with relative paths', () => { - expect( - userDepsTemplate({ - globalImports, - fixturePaths, - decoratorPaths, - rendererConfig, - rootDir: '/Users/ovidiu/cosmos', - relativeToDir: '/Users/ovidiu/cosmos/src', - }) - ).toMatchSnapshot(); -}); diff --git a/packages/react-cosmos/src/userDeps/userDepsTemplate.ts b/packages/react-cosmos/src/userDeps/userDepsTemplate.ts deleted file mode 100644 index 621eda6a20..0000000000 --- a/packages/react-cosmos/src/userDeps/userDepsTemplate.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - userDepsImportMap, - userDepsImportPath, - UserDepsTemplateArgs, -} from './userDepsShared.js'; - -export function userDepsTemplate({ - globalImports, - fixturePaths, - decoratorPaths, - rendererConfig, - rootDir, - relativeToDir, -}: UserDepsTemplateArgs) { - const fixtures = userDepsImportMap(fixturePaths, rootDir, relativeToDir); - const fixtureKeys = Object.keys(fixtures); - - const decorators = userDepsImportMap(decoratorPaths, rootDir, relativeToDir); - const decoratorKeys = Object.keys(decorators); - - return ` -// This file is automatically generated by Cosmos. Add it to .gitignore and -// only edit if you know what you're doing. - -// Keeping global imports here is superior to making them bundle entry points -// because this way they become hot-reloadable. -${globalImports - .map(p => `import '${userDepsImportPath(p, relativeToDir)}';`) - .join(`\n`)} - -${fixtureKeys - .map((k, i) => `import fixture${i} from '${fixtures[k]}';`) - .join(`\n`)} - -${decoratorKeys - .map((k, i) => `import decorator${i} from '${decorators[k]}';`) - .join(`\n`)} - -export const rendererConfig = ${JSON.stringify(rendererConfig, null, 2)}; - -const fixtures = { -${fixtureKeys - .map((k, i) => ` '${k}': { module: { default: fixture${i} } }`) - .join(`,\n`)} -}; - -const decorators = { -${decoratorKeys - .map((k, i) => ` '${k}': { module: { default: decorator${i} } }`) - .join(`,\n`)} -}; - -export const moduleWrappers = { - lazy: false, - fixtures, - decorators, -}; -`.trimStart(); -} diff --git a/packages/react-cosmos/src/userModules/__snapshots__/userImportsLazyTemplate.test.ts.snap b/packages/react-cosmos/src/userModules/__snapshots__/userImportsLazyTemplate.test.ts.snap new file mode 100644 index 0000000000..d1c2506636 --- /dev/null +++ b/packages/react-cosmos/src/userModules/__snapshots__/userImportsLazyTemplate.test.ts.snap @@ -0,0 +1,117 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should generate TypeScript user imports with absolute paths 1`] = ` +"// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. + +import { RendererConfig, UserModuleWrappers } from 'react-cosmos-core'; + +import '/Users/ovidiu/cosmos/src/polyfills.js'; +import '/Users/ovidiu/cosmos/src/global.css'; + +export const rendererConfig: RendererConfig = { + "playgroundUrl": "http://localhost:5002" +}; + +const fixtures = { + 'src/Counter/Counter.fixture.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/Counter/Counter.fixture.js') }, + 'src/CounterButton/CounterButton.fixture.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.js') } +}; + +const decorators = { + 'src/cosmos.decorator.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/cosmos.decorator.js') } +}; + +export const moduleWrappers: UserModuleWrappers = { + lazy: true, + fixtures, + decorators, +}; +" +`; + +exports[`should generate TypeScript user imports with relative paths 1`] = ` +"// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. + +import { RendererConfig, UserModuleWrappers } from 'react-cosmos-core'; + +import './polyfills.js'; +import './global.css'; + +export const rendererConfig: RendererConfig = { + "playgroundUrl": "http://localhost:5002" +}; + +const fixtures = { + 'src/Counter/Counter.fixture.tsx': { getModule: () => import('./Counter/Counter.fixture.js') }, + 'src/CounterButton/CounterButton.fixture.tsx': { getModule: () => import('./CounterButton/CounterButton.fixture.js') } +}; + +const decorators = { + 'src/cosmos.decorator.tsx': { getModule: () => import('./cosmos.decorator.js') } +}; + +export const moduleWrappers: UserModuleWrappers = { + lazy: true, + fixtures, + decorators, +}; +" +`; + +exports[`should generate user imports with absolute paths 1`] = ` +"// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. + +import '/Users/ovidiu/cosmos/src/polyfills.ts'; +import '/Users/ovidiu/cosmos/src/global.css'; + +export const rendererConfig = { + "playgroundUrl": "http://localhost:5002" +}; + +const fixtures = { + 'src/Counter/Counter.fixture.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/Counter/Counter.fixture.tsx') }, + 'src/CounterButton/CounterButton.fixture.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.tsx') } +}; + +const decorators = { + 'src/cosmos.decorator.tsx': { getModule: () => import('/Users/ovidiu/cosmos/src/cosmos.decorator.tsx') } +}; + +export const moduleWrappers = { + lazy: true, + fixtures, + decorators, +}; +" +`; + +exports[`should generate user imports with relative paths 1`] = ` +"// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. + +import './polyfills.ts'; +import './global.css'; + +export const rendererConfig = { + "playgroundUrl": "http://localhost:5002" +}; + +const fixtures = { + 'src/Counter/Counter.fixture.tsx': { getModule: () => import('./Counter/Counter.fixture.tsx') }, + 'src/CounterButton/CounterButton.fixture.tsx': { getModule: () => import('./CounterButton/CounterButton.fixture.tsx') } +}; + +const decorators = { + 'src/cosmos.decorator.tsx': { getModule: () => import('./cosmos.decorator.tsx') } +}; + +export const moduleWrappers = { + lazy: true, + fixtures, + decorators, +}; +" +`; diff --git a/packages/react-cosmos/src/userModules/__snapshots__/userImportsTemplate.test.ts.snap b/packages/react-cosmos/src/userModules/__snapshots__/userImportsTemplate.test.ts.snap new file mode 100644 index 0000000000..f8a3c9d5ac --- /dev/null +++ b/packages/react-cosmos/src/userModules/__snapshots__/userImportsTemplate.test.ts.snap @@ -0,0 +1,137 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should generate TypeScript user imports with absolute paths 1`] = ` +"// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. + +import { RendererConfig, UserModuleWrappers } from 'react-cosmos-core'; + +import '/Users/ovidiu/cosmos/src/polyfills.js'; +import '/Users/ovidiu/cosmos/src/global.css'; + +import * as fixture0 from '/Users/ovidiu/cosmos/src/Counter/Counter.fixture.js'; +import * as fixture1 from '/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.js'; + +import * as decorator0 from '/Users/ovidiu/cosmos/src/cosmos.decorator.js'; + +export const rendererConfig: RendererConfig = { + "playgroundUrl": "http://localhost:5002" +}; + +const fixtures = { + 'src/Counter/Counter.fixture.tsx': { module: fixture0 }, + 'src/CounterButton/CounterButton.fixture.tsx': { module: fixture1 } +}; + +const decorators = { + 'src/cosmos.decorator.tsx': { module: decorator0 } +}; + +export const moduleWrappers: UserModuleWrappers = { + lazy: false, + fixtures, + decorators, +}; +" +`; + +exports[`should generate TypeScript user imports with relative paths 1`] = ` +"// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. + +import { RendererConfig, UserModuleWrappers } from 'react-cosmos-core'; + +import './polyfills.js'; +import './global.css'; + +import * as fixture0 from './Counter/Counter.fixture.js'; +import * as fixture1 from './CounterButton/CounterButton.fixture.js'; + +import * as decorator0 from './cosmos.decorator.js'; + +export const rendererConfig: RendererConfig = { + "playgroundUrl": "http://localhost:5002" +}; + +const fixtures = { + 'src/Counter/Counter.fixture.tsx': { module: fixture0 }, + 'src/CounterButton/CounterButton.fixture.tsx': { module: fixture1 } +}; + +const decorators = { + 'src/cosmos.decorator.tsx': { module: decorator0 } +}; + +export const moduleWrappers: UserModuleWrappers = { + lazy: false, + fixtures, + decorators, +}; +" +`; + +exports[`should generate user imports with absolute paths 1`] = ` +"// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. + +import '/Users/ovidiu/cosmos/src/polyfills.ts'; +import '/Users/ovidiu/cosmos/src/global.css'; + +import * as fixture0 from '/Users/ovidiu/cosmos/src/Counter/Counter.fixture.tsx'; +import * as fixture1 from '/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.tsx'; + +import * as decorator0 from '/Users/ovidiu/cosmos/src/cosmos.decorator.tsx'; + +export const rendererConfig = { + "playgroundUrl": "http://localhost:5002" +}; + +const fixtures = { + 'src/Counter/Counter.fixture.tsx': { module: fixture0 }, + 'src/CounterButton/CounterButton.fixture.tsx': { module: fixture1 } +}; + +const decorators = { + 'src/cosmos.decorator.tsx': { module: decorator0 } +}; + +export const moduleWrappers = { + lazy: false, + fixtures, + decorators, +}; +" +`; + +exports[`should generate user imports with relative paths 1`] = ` +"// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. + +import './polyfills.ts'; +import './global.css'; + +import * as fixture0 from './Counter/Counter.fixture.tsx'; +import * as fixture1 from './CounterButton/CounterButton.fixture.tsx'; + +import * as decorator0 from './cosmos.decorator.tsx'; + +export const rendererConfig = { + "playgroundUrl": "http://localhost:5002" +}; + +const fixtures = { + 'src/Counter/Counter.fixture.tsx': { module: fixture0 }, + 'src/CounterButton/CounterButton.fixture.tsx': { module: fixture1 } +}; + +const decorators = { + 'src/cosmos.decorator.tsx': { module: decorator0 } +}; + +export const moduleWrappers = { + lazy: false, + fixtures, + decorators, +}; +" +`; diff --git a/packages/react-cosmos/src/userDeps/findUserModulePaths.ts b/packages/react-cosmos/src/userModules/findUserModulePaths.ts similarity index 100% rename from packages/react-cosmos/src/userDeps/findUserModulePaths.ts rename to packages/react-cosmos/src/userModules/findUserModulePaths.ts diff --git a/packages/react-cosmos/src/userDeps/fixtureWatcher.ts b/packages/react-cosmos/src/userModules/fixtureWatcher.ts similarity index 100% rename from packages/react-cosmos/src/userDeps/fixtureWatcher.ts rename to packages/react-cosmos/src/userModules/fixtureWatcher.ts diff --git a/packages/react-cosmos/src/userDeps/generateUserDepsModule.ts b/packages/react-cosmos/src/userModules/generateUserImports.ts similarity index 64% rename from packages/react-cosmos/src/userDeps/generateUserDepsModule.ts rename to packages/react-cosmos/src/userModules/generateUserImports.ts index b3cbbe87de..2a34169480 100644 --- a/packages/react-cosmos/src/userDeps/generateUserDepsModule.ts +++ b/packages/react-cosmos/src/userModules/generateUserImports.ts @@ -1,18 +1,20 @@ import { CosmosConfig } from '../cosmosConfig/types.js'; +import { Json } from '../utils/json.js'; import { findUserModulePaths } from './findUserModulePaths.js'; -import { Json } from './shared.js'; -import { userDepsLazyTemplate } from './userDepsLazyTemplate.js'; -import { userDepsTemplate } from './userDepsTemplate.js'; +import { userImportsLazyTemplate } from './userImportsLazyTemplate.js'; +import { userImportsTemplate } from './userImportsTemplate.js'; type Args = { cosmosConfig: CosmosConfig; rendererConfig: Json; relativeToDir: string | null; + typeScript: boolean; }; -export function generateUserDepsModule({ +export function generateUserImports({ cosmosConfig, rendererConfig, relativeToDir, + typeScript, }: Args): string { const { rootDir, fixturesDir, fixtureFileSuffix, globalImports, ignore } = cosmosConfig; @@ -23,7 +25,9 @@ export function generateUserDepsModule({ ignore, }); - const template = cosmosConfig.lazy ? userDepsLazyTemplate : userDepsTemplate; + const template = cosmosConfig.lazy + ? userImportsLazyTemplate + : userImportsTemplate; return template({ globalImports, @@ -32,5 +36,6 @@ export function generateUserDepsModule({ rendererConfig, rootDir, relativeToDir, + typeScript, }); } diff --git a/packages/react-cosmos/src/userDeps/getUserModules.ts b/packages/react-cosmos/src/userModules/importUserModules.ts similarity index 97% rename from packages/react-cosmos/src/userDeps/getUserModules.ts rename to packages/react-cosmos/src/userModules/importUserModules.ts index f3ad5fef38..f157a00d5b 100644 --- a/packages/react-cosmos/src/userDeps/getUserModules.ts +++ b/packages/react-cosmos/src/userModules/importUserModules.ts @@ -9,7 +9,7 @@ type UserModules = { decorators: ByPath; }; -export function getUserModules({ +export function importUserModules({ rootDir, fixturesDir, fixtureFileSuffix, diff --git a/packages/react-cosmos/src/userModules/shared.ts b/packages/react-cosmos/src/userModules/shared.ts new file mode 100644 index 0000000000..f3a808070d --- /dev/null +++ b/packages/react-cosmos/src/userModules/shared.ts @@ -0,0 +1,63 @@ +import path from 'path'; +import { replaceKeys } from 'react-cosmos-core'; +import { Json } from '../utils/json.js'; +import { slash } from '../utils/slash.js'; + +export type UserImportsTemplateArgs = { + globalImports: string[]; + fixturePaths: string[]; + decoratorPaths: string[]; + rendererConfig: Json; + rootDir: string; + relativeToDir: string | null; + typeScript: boolean; +}; + +// NODE: These can be made configurable if a proper need arises +const FIXTURE_PATTERNS = [ + '**//**/*.{js,jsx,ts,tsx}', + '**/*..{js,jsx,ts,tsx}', +]; +const DECORATOR_PATTERNS = ['**/cosmos.decorator.{js,jsx,ts,tsx}']; + +export function getFixturePatterns( + fixturesDir: string, + fixtureFileSuffix: string +): string[] { + return FIXTURE_PATTERNS.map(pattern => + replaceKeys(pattern, { + '': fixturesDir, + '': fixtureFileSuffix, + }) + ); +} + +export function getDecoratorPatterns() { + return DECORATOR_PATTERNS; +} + +export function createImportMap( + paths: string[], + rootDir: string, + relativeToDir: string | null +): Record { + return paths.reduce( + (acc, p) => ({ + ...acc, + [importKeyPath(p, rootDir)]: importPath(p, relativeToDir), + }), + {} + ); +} + +export function importKeyPath(filePath: string, rootDir: string) { + return slash(path.relative(rootDir, filePath)); +} + +export function importPath(filePath: string, relativeToDir: string | null) { + return slash( + relativeToDir + ? `.${path.sep}${path.relative(relativeToDir, filePath)}` + : filePath + ); +} diff --git a/packages/react-cosmos/src/userModules/userImportsLazyTemplate.test.ts b/packages/react-cosmos/src/userModules/userImportsLazyTemplate.test.ts new file mode 100644 index 0000000000..6e5c8117c8 --- /dev/null +++ b/packages/react-cosmos/src/userModules/userImportsLazyTemplate.test.ts @@ -0,0 +1,74 @@ +import { RendererConfig } from 'react-cosmos-core'; +import { userImportsLazyTemplate } from './userImportsLazyTemplate.js'; + +const globalImports = [ + '/Users/ovidiu/cosmos/src/polyfills.ts', + '/Users/ovidiu/cosmos/src/global.css', +]; + +const fixturePaths = [ + '/Users/ovidiu/cosmos/src/Counter/Counter.fixture.tsx', + '/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.tsx', +]; + +const decoratorPaths = ['/Users/ovidiu/cosmos/src/cosmos.decorator.tsx']; + +const rendererConfig: RendererConfig = { + playgroundUrl: 'http://localhost:5002', +}; + +it('should generate user imports with absolute paths', () => { + expect( + userImportsLazyTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir: '/Users/ovidiu/cosmos', + relativeToDir: null, + typeScript: false, + }) + ).toMatchSnapshot(); +}); + +it('should generate user imports with relative paths', () => { + expect( + userImportsLazyTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir: '/Users/ovidiu/cosmos', + relativeToDir: '/Users/ovidiu/cosmos/src', + typeScript: false, + }) + ).toMatchSnapshot(); +}); + +it('should generate TypeScript user imports with absolute paths', () => { + expect( + userImportsLazyTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir: '/Users/ovidiu/cosmos', + relativeToDir: null, + typeScript: true, + }) + ).toMatchSnapshot(); +}); + +it('should generate TypeScript user imports with relative paths', () => { + expect( + userImportsLazyTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir: '/Users/ovidiu/cosmos', + relativeToDir: '/Users/ovidiu/cosmos/src', + typeScript: true, + }) + ).toMatchSnapshot(); +}); diff --git a/packages/react-cosmos/src/userModules/userImportsLazyTemplate.ts b/packages/react-cosmos/src/userModules/userImportsLazyTemplate.ts new file mode 100644 index 0000000000..b5540a9ec8 --- /dev/null +++ b/packages/react-cosmos/src/userModules/userImportsLazyTemplate.ts @@ -0,0 +1,83 @@ +import { flatten } from 'lodash-es'; +import { + UserImportsTemplateArgs, + createImportMap, + importPath, +} from './shared.js'; + +export function userImportsLazyTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir, + relativeToDir, + typeScript, +}: UserImportsTemplateArgs) { + function ext(filePath: string) { + return typeScript ? filePath.replace(/\.tsx?$/, '.js') : filePath; + } + + function ts(annotation: string) { + return typeScript ? annotation : ''; + } + + const globalImportItems = globalImports.map( + p => `import '${ext(importPath(p, relativeToDir))}';` + ); + + const fixtures = createImportMap(fixturePaths, rootDir, relativeToDir); + const fixtureKeys = Object.keys(fixtures); + const fixtureItems = fixtureKeys.map( + k => ` '${k}': { getModule: () => import('${ext(fixtures[k])}') }` + ); + + const decorators = createImportMap(decoratorPaths, rootDir, relativeToDir); + const decoratorKeys = Object.keys(decorators); + const decoratorItems = decoratorKeys.map( + k => ` '${k}': { getModule: () => import('${ext(decorators[k])}') }` + ); + + const rendererConfigStr = JSON.stringify(rendererConfig, null, 2); + + let importGroups = [globalImportItems]; + if (typeScript) { + importGroups.unshift([ + `import { RendererConfig, UserModuleWrappers } from 'react-cosmos-core';`, + ]); + } + + return ` +// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. +${importsStr(importGroups)} +export const rendererConfig${ts(': RendererConfig')} = ${rendererConfigStr}; + +const fixtures = {${objBody(fixtureItems)}}; + +const decorators = {${objBody(decoratorItems)}}; + +export const moduleWrappers${ts(': UserModuleWrappers')} = { + lazy: true, + fixtures, + decorators, +}; +`.trimStart(); +} + +function importsStr(items: string[][]) { + if (flatten(items).length === 0) { + return ''; + } + + const str = items + .filter(rows => rows.length > 0) + .map(rows => rows.join('\n')) + .join('\n\n'); + + return `\n${str}\n`; +} + +function objBody(items: string[]) { + return items.length > 0 ? `\n${items.join(`,\n`)}\n` : ''; +} diff --git a/packages/react-cosmos/src/userModules/userImportsTemplate.test.ts b/packages/react-cosmos/src/userModules/userImportsTemplate.test.ts new file mode 100644 index 0000000000..90eba828e6 --- /dev/null +++ b/packages/react-cosmos/src/userModules/userImportsTemplate.test.ts @@ -0,0 +1,74 @@ +import { RendererConfig } from 'react-cosmos-core'; +import { userImportsTemplate } from './userImportsTemplate.js'; + +const globalImports = [ + '/Users/ovidiu/cosmos/src/polyfills.ts', + '/Users/ovidiu/cosmos/src/global.css', +]; + +const fixturePaths = [ + '/Users/ovidiu/cosmos/src/Counter/Counter.fixture.tsx', + '/Users/ovidiu/cosmos/src/CounterButton/CounterButton.fixture.tsx', +]; + +const decoratorPaths = ['/Users/ovidiu/cosmos/src/cosmos.decorator.tsx']; + +const rendererConfig: RendererConfig = { + playgroundUrl: 'http://localhost:5002', +}; + +it('should generate user imports with absolute paths', () => { + expect( + userImportsTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir: '/Users/ovidiu/cosmos', + relativeToDir: null, + typeScript: false, + }) + ).toMatchSnapshot(); +}); + +it('should generate user imports with relative paths', () => { + expect( + userImportsTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir: '/Users/ovidiu/cosmos', + relativeToDir: '/Users/ovidiu/cosmos/src', + typeScript: false, + }) + ).toMatchSnapshot(); +}); + +it('should generate TypeScript user imports with absolute paths', () => { + expect( + userImportsTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir: '/Users/ovidiu/cosmos', + relativeToDir: null, + typeScript: true, + }) + ).toMatchSnapshot(); +}); + +it('should generate TypeScript user imports with relative paths', () => { + expect( + userImportsTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir: '/Users/ovidiu/cosmos', + relativeToDir: '/Users/ovidiu/cosmos/src', + typeScript: true, + }) + ).toMatchSnapshot(); +}); diff --git a/packages/react-cosmos/src/userModules/userImportsTemplate.ts b/packages/react-cosmos/src/userModules/userImportsTemplate.ts new file mode 100644 index 0000000000..ebb09294d7 --- /dev/null +++ b/packages/react-cosmos/src/userModules/userImportsTemplate.ts @@ -0,0 +1,89 @@ +import { flatten } from 'lodash-es'; +import { + UserImportsTemplateArgs, + createImportMap, + importPath, +} from './shared.js'; + +export function userImportsTemplate({ + globalImports, + fixturePaths, + decoratorPaths, + rendererConfig, + rootDir, + relativeToDir, + typeScript, +}: UserImportsTemplateArgs) { + function ext(filePath: string) { + return typeScript ? filePath.replace(/\.tsx?$/, '.js') : filePath; + } + + function ts(annotation: string) { + return typeScript ? annotation : ''; + } + + const globalImportItems = globalImports.map( + p => `import '${ext(importPath(p, relativeToDir))}';` + ); + + const fixtures = createImportMap(fixturePaths, rootDir, relativeToDir); + const fixtureKeys = Object.keys(fixtures); + const fixtureImports = fixtureKeys.map( + (k, i) => `import * as fixture${i} from '${ext(fixtures[k])}';` + ); + const fixtureItems = fixtureKeys.map( + (k, i) => ` '${k}': { module: fixture${i} }` + ); + + const decorators = createImportMap(decoratorPaths, rootDir, relativeToDir); + const decoratorKeys = Object.keys(decorators); + const decoratorImports = decoratorKeys.map( + (k, i) => `import * as decorator${i} from '${ext(decorators[k])}';` + ); + const decoratorItems = decoratorKeys.map( + (k, i) => ` '${k}': { module: decorator${i} }` + ); + + const rendererConfigStr = JSON.stringify(rendererConfig, null, 2); + + let importGroups = [globalImportItems, fixtureImports, decoratorImports]; + if (typeScript) { + importGroups.unshift([ + `import { RendererConfig, UserModuleWrappers } from 'react-cosmos-core';`, + ]); + } + + return ` +// This file is automatically generated by Cosmos. Add it to .gitignore and +// only edit if you know what you're doing. +${importsStr(importGroups)} +export const rendererConfig${ts(': RendererConfig')} = ${rendererConfigStr}; + +const fixtures = {${objBody(fixtureItems)}}; + +const decorators = {${objBody(decoratorItems)}}; + +export const moduleWrappers${ts(': UserModuleWrappers')} = { + lazy: false, + fixtures, + decorators, +}; +`.trimStart(); +} + +function importsStr(items: string[][]) { + if (flatten(items).length === 0) { + return ''; + } + + const str = items + .filter(rows => rows.length > 0) + .map(rows => rows.join('\n')) + .join('\n\n'); + + return `\n${str}\n`; +} + +function objBody(items: string[]) { + return items.length > 0 ? `\n${items.join(`,\n`)}\n` : ''; +} diff --git a/packages/react-cosmos/src/utils/json.ts b/packages/react-cosmos/src/utils/json.ts new file mode 100644 index 0000000000..94409522a6 --- /dev/null +++ b/packages/react-cosmos/src/utils/json.ts @@ -0,0 +1,7 @@ +export type Json = + | string + | number + | boolean + | null + | { [property: string]: Json } + | Json[]; diff --git a/scripts/build.ts b/scripts/build.ts index ef66b5140a..91eb90924f 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -44,8 +44,8 @@ const builders: Partial> & { default: Builder } = { await buildPkgTs(pkgName, 'tsconfig.build.client.json'); await buildPkgTs(pkgName, 'tsconfig.build.server.json'); await fs.copyFile( - pkgPath(pkgName, 'src/server/webpackConfig/userDepsLoader.cjs'), - pkgPath(pkgName, 'dist/server/webpackConfig/userDepsLoader.cjs') + pkgPath(pkgName, 'src/server/webpackConfig/userImportsLoader.cjs'), + pkgPath(pkgName, 'dist/server/webpackConfig/userImportsLoader.cjs') ); await buildPkgTs(pkgName, 'tsconfig.build.ui.json'); await buildPkgWebpack(pkgName, 'src/ui/webpack.config.js'); diff --git a/website/.gitignore b/website/.gitignore index a804cf7ab3..57375327ec 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -1,3 +1,3 @@ dist cosmos-export -cosmos.userdeps.js +cosmos.imports.ts