@umijs/preset-umi 4.2.6-alpha.4 → 4.2.6-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -105,7 +105,10 @@ umi build --clean
105
105
  umi: (0, import_path.join)(api.paths.absTmpPath, "umi.ts")
106
106
  }
107
107
  });
108
- const shouldUseAutomaticRuntime = ((_a = api.appData.react) == null ? void 0 : _a.version) && import_utils.semver.gte(api.appData.react.version, "16.14.0");
108
+ const shouldUseAutomaticRuntime = ((_a = api.appData.react) == null ? void 0 : _a.version) && // why not 16.14.0 ?
109
+ // it will break the config of externals, when externals
110
+ // does not handle the react/runtime
111
+ import_utils.semver.gte(api.appData.react.version, "17.0.0");
109
112
  const opts = {
110
113
  react: {
111
114
  runtime: shouldUseAutomaticRuntime ? "automatic" : "classic"
@@ -309,7 +309,7 @@ PORT=8888 umi dev
309
309
  umi: (0, import_path.join)(api.paths.absTmpPath, "umi.ts")
310
310
  }
311
311
  });
312
- const shouldUseAutomaticRuntime = ((_c = api.appData.react) == null ? void 0 : _c.version) && import_utils.semver.gte(api.appData.react.version, "16.14.0");
312
+ const shouldUseAutomaticRuntime = ((_c = api.appData.react) == null ? void 0 : _c.version) && import_utils.semver.gte(api.appData.react.version, "17.0.0");
313
313
  const opts = {
314
314
  react: {
315
315
  runtime: shouldUseAutomaticRuntime ? "automatic" : "classic"
@@ -34,10 +34,7 @@ __export(getBabelOpts_exports, {
34
34
  module.exports = __toCommonJS(getBabelOpts_exports);
35
35
  var import_utils = require("@umijs/utils");
36
36
  async function getBabelOpts(opts) {
37
- const shouldUseAutomaticRuntime = import_utils.semver.gte(
38
- opts.api.appData.react.version,
39
- "16.14.0"
40
- );
37
+ const shouldUseAutomaticRuntime = import_utils.semver.gte(opts.api.appData.react.version, "17.0.0");
41
38
  const babelPresetOpts = await opts.api.applyPlugins({
42
39
  key: "modifyBabelPresetOpts",
43
40
  initialValue: {
@@ -55,8 +55,11 @@ var configPlugins_default = (api) => {
55
55
  cwd: api.cwd,
56
56
  dep: "react-dom"
57
57
  }) || (0, import_path.dirname)(require.resolve("react-dom/package.json"));
58
- const reactDOMVersion = require((0, import_path.join)(reactDOMPath, "package.json")).version;
59
- const isLT18 = !reactDOMVersion.startsWith("18.");
58
+ const isLT18 = (() => {
59
+ const reactDOMVersion = require((0, import_path.join)(reactDOMPath, "package.json")).version;
60
+ const majorVersion = parseInt(reactDOMVersion.split(".")[0], 10);
61
+ return majorVersion < 18;
62
+ })();
60
63
  const configDefaults = {
61
64
  alias: {
62
65
  umi: "@@/exports",
@@ -1,6 +1,7 @@
1
1
  import { IApi } from '../../types';
2
2
  export declare function getGlobalVars(opts: {
3
3
  content: string;
4
+ fileName: string;
4
5
  }): Promise<string[]>;
5
6
  declare const _default: (api: IApi) => void;
6
7
  export default _default;
@@ -47,7 +47,8 @@ async function checkDir(opts) {
47
47
  const varMap = {};
48
48
  for (const jsFile of jsFiles) {
49
49
  const vars = await getGlobalVars({
50
- content: import_fs.default.readFileSync(import_path.default.join(opts.dir, jsFile), "utf-8")
50
+ content: import_fs.default.readFileSync(import_path.default.join(opts.dir, jsFile), "utf-8"),
51
+ fileName: jsFile
51
52
  });
52
53
  for (const v of vars) {
53
54
  varMap[v] = varMap[v] || [];
@@ -69,23 +70,29 @@ async function checkDir(opts) {
69
70
  import_utils.logger.info(`[esbuildHelperChecker] No conflicts found.`);
70
71
  }
71
72
  async function getGlobalVars(opts) {
72
- const ast = parser.parse(opts.content, {
73
- sourceType: "module",
74
- sourceFilename: "foo.js",
75
- plugins: []
76
- });
77
73
  const vars = [];
78
- ast.program.body.forEach((node) => {
79
- if (t.isVariableDeclaration(node)) {
80
- node.declarations.forEach((declaration) => {
81
- if (t.isVariableDeclarator(declaration)) {
82
- if (t.isIdentifier(declaration.id)) {
83
- vars.push(declaration.id.name);
74
+ try {
75
+ const ast = parser.parse(opts.content, {
76
+ sourceType: "module",
77
+ sourceFilename: "foo.js",
78
+ plugins: []
79
+ });
80
+ ast.program.body.forEach((node) => {
81
+ if (t.isVariableDeclaration(node)) {
82
+ node.declarations.forEach((declaration) => {
83
+ if (t.isVariableDeclarator(declaration)) {
84
+ if (t.isIdentifier(declaration.id)) {
85
+ vars.push(declaration.id.name);
86
+ }
84
87
  }
85
- }
86
- });
87
- }
88
- });
88
+ });
89
+ }
90
+ });
91
+ } catch (error) {
92
+ import_utils.logger.error(
93
+ `[esbuildHelperChecker] Failed to parse ${opts.fileName}, ${error.message}`
94
+ );
95
+ }
89
96
  return vars;
90
97
  }
91
98
  var esbuildHelperChecker_default = (api) => {
@@ -0,0 +1,3 @@
1
+ import { IApi } from '../../types';
2
+ declare const _default: (api: IApi) => void;
3
+ export default _default;
@@ -0,0 +1,77 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/features/forget/forget.ts
30
+ var forget_exports = {};
31
+ __export(forget_exports, {
32
+ default: () => forget_default
33
+ });
34
+ module.exports = __toCommonJS(forget_exports);
35
+ var forget_default = (api) => {
36
+ api.describe({
37
+ key: "forget",
38
+ config: {
39
+ schema({ zod }) {
40
+ return zod.object({
41
+ ReactCompilerConfig: zod.object({}).optional()
42
+ });
43
+ }
44
+ },
45
+ enableBy: api.EnableBy.config
46
+ });
47
+ api.onCheckConfig(() => {
48
+ if (api.config.mfsu) {
49
+ throw new Error(
50
+ `forget is not compatible with mfsu, please disable mfsu first.`
51
+ );
52
+ }
53
+ if (api.config.mako) {
54
+ throw new Error(
55
+ `forget is not compatible with mako, please disable mako first.`
56
+ );
57
+ }
58
+ });
59
+ api.onCheck(() => {
60
+ let reactMajorVersion = api.appData.react.version.split(".")[0];
61
+ if (reactMajorVersion < 19) {
62
+ throw new Error(
63
+ `forget is only compatible with React 19 and above, please upgrade your React version.`
64
+ );
65
+ }
66
+ });
67
+ api.modifyConfig((memo) => {
68
+ let ReactCompilerConfig = api.userConfig.forget.ReactCompilerConfig || {};
69
+ return {
70
+ ...memo,
71
+ extraBabelPlugins: [
72
+ ...memo.extraBabelPlugins || [],
73
+ [require.resolve("babel-plugin-react-compiler"), ReactCompilerConfig]
74
+ ]
75
+ };
76
+ });
77
+ };
@@ -32,8 +32,8 @@ __export(mako_exports, {
32
32
  default: () => mako_default
33
33
  });
34
34
  module.exports = __toCommonJS(mako_exports);
35
- var import_utils = require("@umijs/utils");
36
35
  var import_path = __toESM(require("path"));
36
+ var import_utils = require("@umijs/utils");
37
37
  var mako_default = (api) => {
38
38
  api.describe({
39
39
  key: "mako",
@@ -72,14 +72,14 @@ var mpa_default = (api) => {
72
72
  api.userConfig.mountElementId
73
73
  );
74
74
  }
75
- const isReact18 = api.appData.react.version.startsWith("18.");
75
+ const isGTEReact18 = api.appData.react.version.split(".")[0] >= 18;
76
76
  api.appData.mpa.entry.forEach((entry) => {
77
77
  const layout = entry.layout || api.config.mpa.layout;
78
78
  const layoutImport = layout ? `import Layout from '${layout}';` : "";
79
79
  const layoutJSX = layout ? `<Layout><App /></Layout>` : `<App />`;
80
80
  const rootElement = `document.getElementById('${entry.mountElementId}')`;
81
- const renderer = isReact18 ? `ReactDOM.createRoot(${rootElement}).render(${layoutJSX});` : `ReactDOM.render(${layoutJSX}, ${rootElement});`;
82
- const reactDOMSource = isReact18 ? "react-dom/client" : "react-dom";
81
+ const renderer = isGTEReact18 ? `ReactDOM.createRoot(${rootElement}).render(${layoutJSX});` : `ReactDOM.render(${layoutJSX}, ${rootElement});`;
82
+ const reactDOMSource = isGTEReact18 ? "react-dom/client" : "react-dom";
83
83
  api.writeTmpFile({
84
84
  path: entry.tmpFilePath,
85
85
  noPluginDir: true,
@@ -41,10 +41,11 @@ var parser = (0, import_utils.importLazy)(
41
41
  var prepare_default = (api) => {
42
42
  function updateAppdata(prepareData) {
43
43
  var _a;
44
- const buildResult = import_utils.lodash.cloneDeep(prepareData.buildResult);
45
- (buildResult.outputFiles || []).forEach((file) => {
46
- file == null ? true : delete file.contents;
47
- });
44
+ const buildResult = {
45
+ ...prepareData.buildResult,
46
+ // we don't need output file in prepare stage
47
+ outputFiles: void 0
48
+ };
48
49
  const nextFileImports = prepareData.fileImports ?? ((_a = api.appData.prepare) == null ? void 0 : _a.fileImports);
49
50
  api.appData.prepare = {
50
51
  buildResult,
@@ -23,6 +23,7 @@ __export(routePreloadOnLoad_exports, {
23
23
  });
24
24
  module.exports = __toCommonJS(routePreloadOnLoad_exports);
25
25
  var import_utils = require("@umijs/utils");
26
+ var import_crypto = require("crypto");
26
27
  var import_fs = require("fs");
27
28
  var import_path = require("path");
28
29
  var import_constants = require("../../constants");
@@ -149,6 +150,7 @@ async function getRoutePathFilesMap(routes, fileChunksMap, opts) {
149
150
  }
150
151
  var routePreloadOnLoad_default = (api) => {
151
152
  let routeChunkFilesMap;
153
+ let preloadJSFileExt = ".js";
152
154
  api.describe({
153
155
  enableBy: () => {
154
156
  var _a;
@@ -165,6 +167,8 @@ var routePreloadOnLoad_default = (api) => {
165
167
  api.addHTMLHeadScripts({
166
168
  fn: () => {
167
169
  if (api.name === "build" && routeChunkFilesMap) {
170
+ const { publicPath } = api.config;
171
+ const displayPublicPath = publicPath === "auto" ? "/" : publicPath;
168
172
  return api.config.tern ? (
169
173
  // map mode
170
174
  [
@@ -177,20 +181,7 @@ var routePreloadOnLoad_default = (api) => {
177
181
  // script mode
178
182
  [
179
183
  {
180
- content: (0, import_fs.readFileSync)(
181
- (0, import_path.join)(
182
- import_constants.TEMPLATES_DIR,
183
- "routePreloadOnLoad/preloadRouteFilesScp.js"
184
- ),
185
- "utf-8"
186
- ).replace(
187
- '"{{routeChunkFilesMap}}"',
188
- JSON.stringify(routeChunkFilesMap)
189
- ).replace("{{basename}}", api.config.base).replace(
190
- '"{{publicPath}}"',
191
- `${// handle runtimePublicPath
192
- api.config.runtimePublicPath ? "window.publicPath||" : ""}"${api.config.publicPath}"`
193
- )
184
+ src: `${displayPublicPath}${import_utils2.PRELOAD_ROUTE_HELPER}${preloadJSFileExt}`
194
185
  }
195
186
  ]
196
187
  );
@@ -231,6 +222,30 @@ var routePreloadOnLoad_default = (api) => {
231
222
  ).fromPairs().value()
232
223
  };
233
224
  }
225
+ if (api.name === "build" && routeChunkFilesMap && !api.config.tern) {
226
+ const content = (0, import_fs.readFileSync)(
227
+ (0, import_path.join)(import_constants.TEMPLATES_DIR, "routePreloadOnLoad/preloadRouteFilesScp.js"),
228
+ "utf-8"
229
+ ).replace(
230
+ '"{{routeChunkFilesMap}}"',
231
+ JSON.stringify(routeChunkFilesMap)
232
+ ).replace("{{basename}}", api.config.base).replace(
233
+ '"{{publicPath}}"',
234
+ `${// handle runtimePublicPath
235
+ api.config.runtimePublicPath ? "window.publicPath||" : ""}"${api.config.publicPath}"`
236
+ );
237
+ if (api.config.hash) {
238
+ preloadJSFileExt = `.${(0, import_crypto.createHash)("md5").update(content).digest("hex").substring(0, 8)}.js`;
239
+ }
240
+ (0, import_fs.writeFileSync)(
241
+ (0, import_path.join)(
242
+ api.paths.absOutputPath,
243
+ `${import_utils2.PRELOAD_ROUTE_HELPER}${preloadJSFileExt}`
244
+ ),
245
+ content,
246
+ "utf-8"
247
+ );
248
+ }
234
249
  }
235
250
  });
236
251
  };
@@ -9,6 +9,7 @@ export interface IPreloadRouteFile {
9
9
  attrs: ([string, string] | [string])[];
10
10
  }
11
11
  export declare const PRELOAD_ROUTE_MAP_SCP_TYPE = "umi-route-chunk-files-map";
12
+ export declare const PRELOAD_ROUTE_HELPER = "preload_helper";
12
13
  export declare function getPreloadRouteFiles(path: string, map: IRouteChunkFilesMap, opts: {
13
14
  publicPath: string;
14
15
  }): IPreloadRouteFile[] | undefined;
@@ -19,11 +19,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  // src/features/routePreloadOnLoad/utils.ts
20
20
  var utils_exports = {};
21
21
  __export(utils_exports, {
22
+ PRELOAD_ROUTE_HELPER: () => PRELOAD_ROUTE_HELPER,
22
23
  PRELOAD_ROUTE_MAP_SCP_TYPE: () => PRELOAD_ROUTE_MAP_SCP_TYPE,
23
24
  getPreloadRouteFiles: () => getPreloadRouteFiles
24
25
  });
25
26
  module.exports = __toCommonJS(utils_exports);
26
27
  var PRELOAD_ROUTE_MAP_SCP_TYPE = "umi-route-chunk-files-map";
28
+ var PRELOAD_ROUTE_HELPER = "preload_helper";
27
29
  function getPreloadRouteFiles(path, map, opts) {
28
30
  var _a;
29
31
  const matched = (
@@ -52,6 +54,7 @@ function getPreloadRouteFiles(path, map, opts) {
52
54
  }
53
55
  // Annotate the CommonJS export names for ESM import in node:
54
56
  0 && (module.exports = {
57
+ PRELOAD_ROUTE_HELPER,
55
58
  PRELOAD_ROUTE_MAP_SCP_TYPE,
56
59
  getPreloadRouteFiles
57
60
  });
@@ -181,16 +181,20 @@ async function getRoutes(opts) {
181
181
  });
182
182
  return routes;
183
183
  }
184
+ var IMPORT_EMPTY_ROUTE_CJS = `() => Promise.resolve(require('./EmptyRoute'))`;
185
+ var IMPORT_EMPTY_ROUTE_ESM = `() => import('./EmptyRoute')`;
184
186
  async function getRouteComponents(opts) {
185
187
  const imports = Object.keys(opts.routes).map((key) => {
186
188
  var _a;
187
189
  const useSuspense = opts.api.appData.framework === "react" ? true : false;
188
190
  const route = opts.routes[key];
191
+ const useCjsModule = ((_a = opts.api.config.routeLoader) == null ? void 0 : _a.moduleType) === "cjs";
189
192
  if (!route.file) {
190
193
  if (process.env.NODE_ENV === "test") {
191
- return `'${key}': require( './EmptyRoute').default,`;
194
+ return `'${key}': require('./EmptyRoute').default,`;
192
195
  }
193
- return useSuspense ? `'${key}': React.lazy(() => import( './EmptyRoute')),` : `'${key}': () => import( './EmptyRoute'),`;
196
+ const importEmptyRoute = useCjsModule ? IMPORT_EMPTY_ROUTE_CJS : IMPORT_EMPTY_ROUTE_ESM;
197
+ return useSuspense ? `'${key}': React.lazy(${importEmptyRoute}),` : `'${key}': ${importEmptyRoute},`;
194
198
  }
195
199
  if (route.hasClientLoader) {
196
200
  route.file = (0, import_path.join)(
@@ -213,7 +217,7 @@ async function getRouteComponents(opts) {
213
217
  if (process.env.NODE_ENV === "test") {
214
218
  return `'${key}': require('${(0, import_utils.winPath)(path)}').default,`;
215
219
  }
216
- if (((_a = opts.api.config.routeLoader) == null ? void 0 : _a.moduleType) === "cjs") {
220
+ if (useCjsModule) {
217
221
  return useSuspense ? `'${key}': React.lazy(() => Promise.resolve(require('${(0, import_utils.winPath)(
218
222
  path
219
223
  )}'))),` : `'${key}': () => Promise.resolve(require('${(0, import_utils.winPath)(path)}')),`;
@@ -112,6 +112,7 @@ var tmpFiles_default = (api) => {
112
112
  },
113
113
  include: [
114
114
  `${baseUrl}.${frameworkName}rc.ts`,
115
+ `${baseUrl}.${frameworkName}rc.*.ts`,
115
116
  `${baseUrl}**/*.d.ts`,
116
117
  `${baseUrl}**/*.ts`,
117
118
  `${baseUrl}**/*.tsx`,
@@ -433,6 +434,9 @@ if (process.env.NODE_ENV === 'development') {
433
434
  const validKeys = await api.applyPlugins({
434
435
  key: "addRuntimePluginKey",
435
436
  initialValue: [
437
+ // why add default?
438
+ // ref: https://github.com/umijs/mako/issues/1026
439
+ ...process.env.OKAM ? ["default"] : [],
436
440
  "patchRoutes",
437
441
  "patchClientRoutes",
438
442
  "modifyContextOpts",
package/dist/index.js CHANGED
@@ -88,6 +88,7 @@ var src_default = () => {
88
88
  require.resolve("./features/mako/mako"),
89
89
  require.resolve("./features/hmrGuardian/hmrGuardian"),
90
90
  require.resolve("./features/routePreloadOnLoad/routePreloadOnLoad"),
91
+ require.resolve("./features/forget/forget"),
91
92
  // commands
92
93
  require.resolve("./commands/build"),
93
94
  require.resolve("./commands/config/config"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umijs/preset-umi",
3
- "version": "4.2.6-alpha.4",
3
+ "version": "4.2.6-alpha.6",
4
4
  "description": "@umijs/preset-umi",
5
5
  "homepage": "https://github.com/umijs/umi/tree/master/packages/preset-umi#readme",
6
6
  "bugs": "https://github.com/umijs/umi/issues",
@@ -21,10 +21,11 @@
21
21
  "dependencies": {
22
22
  "@iconify/utils": "2.1.1",
23
23
  "@svgr/core": "6.5.1",
24
- "@umijs/bundler-mako": "0.4.18-canary.20240520.1",
24
+ "@umijs/bundler-mako": "0.4.17",
25
25
  "@umijs/es-module-parser": "0.0.7",
26
26
  "@umijs/history": "5.3.1",
27
27
  "babel-plugin-dynamic-import-node": "2.3.3",
28
+ "babel-plugin-react-compiler": "0.0.0-experimental-c23de8d-20240515",
28
29
  "click-to-react-component": "^1.0.8",
29
30
  "core-js": "3.34.0",
30
31
  "current-script-polyfill": "1.0.0",
@@ -40,21 +41,21 @@
40
41
  "react-router": "6.3.0",
41
42
  "react-router-dom": "6.3.0",
42
43
  "regenerator-runtime": "0.13.11",
43
- "@umijs/babel-preset-umi": "4.2.6-alpha.4",
44
- "@umijs/bundler-vite": "4.2.6-alpha.4",
45
- "@umijs/ast": "4.2.6-alpha.4",
46
- "@umijs/bundler-esbuild": "4.2.6-alpha.4",
47
- "@umijs/bundler-utils": "4.2.6-alpha.4",
48
- "@umijs/bundler-webpack": "4.2.6-alpha.4",
49
- "@umijs/mfsu": "4.2.6-alpha.4",
50
- "@umijs/core": "4.2.6-alpha.4",
51
- "@umijs/renderer-react": "4.2.6-alpha.4",
52
- "@umijs/server": "4.2.6-alpha.4",
44
+ "@umijs/ast": "4.2.6-alpha.6",
45
+ "@umijs/bundler-utils": "4.2.6-alpha.6",
46
+ "@umijs/bundler-esbuild": "4.2.6-alpha.6",
47
+ "@umijs/babel-preset-umi": "4.2.6-alpha.6",
48
+ "@umijs/bundler-vite": "4.2.6-alpha.6",
53
49
  "@umijs/did-you-know": "1.0.3",
54
- "@umijs/zod2ts": "4.2.6-alpha.4",
55
- "@umijs/plugin-run": "4.2.6-alpha.4",
56
- "@umijs/utils": "4.2.6-alpha.4",
57
- "@umijs/ui": "3.0.1"
50
+ "@umijs/bundler-webpack": "4.2.6-alpha.6",
51
+ "@umijs/core": "4.2.6-alpha.6",
52
+ "@umijs/server": "4.2.6-alpha.6",
53
+ "@umijs/mfsu": "4.2.6-alpha.6",
54
+ "@umijs/plugin-run": "4.2.6-alpha.6",
55
+ "@umijs/renderer-react": "4.2.6-alpha.6",
56
+ "@umijs/ui": "3.0.1",
57
+ "@umijs/utils": "4.2.6-alpha.6",
58
+ "@umijs/zod2ts": "4.2.6-alpha.6"
58
59
  },
59
60
  "devDependencies": {
60
61
  "@manypkg/get-packages": "1.1.3",
@@ -41,6 +41,12 @@ export function createHistory(opts: any) {
41
41
  return h;
42
42
  }
43
43
 
44
+ export function setHistory(h: UmiHistory) {
45
+ if (h) {
46
+ history = h;
47
+ }
48
+ }
49
+
44
50
  // Patch `to` to support basename
45
51
  // Refs:
46
52
  // https://github.com/remix-run/history/blob/3e9dab4/packages/history/index.ts#L484