Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate playroom internals to TypeScript #313

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/strange-spies-train.md
@@ -0,0 +1,5 @@
---
'playroom': patch
---

Migrate internals to Typescript. This moves all of the files under src/ to TypeScript.
2 changes: 1 addition & 1 deletion cypress/tsconfig.json
Expand Up @@ -4,6 +4,6 @@
"isolatedModules": false,
"types": ["cypress"]
},
"include": ["**/*"], // override the include from the root tsconfig
"include": ["**/*", "../src/userDefinedModules.d.ts"], // override the include from the root tsconfig
"exclude": [] // override the exclude from the root tsconfig
}
9 changes: 6 additions & 3 deletions lib/makeWebpackConfig.js
Expand Up @@ -10,9 +10,12 @@ const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const playroomPath = path.resolve(__dirname, '..');

// All of the paths containing files used internally by Playroom should be listed in this array
const includePaths = [
path.resolve(playroomPath, 'lib'),
path.resolve(playroomPath, 'src'),
path.resolve(playroomPath, 'utils'),
];

module.exports = async (playroomConfig, options) => {
Expand Down Expand Up @@ -40,9 +43,9 @@ module.exports = async (playroomConfig, options) => {
const ourConfig = {
mode: options.production ? 'production' : 'development',
entry: {
index: [require.resolve('../src/index.js')],
frame: [require.resolve('../src/frame.js')],
preview: [require.resolve('../src/preview.js')],
index: [require.resolve('../src/index.tsx')],
frame: [require.resolve('../src/frame.tsx')],
preview: [require.resolve('../src/preview.tsx')],
},
output: {
filename: '[name].[contenthash].js',
Expand Down
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -2,8 +2,8 @@
"name": "playroom",
"version": "0.34.1",
"description": "Design with code, powered by your own component library",
"main": "utils/index.js",
"types": "utils/index.d.ts",
"main": "utils/index.ts",
"types": "utils/index.ts",
Comment on lines +5 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to ship typescript as source? The alternative would be a build step. We could just run tsc for now, and graduate to something that offers proper CJS/ESM support later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reflecting on this, maybe for now we could just keep the separate .js and .d.ts files for simplicity, and think about a potential build process later on.

"bin": {
"playroom": "bin/cli.cjs"
},
Expand Down Expand Up @@ -116,6 +116,7 @@
"@changesets/cli": "^2.25.2",
"@octokit/rest": "^19.0.5",
"@types/jest": "^29.2.4",
"@types/webpack-env": "^1.18.4",
"concurrently": "^7.6.0",
"cypress": "^12.0.2",
"eslint": "^8.44.0",
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions src/Playroom/CodeEditor/CodeMirror2.tsx
Expand Up @@ -12,7 +12,7 @@

/* eslint-disable */
import * as React from 'react';
import * as codemirror from 'codemirror';
import codemirror from 'codemirror';

declare let global: any;
declare let require: any;
Expand All @@ -21,10 +21,7 @@ const SERVER_RENDERED =
typeof navigator === 'undefined' ||
(typeof global !== 'undefined' && global.PREVENT_CODEMIRROR_RENDER === true);

let cm: typeof codemirror.default;
if (!SERVER_RENDERED) {
cm = require('codemirror');
}
let cm = codemirror;

export interface IDefineModeOptions {
fn: () => codemirror.Mode<any>;
Expand Down
9 changes: 4 additions & 5 deletions src/Playroom/Frame.tsx
@@ -1,19 +1,18 @@
import type React from 'react';
import type { ReactNode } from 'react';
import type { ComponentType, ReactNode } from 'react';
import { useParams } from '../utils/params';
import CatchErrors from './CatchErrors/CatchErrors';
// @ts-expect-error
import RenderCode from './RenderCode/RenderCode';

interface FrameProps {
themes: Record<string, any>;
components: Array<any>;
FrameComponent: React.ComponentType<{
components: Record<string, ComponentType>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be ComponentType<any> so it will accept any component? Leaving out the generic argument results in it defaulting to {}, so only components that don't take any props would be accepted.

FrameComponent: ComponentType<{
themeName: string | null;
theme: string;
children?: ReactNode;
}>;
}

export default function Frame({
themes,
components,
Expand Down
11 changes: 0 additions & 11 deletions src/Playroom/Frames/frameSrc.d.ts

This file was deleted.

11 changes: 0 additions & 11 deletions src/Playroom/Frames/frameSrc.js

This file was deleted.

17 changes: 17 additions & 0 deletions src/Playroom/Frames/frameSrc.ts
@@ -0,0 +1,17 @@
import type { FrameSrcHandler } from '../../internalTypes';

import frameConfig from '__PLAYROOM_ALIAS__FRAME_COMPONENT__';

const defaultFrameSrc: FrameSrcHandler = (
{ code, themeName },
{ baseUrl, paramType }
) =>
`${baseUrl}frame.html${
paramType === 'hash' ? '#' : ''
}?themeName=${encodeURIComponent(themeName)}&code=${encodeURIComponent(
code
)}`;

export default (frameConfig.frameSrc
? frameConfig.frameSrc
: defaultFrameSrc) as FrameSrcHandler;
1 change: 0 additions & 1 deletion src/Playroom/Preview.tsx
Expand Up @@ -5,7 +5,6 @@ import { useParams } from '../utils/params';
import { compileJsx } from '../utils/compileJsx';
import SplashScreen from './SplashScreen/SplashScreen';
import CatchErrors from './CatchErrors/CatchErrors';
// @ts-expect-error
import RenderCode from './RenderCode/RenderCode';

import * as styles from './Preview.css';
Expand Down
@@ -1,4 +1,4 @@
import React from 'react';
import React, { type ComponentType } from 'react';
import scopeEval from 'scope-eval';

// eslint-disable-next-line import/no-unresolved
Expand All @@ -9,7 +9,13 @@ import {
ReactFragmentPragma,
} from '../../utils/compileJsx';

export default function RenderCode({ code, scope }) {
export default function RenderCode({
code,
scope,
}: {
code: string | undefined;
scope: Record<string, ComponentType>;
Copy link
Contributor

@askoufis askoufis Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think ComponentType is correct here. You can inject whatever you want with scope, a constant, a function, a component, etc. Maybe Record<string, any> would be more appropriate.

}) {
const userScope = {
...(useScope() ?? {}),
...scope,
Expand Down
2 changes: 0 additions & 2 deletions src/components.js

This file was deleted.

4 changes: 4 additions & 0 deletions src/components.ts
@@ -0,0 +1,4 @@
import * as components from '__PLAYROOM_ALIAS__COMPONENTS__';

// eslint-disable-next-line @typescript-eslint/consistent-type-imports
export default components as typeof import('__PLAYROOM_ALIAS__COMPONENTS__').default;
35 changes: 0 additions & 35 deletions src/frame.js

This file was deleted.

39 changes: 39 additions & 0 deletions src/frame.tsx
@@ -0,0 +1,39 @@
import { renderElement } from './render';
import Frame from './Playroom/Frame';
import { hmrAccept } from './utils/hmr';
import playroomThemes from './themes';
import playroomComponents from './components';
import PlayroomFrameComponent from './frameComponent';

const outlet = document.createElement('div');
document.body.appendChild(outlet);

const renderFrame = ({
themes = playroomThemes,
components = playroomComponents,
FrameComponent = PlayroomFrameComponent,
} = {}) => {
renderElement(
<Frame
components={components}
themes={themes}
FrameComponent={FrameComponent}
/>,
outlet
);
};
renderFrame();

hmrAccept((accept) => {
accept('./components', () => {
renderFrame({ components: playroomComponents });
});

accept('./themes', () => {
renderFrame({ themes: playroomThemes });
});

accept('./frameComponent', () => {
renderFrame({ FrameComponent: PlayroomFrameComponent });
});
});
4 changes: 0 additions & 4 deletions src/frameComponent.js

This file was deleted.

3 changes: 3 additions & 0 deletions src/frameComponent.ts
@@ -0,0 +1,3 @@
import frameComponent from '__PLAYROOM_ALIAS__FRAME_COMPONENT__';

export default frameComponent;
4 changes: 4 additions & 0 deletions src/images.d.ts
@@ -0,0 +1,4 @@
declare module '*.png' {
const value: any;
export default value;
}
32 changes: 0 additions & 32 deletions src/index.d.ts

This file was deleted.

32 changes: 16 additions & 16 deletions src/index.js → src/index.tsx
Expand Up @@ -4,6 +4,10 @@ import { StoreProvider } from './StoreContext/StoreContext';
import playroomConfig from './config';
import faviconPath from '../images/favicon.png';
import faviconInvertedPath from '../images/favicon-inverted.png';
import playroomThemes from './themes';
import playroomComponents from './components';
import playroomSnippets from './snippets';
import { hmrAccept } from './utils/hmr';

const polyfillIntersectionObserver = () =>
typeof window.IntersectionObserver !== 'undefined'
Expand All @@ -26,9 +30,9 @@ polyfillIntersectionObserver().then(() => {
}

const renderPlayroom = ({
themes = require('./themes'),
components = require('./components'),
snippets = require('./snippets'),
themes = playroomThemes,
components = playroomComponents,
snippets = playroomSnippets,
} = {}) => {
const themeNames = Object.keys(themes);

Expand All @@ -43,29 +47,25 @@ polyfillIntersectionObserver().then(() => {
components={filteredComponents}
widths={widths}
themes={themeNames}
snippets={
typeof snippets.default !== 'undefined'
? snippets.default
: snippets
}
snippets={snippets}
/>
</StoreProvider>,
outlet
);
};
renderPlayroom();

if (module.hot) {
module.hot.accept('./components', () => {
renderPlayroom({ components: require('./components') });
hmrAccept((accept) => {
accept('./components', () => {
renderPlayroom({ components: playroomComponents });
});

module.hot.accept('./themes', () => {
renderPlayroom({ themes: require('./themes') });
accept('./themes', () => {
renderPlayroom({ themes: playroomThemes });
});

module.hot.accept('./snippets', () => {
renderPlayroom({ snippets: require('./snippets') });
accept('./snippets', () => {
renderPlayroom({ snippets: playroomSnippets });
});
}
});
});