almostnode 0.2.2 → 0.2.4
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.
- package/dist/frameworks/next-dev-server.d.ts +10 -0
- package/dist/frameworks/next-dev-server.d.ts.map +1 -1
- package/dist/index.cjs +190 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +187 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/frameworks/next-dev-server.ts +209 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "almostnode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Node.js in your browser. Just like that.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -102,4 +102,4 @@
|
|
|
102
102
|
"vite": "^5.4.0",
|
|
103
103
|
"vitest": "^4.0.18"
|
|
104
104
|
}
|
|
105
|
-
}
|
|
105
|
+
}
|
|
@@ -816,6 +816,16 @@ export class NextDevServer extends DevServer {
|
|
|
816
816
|
return this.serveNextShim(pathname);
|
|
817
817
|
}
|
|
818
818
|
|
|
819
|
+
// Serve page components for client-side navigation (Pages Router)
|
|
820
|
+
if (pathname.startsWith('/_next/pages/')) {
|
|
821
|
+
return this.servePageComponent(pathname);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Serve app components for client-side navigation (App Router)
|
|
825
|
+
if (pathname.startsWith('/_next/app/')) {
|
|
826
|
+
return this.serveAppComponent(pathname);
|
|
827
|
+
}
|
|
828
|
+
|
|
819
829
|
// Static assets from /_next/static/*
|
|
820
830
|
if (pathname.startsWith('/_next/static/')) {
|
|
821
831
|
return this.serveStaticAsset(pathname);
|
|
@@ -895,6 +905,50 @@ export class NextDevServer extends DevServer {
|
|
|
895
905
|
return this.notFound(pathname);
|
|
896
906
|
}
|
|
897
907
|
|
|
908
|
+
/**
|
|
909
|
+
* Serve page components for client-side navigation
|
|
910
|
+
* Maps /_next/pages/index.js → /pages/index.jsx (transformed)
|
|
911
|
+
*/
|
|
912
|
+
private async servePageComponent(pathname: string): Promise<ResponseData> {
|
|
913
|
+
// Extract the route from /_next/pages/about.js → /about
|
|
914
|
+
const route = pathname
|
|
915
|
+
.replace('/_next/pages', '')
|
|
916
|
+
.replace(/\.js$/, '');
|
|
917
|
+
|
|
918
|
+
// Resolve the actual page file
|
|
919
|
+
const pageFile = this.resolvePageFile(route);
|
|
920
|
+
|
|
921
|
+
if (!pageFile) {
|
|
922
|
+
return this.notFound(pathname);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Transform and serve the page component as a JS module
|
|
926
|
+
// Use the actual file path (pageFile) for both reading and determining the loader
|
|
927
|
+
return this.transformAndServe(pageFile, pageFile);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Serve app components for client-side navigation (App Router)
|
|
932
|
+
* Maps /_next/app/app/about/page.js → /app/about/page.tsx (transformed)
|
|
933
|
+
*/
|
|
934
|
+
private async serveAppComponent(pathname: string): Promise<ResponseData> {
|
|
935
|
+
// Extract the file path from /_next/app/app/about/page.js → /app/about/page
|
|
936
|
+
const filePath = pathname
|
|
937
|
+
.replace('/_next/app', '')
|
|
938
|
+
.replace(/\.js$/, '');
|
|
939
|
+
|
|
940
|
+
// Try different extensions
|
|
941
|
+
const extensions = ['.tsx', '.jsx', '.ts', '.js'];
|
|
942
|
+
for (const ext of extensions) {
|
|
943
|
+
const fullPath = filePath + ext;
|
|
944
|
+
if (this.exists(fullPath)) {
|
|
945
|
+
return this.transformAndServe(fullPath, fullPath);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return this.notFound(pathname);
|
|
950
|
+
}
|
|
951
|
+
|
|
898
952
|
/**
|
|
899
953
|
* Handle API route requests
|
|
900
954
|
*/
|
|
@@ -1645,17 +1699,117 @@ export class NextDevServer extends DevServer {
|
|
|
1645
1699
|
<script type="module">
|
|
1646
1700
|
import React from 'react';
|
|
1647
1701
|
import ReactDOM from 'react-dom/client';
|
|
1648
|
-
import Page from '${pageModulePath}';
|
|
1649
|
-
${layoutImports}
|
|
1650
1702
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1703
|
+
const virtualBase = '${virtualPrefix}';
|
|
1704
|
+
|
|
1705
|
+
// Convert URL path to app router page module path
|
|
1706
|
+
function getAppPageModulePath(pathname) {
|
|
1707
|
+
let route = pathname;
|
|
1708
|
+
if (route.startsWith(virtualBase)) {
|
|
1709
|
+
route = route.slice(virtualBase.length);
|
|
1710
|
+
}
|
|
1711
|
+
route = route.replace(/^\\/+/, '/') || '/';
|
|
1712
|
+
// App Router: / -> /app/page, /about -> /app/about/page
|
|
1713
|
+
const pagePath = route === '/' ? '/app/page' : '/app' + route + '/page';
|
|
1714
|
+
return virtualBase + '/_next/app' + pagePath + '.js';
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// Get layout paths for a route
|
|
1718
|
+
function getLayoutPaths(pathname) {
|
|
1719
|
+
let route = pathname;
|
|
1720
|
+
if (route.startsWith(virtualBase)) {
|
|
1721
|
+
route = route.slice(virtualBase.length);
|
|
1722
|
+
}
|
|
1723
|
+
route = route.replace(/^\\/+/, '/') || '/';
|
|
1724
|
+
|
|
1725
|
+
// Build layout paths from root to current route
|
|
1726
|
+
const layouts = [virtualBase + '/_next/app/app/layout.js'];
|
|
1727
|
+
if (route !== '/') {
|
|
1728
|
+
const segments = route.split('/').filter(Boolean);
|
|
1729
|
+
let currentPath = '/app';
|
|
1730
|
+
for (const segment of segments) {
|
|
1731
|
+
currentPath += '/' + segment;
|
|
1732
|
+
layouts.push(virtualBase + '/_next/app' + currentPath + '/layout.js');
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return layouts;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// Dynamic page loader
|
|
1739
|
+
async function loadPage(pathname) {
|
|
1740
|
+
const modulePath = getAppPageModulePath(pathname);
|
|
1741
|
+
try {
|
|
1742
|
+
const module = await import(/* @vite-ignore */ modulePath);
|
|
1743
|
+
return module.default;
|
|
1744
|
+
} catch (e) {
|
|
1745
|
+
console.error('[Navigation] Failed to load page:', modulePath, e);
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// Load layouts (with caching)
|
|
1751
|
+
const layoutCache = new Map();
|
|
1752
|
+
async function loadLayouts(pathname) {
|
|
1753
|
+
const layoutPaths = getLayoutPaths(pathname);
|
|
1754
|
+
const layouts = [];
|
|
1755
|
+
for (const path of layoutPaths) {
|
|
1756
|
+
if (layoutCache.has(path)) {
|
|
1757
|
+
layouts.push(layoutCache.get(path));
|
|
1758
|
+
} else {
|
|
1759
|
+
try {
|
|
1760
|
+
const module = await import(/* @vite-ignore */ path);
|
|
1761
|
+
layoutCache.set(path, module.default);
|
|
1762
|
+
layouts.push(module.default);
|
|
1763
|
+
} catch (e) {
|
|
1764
|
+
// Layout might not exist for this segment, skip
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return layouts;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// Router component
|
|
1772
|
+
function Router() {
|
|
1773
|
+
const [Page, setPage] = React.useState(null);
|
|
1774
|
+
const [layouts, setLayouts] = React.useState([]);
|
|
1775
|
+
const [path, setPath] = React.useState(window.location.pathname);
|
|
1776
|
+
|
|
1777
|
+
React.useEffect(() => {
|
|
1778
|
+
Promise.all([loadPage(path), loadLayouts(path)]).then(([P, L]) => {
|
|
1779
|
+
if (P) setPage(() => P);
|
|
1780
|
+
setLayouts(L);
|
|
1781
|
+
});
|
|
1782
|
+
}, []);
|
|
1783
|
+
|
|
1784
|
+
React.useEffect(() => {
|
|
1785
|
+
const handleNavigation = async () => {
|
|
1786
|
+
const newPath = window.location.pathname;
|
|
1787
|
+
if (newPath !== path) {
|
|
1788
|
+
setPath(newPath);
|
|
1789
|
+
const [P, L] = await Promise.all([loadPage(newPath), loadLayouts(newPath)]);
|
|
1790
|
+
if (P) setPage(() => P);
|
|
1791
|
+
setLayouts(L);
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
window.addEventListener('popstate', handleNavigation);
|
|
1795
|
+
return () => window.removeEventListener('popstate', handleNavigation);
|
|
1796
|
+
}, [path]);
|
|
1797
|
+
|
|
1798
|
+
if (!Page) return null;
|
|
1799
|
+
|
|
1800
|
+
// Build nested layout structure
|
|
1801
|
+
let content = React.createElement(Page);
|
|
1802
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
1803
|
+
content = React.createElement(layouts[i], null, content);
|
|
1804
|
+
}
|
|
1805
|
+
return content;
|
|
1653
1806
|
}
|
|
1654
1807
|
|
|
1808
|
+
// Mark that we've initialized (for testing no-reload)
|
|
1809
|
+
window.__NEXT_INITIALIZED__ = Date.now();
|
|
1810
|
+
|
|
1655
1811
|
ReactDOM.createRoot(document.getElementById('__next')).render(
|
|
1656
|
-
React.createElement(React.StrictMode, null,
|
|
1657
|
-
React.createElement(App)
|
|
1658
|
-
)
|
|
1812
|
+
React.createElement(React.StrictMode, null, React.createElement(Router))
|
|
1659
1813
|
);
|
|
1660
1814
|
</script>
|
|
1661
1815
|
</body>
|
|
@@ -1837,30 +1991,63 @@ export class NextDevServer extends DevServer {
|
|
|
1837
1991
|
<script type="module">
|
|
1838
1992
|
import React from 'react';
|
|
1839
1993
|
import ReactDOM from 'react-dom/client';
|
|
1840
|
-
import Page from '${pageModulePath}';
|
|
1841
1994
|
|
|
1842
|
-
|
|
1843
|
-
function App() {
|
|
1844
|
-
const [currentPath, setCurrentPath] = React.useState(window.location.pathname);
|
|
1995
|
+
const virtualBase = '${virtualPrefix}';
|
|
1845
1996
|
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1997
|
+
// Convert URL path to page module path
|
|
1998
|
+
function getPageModulePath(pathname) {
|
|
1999
|
+
let route = pathname;
|
|
2000
|
+
if (route.startsWith(virtualBase)) {
|
|
2001
|
+
route = route.slice(virtualBase.length);
|
|
2002
|
+
}
|
|
2003
|
+
route = route.replace(/^\\/+/, '/') || '/';
|
|
2004
|
+
const modulePath = route === '/' ? '/index' : route;
|
|
2005
|
+
return virtualBase + '/_next/pages' + modulePath + '.js';
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// Dynamic page loader
|
|
2009
|
+
async function loadPage(pathname) {
|
|
2010
|
+
const modulePath = getPageModulePath(pathname);
|
|
2011
|
+
try {
|
|
2012
|
+
const module = await import(/* @vite-ignore */ modulePath);
|
|
2013
|
+
return module.default;
|
|
2014
|
+
} catch (e) {
|
|
2015
|
+
console.error('[Navigation] Failed to load:', modulePath, e);
|
|
2016
|
+
return null;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// Router component
|
|
2021
|
+
function Router() {
|
|
2022
|
+
const [Page, setPage] = React.useState(null);
|
|
2023
|
+
const [path, setPath] = React.useState(window.location.pathname);
|
|
1852
2024
|
|
|
1853
|
-
|
|
1854
|
-
|
|
2025
|
+
React.useEffect(() => {
|
|
2026
|
+
loadPage(path).then(C => C && setPage(() => C));
|
|
1855
2027
|
}, []);
|
|
1856
2028
|
|
|
2029
|
+
React.useEffect(() => {
|
|
2030
|
+
const handleNavigation = async () => {
|
|
2031
|
+
const newPath = window.location.pathname;
|
|
2032
|
+
if (newPath !== path) {
|
|
2033
|
+
setPath(newPath);
|
|
2034
|
+
const C = await loadPage(newPath);
|
|
2035
|
+
if (C) setPage(() => C);
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
window.addEventListener('popstate', handleNavigation);
|
|
2039
|
+
return () => window.removeEventListener('popstate', handleNavigation);
|
|
2040
|
+
}, [path]);
|
|
2041
|
+
|
|
2042
|
+
if (!Page) return null;
|
|
1857
2043
|
return React.createElement(Page);
|
|
1858
2044
|
}
|
|
1859
2045
|
|
|
2046
|
+
// Mark that we've initialized (for testing no-reload)
|
|
2047
|
+
window.__NEXT_INITIALIZED__ = Date.now();
|
|
2048
|
+
|
|
1860
2049
|
ReactDOM.createRoot(document.getElementById('__next')).render(
|
|
1861
|
-
React.createElement(React.StrictMode, null,
|
|
1862
|
-
React.createElement(App)
|
|
1863
|
-
)
|
|
2050
|
+
React.createElement(React.StrictMode, null, React.createElement(Router))
|
|
1864
2051
|
);
|
|
1865
2052
|
</script>
|
|
1866
2053
|
</body>
|