diff --git a/.circleci/config.yml b/.circleci/config.yml index f6dbe5910f..de22b6a2ab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,6 +36,7 @@ jobs: # Test build - run: yarn dist - run: yarn build + - run: yarn build:plugin - run: yarn export - run: command: yarn start diff --git a/.gitignore b/.gitignore index 1f6fc868bf..87d8fc3406 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ npm-debug.log yarn-error.log packages/*/dist -packages/react-cosmos-playground2/src/plugins/pluginEntry.ts coverage cypress/screenshots diff --git a/cypress/tests/native.js b/cypress/tests/native.js index 921dd49812..7e96cfd9d5 100644 --- a/cypress/tests/native.js +++ b/cypress/tests/native.js @@ -23,13 +23,13 @@ describe('Native', () => { }); it('has fixture paths', () => { - userDepsContainsModule('__fixtures__/Hello World.ts'); - userDepsContainsModule('Counter/index.fixture.tsx'); - userDepsContainsModule('WelcomeMessage/index.fixture.tsx'); + userDepsContainsModule('components/__fixtures__/Hello World.ts'); + userDepsContainsModule('components/Counter/index.fixture.tsx'); + userDepsContainsModule('components/WelcomeMessage/index.fixture.tsx'); }); it('has decorator paths', () => { - userDepsContainsModule('WelcomeMessage/cosmos.decorator.tsx'); + userDepsContainsModule('components/WelcomeMessage/cosmos.decorator.tsx'); }); }); }); diff --git a/example/.gitignore b/example/.gitignore index adda7e047f..fa440e2d98 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,2 +1,3 @@ cosmos-export cosmos.userdeps.js +booleanInputPlugin/dist diff --git a/example/booleanInputPlugin/cosmos.plugin.json b/example/booleanInputPlugin/cosmos.plugin.json new file mode 100644 index 0000000000..20de054c2e --- /dev/null +++ b/example/booleanInputPlugin/cosmos.plugin.json @@ -0,0 +1,4 @@ +{ + "name": "Boolean input", + "ui": "dist/ui" +} diff --git a/example/booleanInputPlugin/src/BooleanInput.tsx b/example/booleanInputPlugin/src/BooleanInput.tsx new file mode 100644 index 0000000000..4a0441212d --- /dev/null +++ b/example/booleanInputPlugin/src/BooleanInput.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +type Props = { + indentLevel: number; + itemName: string; + value: boolean; + onChange: (value: boolean) => unknown; +}; + +export function BooleanInput({ + indentLevel, + itemName, + value, + onChange, +}: Props) { + return ( + + ); +} diff --git a/example/booleanInputPlugin/src/ui.tsx b/example/booleanInputPlugin/src/ui.tsx new file mode 100644 index 0000000000..6cfd71fe19 --- /dev/null +++ b/example/booleanInputPlugin/src/ui.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ValueInputSlotProps } from 'react-cosmos-playground2/plugin'; +import { createPlugin } from 'react-plugin'; +import { BooleanInput } from './BooleanInput'; + +type BooleanInputPluginSpec = { + name: 'booleanInputPlugin'; +}; + +const { plug, register } = createPlugin({ + name: 'booleanInputPlugin', +}); + +plug('valueInput', ({ slotProps, children }) => { + const { item, itemName, parents, onInputChange } = slotProps; + + if (item.type === 'primitive' && typeof item.value === 'boolean') + return ( + + ); + + // Fall back to default inputs + return <>{children}; +}); + +register(); diff --git a/example/booleanInputPlugin/webpack.config.js b/example/booleanInputPlugin/webpack.config.js new file mode 100644 index 0000000000..a36b609f38 --- /dev/null +++ b/example/booleanInputPlugin/webpack.config.js @@ -0,0 +1,38 @@ +const { join } = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +const src = join(__dirname, 'src'); +const dist = join(__dirname, 'dist'); +const env = process.env.NODE_ENV || 'development'; + +const plugins = []; + +module.exports = { + mode: env, + devtool: false, + entry: join(src, 'ui.tsx'), + output: { + path: dist, + filename: 'ui.js', + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + }, + externals: { + 'react-dom': 'ReactDom', + 'react-plugin': 'ReactPlugin', + react: 'React', + }, + module: { + rules: [ + { + test: /\.tsx?$/, + include: src, + use: { + loader: 'babel-loader', + }, + }, + ], + }, + plugins, +}; diff --git a/example/Counter/index.fixture.tsx b/example/components/Counter/index.fixture.tsx similarity index 100% rename from example/Counter/index.fixture.tsx rename to example/components/Counter/index.fixture.tsx diff --git a/example/Counter/index.tsx b/example/components/Counter/index.tsx similarity index 100% rename from example/Counter/index.tsx rename to example/components/Counter/index.tsx diff --git a/example/CounterButton/index.fixture.tsx b/example/components/CounterButton/index.fixture.tsx similarity index 100% rename from example/CounterButton/index.fixture.tsx rename to example/components/CounterButton/index.fixture.tsx diff --git a/example/CounterButton/index.tsx b/example/components/CounterButton/index.tsx similarity index 100% rename from example/CounterButton/index.tsx rename to example/components/CounterButton/index.tsx diff --git a/example/NestedDecorators/Fixture.tsx b/example/components/NestedDecorators/Fixture.tsx similarity index 100% rename from example/NestedDecorators/Fixture.tsx rename to example/components/NestedDecorators/Fixture.tsx diff --git a/example/NestedDecorators/Input.tsx b/example/components/NestedDecorators/Input.tsx similarity index 100% rename from example/NestedDecorators/Input.tsx rename to example/components/NestedDecorators/Input.tsx diff --git a/example/NestedDecorators/index.fixture.tsx b/example/components/NestedDecorators/index.fixture.tsx similarity index 100% rename from example/NestedDecorators/index.fixture.tsx rename to example/components/NestedDecorators/index.fixture.tsx diff --git a/example/WelcomeMessage/cosmos.decorator.tsx b/example/components/WelcomeMessage/cosmos.decorator.tsx similarity index 100% rename from example/WelcomeMessage/cosmos.decorator.tsx rename to example/components/WelcomeMessage/cosmos.decorator.tsx diff --git a/example/WelcomeMessage/index.fixture.tsx b/example/components/WelcomeMessage/index.fixture.tsx similarity index 100% rename from example/WelcomeMessage/index.fixture.tsx rename to example/components/WelcomeMessage/index.fixture.tsx diff --git a/example/WelcomeMessage/index.tsx b/example/components/WelcomeMessage/index.tsx similarity index 100% rename from example/WelcomeMessage/index.tsx rename to example/components/WelcomeMessage/index.tsx diff --git a/example/__fixtures__/Controls playground.tsx b/example/components/__fixtures__/Controls playground.tsx similarity index 100% rename from example/__fixtures__/Controls playground.tsx rename to example/components/__fixtures__/Controls playground.tsx diff --git a/example/__fixtures__/Hello World.ts b/example/components/__fixtures__/Hello World.ts similarity index 100% rename from example/__fixtures__/Hello World.ts rename to example/components/__fixtures__/Hello World.ts diff --git a/example/__fixtures__/Props playground.tsx b/example/components/__fixtures__/Props playground.tsx similarity index 100% rename from example/__fixtures__/Props playground.tsx rename to example/components/__fixtures__/Props playground.tsx diff --git a/example/__snapshots__/fixtures.test.ts.snap b/example/components/__snapshots__/fixtures.test.ts.snap similarity index 67% rename from example/__snapshots__/fixtures.test.ts.snap rename to example/components/__snapshots__/fixtures.test.ts.snap index 87a8d9db4c..0a09034302 100644 --- a/example/__snapshots__/fixtures.test.ts.snap +++ b/example/components/__snapshots__/fixtures.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`returns fixture elements: __fixtures__/Controls playground.tsx 1`] = ` +exports[`returns fixture elements: components/__fixtures__/Controls playground.tsx 1`] = ` Array [
     {
@@ -49,9 +49,9 @@ Array [
 ]
 `;
 
-exports[`returns fixture elements: __fixtures__/Hello World.ts 1`] = `"Hello World!"`;
+exports[`returns fixture elements: components/__fixtures__/Hello World.ts 1`] = `"Hello World!"`;
 
-exports[`returns fixture elements: __fixtures__/Props playground.tsx 1`] = `
+exports[`returns fixture elements: components/__fixtures__/Props playground.tsx 1`] = `
 
   {
   "string": "How are you doing?",
@@ -70,7 +70,7 @@ exports[`returns fixture elements: __fixtures__/Props playground.tsx 1`] = `
 
`; -exports[`returns fixture elements: Counter/index.fixture.tsx default 1`] = ` +exports[`returns fixture elements: components/Counter/index.fixture.tsx default 1`] = ` `; -exports[`returns fixture elements: Counter/index.fixture.tsx large number 1`] = ` +exports[`returns fixture elements: components/Counter/index.fixture.tsx large number 1`] = ` `; -exports[`returns fixture elements: Counter/index.fixture.tsx small number 1`] = ` +exports[`returns fixture elements: components/Counter/index.fixture.tsx small number 1`] = ` `; -exports[`returns fixture elements: CounterButton/index.fixture.tsx 1`] = ` +exports[`returns fixture elements: components/CounterButton/index.fixture.tsx 1`] = ` `; -exports[`returns fixture elements: NestedDecorators/index.fixture.tsx 1`] = ` +exports[`returns fixture elements: components/NestedDecorators/index.fixture.tsx 1`] = ` (layout: wide @@ -134,7 +134,7 @@ exports[`returns fixture elements: NestedDecorators/index.fixture.tsx 1`] = ` `; -exports[`returns fixture elements: WelcomeMessage/index.fixture.tsx 1`] = ` +exports[`returns fixture elements: components/WelcomeMessage/index.fixture.tsx 1`] = `
{ expect.hasAssertions(); diff --git a/example/fixtures2.test.ts b/example/components/fixtures2.test.ts similarity index 54% rename from example/fixtures2.test.ts rename to example/components/fixtures2.test.ts index 93461cf3ed..0cc8e86edb 100644 --- a/example/fixtures2.test.ts +++ b/example/components/fixtures2.test.ts @@ -1,6 +1,6 @@ import { getCosmosConfigAtPath, getFixtures2 } from 'react-cosmos'; -const cosmosConfig = getCosmosConfigAtPath(require.resolve('./cosmos.config')); +const cosmosConfig = getCosmosConfigAtPath(require.resolve('../cosmos.config')); it('returns fixture info', async () => { const fixtures = getFixtures2(cosmosConfig); @@ -12,10 +12,10 @@ it('returns fixture info', async () => { name: 'default', parents: [], playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22default%22%7D', - relativeFilePath: 'Counter/index.fixture.tsx', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22default%22%7D', + relativeFilePath: 'components/Counter/index.fixture.tsx', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22default%22%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22default%22%7D', treePath: ['Counter', 'default'], }, { @@ -25,10 +25,10 @@ it('returns fixture info', async () => { name: 'small number', parents: [], playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22small%20number%22%7D', - relativeFilePath: 'Counter/index.fixture.tsx', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22small%20number%22%7D', + relativeFilePath: 'components/Counter/index.fixture.tsx', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22small%20number%22%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22small%20number%22%7D', treePath: ['Counter', 'small number'], }, { @@ -38,10 +38,10 @@ it('returns fixture info', async () => { name: 'large number', parents: [], playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22large%20number%22%7D', - relativeFilePath: 'Counter/index.fixture.tsx', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22large%20number%22%7D', + relativeFilePath: 'components/Counter/index.fixture.tsx', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22large%20number%22%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22large%20number%22%7D', treePath: ['Counter', 'large number'], }, { @@ -50,11 +50,11 @@ it('returns fixture info', async () => { getElement: expect.any(Function), name: null, parents: [], - relativeFilePath: '__fixtures__/Controls playground.tsx', + relativeFilePath: 'components/__fixtures__/Controls playground.tsx', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22__fixtures__%2FControls%20playground.tsx%22%2C%22name%22%3Anull%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FControls%20playground.tsx%22%2C%22name%22%3Anull%7D', playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22__fixtures__%2FControls%20playground.tsx%22%2C%22name%22%3Anull%7D', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FControls%20playground.tsx%22%2C%22name%22%3Anull%7D', treePath: ['Controls playground'], }, { @@ -64,10 +64,10 @@ it('returns fixture info', async () => { name: null, parents: [], playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22__fixtures__%2FHello%20World.ts%22%2C%22name%22%3Anull%7D', - relativeFilePath: '__fixtures__/Hello World.ts', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FHello%20World.ts%22%2C%22name%22%3Anull%7D', + relativeFilePath: 'components/__fixtures__/Hello World.ts', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22__fixtures__%2FHello%20World.ts%22%2C%22name%22%3Anull%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FHello%20World.ts%22%2C%22name%22%3Anull%7D', treePath: ['Hello World'], }, { @@ -76,11 +76,11 @@ it('returns fixture info', async () => { getElement: expect.any(Function), name: null, parents: [], - relativeFilePath: '__fixtures__/Props playground.tsx', + relativeFilePath: 'components/__fixtures__/Props playground.tsx', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22__fixtures__%2FProps%20playground.tsx%22%2C%22name%22%3Anull%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FProps%20playground.tsx%22%2C%22name%22%3Anull%7D', playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22__fixtures__%2FProps%20playground.tsx%22%2C%22name%22%3Anull%7D', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FProps%20playground.tsx%22%2C%22name%22%3Anull%7D', treePath: ['Props playground'], }, { @@ -90,10 +90,10 @@ it('returns fixture info', async () => { name: null, parents: [], playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22CounterButton%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', - relativeFilePath: 'CounterButton/index.fixture.tsx', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FCounterButton%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', + relativeFilePath: 'components/CounterButton/index.fixture.tsx', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22CounterButton%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FCounterButton%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', treePath: ['CounterButton'], }, { @@ -103,10 +103,10 @@ it('returns fixture info', async () => { name: null, parents: [], playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22NestedDecorators%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', - relativeFilePath: 'NestedDecorators/index.fixture.tsx', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FNestedDecorators%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', + relativeFilePath: 'components/NestedDecorators/index.fixture.tsx', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22NestedDecorators%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FNestedDecorators%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', treePath: ['NestedDecorators'], }, { @@ -116,10 +116,10 @@ it('returns fixture info', async () => { name: null, parents: [], playgroundUrl: - 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22WelcomeMessage%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', - relativeFilePath: 'WelcomeMessage/index.fixture.tsx', + 'http://localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FWelcomeMessage%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', + relativeFilePath: 'components/WelcomeMessage/index.fixture.tsx', rendererUrl: - 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22WelcomeMessage%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', + 'http://localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FWelcomeMessage%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D', treePath: ['WelcomeMessage'], }, ]); diff --git a/example/urls.test.ts b/example/urls.test.ts index c8380e9dbc..390e949f78 100644 --- a/example/urls.test.ts +++ b/example/urls.test.ts @@ -6,15 +6,15 @@ it('returns playground URLs', async () => { const rendererUrls = await getFixtureUrls({ cosmosConfig }); expect(rendererUrls).toMatchInlineSnapshot(` Array [ - "localhost:5000/?fixtureId=%7B%22path%22%3A%22__fixtures__%2FControls%20playground.tsx%22%2C%22name%22%3Anull%7D", - "localhost:5000/?fixtureId=%7B%22path%22%3A%22__fixtures__%2FHello%20World.ts%22%2C%22name%22%3Anull%7D", - "localhost:5000/?fixtureId=%7B%22path%22%3A%22__fixtures__%2FProps%20playground.tsx%22%2C%22name%22%3Anull%7D", - "localhost:5000/?fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22default%22%7D", - "localhost:5000/?fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22small%20number%22%7D", - "localhost:5000/?fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22large%20number%22%7D", - "localhost:5000/?fixtureId=%7B%22path%22%3A%22CounterButton%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", - "localhost:5000/?fixtureId=%7B%22path%22%3A%22NestedDecorators%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", - "localhost:5000/?fixtureId=%7B%22path%22%3A%22WelcomeMessage%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FControls%20playground.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FHello%20World.ts%22%2C%22name%22%3Anull%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FProps%20playground.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22default%22%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22small%20number%22%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22large%20number%22%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FCounterButton%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FNestedDecorators%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/?fixtureId=%7B%22path%22%3A%22components%2FWelcomeMessage%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", ] `); }); @@ -23,15 +23,15 @@ it('returns renderer URLs in full screen mode', async () => { const rendererUrls = await getFixtureUrls({ cosmosConfig, fullScreen: true }); expect(rendererUrls).toMatchInlineSnapshot(` Array [ - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22__fixtures__%2FControls%20playground.tsx%22%2C%22name%22%3Anull%7D", - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22__fixtures__%2FHello%20World.ts%22%2C%22name%22%3Anull%7D", - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22__fixtures__%2FProps%20playground.tsx%22%2C%22name%22%3Anull%7D", - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22default%22%7D", - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22small%20number%22%7D", - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22Counter%2Findex.fixture.tsx%22%2C%22name%22%3A%22large%20number%22%7D", - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22CounterButton%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22NestedDecorators%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", - "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22WelcomeMessage%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FControls%20playground.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FHello%20World.ts%22%2C%22name%22%3Anull%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2F__fixtures__%2FProps%20playground.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22default%22%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22small%20number%22%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FCounter%2Findex.fixture.tsx%22%2C%22name%22%3A%22large%20number%22%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FCounterButton%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FNestedDecorators%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", + "localhost:5000/_renderer.html?_fixtureId=%7B%22path%22%3A%22components%2FWelcomeMessage%2Findex.fixture.tsx%22%2C%22name%22%3Anull%7D", ] `); }); diff --git a/package.json b/package.json index 9ff6d9a00c..cb8a307bcc 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dist": "yarn link-entries dist", "build:clear": "yarn babel-ts-node ./scripts/clearBuild.ts", "build": "yarn babel-ts-node ./scripts/build.ts", + "build:plugin": "webpack --config example/booleanInputPlugin/webpack.config.js", "start": "yarn cosmos --root-dir example", "export": "yarn cosmos-export --root-dir example", "start:export": "http-server -p 5001 ./example/cosmos-export", diff --git a/packages/react-cosmos-playground2/.gitignore b/packages/react-cosmos-playground2/.gitignore new file mode 100644 index 0000000000..564bb617d6 --- /dev/null +++ b/packages/react-cosmos-playground2/.gitignore @@ -0,0 +1 @@ +src/plugins/pluginEntry.ts diff --git a/packages/react-cosmos-playground2/.npmignore b/packages/react-cosmos-playground2/.npmignore index 02b3e9b0b3..c24d02f905 100644 --- a/packages/react-cosmos-playground2/.npmignore +++ b/packages/react-cosmos-playground2/.npmignore @@ -1,4 +1,5 @@ src +babel.config.js +cosmos.config.js tsconfig.build.json webpack.config.js -cosmos.config.js diff --git a/packages/react-cosmos-playground2/cosmos.config.json b/packages/react-cosmos-playground2/cosmos.config.json index 1fff429e4e..7d579ad6e6 100644 --- a/packages/react-cosmos-playground2/cosmos.config.json +++ b/packages/react-cosmos-playground2/cosmos.config.json @@ -1,4 +1,5 @@ { + "disablePlugins": true, "globalImports": [ "src/global/cosmos.loadPlugins" ], diff --git a/packages/react-cosmos-playground2/plugin.d.ts b/packages/react-cosmos-playground2/plugin.d.ts new file mode 100644 index 0000000000..e521618b0c --- /dev/null +++ b/packages/react-cosmos-playground2/plugin.d.ts @@ -0,0 +1 @@ +export * from './dist/plugin'; diff --git a/packages/react-cosmos-playground2/src/index.tsx b/packages/react-cosmos-playground2/src/index.tsx index 73b97be8a8..cb0117da12 100644 --- a/packages/react-cosmos-playground2/src/index.tsx +++ b/packages/react-cosmos-playground2/src/index.tsx @@ -1,34 +1,58 @@ +// Do not change the import order in this file! import 'regenerator-runtime/runtime'; import 'core-js/features/promise'; import 'core-js/features/array/find'; import 'core-js/features/array/includes'; import 'whatwg-fetch'; -import React from 'react'; -import { render } from 'react-dom'; +import * as React from 'react'; +import * as ReactDom from 'react-dom'; import * as ReactPlugin from 'react-plugin'; +import { CosmosPluginConfig } from 'react-cosmos-plugin'; import { GlobalStyle } from './global/style'; import { CoreSpec } from './plugins/Core/public'; import { DEFAULT_PLUGIN_CONFIG } from './shared/plugin'; import './plugins/pluginEntry'; +declare global { + interface Window { + ReactPlugin: any; + React: any; + ReactDom: any; + } +} + +// Enable external plugins to use a shared copy of react-plugin. Also enable +// fiddling with plugins from browser console :D. +window.ReactPlugin = ReactPlugin; +window.React = React; +window.ReactDom = ReactDom; + // Config can also contain keys for 3rd party plugins export type PlaygroundConfig = { core: CoreSpec['config']; [pluginName: string]: {}; }; -// Enable external plugins to use a shared copy of react-plugin. Also enable -// fiddling with plugins from browser console :D. -(window as any).ReactPlugin = ReactPlugin; +export type PlaygroundMountArgs = { + playgroundConfig: PlaygroundConfig; + pluginConfigs: CosmosPluginConfig[]; +}; -export default function mount(userConfig: PlaygroundConfig) { +export default function mount({ + playgroundConfig, + pluginConfigs, +}: PlaygroundMountArgs) { const { loadPlugins, Slot } = ReactPlugin; - const config = { ...DEFAULT_PLUGIN_CONFIG, ...userConfig }; + pluginConfigs.forEach(pluginConfig => { + if (pluginConfig.ui) loadPluginScript(pluginConfig.ui); + }); + + const config = { ...DEFAULT_PLUGIN_CONFIG, ...playgroundConfig }; loadPlugins({ config }); - render( + ReactDom.render( <> @@ -36,3 +60,13 @@ export default function mount(userConfig: PlaygroundConfig) { document.getElementById('root') ); } + +function loadPluginScript(scriptPath: string) { + console.log(`[Cosmos] Loading plugin script at ${scriptPath}`); + + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = `_plugin/${encodeURIComponent(scriptPath)}`; + + document.getElementsByTagName('head')[0].appendChild(script); +} diff --git a/packages/react-cosmos-playground2/src/plugin.ts b/packages/react-cosmos-playground2/src/plugin.ts new file mode 100644 index 0000000000..8be282a813 --- /dev/null +++ b/packages/react-cosmos-playground2/src/plugin.ts @@ -0,0 +1 @@ +export { ValueInputSlotProps } from './shared/slots/ValueInputSlot'; diff --git a/packages/react-cosmos-playground2/src/plugins/BuildNotifications/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/BuildNotifications/cosmos.plugin.json index 0d62855fa5..8ae98926c0 100644 --- a/packages/react-cosmos-playground2/src/plugins/BuildNotifications/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/BuildNotifications/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Build notifications", - "ui": "./index.ts" + "ui": "index.ts" } diff --git a/packages/react-cosmos-playground2/src/plugins/ClassStatePanel/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/ClassStatePanel/cosmos.plugin.json index 61383836f3..974ba9302a 100644 --- a/packages/react-cosmos-playground2/src/plugins/ClassStatePanel/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/ClassStatePanel/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Class state panel", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/ContentOverlay/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/ContentOverlay/cosmos.plugin.json index d4f81d2e6e..4b18b70452 100644 --- a/packages/react-cosmos-playground2/src/plugins/ContentOverlay/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/ContentOverlay/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Content overlay", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/ControlPanel/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/ControlPanel/cosmos.plugin.json index b662b40865..521267dbc3 100644 --- a/packages/react-cosmos-playground2/src/plugins/ControlPanel/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/ControlPanel/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Control panel", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/Core/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/Core/cosmos.plugin.json index 88384b6ecf..3b35408c5a 100644 --- a/packages/react-cosmos-playground2/src/plugins/Core/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/Core/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Core", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/EditFixtureButton/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/EditFixtureButton/cosmos.plugin.json index b080361f08..48436fcaf5 100644 --- a/packages/react-cosmos-playground2/src/plugins/EditFixtureButton/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/EditFixtureButton/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Edit fixture button", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/FixtureBookmark/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/FixtureBookmark/cosmos.plugin.json index 3082486f0e..f9d2a74696 100644 --- a/packages/react-cosmos-playground2/src/plugins/FixtureBookmark/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/FixtureBookmark/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Fixture bookmark", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/FixtureSearch/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/FixtureSearch/cosmos.plugin.json index a7de160bb1..6d75bb95f8 100644 --- a/packages/react-cosmos-playground2/src/plugins/FixtureSearch/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/FixtureSearch/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Fixture search", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/FixtureTree/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/FixtureTree/cosmos.plugin.json index 00195c061e..731060ac51 100644 --- a/packages/react-cosmos-playground2/src/plugins/FixtureTree/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/FixtureTree/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Fixture tree", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/FullScreenButton/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/FullScreenButton/cosmos.plugin.json index 54d5b31a15..9e1b2c3662 100644 --- a/packages/react-cosmos-playground2/src/plugins/FullScreenButton/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/FullScreenButton/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Full screen button", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/MessageHandler/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/MessageHandler/cosmos.plugin.json index 2489a62ad2..1003f80278 100644 --- a/packages/react-cosmos-playground2/src/plugins/MessageHandler/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/MessageHandler/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Message handler", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/Notifications/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/Notifications/cosmos.plugin.json index 38350c65fa..d833862d49 100644 --- a/packages/react-cosmos-playground2/src/plugins/Notifications/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/Notifications/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Notifications", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/PluginList/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/PluginList/cosmos.plugin.json index e99f1ee8b3..10bf94f05c 100644 --- a/packages/react-cosmos-playground2/src/plugins/PluginList/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/PluginList/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Plugin list", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/PropsPanel/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/PropsPanel/cosmos.plugin.json index d86a515d97..6dfd27ba03 100644 --- a/packages/react-cosmos-playground2/src/plugins/PropsPanel/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/PropsPanel/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Props panel", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/RemoteRenderer/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/RemoteRenderer/cosmos.plugin.json index c234275725..29a5c30208 100644 --- a/packages/react-cosmos-playground2/src/plugins/RemoteRenderer/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/RemoteRenderer/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Remote renderer", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/RendererCore/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/RendererCore/cosmos.plugin.json index bad21122f2..f7c6a618b8 100644 --- a/packages/react-cosmos-playground2/src/plugins/RendererCore/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/RendererCore/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Renderer core", - "ui": "./index.ts" + "ui": "index.ts" } diff --git a/packages/react-cosmos-playground2/src/plugins/RendererPreview/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/RendererPreview/cosmos.plugin.json index ea13cabdd6..fbf8b9c8a3 100644 --- a/packages/react-cosmos-playground2/src/plugins/RendererPreview/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/RendererPreview/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Renderer preview", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/RendererSelect/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/RendererSelect/cosmos.plugin.json index 247bb1c128..15304ad838 100644 --- a/packages/react-cosmos-playground2/src/plugins/RendererSelect/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/RendererSelect/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Renderer select", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/ResponsivePreview/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/ResponsivePreview/cosmos.plugin.json index 8ddb743ea9..d620bd9a49 100644 --- a/packages/react-cosmos-playground2/src/plugins/ResponsivePreview/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/ResponsivePreview/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Responsive preview", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/Root/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/Root/cosmos.plugin.json index d0c363d788..5807885c63 100644 --- a/packages/react-cosmos-playground2/src/plugins/Root/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/Root/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Root", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/Router/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/Router/cosmos.plugin.json index 9f350be4b4..744e6c635e 100644 --- a/packages/react-cosmos-playground2/src/plugins/Router/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/Router/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Router", - "ui": "./index.ts" + "ui": "index.ts" } diff --git a/packages/react-cosmos-playground2/src/plugins/SelectControl/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/SelectControl/cosmos.plugin.json index 40cb08b804..42e8e3ed29 100644 --- a/packages/react-cosmos-playground2/src/plugins/SelectControl/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/SelectControl/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Select control", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/StandardControl/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/StandardControl/cosmos.plugin.json index dd156f2fc3..f23524709e 100644 --- a/packages/react-cosmos-playground2/src/plugins/StandardControl/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/StandardControl/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Standard control", - "ui": "./index.tsx" + "ui": "index.tsx" } diff --git a/packages/react-cosmos-playground2/src/plugins/Storage/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/Storage/cosmos.plugin.json index 89d92fbb7e..884df06644 100644 --- a/packages/react-cosmos-playground2/src/plugins/Storage/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/Storage/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Storage", - "ui": "./index.ts" + "ui": "index.ts" } diff --git a/packages/react-cosmos-playground2/src/plugins/WebpackHmrNotification/cosmos.plugin.json b/packages/react-cosmos-playground2/src/plugins/WebpackHmrNotification/cosmos.plugin.json index 4eb31d50c3..2f2e3bbc53 100644 --- a/packages/react-cosmos-playground2/src/plugins/WebpackHmrNotification/cosmos.plugin.json +++ b/packages/react-cosmos-playground2/src/plugins/WebpackHmrNotification/cosmos.plugin.json @@ -1,4 +1,4 @@ { "name": "Webpack HMR notification", - "ui": "./index.ts" + "ui": "index.ts" } diff --git a/packages/react-cosmos-playground2/webpack.config.js b/packages/react-cosmos-playground2/webpack.config.js index d59017059c..3f5eeb350e 100644 --- a/packages/react-cosmos-playground2/webpack.config.js +++ b/packages/react-cosmos-playground2/webpack.config.js @@ -18,9 +18,7 @@ if (env === 'development') { module.exports = { mode: env, - // Besides other advantages, cheap-module-source-map is compatible with - // React.componentDidCatch https://github.com/facebook/react/issues/10441 - devtool: 'cheap-module-source-map', + devtool: false, entry: src, output: { libraryTarget: 'umd', diff --git a/packages/react-cosmos-plugin/.npmignore b/packages/react-cosmos-plugin/.npmignore new file mode 100644 index 0000000000..c74f926a30 --- /dev/null +++ b/packages/react-cosmos-plugin/.npmignore @@ -0,0 +1,2 @@ +src +tsconfig.build.json diff --git a/packages/react-cosmos-plugin/src/getCosmosPluginConfigs.test.ts b/packages/react-cosmos-plugin/src/getCosmosPluginConfigs.test.ts index f7d477467e..6d6780d351 100644 --- a/packages/react-cosmos-plugin/src/getCosmosPluginConfigs.test.ts +++ b/packages/react-cosmos-plugin/src/getCosmosPluginConfigs.test.ts @@ -8,162 +8,132 @@ it('loads playground plugins', () => { ); const configs = getCosmosPluginConfigs(packagesDir); - const normalizedConfigs = configs.map(plugin => ({ - ...plugin, - ui: plugin.ui.map(uiPath => uiPath.replace(/^.+\/src/, '')), - })); - - expect(normalizedConfigs).toMatchInlineSnapshot(` + expect(configs).toMatchInlineSnapshot(` Array [ Object { "name": "Build notifications", - "ui": Array [ - "/plugins/BuildNotifications/index.ts", - ], + "rootDir": "BuildNotifications", + "ui": "BuildNotifications/index.ts", }, Object { "name": "Class state panel", - "ui": Array [ - "/plugins/ClassStatePanel/index.tsx", - ], + "rootDir": "ClassStatePanel", + "ui": "ClassStatePanel/index.tsx", }, Object { "name": "Content overlay", - "ui": Array [ - "/plugins/ContentOverlay/index.tsx", - ], + "rootDir": "ContentOverlay", + "ui": "ContentOverlay/index.tsx", }, Object { "name": "Control panel", - "ui": Array [ - "/plugins/ControlPanel/index.tsx", - ], + "rootDir": "ControlPanel", + "ui": "ControlPanel/index.tsx", }, Object { "name": "Core", - "ui": Array [ - "/plugins/Core/index.tsx", - ], + "rootDir": "Core", + "ui": "Core/index.tsx", }, Object { "name": "Edit fixture button", - "ui": Array [ - "/plugins/EditFixtureButton/index.tsx", - ], + "rootDir": "EditFixtureButton", + "ui": "EditFixtureButton/index.tsx", }, Object { "name": "Fixture bookmark", - "ui": Array [ - "/plugins/FixtureBookmark/index.tsx", - ], + "rootDir": "FixtureBookmark", + "ui": "FixtureBookmark/index.tsx", }, Object { "name": "Fixture search", - "ui": Array [ - "/plugins/FixtureSearch/index.tsx", - ], + "rootDir": "FixtureSearch", + "ui": "FixtureSearch/index.tsx", }, Object { "name": "Fixture tree", - "ui": Array [ - "/plugins/FixtureTree/index.tsx", - ], + "rootDir": "FixtureTree", + "ui": "FixtureTree/index.tsx", }, Object { "name": "Full screen button", - "ui": Array [ - "/plugins/FullScreenButton/index.tsx", - ], + "rootDir": "FullScreenButton", + "ui": "FullScreenButton/index.tsx", }, Object { "name": "Message handler", - "ui": Array [ - "/plugins/MessageHandler/index.tsx", - ], + "rootDir": "MessageHandler", + "ui": "MessageHandler/index.tsx", }, Object { "name": "Notifications", - "ui": Array [ - "/plugins/Notifications/index.tsx", - ], + "rootDir": "Notifications", + "ui": "Notifications/index.tsx", }, Object { "name": "Plugin list", - "ui": Array [ - "/plugins/PluginList/index.tsx", - ], + "rootDir": "PluginList", + "ui": "PluginList/index.tsx", }, Object { "name": "Props panel", - "ui": Array [ - "/plugins/PropsPanel/index.tsx", - ], + "rootDir": "PropsPanel", + "ui": "PropsPanel/index.tsx", }, Object { "name": "Remote renderer", - "ui": Array [ - "/plugins/RemoteRenderer/index.tsx", - ], + "rootDir": "RemoteRenderer", + "ui": "RemoteRenderer/index.tsx", }, Object { "name": "Renderer core", - "ui": Array [ - "/plugins/RendererCore/index.ts", - ], + "rootDir": "RendererCore", + "ui": "RendererCore/index.ts", }, Object { "name": "Renderer preview", - "ui": Array [ - "/plugins/RendererPreview/index.tsx", - ], + "rootDir": "RendererPreview", + "ui": "RendererPreview/index.tsx", }, Object { "name": "Renderer select", - "ui": Array [ - "/plugins/RendererSelect/index.tsx", - ], + "rootDir": "RendererSelect", + "ui": "RendererSelect/index.tsx", }, Object { "name": "Responsive preview", - "ui": Array [ - "/plugins/ResponsivePreview/index.tsx", - ], + "rootDir": "ResponsivePreview", + "ui": "ResponsivePreview/index.tsx", }, Object { "name": "Root", - "ui": Array [ - "/plugins/Root/index.tsx", - ], + "rootDir": "Root", + "ui": "Root/index.tsx", }, Object { "name": "Router", - "ui": Array [ - "/plugins/Router/index.ts", - ], + "rootDir": "Router", + "ui": "Router/index.ts", }, Object { "name": "Select control", - "ui": Array [ - "/plugins/SelectControl/index.tsx", - ], + "rootDir": "SelectControl", + "ui": "SelectControl/index.tsx", }, Object { "name": "Standard control", - "ui": Array [ - "/plugins/StandardControl/index.tsx", - ], + "rootDir": "StandardControl", + "ui": "StandardControl/index.tsx", }, Object { "name": "Storage", - "ui": Array [ - "/plugins/Storage/index.ts", - ], + "rootDir": "Storage", + "ui": "Storage/index.ts", }, Object { "name": "Webpack HMR notification", - "ui": Array [ - "/plugins/WebpackHmrNotification/index.ts", - ], + "rootDir": "WebpackHmrNotification", + "ui": "WebpackHmrNotification/index.ts", }, ] `); diff --git a/packages/react-cosmos-plugin/src/getCosmosPluginConfigs.ts b/packages/react-cosmos-plugin/src/getCosmosPluginConfigs.ts index 3ed6de5685..1ab47b3f0f 100644 --- a/packages/react-cosmos-plugin/src/getCosmosPluginConfigs.ts +++ b/packages/react-cosmos-plugin/src/getCosmosPluginConfigs.ts @@ -1,41 +1,53 @@ import glob from 'glob'; import path from 'path'; +import resolveFrom from 'resolve-from'; // TODO: Validate config schema on config import type RawCosmosPluginConfig = { name: string; - ui: string[] | string; + ui?: string; }; export type CosmosPluginConfig = { name: string; - ui: string[]; + rootDir: string; + ui?: string; }; -export function getCosmosPluginConfigs(rootDir: string) { - const configPaths = getCosmosPluginConfigPaths(rootDir); - return configPaths.map(configPath => getCosmosPluginConfig(configPath)); +export function getCosmosPluginConfigs(rootDir: string, ignore?: string[]) { + const configPaths = getCosmosPluginConfigPaths(rootDir, ignore); + return configPaths.map(configPath => + getCosmosPluginConfig(rootDir, configPath) + ); } -function getCosmosPluginConfigPaths(rootDir: string) { +function getCosmosPluginConfigPaths(rootDir: string, ignore?: string[]) { return glob.sync('**/cosmos.plugin.json', { cwd: rootDir, absolute: true, + ignore, }); } -export function getCosmosPluginConfig(configPath: string): CosmosPluginConfig { +export function getCosmosPluginConfig( + rootDir: string, + configPath: string +): CosmosPluginConfig { const rawConfig = require(configPath) as RawCosmosPluginConfig; - const rootDir = path.dirname(configPath); - const rawUi = Array.isArray(rawConfig.ui) ? rawConfig.ui : [rawConfig.ui]; + const pluginRootDir = path.dirname(configPath); + const relativePluginRootDir = path.relative(rootDir, pluginRootDir); - return { + const config: CosmosPluginConfig = { name: rawConfig.name, - ui: rawUi.map(uiPath => resolveModulePath(rootDir, uiPath)), + rootDir: relativePluginRootDir, }; -} -function resolveModulePath(rootDir: string, relativePath: string) { - // TODO: Handle missing paths - return require.resolve(path.resolve(rootDir, relativePath)); + if (rawConfig.ui) { + const uiPath = path.join(pluginRootDir, rawConfig.ui); + const resolvedUiPath = resolveFrom.silent(pluginRootDir, uiPath); + // TODO: Handle missing path + if (resolvedUiPath) config.ui = path.relative(rootDir, resolvedUiPath); + } + + return config; } diff --git a/packages/react-cosmos-shared2/.npmignore b/packages/react-cosmos-shared2/.npmignore index c74f926a30..a23a9213df 100644 --- a/packages/react-cosmos-shared2/.npmignore +++ b/packages/react-cosmos-shared2/.npmignore @@ -1,2 +1,3 @@ src +babel.config.js tsconfig.build.json diff --git a/packages/react-cosmos/.npmignore b/packages/react-cosmos/.npmignore index 85de9cf933..a23a9213df 100644 --- a/packages/react-cosmos/.npmignore +++ b/packages/react-cosmos/.npmignore @@ -1 +1,3 @@ src +babel.config.js +tsconfig.build.json diff --git a/packages/react-cosmos/config.schema.json b/packages/react-cosmos/config.schema.json index 5624360dbe..e86610fd84 100644 --- a/packages/react-cosmos/config.schema.json +++ b/packages/react-cosmos/config.schema.json @@ -17,6 +17,10 @@ "type": "string", "minLength": 1 }, + "disablePlugins": { + "description": "Disable plugins", + "type": "boolean" + }, "staticPath": { "description": "Dir path to serve static assets from.", "type": "string", @@ -39,7 +43,10 @@ "watchDirs": { "description": "Directories where the Cosmos server watches for fixture files changes while running. [default: [\".\"]]", "type": "array", - "items": { "type": "string", "minLength": 1 }, + "items": { + "type": "string", + "minLength": 1 + }, "uniqueItems": true }, "userDepsFilePath": { @@ -49,7 +56,15 @@ }, "hostname": { "description": "Dev server hostname. Set to null to accept connections with any hostname. [default: null]", - "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] }, "port": { "description": "Dev server port. [default: 5000]", @@ -88,19 +103,33 @@ "type": "object", "description": "Advanced HTTP proxy config.", "additionalProperties": true, - "required": ["target"], + "required": [ + "target" + ], "properties": { - "target": { "type": "string" }, - "secure": { "type": "boolean" }, + "target": { + "type": "string" + }, + "secure": { + "type": "boolean" + }, "pathRewrite": { "type": "object", "patternProperties": { - ".*": { "type": "string" } + ".*": { + "type": "string" + } } }, "logLevel": { "type": "string", - "enum": ["error", "debug", "info", "warn", "silent"] + "enum": [ + "error", + "debug", + "info", + "warn", + "silent" + ] } } } @@ -111,7 +140,10 @@ "globalImports": { "description": "Modules to be imported before loading components. Stuff like reset.css, polyfills, etc.", "type": "array", - "items": { "type": "string", "minLength": 1 }, + "items": { + "type": "string", + "minLength": 1 + }, "uniqueItems": true }, "dom": { @@ -120,7 +152,10 @@ "properties": { "containerQuerySelector": { "description": "Document selector for existing element to use as component parent (eg. #root). A blank container element is created from scratch if no selector is provided. [default: null]", - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "minLength": 1 } } @@ -132,11 +167,27 @@ "properties": { "configPath": { "description": "Path to an existing webpack config, which Cosmos will reuse to build your code. Set to null to disable this behavior. [default \"webpack.config.js\"]", - "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] }, "overridePath": { "description": "Path to a user module that customizes the webpack config used by Cosmos. Set to null to disable this behavior. [default \"webpack.override.js\"]", - "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] }, "hotReload": { "description": "Enable webpack's Hot Module Replacement. [default: true]", @@ -159,11 +210,24 @@ "items": { "type": "object", "additionalProperties": false, - "required": ["label", "width", "height"], + "required": [ + "label", + "width", + "height" + ], "properties": { - "label": { "type": "string", "minLength": 1 }, - "width": { "type": "number", "minimum": 1 }, - "height": { "type": "number", "minimum": 1 } + "label": { + "type": "string", + "minLength": 1 + }, + "width": { + "type": "number", + "minimum": 1 + }, + "height": { + "type": "number", + "minimum": 1 + } } } } diff --git a/packages/react-cosmos/src/config/createCosmosConfig.ts b/packages/react-cosmos/src/config/createCosmosConfig.ts index ca5e5ef9e8..ed393b1f61 100644 --- a/packages/react-cosmos/src/config/createCosmosConfig.ts +++ b/packages/react-cosmos/src/config/createCosmosConfig.ts @@ -11,6 +11,7 @@ export function createCosmosConfig( ...cosmosConfigInput, rootDir, exportPath: getExportPath(cosmosConfigInput, rootDir), + disablePlugins: cosmosConfigInput.disablePlugins ?? false, fixtureFileSuffix: getFixtureFileSuffix(cosmosConfigInput), fixturesDir: getFixturesDir(cosmosConfigInput), globalImports: getGlobalImports(cosmosConfigInput, rootDir), diff --git a/packages/react-cosmos/src/config/detectCosmosConfig.ts b/packages/react-cosmos/src/config/detectCosmosConfig.ts index d82d618eee..e3a2471fd4 100644 --- a/packages/react-cosmos/src/config/detectCosmosConfig.ts +++ b/packages/react-cosmos/src/config/detectCosmosConfig.ts @@ -6,42 +6,45 @@ import { getCosmosConfigAtPath } from './getCosmosConfigAtPath'; import { getCurrentDir } from './shared'; export function detectCosmosConfig() { - const rootDir = detectRootDir(); - const cosmosConfigPath = detectCosmosConfigPath(rootDir); - return moduleExists(cosmosConfigPath) + const cosmosConfigPath = detectCosmosConfigPath(); + return cosmosConfigPath ? getCosmosConfigAtPath(cosmosConfigPath) - : createCosmosConfig(rootDir); + : createCosmosConfig(detectRootDir()); } -// CLI support for --root-dir relative/path/project -function detectRootDir() { +export function detectCosmosConfigPath() { + const rootDir = detectRootDir(); const cliArgs = getCliArgs(); - if (typeof cliArgs.rootDir !== 'string') { - return getCurrentDir(); - } - const absPath = path.resolve(getCurrentDir(), cliArgs.rootDir); - if (!dirExists(absPath)) - throw new Error(`[Cosmos] Dir not found at path: ${cliArgs.rootDir}`); + // CLI support for --config path/to/cosmos.config.json + if (typeof cliArgs.config === 'string') { + if (path.extname(cliArgs.config) !== '.json') + throw new Error( + `[Cosmos] Invalid config file type: ${cliArgs.config} (must be .json)` + ); + + const absPath = path.resolve(rootDir, cliArgs.config); + if (!fileExists(absPath)) + throw new Error(`[Cosmos] Config not found at path: ${cliArgs.config}`); - return absPath; + return absPath; + } + + const defaultConfigPath = path.join(rootDir, 'cosmos.config.json'); + return moduleExists(defaultConfigPath) ? defaultConfigPath : null; } -// CLI support for --config relative/path/to/cosmos.config.json -function detectCosmosConfigPath(rootDir: string) { +function detectRootDir() { const cliArgs = getCliArgs(); - if (typeof cliArgs.config !== 'string') { - return path.join(rootDir, 'cosmos.config.json'); - } - if (path.extname(cliArgs.config) !== '.json') - throw new Error( - `[Cosmos] Invalid config file type: ${cliArgs.config} (must be .json)` - ); + // CLI support for --root-dir path/to/project + if (typeof cliArgs.rootDir === 'string') { + const absPath = path.resolve(getCurrentDir(), cliArgs.rootDir); + if (!dirExists(absPath)) + throw new Error(`[Cosmos] Dir not found at path: ${cliArgs.rootDir}`); - const absPath = path.resolve(rootDir, cliArgs.config); - if (!fileExists(absPath)) - throw new Error(`[Cosmos] Config not found at path: ${cliArgs.config}`); + return absPath; + } - return absPath; + return getCurrentDir(); } diff --git a/packages/react-cosmos/src/config/index.ts b/packages/react-cosmos/src/config/index.ts index e77247a750..3ab4791633 100644 --- a/packages/react-cosmos/src/config/index.ts +++ b/packages/react-cosmos/src/config/index.ts @@ -1,5 +1,8 @@ -export { CosmosConfig } from './shared'; -export { resolveModule } from './resolve'; export { createCosmosConfig } from './createCosmosConfig'; -export { detectCosmosConfig } from './detectCosmosConfig'; +export { + detectCosmosConfig, + detectCosmosConfigPath, +} from './detectCosmosConfig'; export { getCosmosConfigAtPath } from './getCosmosConfigAtPath'; +export { resolveModule } from './resolve'; +export { CosmosConfig } from './shared'; diff --git a/packages/react-cosmos/src/config/shared.ts b/packages/react-cosmos/src/config/shared.ts index 8873233e84..02ada07f65 100644 --- a/packages/react-cosmos/src/config/shared.ts +++ b/packages/react-cosmos/src/config/shared.ts @@ -7,6 +7,7 @@ interface HttpsOptions { export type CosmosConfig = { exportPath: string; + disablePlugins: boolean; fixtureFileSuffix: string; fixturesDir: string; globalImports: string[]; diff --git a/packages/react-cosmos/src/shared/devServer/app.ts b/packages/react-cosmos/src/shared/devServer/app.ts index 3d7a8c5be1..bca7d64935 100644 --- a/packages/react-cosmos/src/shared/devServer/app.ts +++ b/packages/react-cosmos/src/shared/devServer/app.ts @@ -1,20 +1,48 @@ import express from 'express'; +import { CosmosPluginConfig } from 'react-cosmos-plugin'; +import resolveFrom from 'resolve-from'; import { CosmosConfig } from '../../config'; -import { getStaticPath } from '../static'; -import { PlatformType } from '../shared'; import { getDevPlaygroundHtml } from '../playgroundHtml'; +import { PlatformType } from '../shared'; +import { getStaticPath } from '../static'; export function createApp( platformType: PlatformType, - cosmosConfig: CosmosConfig + cosmosConfig: CosmosConfig, + pluginConfigs: CosmosPluginConfig[] ) { const app = express(); - const playgroundHtml = getDevPlaygroundHtml(platformType, cosmosConfig); + const playgroundHtml = getDevPlaygroundHtml( + platformType, + cosmosConfig, + pluginConfigs + ); app.get('/', (req: express.Request, res: express.Response) => { res.send(playgroundHtml); }); + app.get( + '/_plugin/:scriptPath', + (req: express.Request, res: express.Response) => { + const { scriptPath } = req.params; + if (!scriptPath) { + res.sendStatus(404); + return; + } + + // TODO: Restrict which scripts can be opened based on plugin configs + const cleanPath = `./${decodeURIComponent(scriptPath)}`; + const absolutePath = resolveFrom.silent(cosmosConfig.rootDir, cleanPath); + if (!absolutePath) { + res.sendStatus(404); + return; + } + + res.sendFile(absolutePath); + } + ); + app.get('/_playground.js', (req: express.Request, res: express.Response) => { res.sendFile(require.resolve('react-cosmos-playground2/dist')); }); diff --git a/packages/react-cosmos/src/shared/devServer/index.ts b/packages/react-cosmos/src/shared/devServer/index.ts index 2171eb81e0..7278a15bc4 100644 --- a/packages/react-cosmos/src/shared/devServer/index.ts +++ b/packages/react-cosmos/src/shared/devServer/index.ts @@ -1,11 +1,18 @@ -import http from 'http'; import express from 'express'; +import http from 'http'; +import path from 'path'; +import { CosmosPluginConfig } from 'react-cosmos-plugin'; import { Message } from 'react-cosmos-shared2/util'; -import { CosmosConfig, detectCosmosConfig } from '../../config'; +import { + CosmosConfig, + detectCosmosConfig, + detectCosmosConfigPath, +} from '../../config'; +import { getPluginConfigs } from '../pluginConfigs'; import { PlatformType } from '../shared'; import { serveStaticDir } from '../static'; -import { createHttpServer } from './httpServer'; import { createApp } from './app'; +import { createHttpServer } from './httpServer'; import { createMessageHandler } from './messageHandler'; type PluginCleanupCallback = () => unknown; @@ -27,8 +34,12 @@ export async function startDevServer( plugins: DevServerPlugin[] = [] ) { const cosmosConfig = detectCosmosConfig(); + logCosmosConfigInfo(); + + const pluginConfigs = getPluginConfigs(cosmosConfig); + logPluginInfo(pluginConfigs); - const app = createApp(platformType, cosmosConfig); + const app = createApp(platformType, cosmosConfig, pluginConfigs); if (cosmosConfig.staticPath) { serveStaticDir(app, cosmosConfig.staticPath, cosmosConfig.publicUrl); } @@ -40,7 +51,7 @@ export async function startDevServer( const msgHandler = createMessageHandler(httpServer.server); async function cleanUp() { - await pluginCleanupCallbacks.map(cleanup => cleanup()); + await Promise.all(pluginCleanupCallbacks.map(cleanup => cleanup())); await httpServer.stop(); msgHandler.cleanUp(); } @@ -64,3 +75,23 @@ export async function startDevServer( return cleanUp; } + +function logCosmosConfigInfo() { + const cosmosConfigPath = detectCosmosConfigPath(); + if (!cosmosConfigPath) { + console.log(`[Cosmos] Using default cosmos config`); + return; + } + + const relConfigPath = path.relative(process.cwd(), cosmosConfigPath); + console.log(`[Cosmos] Using cosmos config found at ${relConfigPath}`); +} + +function logPluginInfo(pluginConfigs: CosmosPluginConfig[]) { + const pluginCount = pluginConfigs.length; + if (pluginCount > 0) { + const pluginLabel = pluginCount === 1 ? 'plugin' : 'plugins'; + const pluginNames = pluginConfigs.map(p => p.name).join(', '); + console.log(`[Cosmos] Found ${pluginCount} ${pluginLabel}: ${pluginNames}`); + } +} diff --git a/packages/react-cosmos/src/shared/export.ts b/packages/react-cosmos/src/shared/export.ts index 9641abbd62..e7185a6ac4 100644 --- a/packages/react-cosmos/src/shared/export.ts +++ b/packages/react-cosmos/src/shared/export.ts @@ -1,7 +1,9 @@ import fs from 'fs-extra'; import path from 'path'; +import { CosmosPluginConfig } from 'react-cosmos-plugin'; import { CosmosConfig, detectCosmosConfig } from '../config'; import { getExportPlaygroundHtml } from './playgroundHtml'; +import { getPluginConfigs } from './pluginConfigs'; import { removeLeadingSlash } from './shared'; import { getStaticPath } from './static'; @@ -14,6 +16,10 @@ export type ExportPlugin = (args: ExportPluginArgs) => unknown; export async function generateExport(plugins: ExportPlugin[] = []) { const cosmosConfig = detectCosmosConfig(); + // Clear previous export (or other files at export path) + const { exportPath } = cosmosConfig; + fs.removeSync(exportPath); + // Copy static assets first, so that the built index.html overrides the its // template file (in case the static assets are served from the root path) copyStaticAssets(cosmosConfig); @@ -58,6 +64,11 @@ function copyStaticAssets(cosmosConfig: CosmosConfig) { function exportPlaygroundFiles(cosmosConfig: CosmosConfig) { const { exportPath } = cosmosConfig; + const pluginConfigs = getPluginConfigs(cosmosConfig); + + pluginConfigs.forEach(pluginConfig => + exportPlugin(cosmosConfig, pluginConfig) + ); fs.copySync( require.resolve('react-cosmos-playground2/dist'), @@ -68,6 +79,25 @@ function exportPlaygroundFiles(cosmosConfig: CosmosConfig) { path.resolve(exportPath, '_cosmos.ico') ); - const playgroundHtml = getExportPlaygroundHtml(cosmosConfig); + const playgroundHtml = getExportPlaygroundHtml(cosmosConfig, pluginConfigs); fs.writeFileSync(path.resolve(exportPath, 'index.html'), playgroundHtml); } + +function exportPlugin( + cosmosConfig: CosmosConfig, + pluginConfig: CosmosPluginConfig +) { + const { rootDir, exportPath } = cosmosConfig; + const pluginExportDir = path.resolve(exportPath, '_plugin'); + + // Copy plugin config + const relConfigPath = path.join(pluginConfig.rootDir, 'cosmos.plugin.json'); + const absConfigPath = path.resolve(rootDir, relConfigPath); + fs.copySync(absConfigPath, path.resolve(pluginExportDir, relConfigPath)); + + // Copy UI script + if (pluginConfig.ui) { + const absUiPath = path.resolve(rootDir, pluginConfig.ui); + fs.copySync(absUiPath, path.resolve(pluginExportDir, pluginConfig.ui)); + } +} diff --git a/packages/react-cosmos/src/shared/playgroundHtml.ts b/packages/react-cosmos/src/shared/playgroundHtml.ts index ff39ad1e60..12c6508370 100644 --- a/packages/react-cosmos/src/shared/playgroundHtml.ts +++ b/packages/react-cosmos/src/shared/playgroundHtml.ts @@ -1,7 +1,11 @@ import fs from 'fs'; -import url from 'url'; import pkgUp from 'pkg-up'; -import { PlaygroundConfig } from 'react-cosmos-playground2'; +import { + PlaygroundConfig, + PlaygroundMountArgs, +} from 'react-cosmos-playground2'; +import { CosmosPluginConfig } from 'react-cosmos-plugin'; +import url from 'url'; import { CosmosConfig } from '../config'; import { PlatformType, replaceKeys } from './shared'; import { getStaticPath } from './static'; @@ -10,20 +14,30 @@ export const RENDERER_FILENAME = '_renderer.html'; export function getDevPlaygroundHtml( platformType: PlatformType, - cosmosConfig: CosmosConfig + cosmosConfig: CosmosConfig, + pluginConfigs: CosmosPluginConfig[] ) { const { ui } = cosmosConfig; return getPlaygroundHtml({ - ...ui, - core: getDevCoreConfig(platformType, cosmosConfig), + playgroundConfig: { + ...ui, + core: getDevCoreConfig(platformType, cosmosConfig), + }, + pluginConfigs, }); } -export function getExportPlaygroundHtml(cosmosConfig: CosmosConfig) { +export function getExportPlaygroundHtml( + cosmosConfig: CosmosConfig, + pluginConfigs: CosmosPluginConfig[] +) { const { ui } = cosmosConfig; return getPlaygroundHtml({ - ...ui, - core: getExportCoreConfig(cosmosConfig), + playgroundConfig: { + ...ui, + core: getExportCoreConfig(cosmosConfig), + }, + pluginConfigs, }); } @@ -78,9 +92,9 @@ function getWebRendererUrl({ publicUrl }: CosmosConfig) { return url.resolve(publicUrl, RENDERER_FILENAME); } -function getPlaygroundHtml(playgroundConfig: PlaygroundConfig) { +function getPlaygroundHtml(playgroundArgs: PlaygroundMountArgs) { return replaceKeys(getPlaygroundHtmlTemplate(), { - __PLAYGROUND_CONFIG: JSON.stringify(playgroundConfig), + __PLAYGROUND_ARGS: JSON.stringify(playgroundArgs), }); } diff --git a/packages/react-cosmos/src/shared/pluginConfigs.ts b/packages/react-cosmos/src/shared/pluginConfigs.ts new file mode 100644 index 0000000000..1e9e846338 --- /dev/null +++ b/packages/react-cosmos/src/shared/pluginConfigs.ts @@ -0,0 +1,9 @@ +import { getCosmosPluginConfigs } from 'react-cosmos-plugin'; +import { CosmosConfig } from '../config'; + +export function getPluginConfigs(cosmosConfig: CosmosConfig) { + const { rootDir, disablePlugins, exportPath } = cosmosConfig; + return disablePlugins + ? [] + : getCosmosPluginConfigs(rootDir, [`${exportPath}/**`]); +} diff --git a/packages/react-cosmos/src/shared/static/index.html b/packages/react-cosmos/src/shared/static/index.html index 04f974195d..53a4fc6759 100644 --- a/packages/react-cosmos/src/shared/static/index.html +++ b/packages/react-cosmos/src/shared/static/index.html @@ -13,7 +13,7 @@
diff --git a/scripts/build.ts b/scripts/build.ts index a8b7cc36b1..a4f8fd4f07 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -4,25 +4,20 @@ import cpy from 'cpy'; import path from 'path'; import { generatePlaygroundPluginEntry } from './generatePlaygroundPluginEntry'; import { - BROWSER_PACKAGES, done, error, + findPackage, getBoolArg, getFormattedPackageList, getUnnamedArg, - NODE_PACKAGES, + Package, + packages, + PackageType, rimrafAsync, } from './shared'; const { stdout, stderr } = process; -const BUILD_ORDER = [ - 'react-cosmos-shared2', - 'react-cosmos-plugin', - 'react-cosmos-playground2', - 'react-cosmos', -]; - const SOURCE_IGNORE_PATHS = [ '**/@types/**/*', '**/__tests__/**/*', @@ -38,8 +33,6 @@ const SOURCE_IGNORE_PATHS = [ const watch = getBoolArg('watch'); (async () => { - const buildablePackages = [...NODE_PACKAGES, ...BROWSER_PACKAGES]; - // TODO: Allow shorthand names (shared => react-cosmos-shared2, etc.) const pkgName = getUnnamedArg(); if (pkgName) { @@ -51,14 +44,13 @@ const watch = getBoolArg('watch'); return; } - if (buildablePackages.indexOf(pkgName) === -1) { + const pkg = findPackage(pkgName); + if (!pkg) { stderr.write( error( `${chalk.bold( pkgName - )} doesn't exist!\nPackages: ${getFormattedPackageList( - buildablePackages - )}\n` + )} doesn't exist!\nPackages: ${getFormattedPackageList()}\n` ) ); process.exit(1); @@ -66,13 +58,13 @@ const watch = getBoolArg('watch'); } stdout.write( - `${watch ? 'Build-watching' : 'Building'} ${chalk.bold(pkgName)}...\n` + `${watch ? 'Build-watching' : 'Building'} ${chalk.bold(pkg.name)}...\n` ); if (watch) { - await watchPackage(pkgName); + await watchPackage(pkg); } else { - await buildPackage(pkgName); + await buildPackage(pkg); } } else { if (watch) { @@ -92,70 +84,70 @@ const watch = getBoolArg('watch'); // Packages are built serially because they depend on each other and // this way TypeScript ensures their interfaces are compatible - for (const curPkgName of BUILD_ORDER) { - await buildPackage(curPkgName); + for (const pkg of packages) { + await buildPackage(pkg); } - stdout.write(`Built ${buildablePackages.length} packages successfully.\n`); + stdout.write(`Built ${packages.length} packages successfully.\n`); } })(); -async function buildPackage(pkgName: string) { +async function buildPackage(pkg: Package) { try { - isBrowserPackage(pkgName) - ? await buildBrowserPackage(pkgName) - : await buildNodePackage(pkgName); - stdout.write(done(`${chalk.bold(pkgName)}\n`)); + pkg.type === PackageType.Browser + ? await buildBrowserPackage(pkg) + : await buildNodePackage(pkg); + stdout.write(done(`${chalk.bold(pkg.name)}\n`)); } catch (err) { - stderr.write(error(`${chalk.bold(pkgName)}\n`)); + stderr.write(error(`${chalk.bold(pkg.name)}\n`)); } } -async function watchPackage(pkgName: string) { - return isBrowserPackage(pkgName) - ? watchBrowserPackage(pkgName) - : watchNodePackage(pkgName); +async function watchPackage(pkg: Package) { + return pkg.type === PackageType.Browser + ? watchBrowserPackage(pkg) + : watchNodePackage(pkg); } -async function buildNodePackage(pkgName: string) { - await clearPreviousBuild(pkgName); - await copyStaticAssets(pkgName); +async function buildNodePackage(pkg: Package) { + await clearPreviousBuild(pkg); + await copyStaticAssets(pkg); await Promise.all([ - await buildNodePackageSource(pkgName), - await buildPackageHeaders(pkgName), + await buildNodePackageSource(pkg), + await buildPackageHeaders(pkg), ]); } -async function watchNodePackage(pkgName: string) { - await clearPreviousBuild(pkgName); - await copyStaticAssets(pkgName); +async function watchNodePackage(pkg: Package) { + await clearPreviousBuild(pkg); + await copyStaticAssets(pkg); // Don't await on returned promises because watch tasks hang indefinitely - watchNodePackageSource(pkgName); - watchPackageHeaders(pkgName); + watchNodePackageSource(pkg); + watchPackageHeaders(pkg); } -async function buildBrowserPackage(pkgName: string) { - await clearPreviousBuild(pkgName); - if (pkgName === 'react-cosmos-playground2') +async function buildBrowserPackage(pkg: Package) { + await clearPreviousBuild(pkg); + if (pkg.name === 'react-cosmos-playground2') await generatePlaygroundPluginEntry(); await Promise.all([ - await buildBrowserPackageSource(pkgName), - await buildPackageHeaders(pkgName), + await buildBrowserPackageSource(pkg), + await buildPackageHeaders(pkg), ]); } -async function watchBrowserPackage(pkgName: string) { - await clearPreviousBuild(pkgName); +async function watchBrowserPackage(pkg: Package) { + await clearPreviousBuild(pkg); // Don't await on returned promises because watch tasks hang indefinitely - watchBrowserPackageSource(pkgName); - watchPackageHeaders(pkgName); + watchBrowserPackageSource(pkg); + watchPackageHeaders(pkg); } -function buildNodePackageSource(pkgName: string) { +function buildNodePackageSource(pkg: Package) { const args = [ - `packages/${pkgName}/src`, + `packages/${pkg.name}/src`, '--out-dir', - `packages/${pkgName}/dist`, + `packages/${pkg.name}/dist`, '--extensions', '.ts,.tsx', '--ignore', @@ -164,11 +156,11 @@ function buildNodePackageSource(pkgName: string) { return runAsyncTask({ cmd: 'babel', args }); } -function watchNodePackageSource(pkgName: string) { +function watchNodePackageSource(pkg: Package) { const args = [ - `packages/${pkgName}/src`, + `packages/${pkg.name}/src`, '--out-dir', - `packages/${pkgName}/dist`, + `packages/${pkg.name}/dist`, '--extensions', '.ts,.tsx', '--ignore', @@ -178,10 +170,10 @@ function watchNodePackageSource(pkgName: string) { return runAsyncTask({ cmd: 'babel', args }); } -async function buildBrowserPackageSource(pkgName: string) { +async function buildBrowserPackageSource(pkg: Package) { const args = [ '--config', - `packages/${pkgName}/webpack.config.js`, + `packages/${pkg.name}/webpack.config.js`, '--display', 'errors-only', ]; @@ -189,21 +181,25 @@ async function buildBrowserPackageSource(pkgName: string) { return runAsyncTask({ cmd: 'webpack', args, env }); } -async function watchBrowserPackageSource(pkgName: string) { +async function watchBrowserPackageSource(pkg: Package) { // Showing webpack output in watch mode because it's nice to get feedback // after saving a file - const args = ['--config', `packages/${pkgName}/webpack.config.js`, '--watch']; + const args = [ + '--config', + `packages/${pkg.name}/webpack.config.js`, + '--watch', + ]; const env = { NODE_ENV: 'development' }; return runAsyncTask({ cmd: 'webpack', args, env }); } -function buildPackageHeaders(pkgName: string) { - const args = ['-b', `packages/${pkgName}/tsconfig.build.json`]; +function buildPackageHeaders(pkg: Package) { + const args = ['-b', `packages/${pkg.name}/tsconfig.build.json`]; return runAsyncTask({ cmd: 'tsc', args }); } -function watchPackageHeaders(pkgName: string) { - const args = ['-b', `packages/${pkgName}/tsconfig.build.json`, '--watch']; +function watchPackageHeaders(pkg: Package) { + const args = ['-b', `packages/${pkg.name}/tsconfig.build.json`, '--watch']; return runAsyncTask({ cmd: 'tsc', args }); } @@ -239,21 +235,17 @@ function runAsyncTask({ cmd, args, env = {} }: RunAsyncTaskArgs) { }); } -async function clearPreviousBuild(pkgName: string) { - await rimrafAsync(`packages/${pkgName}/dist`); +async function clearPreviousBuild(pkg: Package) { + await rimrafAsync(`packages/${pkg.name}/dist`); } const STATIC_PATH = 'shared/static'; -async function copyStaticAssets(pkgName: string) { - if (pkgName === 'react-cosmos') { +async function copyStaticAssets(pkg: Package) { + if (pkg.name === 'react-cosmos') { await cpy(`src/${STATIC_PATH}/**`, `dist/${STATIC_PATH}`, { - cwd: path.join(__dirname, `../packages/react-cosmos`), + cwd: path.join(__dirname, `../packages/${pkg.name}`), parents: false, }); } } - -function isBrowserPackage(pkgName: string) { - return BROWSER_PACKAGES.indexOf(pkgName) !== -1; -} diff --git a/scripts/clearBuild.ts b/scripts/clearBuild.ts index 9e41b84cba..1c938d638e 100644 --- a/scripts/clearBuild.ts +++ b/scripts/clearBuild.ts @@ -1,4 +1,4 @@ -import { globAsync, rimrafAsync, done } from './shared'; +import { done, globAsync, rimrafAsync } from './shared'; (async () => { const distPaths = (await globAsync(`./packages/*/dist`)) as string[]; diff --git a/scripts/generatePlaygroundPluginEntry.ts b/scripts/generatePlaygroundPluginEntry.ts index a8381e8ba0..072f7ee246 100644 --- a/scripts/generatePlaygroundPluginEntry.ts +++ b/scripts/generatePlaygroundPluginEntry.ts @@ -13,7 +13,9 @@ export async function generatePlaygroundPluginEntry() { const pluginConfigs = getCosmosPluginConfigs(packagesDir); const uiPluginPaths: string[] = []; - pluginConfigs.forEach(pluginConfig => uiPluginPaths.push(...pluginConfig.ui)); + pluginConfigs.forEach(pluginConfig => { + if (pluginConfig.ui) uiPluginPaths.push(`./${pluginConfig.ui}`); + }); const entryPath = path.join(packagesDir, 'pluginEntry.ts'); await outputFile(entryPath, createPluginsEntry(uiPluginPaths), 'utf8'); diff --git a/scripts/linkEntries.ts b/scripts/linkEntries.ts index caa4b2f5b9..d52bbb7b19 100644 --- a/scripts/linkEntries.ts +++ b/scripts/linkEntries.ts @@ -1,26 +1,40 @@ import chalk from 'chalk'; import { - PackageNames, - NODE_PACKAGES, - BROWSER_PACKAGES, + done, + error, + findPackage, + getFormattedPackageList, + getUnnamedArg, globAsync, + Package, + packages, readFileAsync, writeFileAsync, - getFormattedPackageList, - getUnnamedArg, - done, - error, } from './shared'; const SRC_DIR = 'src'; const DIST_DIR = 'dist'; + type TargetDir = typeof SRC_DIR | typeof DIST_DIR; +// These classes need to be defined at the top of this file +// https://stackoverflow.com/a/59171646/128816 +class InvalidTargetDir extends Error { + constructor() { + super('Invalid target dir!'); + } +} + +class InvalidTargetPackage extends Error { + constructor(targetPackage: string) { + super(`Invalid package name ${chalk.bold(targetPackage)}!`); + } +} + (async () => { - const packages = [...NODE_PACKAGES, ...BROWSER_PACKAGES]; try { const targetDir = getTargetDir(); - const targetPackages = getTargetPackages(packages); + const targetPackages = getTargetPackages(); const entryPoints = await getPackageEntryPoints(targetPackages); await Promise.all( @@ -39,9 +53,7 @@ type TargetDir = typeof SRC_DIR | typeof DIST_DIR; ); } else if (err instanceof InvalidTargetPackage) { console.log( - error( - `${err.message}\nNode packages: ${getFormattedPackageList(packages)}` - ) + error(`${err.message}\nPackages: ${getFormattedPackageList()}`) ); } else { throw err; @@ -60,14 +72,14 @@ async function linkFileRequiresToDir(filePath: string, targetDir: TargetDir) { } async function getPackageEntryPoints( - packages: PackageNames + targetPackages: Package[] ): Promise { - if (packages.length === 0) { + if (targetPackages.length === 0) throw new Error('No package entry points to link for empty package list'); - } + const packageNames = targetPackages.map(p => p.name); const pkgMatch = - packages.length > 1 ? `{${packages.join(',')}}` : packages[0]; + packageNames.length > 1 ? `{${packageNames.join(',')}}` : packageNames[0]; return globAsync(`./packages/${pkgMatch}/{*,bin/*}.{js,d.ts}`); } @@ -81,23 +93,24 @@ function getTargetDir(): TargetDir { return targetDir; } -function getTargetPackages(nodePackages: PackageNames): string[] { - let packages: PackageNames = []; - let pkg = getUnnamedArg(1); +function getTargetPackages(): Package[] { + let targetPackages: Package[] = []; + let pkgName = getUnnamedArg(1); - if (!pkg) { - return nodePackages; - } + if (!pkgName) return packages; do { - if (typeof pkg !== 'string' || nodePackages.indexOf(pkg) === -1) { - throw new InvalidTargetPackage(String(pkg)); - } - packages = [...packages, pkg]; - pkg = getUnnamedArg(packages.length + 1); - } while (pkg); + if (typeof pkgName !== 'string') + throw new InvalidTargetPackage(String(pkgName)); + + const pkg = findPackage(pkgName); + if (!pkg) throw new InvalidTargetPackage(pkgName); + + targetPackages.push(pkg); + pkgName = getUnnamedArg(targetPackages.length + 1); + } while (pkgName); - return packages; + return targetPackages; } function isValidTargetDir(targetDir: unknown): targetDir is TargetDir { @@ -106,15 +119,3 @@ function isValidTargetDir(targetDir: unknown): targetDir is TargetDir { (targetDir === SRC_DIR || targetDir === DIST_DIR) ); } - -class InvalidTargetDir extends Error { - constructor() { - super('Invalid target dir!'); - } -} - -class InvalidTargetPackage extends Error { - constructor(targetPackage: string) { - super(`Invalid package name ${chalk.bold(targetPackage)}!`); - } -} diff --git a/scripts/shared.ts b/scripts/shared.ts index 30b784b629..b01e0a54ba 100644 --- a/scripts/shared.ts +++ b/scripts/shared.ts @@ -1,8 +1,8 @@ +import chalk from 'chalk'; import { readFile, writeFile } from 'fs'; import glob from 'glob'; import rimraf from 'rimraf'; import { argv } from 'yargs'; -import chalk from 'chalk'; type ArgValue = void | null | boolean | number | string; @@ -13,15 +13,40 @@ export const readFileAsync = asyncify(readFile); export const writeFileAsync = asyncify(writeFile); export const rimrafAsync = asyncify(rimraf); -export const NODE_PACKAGES = [ - 'react-cosmos-shared2', - 'react-cosmos-plugin', - 'react-cosmos', +export enum PackageType { + Node, + Browser, +} + +export type NodePackage = { + type: PackageType.Node; + name: string; +}; + +export type BrowserPackage = { + type: PackageType.Browser; + name: string; +}; + +export type Package = NodePackage | BrowserPackage; + +// Warning: The order matters! +export const packages: Package[] = [ + { type: PackageType.Node, name: 'react-cosmos-shared2' }, + { type: PackageType.Node, name: 'react-cosmos-plugin' }, + { type: PackageType.Browser, name: 'react-cosmos-playground2' }, + { type: PackageType.Node, name: 'react-cosmos' }, ]; -export const BROWSER_PACKAGES = ['react-cosmos-playground2']; -export function getFormattedPackageList(pkgNames: PackageNames) { - return ['', ...pkgNames].join('\n - '); +export function findPackage(pkgName: string) { + return packages.find( + // Allow shorthand names (shared => react-cosmos-shared2, etc.) + p => p.name === pkgName || p.name === `react-cosmos-${pkgName}` + ); +} + +export function getFormattedPackageList() { + return ['', ...packages.map(p => p.name)].join('\n - '); } export function getUnnamedArg(index: number = 0): void | number | string {