Skip to content

Commit

Permalink
Polaris sandbox implementation based on playroom.
Browse files Browse the repository at this point in the history
  • Loading branch information
gwyneplaine committed Sep 26, 2022
1 parent da094b9 commit 6030387
Show file tree
Hide file tree
Showing 36 changed files with 3,248 additions and 84 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ node_modules
/polaris-tokens/build
/polaris.shopify.com/public/sitemap.xml
/polaris.shopify.com/public/og-images
/polaris.shopify.com/public/icons
/polaris.shopify.com/public/playroom
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
node_modules
/polaris-react/build
/polaris-react/build-internal
/polaris.shopify.com/public/sandbox
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"preversion-packages": "turbo run preversion",
"version-packages": "yarn preversion-packages && changeset version",
"release": "turbo run build --filter=polaris.shopify.com^... && changeset publish",
"preversion": "echo \"Error: use @changsets/cli to version packages\" && exit 1"
"preversion": "echo \"Error: use @changsets/cli to version packages\" && exit 1",
"postinstall": "patch-package"
},
"devDependencies": {
"@babel/core": "^7.15.0",
Expand Down Expand Up @@ -68,6 +69,8 @@
"jest-preset-stylelint": "^5.0.3",
"jest-watch-typeahead": "^1.0.0",
"npm-run-all": "^4.1.5",
"patch-package": "^6.4.7",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.5.0",
"rollup": "^2.70.2",
"rollup-plugin-node-externals": "^4.0.0",
Expand Down
80 changes: 80 additions & 0 deletions patches/playroom+0.28.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
diff --git a/node_modules/playroom/README.md b/node_modules/playroom/README.md
index 6c82bbe..f05b80b 100644
--- a/node_modules/playroom/README.md
+++ b/node_modules/playroom/README.md
@@ -160,6 +160,12 @@ export { themeB } from './themeB';
// etc...
```

+## Additional Code Transformations
+
+A hook into the internal processing of code is available via the `processCode` option, which is a path to a file that exports a function that receives the code as entered into the editor, and returns the new code to be rendered.
+
+One example is [wrapping code in an IIFE for state support](https://github.com/seek-oss/playroom/issues/66).
+
## TypeScript Support

If a `tsconfig.json` file is present in your project, static prop types are parsed using [react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript) to provide better autocompletion in the Playroom editor.
diff --git a/node_modules/playroom/lib/defaultModules/processCode.js b/node_modules/playroom/lib/defaultModules/processCode.js
new file mode 100644
index 0000000..36a436c
--- /dev/null
+++ b/node_modules/playroom/lib/defaultModules/processCode.js
@@ -0,0 +1 @@
+export default code => code;
diff --git a/node_modules/playroom/lib/makeWebpackConfig.js b/node_modules/playroom/lib/makeWebpackConfig.js
index 56defa7..1e7cf3b 100644
--- a/node_modules/playroom/lib/makeWebpackConfig.js
+++ b/node_modules/playroom/lib/makeWebpackConfig.js
@@ -54,6 +54,9 @@ module.exports = async (playroomConfig, options) => {
__PLAYROOM_ALIAS__USE_SCOPE__: playroomConfig.scope
? relativeResolve(playroomConfig.scope)
: require.resolve('./defaultModules/useScope'),
+ __PLAYROOM_ALIAS__PROCESS_CODE__: playroomConfig.processCode
+ ? relativeResolve(playroomConfig.processCode)
+ : require.resolve('./defaultModules/processCode'),
},
},
module: {
diff --git a/node_modules/playroom/src/utils/compileJsx.ts b/node_modules/playroom/src/utils/compileJsx.ts
index dadea77..82d080c 100644
--- a/node_modules/playroom/src/utils/compileJsx.ts
+++ b/node_modules/playroom/src/utils/compileJsx.ts
@@ -1,9 +1,18 @@
import { transform } from '@babel/standalone';
+/* eslint-disable-next-line import/no-unresolved */
+import processCode from '__PLAYROOM_ALIAS__PROCESS_CODE__';

-export const compileJsx = (code: string) =>
- transform(`<React.Fragment>${code.trim() || ''}</React.Fragment>`, {
+export const compileJsx = (code: string) => {
+ const processedCode = processCode(code);
+
+ if (typeof processedCode !== 'string') {
+ throw new Error('processCode function must return a string of code.');
+ }
+
+ return transform(`<React.Fragment>${processedCode.trim()}</React.Fragment>`, {
presets: ['react'],
}).code;
+}

export const validateCode = (code: string) => {
try {
diff --git a/node_modules/playroom/src/utils/formatting.ts b/node_modules/playroom/src/utils/formatting.ts
index a1819bf..70ac15c 100644
--- a/node_modules/playroom/src/utils/formatting.ts
+++ b/node_modules/playroom/src/utils/formatting.ts
@@ -133,10 +133,10 @@ export const formatAndInsert = ({
snippet,
});

- return formatCode({
+ return {
code: newCode,
cursor: updatedCursor,
- });
+ };
};

export const formatForInsertion = ({
1 change: 1 addition & 0 deletions polaris-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,4 @@ export {
SELECT_ALL_ITEMS as INDEX_TABLE_SELECT_ALL_ITEMS,
SelectionType as IndexTableSelectionType,
} from './utilities/index-provider';
export {useBreakpoints} from './utilities/breakpoints';
1 change: 1 addition & 0 deletions polaris.shopify.com/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
root: true,
extends: ['next/core-web-vitals'],
ignorePatterns: ['public/sandbox'],
rules: {},
};
6 changes: 6 additions & 0 deletions polaris.shopify.com/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Not a TS file because our playroom.config.js needs to access it also, and can't understand ts imports.
module.exports = {
playroom: {
baseUrl: '/playroom/',
},
};
32 changes: 32 additions & 0 deletions polaris.shopify.com/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ const nextConfig = {
experimental: {
scrollRestoration: true,
},
async rewrites() {
return [
// We want to rewrite the sandbox route in production
// to point at the public directory that our playroom assets are built to
// We leverage a rewrite here instead of a redirect in order to preserve
// a "pretty" url for the main playroom editor.
...(process.env.NODE_ENV !== 'production'
? [
{
source: '/playroom/:path*',
destination: 'http://localhost:9000/:path*',
},
]
: []),
];
},
async headers() {
return [
{
Expand All @@ -31,6 +47,22 @@ const nextConfig = {

async redirects() {
return [
// We run a redirect to port 9000 for non prod environments
// as playroom files aren't built to the public directory in dev mode.
// We redirect to /preview/index.html here because Playroom's webpack is configured
// to generate an html file for the preview page that reaches for assets in the root directory via a relative path.
// In this case we don't care about a pretty url, and want to make absolutely certain that the browser is pointing to preview/index.html
// such that it resolves the relative asset requests correctly.
{
source: '/playroom',
destination: '/playroom/index.html',
permanent: true,
},
{
source: '/playroom/preview',
destination: '/playroom/preview/index.html',
permanent: true,
},
{
source: '/components/get-started',
destination: '/components',
Expand Down
18 changes: 12 additions & 6 deletions polaris.shopify.com/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
"version": "0.13.0",
"private": true,
"scripts": {
"build": "next build",
"dev": "open http://localhost:3000 && next dev",
"build": "yarn playroom:build && next build",
"dev": "concurrently \"open http://localhost:3000 && next dev\" \"npm:playroom:start\"",
"start": "next start",
"lint": "run-p lint:*",
"lint:js": "TIMING=1 eslint --cache .",
"lint:styles": "stylelint '**/*.{css,scss}'",
"clean": "rm -rf .turbo node_modules .next *.tsbuildinfo",
"create-component": "generact --root src/components src/components/Template/Template.tsx",
"gen-assets": "node scripts/gen-assets.mjs",
"get-props": "ts-node --skip-project ./scripts/get-props.ts"
"get-props": "ts-node --skip-project ./scripts/get-props.ts",
"playroom:start": "playroom start",
"playroom:build": "playroom build"
},
"dependencies": {
"@floating-ui/react-dom-interactions": "^0.6.1",
"@floating-ui/react-dom-interactions": "^0.10.1",
"@headlessui/react": "^1.6.5",
"@shopify/polaris": "^10.0.0",
"@shopify/polaris-icons": "^5.4.0",
Expand All @@ -28,8 +30,8 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-markdown": "^8.0.2",
"use-dark-mode": "^2.3.1",
"remark-gfm": "^3.0.1"
"remark-gfm": "^3.0.1",
"use-dark-mode": "^2.3.1"
},
"devDependencies": {
"@types/gtag.js": "^0.0.10",
Expand All @@ -38,16 +40,20 @@
"@types/node": "17.0.21",
"@types/prismjs": "^1.26.0",
"@types/react": "*",
"concurrently": "ˆ7.3.0",
"eslint-config-next": "12.1.0",
"eslint": "8.10.0",
"execa": "^6.1.0",
"frontmatter": "^0.0.3",
"babel-plugin-preval": "^5.1.0",
"get-site-urls": "3.0.0-alpha.1",
"generact": "^0.4.0",
"globby": "^11.1.0",
"js-yaml": "^4.1.0",
"playroom": "^0.28.0",
"marked": "^4.0.16",
"puppeteer": "^16.0.0",
"style-loader": "^3.3.1",
"rehype-raw": "^6.1.1",
"sass": "^1.49.9",
"typescript": "^4.7.4"
Expand Down
15 changes: 11 additions & 4 deletions polaris.shopify.com/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {AppProps} from 'next/app';
import Head from 'next/head';
import Script from 'next/script';
import {useEffect} from 'react';
import {Fragment, useEffect} from 'react';
import {useRouter} from 'next/router';

import '../src/styles/globals.scss';
Expand All @@ -16,10 +16,17 @@ const gaPageView = (url: string) => {
// Remove dark mode flicker. Minified version of https://github.com/donavon/use-dark-mode/blob/develop/noflash.js.txt
const noflash = `!function(){var b="darkMode",g="dark-mode",j="light-mode";function d(a){document.body.classList.add(a?g:j),document.body.classList.remove(a?j:g)}var e="(prefers-color-scheme: dark)",c=window.matchMedia(e),h=c.media===e,a=null;try{a=localStorage.getItem(b)}catch(k){}var f=null!==a;if(f&&(a=JSON.parse(a)),f)d(a);else if(h)d(c.matches),localStorage.setItem(b,c.matches);else{var i=document.body.classList.contains(g);localStorage.setItem(b,JSON.stringify(i))}}()`;

function MyApp({Component, pageProps}: AppProps) {
function MyApp({Component, pageProps, ...appProps}: AppProps) {
const router = useRouter();
const isProd = process.env.NODE_ENV === 'production';

// We're using router.pathname here to check for a specific incoming route to render in a Fragment instead of
// the Page component. This will work fine for statically generated assets / pages
// Any SSR pages may break due to router sometimes being undefined on first render.
// see https://stackoverflow.com/questions/61040790/userouter-withrouter-receive-undefined-on-query-in-first-render

const isLayoutNeeded = !appProps.router.asPath.startsWith('/sandbox');
const LayoutComponent = isLayoutNeeded ? Page : Fragment;
useEffect(() => {
if (!isProd) return;

Expand Down Expand Up @@ -64,7 +71,7 @@ function MyApp({Component, pageProps}: AppProps) {
</>
) : null}

<Page>
<LayoutComponent>
<script dangerouslySetInnerHTML={{__html: noflash}}></script>

<Head>
Expand All @@ -74,7 +81,7 @@ function MyApp({Component, pageProps}: AppProps) {
</Head>

<Component {...pageProps} />
</Page>
</LayoutComponent>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {Link, Page, Card, DataTable} from '@shopify/polaris';
import {Link, Page, Card, DataTable, useBreakpoints} from '@shopify/polaris';
import {useState} from 'react';
import {useMedia} from '../../src/utils/hooks';
import {withPolarisExample} from '../../src/components/PolarisExampleWrapper';

function DataTableWithFixedFirstColumnsExample() {
Expand Down Expand Up @@ -277,8 +276,8 @@ function DataTableWithFixedFirstColumnsExample() {
],
];
const [sortedRows, setSortedRows] = useState(rows);
const showFixedColumns = useMedia('screen and (max-width: 850px)');
const fixedFirstColumns = showFixedColumns ? 2 : 0;
const {mdDown, mdOnly} = useBreakpoints();
const fixedFirstColumns = mdDown || mdOnly ? 2 : 0;

return (
<Page title="Sales by product">
Expand Down
86 changes: 86 additions & 0 deletions polaris.shopify.com/pages/sandbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import SandboxHeader from '../src/components/SandboxHeader';
import useDarkMode from 'use-dark-mode';
import {useEffect, useRef} from 'react';
import {useRouter} from 'next/router';
import type {InferGetServerSidePropsType, GetServerSideProps} from 'next';

export const getServerSideProps: GetServerSideProps = async ({query}) => {
// We need to pass initial query param to our nested iframe
const initialSearchParams = new URLSearchParams(
query as Record<string, string>,
).toString();
return {
props: {
initialSearchParams: `?${initialSearchParams}`,
},
};
};

export default function Sandbox({
initialSearchParams,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const darkMode = useDarkMode(false);
const iframeRef = useRef<HTMLIFrameElement>(null);
const router = useRouter();
const searchValue = useRef('');

useEffect(() => {
/**
* We want to mirror the iframes url in the parent (aka browser) to support URL sharing.
* the iframes onload handler isn't invoked when the iframes url changes so we're polling here instead.
*/
const iframeUrlPoll = setInterval(() => {
if (
iframeRef?.current?.contentWindow &&
iframeRef.current.contentWindow.location.search !== searchValue.current
) {
searchValue.current = iframeRef.current.contentWindow.location.search;
const iframeQueryObj = Object.fromEntries(
new URLSearchParams(searchValue.current),
);

router.replace(
{
query: iframeQueryObj,
},
undefined,
{
shallow: true,
},
);
}
}, 200);
return () => clearInterval(iframeUrlPoll);
}, []);

return (
<div
className="styles-for-site-but-not-polaris-examples"
style={{
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100vh',
margin: 0,
padding: 0,
}}
>
<SandboxHeader
darkMode={darkMode}
url={typeof window !== 'undefined' ? window.location.href : ''}
/>
<iframe
id="main"
ref={iframeRef}
style={{
border: 0,
padding: 0,
margin: 0,
}}
src={`/playroom${initialSearchParams}`}
width="100%"
height="100%"
/>
</div>
);
}

0 comments on commit 6030387

Please sign in to comment.