Browse Source

[Beta] Lazy-load linter code (#4675)

* [Beta] Lazy-load linter code

* Split utils into separate files
main
dan 3 years ago
committed by GitHub
parent
commit
20ad0e8ca9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      beta/src/components/MDX/Sandpack/CustomPreset.tsx
  2. 7
      beta/src/components/MDX/Sandpack/Preview.tsx
  3. 2
      beta/src/components/MDX/Sandpack/SandpackRoot.tsx
  4. 46
      beta/src/components/MDX/Sandpack/computeViewportSize.ts
  5. 53
      beta/src/components/MDX/Sandpack/createFileMap.ts
  6. 2
      beta/src/components/MDX/Sandpack/index.tsx
  7. 2
      beta/src/components/MDX/Sandpack/runESLint.tsx
  8. 35
      beta/src/components/MDX/Sandpack/useSandpackLint.tsx
  9. 124
      beta/src/components/MDX/Sandpack/utils.ts

6
beta/src/components/MDX/Sandpack/CustomPreset.tsx

@ -18,7 +18,7 @@ import {IconChevron} from 'components/Icon/IconChevron';
import {NavigationBar} from './NavigationBar';
import {Preview} from './Preview';
import {CustomTheme} from './Themes';
import {useSandpackLint} from './utils';
import {useSandpackLint} from './useSandpackLint';
export function CustomPreset({
isSingleFile,
@ -31,7 +31,7 @@ export function CustomPreset({
devToolsLoaded: boolean;
onDevToolsLoad: () => void;
}) {
const {lintErrors, onLint} = useSandpackLint();
const {lintErrors, lintExtensions} = useSandpackLint();
const lineCountRef = React.useRef<{[key: string]: number}>({});
const containerRef = React.useRef<HTMLDivElement>(null);
const {sandpack} = useSandpack();
@ -64,7 +64,7 @@ export function CustomPreset({
showInlineErrors
showTabs={false}
showRunButton={false}
extensions={[onLint]}
extensions={lintExtensions}
/>
<Preview
className="order-last xl:order-2"

7
beta/src/components/MDX/Sandpack/Preview.tsx

@ -8,8 +8,11 @@ import {useSandpack, LoadingOverlay} from '@codesandbox/sandpack-react';
import cn from 'classnames';
import {Error} from './Error';
import {computeViewportSize, generateRandomId} from './utils';
import type {LintDiagnostic} from './utils';
import {computeViewportSize} from './computeViewportSize';
import type {LintDiagnostic} from './useSandpackLint';
const generateRandomId = (): string =>
Math.floor(Math.random() * 10000).toString();
type CustomPreviewProps = {
className?: string;

2
beta/src/components/MDX/Sandpack/SandpackRoot.tsx

@ -6,7 +6,7 @@ import * as React from 'react';
import {SandpackProvider} from '@codesandbox/sandpack-react';
import {SandpackLogLevel} from '@codesandbox/sandpack-client';
import {CustomPreset} from './CustomPreset';
import {createFileMap} from './utils';
import {createFileMap} from './createFileMap';
import type {SandpackSetup} from '@codesandbox/sandpack-react';

46
beta/src/components/MDX/Sandpack/computeViewportSize.ts

@ -0,0 +1,46 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
export type ViewportSizePreset =
| 'iPhone X'
| 'Pixel 2'
| 'iPad'
| 'Moto G4'
| 'Surface Duo';
export type ViewportSize =
| ViewportSizePreset
| 'auto'
| {width: number; height: number};
export type ViewportOrientation = 'portrait' | 'landscape';
const VIEWPORT_SIZE_PRESET_MAP: Record<
ViewportSizePreset,
{x: number; y: number}
> = {
'iPhone X': {x: 375, y: 812},
iPad: {x: 768, y: 1024},
'Pixel 2': {x: 411, y: 731},
'Moto G4': {x: 360, y: 640},
'Surface Duo': {x: 540, y: 720},
};
export const computeViewportSize = (
viewport: ViewportSize,
orientation: ViewportOrientation
): {width?: number; height?: number} => {
if (viewport === 'auto') {
return {};
}
if (typeof viewport === 'string') {
const {x, y} = VIEWPORT_SIZE_PRESET_MAP[viewport];
return orientation === 'portrait'
? {width: x, height: y}
: {width: y, height: x};
}
return viewport;
};

53
beta/src/components/MDX/Sandpack/createFileMap.ts

@ -0,0 +1,53 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import type {SandpackFile} from '@codesandbox/sandpack-react';
export const createFileMap = (codeSnippets: any) => {
return codeSnippets.reduce(
(result: Record<string, SandpackFile>, codeSnippet: React.ReactElement) => {
if (codeSnippet.props.mdxType !== 'pre') {
return result;
}
const {props} = codeSnippet.props.children;
let filePath; // path in the folder structure
let fileHidden = false; // if the file is available as a tab
let fileActive = false; // if the file tab is shown by default
if (props.metastring) {
const [name, ...params] = props.metastring.split(' ');
filePath = '/' + name;
if (params.includes('hidden')) {
fileHidden = true;
}
if (params.includes('active')) {
fileActive = true;
}
} else {
if (props.className === 'language-js') {
filePath = '/App.js';
} else if (props.className === 'language-css') {
filePath = '/styles.css';
} else {
throw new Error(
`Code block is missing a filename: ${props.children}`
);
}
}
if (result[filePath]) {
throw new Error(
`File ${filePath} was defined multiple times. Each file snippet should have a unique path name`
);
}
result[filePath] = {
code: props.children as string,
hidden: fileHidden,
active: fileActive,
};
return result;
},
{}
);
};

2
beta/src/components/MDX/Sandpack/index.tsx

@ -4,7 +4,7 @@
import * as React from 'react';
import dynamic from 'next/dynamic';
import {createFileMap} from './utils';
import {createFileMap} from './createFileMap';
const SandpackRoot = dynamic(() => import('./SandpackRoot'), {suspense: true});

2
beta/src/components/MDX/Sandpack/eslint-integration.tsx → beta/src/components/MDX/Sandpack/runESLint.tsx

@ -39,7 +39,7 @@ const options = {
},
};
export const lintDiagnostic = (
export const runESLint = (
doc: Text
): {errors: any[]; codeMirrorPayload: Diagnostic[]} => {
const codeString = doc.toString();

35
beta/src/components/MDX/Sandpack/useSandpackLint.tsx

@ -0,0 +1,35 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
// @ts-nocheck
import {useState, useEffect} from 'react';
import {linter} from '@codemirror/lint';
import type {Diagnostic} from '@codemirror/lint';
import type {Text} from '@codemirror/text';
import type {EditorView} from '@codemirror/view';
export type LintDiagnostic = {
line: number;
column: number;
severity: 'warning' | 'error';
message: string;
}[];
export const useSandpackLint = () => {
const [lintErrors, setLintErrors] = useState<LintDiagnostic>([]);
// TODO: ideally @codemirror/linter would be code-split too but I don't know how
// because Sandpack seems to ignore updates to the "extensions" prop.
const onLint = linter(async (props: EditorView) => {
const {runESLint} = await import('./runESLint');
const editorState = props.state.doc;
let {errors, codeMirrorPayload} = runESLint(editorState);
// Only show errors from rules, not parsing errors etc
setLintErrors(errors.filter((e) => !e.fatal));
return codeMirrorPayload;
});
return {lintErrors, lintExtensions: [onLint]};
};

124
beta/src/components/MDX/Sandpack/utils.ts

@ -1,124 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import {useState} from 'react';
import {lintDiagnostic} from './eslint-integration';
import {linter} from '@codemirror/lint';
import type {EditorView} from '@codemirror/view';
import type {SandpackFile} from '@codesandbox/sandpack-react';
export type ViewportSizePreset =
| 'iPhone X'
| 'Pixel 2'
| 'iPad'
| 'Moto G4'
| 'Surface Duo';
export type ViewportSize =
| ViewportSizePreset
| 'auto'
| {width: number; height: number};
export type ViewportOrientation = 'portrait' | 'landscape';
export const generateRandomId = (): string =>
Math.floor(Math.random() * 10000).toString();
const VIEWPORT_SIZE_PRESET_MAP: Record<
ViewportSizePreset,
{x: number; y: number}
> = {
'iPhone X': {x: 375, y: 812},
iPad: {x: 768, y: 1024},
'Pixel 2': {x: 411, y: 731},
'Moto G4': {x: 360, y: 640},
'Surface Duo': {x: 540, y: 720},
};
export const computeViewportSize = (
viewport: ViewportSize,
orientation: ViewportOrientation
): {width?: number; height?: number} => {
if (viewport === 'auto') {
return {};
}
if (typeof viewport === 'string') {
const {x, y} = VIEWPORT_SIZE_PRESET_MAP[viewport];
return orientation === 'portrait'
? {width: x, height: y}
: {width: y, height: x};
}
return viewport;
};
export const createFileMap = (codeSnippets: any) => {
return codeSnippets.reduce(
(result: Record<string, SandpackFile>, codeSnippet: React.ReactElement) => {
if (codeSnippet.props.mdxType !== 'pre') {
return result;
}
const {props} = codeSnippet.props.children;
let filePath; // path in the folder structure
let fileHidden = false; // if the file is available as a tab
let fileActive = false; // if the file tab is shown by default
if (props.metastring) {
const [name, ...params] = props.metastring.split(' ');
filePath = '/' + name;
if (params.includes('hidden')) {
fileHidden = true;
}
if (params.includes('active')) {
fileActive = true;
}
} else {
if (props.className === 'language-js') {
filePath = '/App.js';
} else if (props.className === 'language-css') {
filePath = '/styles.css';
} else {
throw new Error(
`Code block is missing a filename: ${props.children}`
);
}
}
if (result[filePath]) {
throw new Error(
`File ${filePath} was defined multiple times. Each file snippet should have a unique path name`
);
}
result[filePath] = {
code: props.children as string,
hidden: fileHidden,
active: fileActive,
};
return result;
},
{}
);
};
export type LintDiagnostic = {
line: number;
column: number;
severity: 'warning' | 'error';
message: string;
}[];
export const useSandpackLint = () => {
const [lintErrors, setLintErrors] = useState<LintDiagnostic>([]);
const onLint = linter((props: EditorView) => {
const editorState = props.state.doc;
return import('./eslint-integration').then((module) => {
let {errors} = module.lintDiagnostic(editorState);
// Only show errors from rules, not parsing errors etc
setLintErrors(errors.filter((e) => !e.fatal));
return module.lintDiagnostic(editorState).codeMirrorPayload;
});
});
return {lintErrors, onLint};
};
Loading…
Cancel
Save