This commit is contained in:
Hengchang Lu 2021-08-25 11:07:31 +08:00 committed by GitHub
parent 8071445e80
commit 575f9b19ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 4536 additions and 4453 deletions

View File

@ -1,21 +1,4 @@
{
"mpa": true,
"plugins": [
[
"build-plugin-prerender",
{
"routes": [
"/home",
"/dashboard"
],
"minify": {
"collapseBooleanAttributes": false,
"collapseWhitespace": false
},
"renderer": {
"maxConcurrentRoutes": 4
}
}
]
]
}
"ssr": "static"
}

View File

@ -1,21 +1,19 @@
{
"name": "exmaple-with-prerender-mpa",
"name": "with-prerender-mpa-exmaple",
"description": "",
"dependencies": {
"ice.js": "^1.0.11",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"build-plugin-prerender": "^1.5.1"
"react-dom": "^16.4.1"
},
"devDependencies": {
"@types/react": "^16.9.13",
"@types/react-dom": "^16.9.4"
},
"scripts": {
"start": "icejs start",
"build": "icejs build"
"start": "../../packages/icejs/bin/ice-cli.js start --mode dev",
"build": "../../packages/icejs/bin/ice-cli.js build --mode prod"
},
"engines": {
"node": ">=8.0.0"
}
}
}

View File

@ -1,17 +1,3 @@
{
"plugins": [
[
"build-plugin-prerender",
{
"routes": [
"/",
"/about",
"/dashboard"
],
"renderer": {
"headless": false
}
}
]
]
}
"ssr": "static"
}

View File

@ -1,8 +1,7 @@
{
"name": "exmaple-basic-prerender",
"name": "with-prerender-spa-example",
"description": "",
"dependencies": {
"ice.js": "^1.4.0",
"react": "^16.8.1",
"react-dom": "^16.8.1"
},
@ -11,10 +10,10 @@
"@types/react-dom": "^16.9.4"
},
"scripts": {
"start": "icejs start --mode dev",
"build": "icejs build --mode prod"
"start": "../../packages/icejs/bin/ice-cli.js start --mode dev",
"build": "../../packages/icejs/bin/ice-cli.js build --mode prod"
},
"engines": {
"node": ">=8.0.0"
}
}
}

View File

@ -5,6 +5,7 @@ const appConfig: IAppConfig = {
rootId: 'ice-container'
},
router: {
// not support hash
type: 'browser',
}
};

View File

@ -1,5 +1,16 @@
import React from 'react';
import { useParams } from 'ice';
export default () => (
<h2>About</h2>
);
function About() {
const { id } = useParams();
return (
<h2>About {id}</h2>
);
}
About.getStaticPaths = async () => {
return await Promise.resolve(['/about/a', '/about/b', '/about/c']);
};
export default About;

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { Link } from 'ice';
const Home = () => {
const Home = ({ data = [] }) => {
return (
<>
<div id="menu">
@ -14,8 +14,11 @@ const Home = () => {
<Link to="/Dashboard">Dashboard</Link>
</li>
<li>
<Link to="/about">About</Link>
<Link to="/about/1">About</Link>
</li>
{
data.map((item: number) => (<li key={item}><strong>{item}</strong></li>))
}
</ul>
</div>
<div>Home</div>

View File

@ -0,0 +1,5 @@
import React from 'react';
export default () => {
return (<div>NotFound</div>);
};

View File

@ -1,21 +1,30 @@
import Home from '@/pages/Home';
import About from '@/pages/About';
import Dashboard from '@/pages/Dashboard';
import NotFound from '@/pages/NotFound';
export default [
{
path: '/',
exact: true,
component: Home
component: Home,
getInitialProps: () => {
return Promise.resolve({ data: [Math.random(), Math.random(), Math.random()] });
}
},
{
path: '/about',
path: '/about/:id',
exact: true,
component: About
component: About,
},
{
path: '/dashboard',
exact: true,
ssr: true, // this route will use ssr in production
component: Dashboard
},
{
path: '*',
component: NotFound,
}
];

View File

@ -22,7 +22,7 @@
"rollback": "ts-node ./scripts/rollback.ts",
"owner": "ts-node ./scripts/owner.ts",
"dependency:check": "ts-node ./scripts/dependency-check.ts",
"clean": "lerna clean --yes && rimraf packages/*/lib",
"clean": "rimraf packages/*/lib",
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./",
"lint:fix": "npm run lint -- --fix",
"test": "jest --forceExit",
@ -69,8 +69,7 @@
"typescript": "^4.0.0"
},
"dependencies": {
"core-js": "^3.6.4",
"path-to-regexp": "6.1.0"
"core-js": "^3.6.4"
},
"resolutions": {
"@typescript-eslint/eslint-plugin": "^4.0.0",

View File

@ -63,4 +63,5 @@ export function runApp(appConfig?: IAppConfig) {
export default {
createBaseApp: frameworkAppBase,
initAppLifeCycles,
}

View File

@ -9,7 +9,7 @@ export const USER_CONFIG = [
},
{
name: 'ssr',
validation: 'boolean'
validation: 'boolean|string'
},
{
name: 'auth',

View File

@ -24,14 +24,15 @@
},
"dependencies": {
"@builder/app-helpers": "^2.3.4",
"@builder/webpack-config": "^1.0.0",
"@loadable/babel-plugin": "^5.13.2",
"@loadable/webpack-plugin": "^5.14.0",
"@builder/webpack-config": "^1.0.0",
"chalk": "^4.0.0",
"cheerio": "^1.0.0-rc.3",
"fs-extra": "^8.1.0",
"html-minifier": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^1.7.0",
"query-string": "^6.13.7"
}
}

View File

@ -0,0 +1,36 @@
import { join } from 'path';
import * as fse from 'fs-extra';
interface PageData {
path: string;
html: string;
strict?: boolean;
sensitive?: boolean;
exact?: boolean;
}
/**
* generate pages html and pages-data.json file
*/
async function generateStaticPages(buildDir: string, renderPagesBundlePath: string) {
const pagesDataPath = join(buildDir, 'server', 'pages-data.json');
// eslint-disable-next-line
const renderPages = require(renderPagesBundlePath);
const htmlTemplate = fse.readFileSync(join(buildDir, 'index.html'), 'utf8');
const pagesData: PageData[] = await renderPages.default({ htmlTemplate });
// write pages html
pagesData.forEach((pageData) => {
const { html, path } = pageData;
const htmlPath = join(buildDir, path, 'index.html');
fse.ensureFileSync(htmlPath);
fse.writeFileSync(htmlPath, html);
});
// write pages data to json
fse.ensureFileSync(pagesDataPath);
fse.writeJSONSync(pagesDataPath, pagesData, { spaces: 2 });
}
export default generateStaticPages;

View File

@ -4,13 +4,14 @@ import { minify } from 'html-minifier';
import LoadablePlugin from '@loadable/webpack-plugin';
import getWebpackConfig from '@builder/webpack-config';
import { formatPath } from '@builder/app-helpers';
import generateStaticPages from './generateStaticPages';
const plugin = async (api): Promise<void> => {
const { context, registerTask, getValue, onGetWebpackConfig, onHook, log, applyMethod, modifyUserConfig } = api;
const { rootDir, command, webpack, commandArgs, userConfig } = context;
const { outputDir } = userConfig;
const TEMP_PATH = getValue('TEMP_PATH');
const { outputDir, ssr, publicPath = '/', devPublicPath = '/' } = userConfig;
const PROJECT_TYPE = getValue('PROJECT_TYPE');
const TEMP_PATH = getValue('TEMP_PATH');
// Note: Compatible plugins to modify configuration
const buildDir = path.join(rootDir, outputDir);
const serverDir = path.join(buildDir, 'server');
@ -19,11 +20,20 @@ const plugin = async (api): Promise<void> => {
// render server entry
const templatePath = path.join(__dirname, '../src/server.ts.ejs');
const ssrEntry = path.join(TEMP_PATH, 'server.ts');
const ssrEntry = path.join(TEMP_PATH, 'plugins/ssr/server.ts');
const routesFileExists = fse.existsSync(path.join(rootDir, 'src', `routes.${PROJECT_TYPE}`));
applyMethod('addRenderFile', templatePath, ssrEntry, { outputDir, routesPath: routesFileExists ? '@' : '.' });
applyMethod(
'addRenderFile',
templatePath,
ssrEntry,
{
outputDir,
routesPath: routesFileExists ? '@' : '.',
publicPath: command === 'build' ? publicPath : devPublicPath
});
const mode = command === 'start' ? 'development' : 'production';
const webpackConfig = getWebpackConfig(mode);
// config DefinePlugin out of onGetWebpackConfig, so it can be modified by user config
webpackConfig
@ -40,6 +50,7 @@ const plugin = async (api): Promise<void> => {
.tap(([args]) => {
return [{
...args,
// will add assets by @loadable/component
inject: false,
}];
});
@ -59,7 +70,7 @@ const plugin = async (api): Promise<void> => {
onGetWebpackConfig('ssr', (config) => {
config.entryPoints.clear();
config.entry('server').add(ssrEntry);
config.entry('index').add(ssrEntry);
config.target('node');
@ -92,10 +103,13 @@ const plugin = async (api): Promise<void> => {
config.output
.path(serverDir)
.filename(serverFilename)
.filename('[name].js')
.publicPath('/')
.libraryTarget('commonjs2');
// not generate vendor
config.optimization.splitChunks({ cacheGroups: {} });
// in case of app with client and server code, webpack-node-externals is helpful to reduce server bundle size
// while by bundle all dependencies, developers do not need to concern about the dependencies of server-side
// TODO: support options to enable nodeExternals
@ -127,6 +141,7 @@ const plugin = async (api): Promise<void> => {
res.send(html);
}
}
if (command === 'start') {
const originalDevMiddleware = config.devServer.get('devMiddleware');
config.devServer.set('devMiddleware', {
@ -171,6 +186,30 @@ const plugin = async (api): Promise<void> => {
});
});
}
if (command === 'build' && ssr === 'static') {
// SSG, pre-render page in production
const ssgTemplatePath = path.join(__dirname, './renderPages.ts.ejs');
const ssgEntryPath = path.join(TEMP_PATH, 'plugins/ssr/renderPages.ts');
const ssgBundlePath = path.join(serverDir, 'renderPages.js');
applyMethod(
'addRenderFile',
ssgTemplatePath,
ssgEntryPath,
{
outputDir,
routesPath: routesFileExists ? '@' : '.',
publicPath: command === 'build' ? publicPath : devPublicPath
}
);
config.entry('renderPages').add(ssgEntryPath);
onHook('after.build.compile', async () => {
await generateStaticPages(buildDir, ssgBundlePath);
await fse.remove(ssgBundlePath);
});
}
});
onHook(`after.${command}.compile`, () => {

View File

@ -0,0 +1,66 @@
import '@/app';
import { join } from 'path';
import routes from '<%- routesPath %>/routes';
import loadable from '@loadable/component';
import * as pathToRegexp from 'path-to-regexp';
const { renderStatic } = require('./server');
export default async function ssgRender(options) {
const { htmlTemplate } = options;
const loadableStatsPath = join(process.cwd(), '<%- outputDir %>', 'loadable-stats.json');
const buildConfig = { loadableStatsPath, publicPath: '<%- publicPath %>' };
const htmlTemplateContent = htmlTemplate || `__ICE_SERVER_HTML_TEMPLATE__`;
const pagesData = [];
const flatRoutes = await getFlatRoutes(routes || []);
for (const flatRoute of flatRoutes) {
const { path = '', getInitialProps, ...rest } = flatRoute;
const keys = [];
pathToRegexp(path, keys);
if (keys.length > 0) {
// don't render and export static page when the route is dynamic, e.g.: /news/:id, /*
continue;
}
const initialContext = { pathname: path, location: { pathname: path } };
const { html } = await renderStatic({ htmlTemplateContent, buildConfig, initialContext });
delete rest.component;
delete rest.routeWrappers;
pagesData.push({ html, path, ...rest });
}
return pagesData;
};
async function getFlatRoutes(routes, parentPath = '') {
return await routes.reduce(async (asyncPrev, route) => {
let prev = await asyncPrev;
const { children, path: currentPath, redirect } = route;
if (children) {
prev = prev.concat(await getFlatRoutes(children, currentPath));
} else if (!redirect) {
route.path = join(parentPath, currentPath);
if (route?.component?.__LOADABLE__) {
route.component = loadable(route.component.dynamicImport);
}
prev.push(route);
const getStaticPaths = route.getStaticPaths || route.component.getStaticPaths;
if (typeof getStaticPaths === 'function') {
const staticPaths = await getStaticPaths();
if (Array.isArray(staticPaths)) {
// add static paths to render
staticPaths.forEach((staticPath) => {
prev.push({
...route,
path: staticPath
})
})
}
}
}
return prev;
}, Promise.resolve([]));
}

View File

@ -1,27 +1,40 @@
import '@/app';
import { join } from 'path';
import * as pathToRegexp from 'path-to-regexp';
import * as cheerio from 'cheerio';
import * as queryString from 'query-string';
import { Helmet } from 'react-helmet';
import { matchPath } from 'ice';
import { setInitialData } from 'react-app-renderer';
import reactAppRendererWithSSR from 'react-app-renderer/lib/server';
import { getAppConfig } from './core/appConfig';
import loadStaticModules from './core/loadStaticModules';
import { emitLifeCycles } from './core/publicAPI';
import app from './core/runApp';
import { InitialContext, ServerContext } from 'react-app-renderer/lib/types';
import { getAppConfig } from '../../core/appConfig';
import loadStaticModules from '../../core/loadStaticModules';
import { emitLifeCycles } from '../../core/publicAPI';
import app from '../../core/runApp';
import routes from '<%- routesPath %>/routes';
import loadable from '@loadable/component';
const chalk = require('chalk');
const parseurl = require('parseurl');
const { createBaseApp } = app;
const { createBaseApp, initAppLifeCycles } = app;
// appConfig set by: import '@/app'
const appConfig = getAppConfig();
const serverRender = async (context, opts = {}) => {
interface GenerateHtml {
htmlTemplateContent: string;
loadableComponentExtractor?: any;
helmet?: any;
bundleContent?: string;
pageInitialProps?: any;
initialData?: any;
error?: Error;
}
const serverRender = async (context: ServerContext, opts = {}) => {
let options;
if (context.req) {
options = { ctx: context, ...opts }
@ -30,44 +43,64 @@ const serverRender = async (context, opts = {}) => {
}
let {
ctx = {},
pathname: originPathname,
pathname,
initialData,
htmlTemplate,
loadableStatsPath,
publicPath = '/'
pagesData = [],
} = options;
if (process.env.NODE_ENV === 'development') {
// loadable-stats.json will be genreated to the build dir in development
// loadable-stats.json will be generated to the build dir in development
loadableStatsPath = join(process.cwd(), '<%- outputDir %>', 'loadable-stats.json');
}
const buildConfig = { loadableStatsPath, publicPath };
const buildConfig = { loadableStatsPath, publicPath: '<%- publicPath %>' };
const htmlTemplateContent = htmlTemplate || `__ICE_SERVER_HTML_TEMPLATE__`;
// get html template
const $ = cheerio.load(htmlTemplateContent, { decodeEntities: false });
// load module to run before createApp ready
loadStaticModules(appConfig);
let pageInitialProps;
let error;
const { req, res } = ctx as any;
const { search, hash, path, pathname: parsedPathname } = parseurl(req);
const pathname = originPathname || parsedPathname;
const parsedQuery = queryString.parse(search);
const initialContext = {
req,
res,
pathname,
query: parsedQuery,
path,
location: { pathname, search, hash, state: null },
let initialContext: InitialContext = { pathname, location: { pathname } };
if (req) {
const { search, hash, path, pathname } = parseurl(req);
const parsedQuery = queryString.parse(search);
initialContext = {
req,
res,
pathname,
query: parsedQuery,
path,
location: { pathname, search, hash, state: null },
};
}
if (process.env.NODE_ENV === 'development') {
return renderStatic({ htmlTemplateContent, initialContext, initialData, buildConfig });
}
const pageData = pagesData.find(({ path, exact: end = false, strict = false, sensitive = false }) => {
try {
const regexp = pathToRegexp(path, [], { end, strict, sensitive });
return regexp.test(initialContext.pathname);
} catch (e) {
return false;
}
});
if (pageData) {
// enable pre-render pages
const { html, ssr } = pageData;
if (ssr) {
return renderStatic({ htmlTemplateContent, initialContext, initialData, buildConfig });
}
return { html };
}
return renderStatic({ htmlTemplateContent, initialContext, initialData, buildConfig });
}
export async function renderStatic({ htmlTemplateContent, initialContext, initialData, buildConfig }) {
let error;
let html = htmlTemplateContent;
try {
// get initial data
if (!initialData) {
@ -82,11 +115,12 @@ const serverRender = async (context, opts = {}) => {
setInitialData(initialData);
// get page initial props
let { component, getInitialProps } = await getComponentByPath(routes, pathname);
let { component, getInitialProps } = await getComponentByPath(routes, initialContext.pathname);
if (component && component.load) {
const loadedPageComponent = await component.load();
component = loadedPageComponent.default;
}
let pageInitialProps = {};
if (getInitialProps) {
console.log('[SSR]', 'getting initial props of page component');
pageInitialProps = await getInitialProps(initialContext);
@ -94,50 +128,79 @@ const serverRender = async (context, opts = {}) => {
// generate bundle content and register global variables in html
console.log('[SSR]', 'generating html content');
const { bundleContent, loadableComponentExtractor } = reactAppRendererWithSSR({
initialContext,
initialData,
pageInitialProps
}, {
appConfig,
buildConfig,
appLifecycle: {
createBaseApp,
emitLifeCycles,
}
});
const { bundleContent, loadableComponentExtractor } =
reactAppRendererWithSSR(
{
initialContext,
initialData,
pageInitialProps,
},
{
appConfig,
buildConfig,
appLifecycle: { createBaseApp, emitLifeCycles, initAppLifeCycles },
}
);
const helmet = Helmet.renderStatic();
$(`#${appConfig.app.rootId || 'ice-container'}`).append(bundleContent);
$('head').append(`<script>
window.__ICE_SSR_ENABLED__=true;
window.__ICE_APP_DATA__=${JSON.stringify(initialData)};
window.__ICE_PAGE_PROPS__=${JSON.stringify(pageInitialProps)};
</script>`);
// lazy load bundle
$('head').append(`${loadableComponentExtractor.getLinkTags()}`);
$('head').append(`${loadableComponentExtractor.getStyleTags()}`);
$('body').append(`${loadableComponentExtractor.getScriptTags()}`);
// react-helmet
$('head').append(`${helmet.title.toString()}`);
$('head').append(`${helmet.meta.toString()}`);
$('head').append(`${helmet.link.toString()}`);
$('head').append(`${helmet.script.toString()}`);
html = generateHtml({
htmlTemplateContent,
loadableComponentExtractor,
helmet,
bundleContent,
initialData,
pageInitialProps,
});
} catch (e) {
error = e;
$('head').append(`
<script>
window.__ICE_SSR_ERROR__ = "${(error instanceof Error ? error.message : error).replace(/\"/g, '\'')}";
</script>`)
logError('[SSR] generate html template error');
html = generateHtml({
htmlTemplateContent,
error,
});
logError('[SSR] generate html template error' + error.message);
}
const html = $.html();
return { html, error, redirectUrl: initialContext.url };
return { html, error, redirectUrl: initialContext.redirectUrl };
}
async function getComponentByPath(routes, currPath) {
export function generateHtml({
htmlTemplateContent,
loadableComponentExtractor,
helmet,
bundleContent,
initialData,
pageInitialProps,
error
}: GenerateHtml) {
const $ = cheerio.load(htmlTemplateContent, { decodeEntities: false });
if (error) {
$('head').append(`
<script>
window.__ICE_SSR_ERROR__ = "${(error instanceof Error ? error.message : error).replace(/\"/g, '\'')}";
</script>`)
return $.html();
}
$(`#${appConfig?.app?.rootId || 'ice-container'}`).append(bundleContent);
$('head').append(`<script>
window.__ICE_SSR_ENABLED__=true;
window.__ICE_APP_DATA__=${JSON.stringify(initialData)};
window.__ICE_PAGE_PROPS__=${JSON.stringify(pageInitialProps)};
</script>`);
// inject assets
$('head').append(`${loadableComponentExtractor.getLinkTags()}`);
$('head').append(`${loadableComponentExtractor.getStyleTags()}`);
$('body').append(`${loadableComponentExtractor.getScriptTags()}`);
// inject tags to header
$('head').append(`${helmet.title.toString()}`);
$('head').append(`${helmet.meta.toString()}`);
$('head').append(`${helmet.link.toString()}`);
$('head').append(`${helmet.script.toString()}`);
return $.html();
}
export async function getComponentByPath(routes, currPath) {
async function findMatchRoute(routeList) {
let matchedRoute = routeList.find(route => {
return matchPath(currPath, route);

View File

@ -122,7 +122,7 @@ const module = ({ appConfig, addDOMRender, buildConfig, setRenderApp, wrapperRou
const [apps, setApps] = useState(null);
const BasicLayout = Layout || DefaultLayout;
const RenderAppRoute = CustomAppRoute || AppRoute;
const RenderAppRoute = (CustomAppRoute || AppRoute) as typeof AppRoute;
useEffect(() => {
(async () => {

View File

@ -28,7 +28,7 @@
"@types/glob": "^7.1.1",
"@types/history": "^4.7.5",
"@types/node": "^12.12.12",
"path-to-regexp": "^6.2.0",
"path-to-regexp": "^1.7.0",
"typescript": "^4.0.0",
"vite": "^2.3.4"
},

View File

@ -1,4 +1,4 @@
import { pathToRegexp } from 'path-to-regexp';
import * as pathToRegexp from 'path-to-regexp';
import joinPath from '../joinPath';
const joinTests: [string[], string][] = [
@ -54,8 +54,6 @@ const regexpPathTests: [string[], string, string[] | null][] = [
[['/login', '/:path(abc|xyz)*'], '/login/abc/abc', ['/login/abc/abc', 'abc/abc']],
[['/login', '/:path(abc|xyz)*'], '/login/xxx', null],
[['/login', '/(.*)'], '/login/xxx', ['/login/xxx', 'xxx']],
[['/abc', '/:test((?!login)[^/]+)'], '/abc/xxx', ['/abc/xxx', 'xxx']],
[['/abc', '/:test((?!login)[^/]+)'], '/abc/login', null],
[['/abc', '/user(s)?/:user'], '/abc/user/123', ['/abc/user/123', undefined, '123']],
[['/abc', '/user(s)?/:user'], '/abc/users/123', ['/abc/users/123', 's', '123']],
];

View File

@ -15,7 +15,6 @@
"@loadable/component": "^5.14.1",
"@loadable/server": "^5.14.0",
"create-app-container": "^0.1.2",
"create-use-router": "^0.1.1",
"query-string": "^6.13.7"
},
"devDependencies": {

View File

@ -2,12 +2,32 @@ import * as queryString from 'query-string';
import type { RuntimeModule } from 'create-app-shared';
export type OnError = (err: Error, componentStack: string) => void
export interface Context {
pathname: string;
path: string;
query: queryString.ParsedQuery<string>;
ssrError: any;
initialContext: InitialContext,
initialData: { [k: string]: any },
pageInitialProps: { [k: string]: any }
}
export interface ServerContext {
req?: Request;
res?: Response;
}
export interface Location {
pathname: string;
search?: string;
hash?: string;
state?: string | null;
}
export interface InitialContext extends ServerContext {
pathname: string;
location?: Location;
path?: string;
query?: queryString.ParsedQuery<string>;
}
export type RenderAppConfig = {
app?: {
rootId?: string;
@ -15,7 +35,7 @@ export type RenderAppConfig = {
onErrorBoundaryHandler?: OnError;
ErrorBoundaryFallback?: React.ComponentType;
errorBoundary?: boolean;
getInitialData?: (context: Context) => Promise<any>;
getInitialData?: (context: InitialContext) => Promise<any>;
},
renderComponent?: React.ComponentType
};

View File

@ -7,7 +7,7 @@ type BuildResult = void | ITaskConfig[];
export async function viteBuild(context: any): Promise<BuildResult> {
const { applyHook, command, commandArgs } = context;
const configArr = context.getWebpackConfig();
await applyHook(`before.${command}.load`, { args: commandArgs, webpackConfig: configArr });
@ -25,4 +25,4 @@ export async function viteBuild(context: any): Promise<BuildResult> {
console.error('CONFIG', chalk.red('Failed to load vite config.'));
throw err;
}
}
}

View File

@ -25,7 +25,7 @@
"fs-extra": "^9.0.1",
"glob": "^7.1.4",
"multer": "^1.4.2",
"path-to-regexp": "^6.0.0",
"path-to-regexp": "^1.7.0",
"lodash.debounce": "^4.0.8"
},
"devDependencies": {

View File

@ -1,5 +1,5 @@
/* eslint @typescript-eslint/no-var-requires:0 */
import { pathToRegexp } from 'path-to-regexp';
import * as pathToRegexp from 'path-to-regexp';
function decodeParam(val) {
if (typeof val !== 'string' || val.length === 0) {
@ -44,4 +44,4 @@ function matchPath(req, mockConfig) {
}
}
export default matchPath;
export default matchPath;

8482
yarn.lock

File diff suppressed because it is too large Load Diff