mirror of https://github.com/alibaba/ice.git
Merge branch 'release/4.0' into feat/environments-api
This commit is contained in:
commit
a9341f44f0
|
|
@ -11,34 +11,21 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x, 18.x]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@v3
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "pnpm_cache_dir=$(pnpm store path)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: |
|
||||
${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
|
||||
.cache
|
||||
key: ${{ runner.os }}-pnpm-store-node-${{ matrix.node-version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-node-${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
- run: npm run setup
|
||||
- run: npm run puppeteer:install
|
||||
- run: npm run dependency:check
|
||||
- run: npm run lint
|
||||
- run: npm run test
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ const { join } = require('path');
|
|||
module.exports = {
|
||||
// Changes the cache location for Puppeteer.
|
||||
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
chrome 55
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { defineConfig } from '@ice/app';
|
||||
|
||||
export default defineConfig(() => ({
|
||||
ssg: false,
|
||||
plugins: [
|
||||
{
|
||||
name: 'custom-runtime',
|
||||
setup: (api) => {
|
||||
// Customize the runtime
|
||||
api.onGetConfig((config) => {
|
||||
// Override the runtime config
|
||||
config.runtime = {
|
||||
exports: [
|
||||
{
|
||||
specifier: ['Meta', 'Title', 'Links', 'Main', 'Scripts'],
|
||||
source: '@ice/runtime',
|
||||
},
|
||||
{
|
||||
specifier: ['defineAppConfig'],
|
||||
source: '@ice/runtime-kit',
|
||||
},
|
||||
],
|
||||
source: '../runtime',
|
||||
server: '@ice/runtime/server',
|
||||
router: {
|
||||
source: '@/routes',
|
||||
},
|
||||
};
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "@examples/custom-runtime",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "ice start",
|
||||
"build": "ice build"
|
||||
},
|
||||
"description": "",
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ice/app": "workspace:*",
|
||||
"@ice/runtime": "workspace:*",
|
||||
"@ice/runtime-kit": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import type { RunClientAppOptions } from '@ice/runtime-kit';
|
||||
import { getAppConfig } from '@ice/runtime-kit';
|
||||
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
const runClientApp = (options: RunClientAppOptions) => {
|
||||
const { basename = '', createRoutes } = options;
|
||||
// Normalize pathname with leading slash
|
||||
const pathname = `/${window.location.pathname.replace(basename, '').replace(/^\/+/, '')}`;
|
||||
|
||||
const routes = createRoutes?.({ renderMode: 'CSR' });
|
||||
const Component = routes?.find(route => route.path === pathname)?.component;
|
||||
|
||||
ReactDOM.render(
|
||||
Component ? <Component /> : <div>404</div>,
|
||||
document.getElementById('ice-container'),
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
getAppConfig,
|
||||
runClientApp,
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { defineAppConfig } from 'ice';
|
||||
|
||||
export default defineAppConfig(() => ({
|
||||
|
||||
}));
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { Meta, Title, Links, Main, Scripts } from 'ice';
|
||||
|
||||
function Document() {
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="description" content="ICE Demo" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Title />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
<Main />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
export default Document;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export default function Home() {
|
||||
return <h1>home</h1>;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export default function Index() {
|
||||
return <h1>index</h1>;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import Index from './pages/index';
|
||||
import Home from './pages/home';
|
||||
|
||||
export default () => [
|
||||
{
|
||||
path: '/',
|
||||
component: Index,
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
component: Home,
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="@ice/app/types" />
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"buildOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"module": "esnext",
|
||||
"target": "es6",
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"rootDir": "./",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": false,
|
||||
"importHelpers": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"ice": [".ice"]
|
||||
}
|
||||
},
|
||||
"include": ["src", ".ice", "ice.config.*"],
|
||||
"exclude": ["build", "public"]
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import type { ComponentWithChildren } from '@ice/runtime/types';
|
||||
import { useState } from 'react';
|
||||
import constate from 'constate';
|
||||
|
||||
|
|
@ -12,7 +11,7 @@ function useCounter() {
|
|||
|
||||
const [CounterProvider, useCounterContext] = constate(useCounter);
|
||||
|
||||
export const StoreProvider: ComponentWithChildren = ({ children }) => {
|
||||
export const StoreProvider = ({ children }) => {
|
||||
return <CounterProvider>{ children } </CounterProvider>;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@
|
|||
"@types/react-dom": "^18.0.2",
|
||||
"express": "^4.19.2",
|
||||
"tslib": "^2.5.0",
|
||||
"tsx": "^3.12.1"
|
||||
"tsx": "3.12.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -19,8 +19,9 @@
|
|||
"dependency:check": "tsx ./scripts/dependencyCheck.ts",
|
||||
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./",
|
||||
"cov": "vitest run --coverage",
|
||||
"test": "vitest",
|
||||
"test": "vitest run",
|
||||
"changeset": "changeset",
|
||||
"puppeteer:install": "tsx ./scripts/puppeteer.ts",
|
||||
"install:frozen": "pnpm install --frozen-lockfile false",
|
||||
"version": "changeset version && pnpm install:frozen",
|
||||
"release": "changeset publish",
|
||||
|
|
@ -61,7 +62,7 @@
|
|||
"react-dom": "^18.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"stylelint": "^15.10.1",
|
||||
"tsx": "^3.12.1",
|
||||
"tsx": "3.12.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vitest": "^0.15.2"
|
||||
},
|
||||
|
|
@ -71,8 +72,11 @@
|
|||
"packageManager": "pnpm@8.9.2",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@rspack/core@0.5.7": "patches/@rspack__core@0.5.7.patch",
|
||||
"unplugin@1.6.0": "patches/unplugin@1.6.0.patch"
|
||||
"unplugin@1.6.0": "patches/unplugin@1.6.0.patch",
|
||||
"@rspack/core@1.2.2": "patches/@rspack__core@1.2.2.patch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/runtime-kit"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
/**
|
||||
* The following code is modified based on
|
||||
* https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/0b960573797bf38926937994c481e4fec9ed8aa6/lib/runtime/RefreshUtils.js
|
||||
*
|
||||
* MIT Licensed
|
||||
* Author Michael Mok
|
||||
* Copyright (c) 2019 Michael Mok
|
||||
* https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/0b960573797bf38926937994c481e4fec9ed8aa6/LICENSE
|
||||
*/
|
||||
|
||||
/* global __webpack_require__ */
|
||||
let Refresh = require('react-refresh/runtime');
|
||||
var Refresh = require('react-refresh/runtime');
|
||||
|
||||
/**
|
||||
* Extracts exports from a webpack module object.
|
||||
|
|
@ -14,19 +24,21 @@ function getModuleExports(moduleId) {
|
|||
// These are likely runtime or dynamically generated modules.
|
||||
return {};
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
let maybeModule = __webpack_require__.c[moduleId];
|
||||
|
||||
var maybeModule = __webpack_require__.c[moduleId];
|
||||
if (typeof maybeModule === 'undefined') {
|
||||
// `moduleId` is available but the module in cache is unavailable,
|
||||
// which indicates the module is somehow corrupted (e.g. broken Webpacak `module` globals).
|
||||
// We will warn the user (as this is likely a mistake) and assume they cannot be refreshed.
|
||||
console.warn(`[React Refresh] Failed to get exports for module: ${moduleId}.`);
|
||||
console.warn(
|
||||
'[React Refresh] Failed to get exports for module: ' + moduleId + '.',
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
let exportsOrPromise = maybeModule.exports;
|
||||
var exportsOrPromise = maybeModule.exports;
|
||||
if (typeof Promise !== 'undefined' && exportsOrPromise instanceof Promise) {
|
||||
return exportsOrPromise.then((exports) => {
|
||||
return exportsOrPromise.then(function (exports) {
|
||||
return exports;
|
||||
});
|
||||
}
|
||||
|
|
@ -42,7 +54,7 @@ function getModuleExports(moduleId) {
|
|||
* @returns {string[]} A React refresh boundary signature array.
|
||||
*/
|
||||
function getReactRefreshBoundarySignature(moduleExports) {
|
||||
let signature = [];
|
||||
var signature = [];
|
||||
signature.push(Refresh.getFamilyByType(moduleExports));
|
||||
|
||||
if (moduleExports == null || typeof moduleExports !== 'object') {
|
||||
|
|
@ -50,7 +62,7 @@ function getReactRefreshBoundarySignature(moduleExports) {
|
|||
return signature;
|
||||
}
|
||||
|
||||
for (let key in moduleExports) {
|
||||
for (var key in moduleExports) {
|
||||
if (key === '__esModule') {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -71,7 +83,7 @@ function createDebounceUpdate() {
|
|||
* A cached setTimeout handler.
|
||||
* @type {number | undefined}
|
||||
*/
|
||||
let refreshTimeout;
|
||||
var refreshTimeout;
|
||||
|
||||
/**
|
||||
* Performs react refresh on a delay and clears the error overlay.
|
||||
|
|
@ -80,7 +92,7 @@ function createDebounceUpdate() {
|
|||
*/
|
||||
function enqueueUpdate(callback) {
|
||||
if (typeof refreshTimeout === 'undefined') {
|
||||
refreshTimeout = setTimeout(() => {
|
||||
refreshTimeout = setTimeout(function () {
|
||||
refreshTimeout = undefined;
|
||||
Refresh.performReactRefresh();
|
||||
callback();
|
||||
|
|
@ -110,27 +122,34 @@ function isSafeExport(key) {
|
|||
key === 'staticDataLoader'
|
||||
);
|
||||
}
|
||||
|
||||
function isReactRefreshBoundary(moduleExports) {
|
||||
if (Refresh.isLikelyComponentType(moduleExports)) {
|
||||
return true;
|
||||
}
|
||||
if (moduleExports === undefined || moduleExports === null || typeof moduleExports !== 'object') {
|
||||
if (
|
||||
moduleExports === undefined ||
|
||||
moduleExports === null ||
|
||||
typeof moduleExports !== 'object'
|
||||
) {
|
||||
// Exit if we can't iterate over exports.
|
||||
return false;
|
||||
}
|
||||
|
||||
let hasExports = false;
|
||||
let areAllExportsComponents = true;
|
||||
for (let key in moduleExports) {
|
||||
var hasExports = false;
|
||||
var areAllExportsComponents = true;
|
||||
for (var key in moduleExports) {
|
||||
hasExports = true;
|
||||
|
||||
if (isSafeExport(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We can (and have to) safely execute getters here,
|
||||
// as Webpack manually assigns harmony exports to getters,
|
||||
// without any side-effects attached.
|
||||
// Ref: https://github.com/webpack/webpack/blob/b93048643fe74de2a6931755911da1212df55897/lib/MainTemplate.js#L281
|
||||
let exportValue = moduleExports[key];
|
||||
var exportValue = moduleExports[key];
|
||||
if (!Refresh.isLikelyComponentType(exportValue)) {
|
||||
areAllExportsComponents = false;
|
||||
}
|
||||
|
|
@ -150,23 +169,27 @@ function isReactRefreshBoundary(moduleExports) {
|
|||
function registerExportsForReactRefresh(moduleExports, moduleId) {
|
||||
if (Refresh.isLikelyComponentType(moduleExports)) {
|
||||
// Register module.exports if it is likely a component
|
||||
Refresh.register(moduleExports, `${moduleId} %exports%`);
|
||||
Refresh.register(moduleExports, moduleId + ' %exports%');
|
||||
}
|
||||
|
||||
if (moduleExports === undefined || moduleExports === null || typeof moduleExports !== 'object') {
|
||||
if (
|
||||
moduleExports === undefined ||
|
||||
moduleExports === null ||
|
||||
typeof moduleExports !== 'object'
|
||||
) {
|
||||
// Exit if we can't iterate over the exports.
|
||||
return;
|
||||
}
|
||||
|
||||
for (let key in moduleExports) {
|
||||
for (var key in moduleExports) {
|
||||
// Skip registering the ES Module indicator
|
||||
if (key === '__esModule') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let exportValue = moduleExports[key];
|
||||
var exportValue = moduleExports[key];
|
||||
if (Refresh.isLikelyComponentType(exportValue)) {
|
||||
let typeID = `${moduleId} %exports% ${key}`;
|
||||
var typeID = moduleId + ' %exports% ' + key;
|
||||
Refresh.register(exportValue, typeID);
|
||||
}
|
||||
}
|
||||
|
|
@ -181,14 +204,14 @@ function registerExportsForReactRefresh(moduleExports, moduleId) {
|
|||
* @returns {boolean} Whether the React refresh boundary should be invalidated.
|
||||
*/
|
||||
function shouldInvalidateReactRefreshBoundary(prevExports, nextExports) {
|
||||
let prevSignature = getReactRefreshBoundarySignature(prevExports);
|
||||
let nextSignature = getReactRefreshBoundarySignature(nextExports);
|
||||
var prevSignature = getReactRefreshBoundarySignature(prevExports);
|
||||
var nextSignature = getReactRefreshBoundarySignature(nextExports);
|
||||
|
||||
if (prevSignature.length !== nextSignature.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let i = 0; i < nextSignature.length; i += 1) {
|
||||
for (var i = 0; i < nextSignature.length; i += 1) {
|
||||
if (prevSignature[i] !== nextSignature[i]) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -197,13 +220,19 @@ function shouldInvalidateReactRefreshBoundary(prevExports, nextExports) {
|
|||
return false;
|
||||
}
|
||||
|
||||
let enqueueUpdate = createDebounceUpdate();
|
||||
function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isTest) {
|
||||
var enqueueUpdate = createDebounceUpdate();
|
||||
function executeRuntime(
|
||||
moduleExports,
|
||||
moduleId,
|
||||
webpackHot,
|
||||
refreshOverlay,
|
||||
isTest,
|
||||
) {
|
||||
registerExportsForReactRefresh(moduleExports, moduleId);
|
||||
|
||||
if (webpackHot) {
|
||||
let isHotUpdate = !!webpackHot.data;
|
||||
let prevExports;
|
||||
var isHotUpdate = !!webpackHot.data;
|
||||
var prevExports;
|
||||
if (isHotUpdate) {
|
||||
prevExports = webpackHot.data.prevExports;
|
||||
}
|
||||
|
|
@ -216,7 +245,7 @@ function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isT
|
|||
* @param {*} data A hot module data object from Webpack HMR.
|
||||
* @returns {void}
|
||||
*/
|
||||
(data) => {
|
||||
function hotDisposeCallback(data) {
|
||||
// We have to mutate the data object to get data registered and cached
|
||||
data.prevExports = moduleExports;
|
||||
},
|
||||
|
|
@ -237,7 +266,7 @@ function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isT
|
|||
window.onHotAcceptError(error.message);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
|
||||
__webpack_require__.c[moduleId].hot.accept(hotErrorHandler);
|
||||
},
|
||||
);
|
||||
|
|
@ -254,7 +283,7 @@ function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isT
|
|||
* A function to dismiss the error overlay after performing React refresh.
|
||||
* @returns {void}
|
||||
*/
|
||||
() => {
|
||||
function updateCallback() {
|
||||
if (typeof refreshOverlay !== 'undefined' && refreshOverlay) {
|
||||
refreshOverlay.clearRuntimeErrors();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
"use strict";
|
||||
const checkVersion = function() {
|
||||
// Skip binding version check, framework will lock the binding version.
|
||||
return null;
|
||||
}
|
||||
exports.checkVersion = checkVersion;
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
"html-entities": "^2.3.2",
|
||||
"core-js": "3.32.0",
|
||||
"caniuse-lite": "^1.0.30001561",
|
||||
"chokidar": "3.5.3",
|
||||
"chokidar": "3.6.0",
|
||||
"esbuild": "^0.17.16",
|
||||
"events": "3.3.0",
|
||||
"jest-worker": "27.5.1",
|
||||
|
|
@ -45,13 +45,16 @@
|
|||
"zod": "^3.22.3",
|
||||
"zod-validation-error": "1.2.0",
|
||||
"terminal-link": "^2.1.1",
|
||||
"@ice/pack-binding": "0.0.13",
|
||||
"mime-types": "2.1.35"
|
||||
"@ice/pack-binding": "1.2.2",
|
||||
"mime-types": "2.1.35",
|
||||
"p-retry": "^6.2.0",
|
||||
"open": "^10.0.3",
|
||||
"@rspack/lite-tapable": "1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rspack/plugin-react-refresh": "0.5.7",
|
||||
"@rspack/dev-server": "0.5.7",
|
||||
"@rspack/core": "0.5.7",
|
||||
"@rspack/plugin-react-refresh": "1.0.1",
|
||||
"@rspack/dev-server": "1.0.10",
|
||||
"@rspack/core": "1.2.2",
|
||||
"@types/less": "^3.0.3",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/webpack-bundle-analyzer": "^4.4.1",
|
||||
|
|
@ -84,9 +87,9 @@
|
|||
"trusted-cert": "1.1.3",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-bundle-analyzer": "4.5.0",
|
||||
"webpack-dev-server": "4.15.0",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"unplugin": "1.6.0",
|
||||
"bonjour-service": "^1.0.13",
|
||||
"bonjour-service": "^1.2.1",
|
||||
"colorette": "^2.0.10",
|
||||
"compression": "^1.7.4",
|
||||
"connect-history-api-fallback": "2.0.0",
|
||||
|
|
@ -95,19 +98,17 @@
|
|||
"graceful-fs": "4.2.10",
|
||||
"http-proxy-middleware": "^2.0.3",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
"open": "^8.0.9",
|
||||
"p-retry": "^4.5.0",
|
||||
"portfinder": "^1.0.28",
|
||||
"rimraf": "^3.0.2",
|
||||
"schema-utils": "^4.0.0",
|
||||
"selfsigned": "^2.0.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"schema-utils": "^4.2.0",
|
||||
"selfsigned": "^2.4.1",
|
||||
"serve-index": "^1.9.1",
|
||||
"sockjs": "^0.3.21",
|
||||
"sockjs": "^0.3.24",
|
||||
"spdy": "^4.0.2",
|
||||
"webpack-dev-middleware": "^5.3.4",
|
||||
"webpack-dev-middleware": "^7.1.0",
|
||||
"ws": "^8.4.2",
|
||||
"globby": "13.1.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
|
||||
"loader-utils": "^2.0.0",
|
||||
"source-map": "0.8.0-beta.0",
|
||||
"find-up": "5.0.0",
|
||||
|
|
|
|||
|
|
@ -25,10 +25,26 @@ export const taskExternals = {
|
|||
const commonDeps = ['terser', 'tapable', 'cssnano', 'terser-webpack-plugin', 'webpack', 'schema-utils',
|
||||
'lodash', 'postcss-preset-env', 'loader-utils', 'find-up', 'common-path-prefix', 'magic-string'];
|
||||
|
||||
const webpackDevServerDeps = ['bonjour-service', 'colorette', 'compression', 'connect-history-api-fallback',
|
||||
'default-gateway', 'express', 'graceful-fs', 'http-proxy-middleware',
|
||||
'ipaddr.js', 'open', 'p-retry', 'portfinder', 'rimraf', 'selfsigned', 'serve-index',
|
||||
'sockjs', 'spdy', 'webpack-dev-middleware', 'ws'];
|
||||
const webpackDevServerDeps = [
|
||||
'bonjour-service',
|
||||
'colorette',
|
||||
'compression',
|
||||
'connect-history-api-fallback',
|
||||
'default-gateway',
|
||||
'express',
|
||||
'graceful-fs',
|
||||
'http-proxy-middleware',
|
||||
'ipaddr.js',
|
||||
'portfinder',
|
||||
'rimraf',
|
||||
'selfsigned',
|
||||
'serve-index',
|
||||
'sockjs',
|
||||
'spdy',
|
||||
'webpack-dev-middleware',
|
||||
'ws',
|
||||
];
|
||||
|
||||
|
||||
commonDeps.concat(webpackDevServerDeps).forEach(dep => taskExternals[dep] = `@ice/bundles/compiled/${dep}`);
|
||||
|
||||
|
|
@ -230,29 +246,17 @@ const tasks = [
|
|||
// Copy the entire directory.
|
||||
// filter out js files and replace with compiled files.
|
||||
const filePaths = globbySync(['**/*'], { cwd: pkgPath, ignore: ['node_modules'] });
|
||||
const filesAddOverwrite = [
|
||||
'dist/util/bindingVersionCheck.js',
|
||||
];
|
||||
filePaths.forEach((filePath) => {
|
||||
const sourcePath = path.join(pkgPath, filePath);
|
||||
const targetFilePath = path.join(targetPath, filePath);
|
||||
fs.ensureDirSync(path.dirname(targetFilePath));
|
||||
if (path.extname(filePath) === '.js') {
|
||||
const matched = filesAddOverwrite.some(filePath => {
|
||||
const matched = sourcePath.split(path.sep).join('/').includes(filePath);
|
||||
if (matched) {
|
||||
fs.copyFileSync(path.join(__dirname, `../override/rspack/${path.basename(filePath)}`), targetFilePath);
|
||||
}
|
||||
return matched;
|
||||
});
|
||||
if (!matched) {
|
||||
const fileContent = fs.readFileSync(sourcePath, 'utf8');
|
||||
fs.writeFileSync(
|
||||
targetFilePath,
|
||||
replaceDeps(fileContent, ['tapable', 'schema-utils', 'graceful-fs'])
|
||||
.replace(new RegExp('require\\(["\']@rspack/binding["\']\\)', 'g'), 'require("@ice/pack-binding")'),
|
||||
);
|
||||
}
|
||||
const fileContent = fs.readFileSync(sourcePath, 'utf8');
|
||||
fs.writeFileSync(
|
||||
targetFilePath,
|
||||
fileContent
|
||||
.replace(new RegExp('require\\(["\']@rspack/binding["\']\\)', 'g'), 'require("@ice/pack-binding")'),
|
||||
);
|
||||
} else {
|
||||
fs.copyFileSync(sourcePath, targetFilePath);
|
||||
}
|
||||
|
|
@ -276,7 +280,8 @@ const tasks = [
|
|||
replaceDeps(fileContent, webpackDevServerDeps.concat([...commonDeps, '@rspack/core', 'webpack-dev-server']))
|
||||
.replace(/webpack-dev-server\//g, '@ice/bundles/compiled/webpack-dev-server/')
|
||||
.replace(/@rspack\/core\//g, '@ice/bundles/compiled/@rspack/core/')
|
||||
.replace(/@rspack\/dev-server\//g, '@ice/bundles/compiled/@rspack/dev-server/'),
|
||||
.replace(/@rspack\/dev-server\//g, '@ice/bundles/compiled/@rspack/dev-server/')
|
||||
.replace(/"webpack-dev-server"/g, '"@ice/bundles/compiled/webpack-dev-server"'),
|
||||
);
|
||||
} else {
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
"@ice/bundles": "workspace:*",
|
||||
"@ice/route-manifest": "workspace:*",
|
||||
"@ice/runtime": "workspace:^",
|
||||
"@ice/runtime-kit": "workspace:^",
|
||||
"@ice/shared-config": "workspace:*",
|
||||
"@ice/webpack-config": "workspace:*",
|
||||
"@ice/rspack-config": "workspace:*",
|
||||
|
|
@ -97,9 +98,9 @@
|
|||
"sass": "^1.50.0",
|
||||
"unplugin": "^1.6.0",
|
||||
"webpack": "^5.88.0",
|
||||
"webpack-dev-server": "4.15.0",
|
||||
"@rspack/core": "0.5.7",
|
||||
"@rspack/dev-server": "0.5.7"
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"@rspack/core": "1.2.2",
|
||||
"@rspack/dev-server": "1.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { TaskConfig } from 'build-scripts';
|
||||
import type { Config } from '@ice/shared-config/types';
|
||||
import type { AppConfig } from '@ice/runtime/types';
|
||||
import type { AppConfig } from '@ice/runtime-kit';
|
||||
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
|
||||
import type { Configuration as RSPackDevServerConfiguration } from '@rspack/dev-server';
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import formatWebpackMessages from '../../utils/formatWebpackMessages.js';
|
|||
function formatStats(stats: Stats | MultiStats, showWarnings = true) {
|
||||
const statsData = stats.toJson({
|
||||
preset: 'errors-warnings',
|
||||
}) as StatsCompilation;
|
||||
}) as unknown as StatsCompilation;
|
||||
|
||||
const { errors, warnings } = formatWebpackMessages(statsData);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ async function bundler(
|
|||
let dataLoaderCompiler: Compiler;
|
||||
let devServer: RspackDevServer;
|
||||
const { rspack } = await import('@ice/bundles/esm/rspack.js');
|
||||
|
||||
// Override the type of rspack, because of rspack is imported from pre-compiled bundle.
|
||||
const rspackConfigs = await getConfig(context, options, rspack as unknown as typeof Rspack);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Config } from '@ice/shared-config/types';
|
||||
import type ora from '@ice/bundles/compiled/ora/index.js';
|
||||
import type { Stats as WebpackStats } from '@ice/bundles/compiled/webpack/index.js';
|
||||
import type { AppConfig } from '@ice/runtime/types';
|
||||
import type { AppConfig } from '@ice/runtime-kit';
|
||||
import type { Configuration, MultiCompiler, MultiStats } from '@rspack/core';
|
||||
import type { Context as DefaultContext, TaskConfig } from 'build-scripts';
|
||||
import type { ServerCompiler, GetAppConfig, GetRoutesConfig, GetDataloaderConfig, ExtendsPluginAPI } from '../types/plugin.js';
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ export async function startDevServer(
|
|||
// Sort by length, shortest path first.
|
||||
a.split('/').filter(Boolean).length - b.split('/').filter(Boolean).length);
|
||||
const webTaskConfig = taskConfigs.find(({ name }) => name === WEB);
|
||||
// @ts-expect-error webpack-dev-server types in Configuration is missing.
|
||||
const originalDevServer: DevServerConfiguration = webpackConfigs[0].devServer;
|
||||
const customMiddlewares = originalDevServer?.setupMiddlewares;
|
||||
const defaultDevServerConfig = await getDefaultServerConfig(originalDevServer, commandArgs);
|
||||
|
|
|
|||
|
|
@ -1,47 +1,46 @@
|
|||
// hijack webpack before import other modules
|
||||
import './requireHook.js';
|
||||
import { createRequire } from 'module';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { createRequire } from 'module';
|
||||
import webpack from '@ice/bundles/compiled/webpack/index.js';
|
||||
import { Context } from 'build-scripts';
|
||||
import type { CommandArgs, CommandName, TaskConfig } from 'build-scripts';
|
||||
import type { Config } from '@ice/shared-config/types';
|
||||
import type { AppConfig } from '@ice/runtime/types';
|
||||
import webpack from '@ice/bundles/compiled/webpack/index.js';
|
||||
import type { AppConfig } from '@ice/runtime-kit';
|
||||
import * as config from './config.js';
|
||||
import test from './commands/test.js';
|
||||
import webpackBundler from './bundler/webpack/index.js';
|
||||
import rspackBundler from './bundler/rspack/index.js';
|
||||
import { RUNTIME_TMP_DIR, WEB, RUNTIME_EXPORTS, SERVER_ENTRY } from './constant.js';
|
||||
import getWatchEvents from './getWatchEvents.js';
|
||||
import pluginWeb from './plugins/web/index.js';
|
||||
import getDefaultTaskConfig from './plugins/task.js';
|
||||
import { getFileExports } from './service/analyze.js';
|
||||
import { getAppExportConfig, getRouteExportConfig } from './service/config.js';
|
||||
import Generator from './service/runtimeGenerator.js';
|
||||
import ServerRunner from './service/ServerRunner.js';
|
||||
import { createServerCompiler } from './service/serverCompiler.js';
|
||||
import createWatch from './service/watchSource.js';
|
||||
import type {
|
||||
DeclarationData,
|
||||
PluginData,
|
||||
ExtendsPluginAPI,
|
||||
} from './types/index.js';
|
||||
import Generator from './service/runtimeGenerator.js';
|
||||
import { createServerCompiler } from './service/serverCompiler.js';
|
||||
import createWatch from './service/watchSource.js';
|
||||
import pluginWeb from './plugins/web/index.js';
|
||||
import test from './commands/test.js';
|
||||
import getWatchEvents from './getWatchEvents.js';
|
||||
import { setEnv, updateRuntimeEnv, getCoreEnvKeys } from './utils/runtimeEnv.js';
|
||||
import getRuntimeModules from './utils/getRuntimeModules.js';
|
||||
import { generateRoutesInfo, getRoutesDefinition } from './routes.js';
|
||||
import * as config from './config.js';
|
||||
import { RUNTIME_TMP_DIR, WEB, RUNTIME_EXPORTS, SERVER_ENTRY, FALLBACK_ENTRY } from './constant.js';
|
||||
import createSpinner from './utils/createSpinner.js';
|
||||
import ServerCompileTask from './utils/ServerCompileTask.js';
|
||||
import { getAppExportConfig, getRouteExportConfig } from './service/config.js';
|
||||
import renderExportsTemplate from './utils/renderExportsTemplate.js';
|
||||
import { getFileExports } from './service/analyze.js';
|
||||
import { logger, createLogger } from './utils/logger.js';
|
||||
import ServerRunner from './service/ServerRunner.js';
|
||||
import RouteManifest from './utils/routeManifest.js';
|
||||
import dynamicImport from './utils/dynamicImport.js';
|
||||
import mergeTaskConfig, { mergeConfig } from './utils/mergeTaskConfig.js';
|
||||
import addPolyfills from './utils/runtimePolyfill.js';
|
||||
import webpackBundler from './bundler/webpack/index.js';
|
||||
import rspackBundler from './bundler/rspack/index.js';
|
||||
import getDefaultTaskConfig from './plugins/task.js';
|
||||
import { multipleServerEntry, renderMultiEntry } from './utils/multipleEntry.js';
|
||||
import createSpinner from './utils/createSpinner.js';
|
||||
import dynamicImport from './utils/dynamicImport.js';
|
||||
import getRuntimeModules from './utils/getRuntimeModules.js';
|
||||
import hasDocument from './utils/hasDocument.js';
|
||||
import { onGetBundlerConfig } from './service/onGetBundlerConfig.js';
|
||||
import { onGetEnvironmentConfig, environmentConfigContext } from './service/onGetEnvironmentConfig.js';
|
||||
import { logger, createLogger } from './utils/logger.js';
|
||||
import mergeTaskConfig, { mergeConfig } from './utils/mergeTaskConfig.js';
|
||||
import RouteManifest from './utils/routeManifest.js';
|
||||
import { setEnv, updateRuntimeEnv, getCoreEnvKeys } from './utils/runtimeEnv.js';
|
||||
import ServerCompileTask from './utils/ServerCompileTask.js';
|
||||
import { generateRoutesInfo } from './routes.js';
|
||||
import GeneratorAPI from './service/generatorAPI.js';
|
||||
import renderTemplate from './service/renderTemplate.js';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
|
@ -73,46 +72,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
command,
|
||||
});
|
||||
|
||||
let entryCode = 'render();';
|
||||
|
||||
const generatorAPI = {
|
||||
addExport: (declarationData: DeclarationData) => {
|
||||
generator.addDeclaration('framework', declarationData);
|
||||
},
|
||||
addExportTypes: (declarationData: DeclarationData) => {
|
||||
generator.addDeclaration('frameworkTypes', declarationData);
|
||||
},
|
||||
addRuntimeOptions: (declarationData: DeclarationData) => {
|
||||
generator.addDeclaration('runtimeOptions', declarationData);
|
||||
},
|
||||
removeRuntimeOptions: (removeSource: string | string[]) => {
|
||||
generator.removeDeclaration('runtimeOptions', removeSource);
|
||||
},
|
||||
addRouteTypes: (declarationData: DeclarationData) => {
|
||||
generator.addDeclaration('routeConfigTypes', declarationData);
|
||||
},
|
||||
addRenderFile: generator.addRenderFile,
|
||||
addRenderTemplate: generator.addTemplateFiles,
|
||||
addEntryCode: (callback: (originalCode: string) => string) => {
|
||||
entryCode = callback(entryCode);
|
||||
},
|
||||
addEntryImportAhead: (declarationData: Pick<DeclarationData, 'source'>, type = 'client') => {
|
||||
if (type === 'both' || type === 'server') {
|
||||
generator.addDeclaration('entryServer', declarationData);
|
||||
}
|
||||
if (type === 'both' || type === 'client') {
|
||||
generator.addDeclaration('entry', declarationData);
|
||||
}
|
||||
},
|
||||
modifyRenderData: generator.modifyRenderData,
|
||||
addDataLoaderImport: (declarationData: DeclarationData) => {
|
||||
generator.addDeclaration('dataLoaderImport', declarationData);
|
||||
},
|
||||
getExportList: (registerKey: string) => {
|
||||
return generator.getExportList(registerKey);
|
||||
},
|
||||
render: generator.render,
|
||||
};
|
||||
const generatorAPI = new GeneratorAPI(generator);
|
||||
// Store server runner for plugins.
|
||||
let serverRunner: ServerRunner;
|
||||
const serverCompileTask = new ServerCompileTask();
|
||||
|
|
@ -154,10 +114,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
throw err;
|
||||
}
|
||||
}
|
||||
// Register framework level API.
|
||||
RUNTIME_EXPORTS.forEach(exports => {
|
||||
generatorAPI.addExport(exports);
|
||||
});
|
||||
|
||||
const routeManifest = new RouteManifest();
|
||||
const ctx = new Context<Config, ExtendsPluginAPI>({
|
||||
rootDir,
|
||||
|
|
@ -250,17 +207,25 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
// Get first task config as default platform config.
|
||||
const platformTaskConfig = taskConfigs[0];
|
||||
|
||||
const iceRuntimePath = '@ice/runtime';
|
||||
const runtimeConfig = platformTaskConfig.config?.runtime;
|
||||
const iceRuntimePath = runtimeConfig?.source || '@ice/runtime';
|
||||
const runtimeExports = runtimeConfig?.exports || RUNTIME_EXPORTS;
|
||||
// Only when code splitting use the default strategy or set to `router`, the router will be lazy loaded.
|
||||
const lazy = [true, 'chunks', 'page', 'page-vendors'].includes(userConfig.codeSplitting);
|
||||
const { routeImports, routeDefinition } = getRoutesDefinition({
|
||||
const runtimeRouter = runtimeConfig?.router;
|
||||
const { routeImports, routeDefinition } = runtimeRouter?.routesDefinition?.({
|
||||
manifest: routesInfo.routes,
|
||||
lazy,
|
||||
});
|
||||
}) || {
|
||||
routeImports: [],
|
||||
routeDefinition: '',
|
||||
};
|
||||
|
||||
const routesFile = runtimeRouter?.source;
|
||||
|
||||
const loaderExports = hasExportAppData || Boolean(routesInfo.loaders);
|
||||
const hasDataLoader = Boolean(userConfig.dataLoader) && loaderExports;
|
||||
// add render data
|
||||
generator.setRenderData({
|
||||
const renderData = {
|
||||
...routesInfo,
|
||||
target,
|
||||
iceRuntimePath,
|
||||
|
|
@ -272,70 +237,31 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
memoryRouter: platformTaskConfig.config.memoryRouter,
|
||||
hydrate: !csr,
|
||||
importCoreJs: polyfill === 'entry',
|
||||
// Enable react-router for web as default.
|
||||
enableRoutes: true,
|
||||
entryCode,
|
||||
entryCode: generatorAPI.getEntryCode(),
|
||||
hasDocument: hasDocument(rootDir),
|
||||
dataLoader: userConfig.dataLoader,
|
||||
hasDataLoader,
|
||||
routeImports,
|
||||
routeDefinition,
|
||||
routesFile: './routes',
|
||||
});
|
||||
routesFile: routesFile?.replace(/\.[^.]+$/, ''),
|
||||
lazy,
|
||||
runtimeServer: runtimeConfig?.server,
|
||||
};
|
||||
dataCache.set('routes', JSON.stringify(routesInfo));
|
||||
dataCache.set('hasExportAppData', hasExportAppData ? 'true' : '');
|
||||
|
||||
// Render exports files if route component export dataLoader / pageConfig.
|
||||
renderExportsTemplate(
|
||||
{
|
||||
...routesInfo,
|
||||
hasExportAppData,
|
||||
},
|
||||
generator.addRenderFile,
|
||||
{
|
||||
rootDir,
|
||||
runtimeDir: RUNTIME_TMP_DIR,
|
||||
templateDir: path.join(templateDir, 'exports'),
|
||||
dataLoader: Boolean(userConfig.dataLoader),
|
||||
},
|
||||
);
|
||||
// Render template to runtime directory.
|
||||
renderTemplate({
|
||||
ctx,
|
||||
taskConfig: platformTaskConfig,
|
||||
routeManifest,
|
||||
generator,
|
||||
generatorAPI,
|
||||
renderData,
|
||||
runtimeExports,
|
||||
templateDir,
|
||||
});
|
||||
|
||||
if (platformTaskConfig.config.server?.fallbackEntry) {
|
||||
// Add fallback entry for server side rendering.
|
||||
generator.addRenderFile('core/entry.server.ts.ejs', FALLBACK_ENTRY, { hydrate: false });
|
||||
}
|
||||
|
||||
if (typeof userConfig.dataLoader === 'object' && userConfig.dataLoader.fetcher) {
|
||||
const {
|
||||
packageName,
|
||||
method,
|
||||
} = userConfig.dataLoader.fetcher;
|
||||
|
||||
generatorAPI.addDataLoaderImport(method ? {
|
||||
source: packageName,
|
||||
alias: {
|
||||
[method]: 'dataLoaderFetcher',
|
||||
},
|
||||
specifier: [method],
|
||||
} : {
|
||||
source: packageName,
|
||||
specifier: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (multipleServerEntry(userConfig, command)) {
|
||||
renderMultiEntry({
|
||||
generator,
|
||||
renderRoutes: routeManifest.getFlattenRoute(),
|
||||
routesManifest: routesInfo.routes,
|
||||
lazy,
|
||||
});
|
||||
}
|
||||
|
||||
// render template before webpack compile
|
||||
const renderStart = new Date().getTime();
|
||||
generator.render();
|
||||
logger.debug('template render cost:', new Date().getTime() - renderStart);
|
||||
if (server.onDemand && command === 'start') {
|
||||
serverRunner = new ServerRunner({
|
||||
speedup: commandArgs.speedup,
|
||||
|
|
@ -377,6 +303,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
routeManifest,
|
||||
lazyRoutes: lazy,
|
||||
ctx,
|
||||
router: runtimeRouter,
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as path from 'path';
|
|||
import * as mrmime from 'mrmime';
|
||||
import fs from 'fs-extra';
|
||||
import type { PluginBuild } from 'esbuild';
|
||||
import type { AssetsManifest } from '@ice/runtime/types';
|
||||
import type { AssetsManifest } from '@ice/runtime-kit';
|
||||
|
||||
export const ASSET_TYPES = [
|
||||
// images
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as path from 'path';
|
|||
import type { Context } from 'build-scripts';
|
||||
import type { Config } from '@ice/shared-config/types';
|
||||
import type { WatchEvent } from './types/plugin.js';
|
||||
import { generateRoutesInfo, getRoutesDefinition } from './routes.js';
|
||||
import { generateRoutesInfo } from './routes.js';
|
||||
import type Generator from './service/runtimeGenerator';
|
||||
import getGlobalStyleGlobPattern from './utils/getGlobalStyleGlobPattern.js';
|
||||
import renderExportsTemplate from './utils/renderExportsTemplate.js';
|
||||
|
|
@ -20,31 +20,38 @@ interface Options {
|
|||
ctx: Context<Config>;
|
||||
routeManifest: RouteManifest;
|
||||
lazyRoutes: boolean;
|
||||
router: {
|
||||
source?: string;
|
||||
template?: string;
|
||||
routesDefinition?: Config['runtime']['router']['routesDefinition'];
|
||||
};
|
||||
}
|
||||
|
||||
const getWatchEvents = (options: Options): WatchEvent[] => {
|
||||
const { generator, targetDir, templateDir, cache, ctx, routeManifest, lazyRoutes } = options;
|
||||
const { generator, targetDir, templateDir, cache, ctx, routeManifest, lazyRoutes, router } = options;
|
||||
const { userConfig: { routes: routesConfig, dataLoader }, configFile, rootDir } = ctx;
|
||||
const watchRoutes: WatchEvent = [
|
||||
/src\/pages\/?[\w*-:.$]+$/,
|
||||
async (eventName: string) => {
|
||||
if (eventName === 'add' || eventName === 'unlink' || eventName === 'change') {
|
||||
const routesRenderData = await generateRoutesInfo(rootDir, routesConfig);
|
||||
const { routeImports, routeDefinition } = getRoutesDefinition({
|
||||
const { routeImports, routeDefinition } = router?.routesDefinition?.({
|
||||
manifest: routesRenderData.routes,
|
||||
lazy: lazyRoutes,
|
||||
});
|
||||
}) || {};
|
||||
const stringifiedData = JSON.stringify(routesRenderData);
|
||||
if (cache.get('routes') !== stringifiedData) {
|
||||
cache.set('routes', stringifiedData);
|
||||
logger.debug(`routes data regenerated: ${stringifiedData}`);
|
||||
if (eventName !== 'change') {
|
||||
// Specify the route files to re-render.
|
||||
generator.renderFile(
|
||||
path.join(templateDir, 'routes.tsx.ejs'),
|
||||
path.join(rootDir, targetDir, 'routes.tsx'),
|
||||
{ routeImports, routeDefinition },
|
||||
);
|
||||
if (router.source && router.template) {
|
||||
generator.renderFile(
|
||||
router.template,
|
||||
router.source,
|
||||
{ routeImports, routeDefinition },
|
||||
);
|
||||
}
|
||||
// Keep generate route manifest for avoid breaking change.
|
||||
generator.renderFile(
|
||||
path.join(templateDir, 'route-manifest.json.ejs'),
|
||||
|
|
|
|||
|
|
@ -39,27 +39,28 @@ export default function createDataLoaderMiddleware(compiler: Compiler): Middlewa
|
|||
return next();
|
||||
}
|
||||
const publicPath = compiler.options.output?.publicPath
|
||||
? `${compiler.options.output.publicPath.replace(/\/$/, '')}/`
|
||||
// Only support string publicPath config for now
|
||||
? `${(compiler.options.output.publicPath as unknown as string)?.replace(/\/$/, '')}/`
|
||||
: '/';
|
||||
const filePath = parse(url || '').pathname;
|
||||
const filename = filePath?.startsWith(publicPath) ? filePath.slice(publicPath.length) : filePath.slice(1);
|
||||
// Mark sure the compiler is ready.
|
||||
await compileTask;
|
||||
const buffer = compiler.getAsset(filename);
|
||||
|
||||
if (!buffer) {
|
||||
return next();
|
||||
}
|
||||
const calcEtag = etag(buffer);
|
||||
const oldEtag = req.headers['if-none-match'];
|
||||
// Only data-loader.js will be matched.
|
||||
res.setHeader('Content-Type', 'text/javascript');
|
||||
res.setHeader('ETag', calcEtag);
|
||||
if (calcEtag === oldEtag) {
|
||||
res.status(304).send();
|
||||
} else {
|
||||
res.send(buffer);
|
||||
}
|
||||
compiler.outputFileSystem.readFile(filename, (err, data) => {
|
||||
if (err) {
|
||||
return next();
|
||||
}
|
||||
const calcEtag = etag(data as Buffer);
|
||||
const oldEtag = req.headers['if-none-match'];
|
||||
// Only data-loader.js will be matched.
|
||||
res.setHeader('Content-Type', 'text/javascript');
|
||||
res.setHeader('ETag', calcEtag);
|
||||
if (calcEtag === oldEtag) {
|
||||
res.status(304).send();
|
||||
} else {
|
||||
res.send(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
return {
|
||||
name: 'data-loader-middleware',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import * as path from 'path';
|
||||
import { createRequire } from 'module';
|
||||
import type { Config } from '@ice/shared-config/types';
|
||||
import { CACHE_DIR, RUNTIME_TMP_DIR } from '../constant.js';
|
||||
import { CACHE_DIR, RUNTIME_TMP_DIR, RUNTIME_EXPORTS } from '../constant.js';
|
||||
import { getRoutesDefinition } from '../routes.js';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const getDefaultTaskConfig = ({ rootDir, command }): Config => {
|
||||
|
|
@ -33,6 +34,16 @@ const getDefaultTaskConfig = ({ rootDir, command }): Config => {
|
|||
logging: process.env.WEBPACK_LOGGING || defaultLogging,
|
||||
minify: command === 'build',
|
||||
useDevServer: true,
|
||||
runtime: {
|
||||
exports: RUNTIME_EXPORTS,
|
||||
source: '@ice/runtime',
|
||||
server: '@ice/runtime/server',
|
||||
router: {
|
||||
routesDefinition: getRoutesDefinition,
|
||||
source: './routes.tsx',
|
||||
template: 'core/routes.tsx.ejs',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import type { DeclarationData } from '../types/index.js';
|
||||
import type Generator from './runtimeGenerator.js';
|
||||
|
||||
class GeneratorAPI {
|
||||
private readonly generator: Generator;
|
||||
private entryCode: string;
|
||||
constructor(generator: Generator) {
|
||||
this.generator = generator;
|
||||
this.entryCode = 'render();';
|
||||
}
|
||||
addExport = (declarationData: DeclarationData): void => {
|
||||
this.generator.addDeclaration('framework', declarationData);
|
||||
};
|
||||
|
||||
addExportTypes = (declarationData: DeclarationData): void => {
|
||||
this.generator.addDeclaration('frameworkTypes', declarationData);
|
||||
};
|
||||
|
||||
addRuntimeOptions = (declarationData: DeclarationData): void => {
|
||||
this.generator.addDeclaration('runtimeOptions', declarationData);
|
||||
};
|
||||
|
||||
removeRuntimeOptions = (removeSource: string | string[]): void => {
|
||||
this.generator.removeDeclaration('runtimeOptions', removeSource);
|
||||
};
|
||||
|
||||
addRouteTypes = (declarationData: DeclarationData): void => {
|
||||
this.generator.addDeclaration('routeConfigTypes', declarationData);
|
||||
};
|
||||
|
||||
addEntryCode = (callback: (originalCode: string) => string): void => {
|
||||
this.entryCode = callback(this.entryCode);
|
||||
};
|
||||
|
||||
addEntryImportAhead = (declarationData: Pick<DeclarationData, 'source'>, type = 'client'): void => {
|
||||
if (type === 'both' || type === 'server') {
|
||||
this.generator.addDeclaration('entryServer', declarationData);
|
||||
}
|
||||
if (type === 'both' || type === 'client') {
|
||||
this.generator.addDeclaration('entry', declarationData);
|
||||
}
|
||||
};
|
||||
|
||||
addRenderFile = (...args: Parameters<Generator['addRenderFile']>): ReturnType<Generator['addRenderFile']> => {
|
||||
return this.generator.addRenderFile(...args);
|
||||
};
|
||||
|
||||
addRenderTemplate = (...args: Parameters<Generator['addTemplateFiles']>): ReturnType<Generator['addTemplateFiles']> => {
|
||||
return this.generator.addTemplateFiles(...args);
|
||||
};
|
||||
|
||||
modifyRenderData = (...args: Parameters<Generator['modifyRenderData']>): ReturnType<Generator['modifyRenderData']> => {
|
||||
return this.generator.modifyRenderData(...args);
|
||||
};
|
||||
|
||||
addDataLoaderImport = (declarationData: DeclarationData): void => {
|
||||
this.generator.addDeclaration('dataLoaderImport', declarationData);
|
||||
};
|
||||
|
||||
getExportList = (registerKey: string) => {
|
||||
return this.generator.getExportList(registerKey);
|
||||
};
|
||||
|
||||
render = (): void => {
|
||||
this.generator.render();
|
||||
};
|
||||
|
||||
getEntryCode = (): string => {
|
||||
return this.entryCode;
|
||||
};
|
||||
}
|
||||
|
||||
export default GeneratorAPI;
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import path from 'path';
|
||||
import type { Context, TaskConfig } from 'build-scripts';
|
||||
import type { Config } from '@ice/shared-config/types';
|
||||
import { FALLBACK_ENTRY, RUNTIME_TMP_DIR } from '../constant.js';
|
||||
import type { RenderData } from '../types/generator.js';
|
||||
import type { ExtendsPluginAPI } from '../types/plugin.js';
|
||||
import renderExportsTemplate from '../utils/renderExportsTemplate.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { multipleServerEntry, renderMultiEntry } from '../utils/multipleEntry.js';
|
||||
import type RouteManifest from '../utils/routeManifest.js';
|
||||
import type GeneratorAPI from './generatorAPI.js';
|
||||
import type Generator from './runtimeGenerator.js';
|
||||
|
||||
interface RenderTemplateOptions {
|
||||
ctx: Context<Config, ExtendsPluginAPI>;
|
||||
taskConfig: TaskConfig<Config>;
|
||||
routeManifest: RouteManifest;
|
||||
generator: Generator;
|
||||
generatorAPI: GeneratorAPI;
|
||||
renderData: RenderData;
|
||||
runtimeExports: Config['runtime']['exports'];
|
||||
templateDir: string;
|
||||
}
|
||||
|
||||
function renderTemplate({
|
||||
ctx,
|
||||
taskConfig,
|
||||
routeManifest,
|
||||
generator,
|
||||
generatorAPI,
|
||||
renderData,
|
||||
runtimeExports,
|
||||
templateDir,
|
||||
}: RenderTemplateOptions): void {
|
||||
// Record start time for performance tracking.
|
||||
const renderStart = performance.now();
|
||||
|
||||
const { rootDir, userConfig, command } = ctx;
|
||||
generator.setRenderData(renderData);
|
||||
|
||||
// Register framework level exports.
|
||||
runtimeExports.forEach(generatorAPI.addExport);
|
||||
|
||||
// Render exports for routes with dataLoader/pageConfig.
|
||||
renderExportsTemplate(
|
||||
renderData,
|
||||
generator.addRenderFile,
|
||||
{
|
||||
rootDir,
|
||||
runtimeDir: RUNTIME_TMP_DIR,
|
||||
templateDir: path.join(templateDir, 'exports'),
|
||||
dataLoader: Boolean(userConfig.dataLoader),
|
||||
},
|
||||
);
|
||||
|
||||
// Handle server-side fallback entry.
|
||||
if (taskConfig.config.server?.fallbackEntry) {
|
||||
generator.addRenderFile('core/entry.server.ts.ejs', FALLBACK_ENTRY, { hydrate: false });
|
||||
}
|
||||
|
||||
// Handle custom router template.
|
||||
const customRouter = taskConfig.config.runtime?.router;
|
||||
|
||||
if (customRouter?.source && customRouter?.template) {
|
||||
generator.addRenderFile(customRouter.template, customRouter.source);
|
||||
}
|
||||
|
||||
// Configure data loader if specified.
|
||||
const dataLoaderFetcher = userConfig.dataLoader?.fetcher;
|
||||
if (typeof userConfig.dataLoader === 'object' && dataLoaderFetcher) {
|
||||
const { packageName, method } = dataLoaderFetcher;
|
||||
|
||||
const importConfig = method ? {
|
||||
source: packageName,
|
||||
alias: { [method]: 'dataLoaderFetcher' },
|
||||
specifier: [method],
|
||||
} : {
|
||||
source: packageName,
|
||||
specifier: '',
|
||||
};
|
||||
|
||||
generatorAPI.addDataLoaderImport(importConfig);
|
||||
}
|
||||
|
||||
// Handle multiple server entries.
|
||||
if (multipleServerEntry(userConfig, command)) {
|
||||
renderMultiEntry({
|
||||
generator,
|
||||
renderRoutes: routeManifest.getFlattenRoute(),
|
||||
routesManifest: routeManifest.getNestedRoute(),
|
||||
lazy: renderData.lazy,
|
||||
});
|
||||
}
|
||||
|
||||
generator.render();
|
||||
logger.debug('template render cost:', performance.now() - renderStart);
|
||||
}
|
||||
|
||||
|
||||
export default renderTemplate;
|
||||
|
|
@ -4,7 +4,7 @@ import type { Configuration, Stats, WebpackOptionsNormalized } from '@ice/bundle
|
|||
import type { esbuild } from '@ice/bundles';
|
||||
import type { DefineExtraRoutes, NestedRouteManifest } from '@ice/route-manifest';
|
||||
import type { Config } from '@ice/shared-config/types';
|
||||
import type { AppConfig, AssetsManifest } from '@ice/runtime/types';
|
||||
import type { AppConfig, AssetsManifest } from '@ice/runtime-kit';
|
||||
import type ServerCompileTask from '../utils/ServerCompileTask.js';
|
||||
import type { CreateLogger } from '../utils/logger.js';
|
||||
import type { OnGetEnvironmentConfig } from '../service/onGetEnvironmentConfig.js';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { AppConfig } from '@ice/runtime';
|
||||
import type { AppConfig } from '@ice/runtime-kit';
|
||||
import type { Config } from '@ice/shared-config/types';
|
||||
import type { TaskConfig } from 'build-scripts';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import path from 'path';
|
||||
import fse from 'fs-extra';
|
||||
import type { RouteItem } from '@ice/runtime/types';
|
||||
import type { RouteItem } from '@ice/runtime';
|
||||
import matchRoutes from '@ice/runtime/matchRoutes';
|
||||
import { logger } from './logger.js';
|
||||
import type RouteManifest from './routeManifest.js';
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import matchRoutes from '@ice/runtime/matchRoutes';
|
||||
import type { NestedRouteManifest } from '@ice/route-manifest';
|
||||
import type { CommandName } from 'build-scripts';
|
||||
import { getRoutesDefinition } from '../routes.js';
|
||||
import type { Config } from '@ice/shared-config/types';
|
||||
import type Generator from '../service/runtimeGenerator.js';
|
||||
import type { UserConfig } from '../types/userConfig.js';
|
||||
import { escapeRoutePath } from './generateEntry.js';
|
||||
|
||||
interface Options {
|
||||
renderRoutes: string[];
|
||||
routesManifest: NestedRouteManifest[];
|
||||
generator: Generator;
|
||||
lazy: boolean;
|
||||
routesDefinition?: Config['runtime']['router']['routesDefinition'];
|
||||
}
|
||||
|
||||
export const multipleServerEntry = (userConfig: UserConfig, command: CommandName): boolean => {
|
||||
|
|
@ -29,7 +29,7 @@ export const formatServerEntry = (route: string) => {
|
|||
};
|
||||
|
||||
export function renderMultiEntry(options: Options) {
|
||||
const { renderRoutes, routesManifest, generator, lazy } = options;
|
||||
const { renderRoutes, routesManifest, generator, lazy, routesDefinition } = options;
|
||||
renderRoutes.forEach((route) => {
|
||||
const routeId = formatRoutePath(route);
|
||||
generator.addRenderFile(
|
||||
|
|
@ -41,13 +41,13 @@ export function renderMultiEntry(options: Options) {
|
|||
);
|
||||
// Generate route file for each route.
|
||||
const matches = matchRoutes(routesManifest, route);
|
||||
const { routeImports, routeDefinition } = getRoutesDefinition({
|
||||
const { routeImports, routeDefinition } = routesDefinition?.({
|
||||
manifest: routesManifest,
|
||||
lazy,
|
||||
matchRoute: (routeItem) => {
|
||||
return matches.some((match) => match.route.id === routeItem.id);
|
||||
},
|
||||
});
|
||||
}) || {};
|
||||
generator.addRenderFile('core/routes.tsx.ejs', `routes.${routeId}.tsx`, {
|
||||
routeImports,
|
||||
routeDefinition,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import * as path from 'path';
|
||||
import fse from 'fs-extra';
|
||||
import type Generator from '../service/runtimeGenerator.js';
|
||||
|
||||
type RenderData = {
|
||||
loaders: string;
|
||||
} & Record<string, any>;
|
||||
import type { RenderData } from '../types/generator.js';
|
||||
|
||||
function renderExportsTemplate(
|
||||
renderData: RenderData,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from 'fs';
|
|||
import * as dotenv from 'dotenv';
|
||||
import { expand as dotenvExpand } from 'dotenv-expand';
|
||||
import type { CommandArgs } from 'build-scripts';
|
||||
import type { AppConfig } from '@ice/runtime/types';
|
||||
import type { AppConfig } from '@ice/runtime-kit';
|
||||
|
||||
export interface Envs {
|
||||
[key: string]: string;
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ export default class ServerCompilerPlugin {
|
|||
compiler.hooks.watchRun.tap(pluginName, () => {
|
||||
this.isCompiling = true;
|
||||
});
|
||||
// @ts-expect-error webpack hooks type not match.
|
||||
compiler.hooks.emit.tapPromise(pluginName, async (compilation: Compilation) => {
|
||||
this.isCompiling = false;
|
||||
await this.compileTask(compilation);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
<% if (importCoreJs) { -%>import 'core-js';<% } %>
|
||||
<%- entry.imports %>
|
||||
import { createElement, Fragment } from 'react';
|
||||
import { runClientApp, getAppConfig } from '<%- iceRuntimePath %>';
|
||||
import { commons, statics } from './runtime-modules';
|
||||
import * as app from '@/app';
|
||||
<% if (enableRoutes) { -%>
|
||||
import createRoutes from './routes';
|
||||
<% if (routesFile) { -%>
|
||||
import createRoutes from '<%- routesFile %>';
|
||||
<% } -%>
|
||||
<%- runtimeOptions.imports %>
|
||||
<% if (dataLoaderImport.imports && hasDataLoader) {-%><%-dataLoaderImport.imports%><% } -%>
|
||||
|
|
@ -15,19 +14,6 @@ const getRouterBasename = () => {
|
|||
const appConfig = getAppConfig(app);
|
||||
return appConfig?.router?.basename ?? <%- basename %> ?? '';
|
||||
}
|
||||
// Add react fragment for split chunks of app.
|
||||
// Otherwise chunk of route component will pack @ice/jsx-runtime and depend on framework bundle.
|
||||
const App = <></>;
|
||||
|
||||
<% if (!dataLoaderImport.imports && hasDataLoader) {-%>
|
||||
let dataLoaderFetcher = (options) => {
|
||||
return window.fetch(options.url, options);
|
||||
}
|
||||
|
||||
let dataLoaderDecorator = (dataLoader) => {
|
||||
return dataLoader;
|
||||
}
|
||||
<% } -%>
|
||||
|
||||
const renderOptions: RunClientAppOptions = {
|
||||
app,
|
||||
|
|
@ -35,11 +21,11 @@ const renderOptions: RunClientAppOptions = {
|
|||
commons,
|
||||
statics,
|
||||
},
|
||||
<% if (enableRoutes) { %>createRoutes,<% } %>
|
||||
<% if (routesFile) { %>createRoutes,<% } %>
|
||||
basename: getRouterBasename(),
|
||||
hydrate: <%- hydrate %>,
|
||||
memoryRouter: <%- memoryRouter || false %>,
|
||||
<% if (hasDataLoader) { -%>
|
||||
<% if (dataLoaderImport.imports && hasDataLoader) { -%>
|
||||
dataLoaderFetcher,
|
||||
dataLoaderDecorator,<% } -%>
|
||||
runtimeOptions: {
|
||||
|
|
@ -50,27 +36,23 @@ const renderOptions: RunClientAppOptions = {
|
|||
},
|
||||
};
|
||||
|
||||
const defaultRender = (customOptions: Partial<RunClientAppOptions> = {}) => {
|
||||
return runClientApp({
|
||||
...renderOptions,
|
||||
...customOptions,
|
||||
runtimeOptions: {
|
||||
...(renderOptions.runtimeOptions || {}),
|
||||
...customOptions.runtimeOptions,
|
||||
const mergeOptions = (customOptions: Partial<RunClientAppOptions> = {}): RunClientAppOptions => ({
|
||||
...renderOptions,
|
||||
...customOptions,
|
||||
runtimeOptions: {
|
||||
...renderOptions.runtimeOptions,
|
||||
...customOptions.runtimeOptions,
|
||||
},
|
||||
});
|
||||
|
||||
const render = () => {
|
||||
return app.runApp?.(
|
||||
(customOptions: Partial<RunClientAppOptions> = {}) => {
|
||||
const options = mergeOptions(customOptions);
|
||||
return runClientApp(options);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const renderApp = (appExport: any, customOptions: Partial<RunClientAppOptions>) => {
|
||||
if (appExport.runApp) {
|
||||
return appExport.runApp(defaultRender, renderOptions);
|
||||
} else {
|
||||
return defaultRender(customOptions);
|
||||
}
|
||||
};
|
||||
|
||||
const render = (customOptions: Partial<RunClientAppOptions> = {}) => {
|
||||
return renderApp(app, customOptions);
|
||||
renderOptions
|
||||
) ?? runClientApp(renderOptions);
|
||||
};
|
||||
|
||||
<%- entryCode %>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import './env.server';
|
||||
<% if (hydrate) {-%>
|
||||
import { getAppConfig, renderToHTML as renderAppToHTML, renderToResponse as renderAppToResponse } from '@ice/runtime/server';
|
||||
import { getAppConfig, renderToHTML as renderAppToHTML, renderToResponse as renderAppToResponse } from '<%- runtimeServer %>';
|
||||
<% } else { -%>
|
||||
import { getAppConfig, getDocumentResponse as renderAppToHTML, renderDocumentToResponse as renderAppToResponse } from '@ice/runtime/server';
|
||||
import { getAppConfig, getDocumentResponse as renderAppToHTML, renderDocumentToResponse as renderAppToResponse } from '<%- runtimeServer %>';
|
||||
<% }-%>
|
||||
<%- entryServer.imports %>
|
||||
<% if (hydrate) {-%>
|
||||
|
|
@ -18,7 +18,7 @@ import type { RenderMode } from '@ice/runtime';
|
|||
import type { RenderToPipeableStreamOptions } from 'react-dom/server';
|
||||
// @ts-ignore
|
||||
import assetsManifest from 'virtual:assets-manifest.json';
|
||||
<% if (hydrate) {-%>
|
||||
<% if (hydrate && routesFile) {-%>
|
||||
import createRoutes from '<%- routesFile %>';
|
||||
<% } else { -%>
|
||||
import routesManifest from './route-manifest.json';
|
||||
|
|
@ -26,7 +26,7 @@ import routesManifest from './route-manifest.json';
|
|||
<% if (dataLoaderImport.imports) {-%><%-dataLoaderImport.imports%><% } -%>
|
||||
<% if (hydrate) {-%><%- runtimeOptions.imports %><% } -%>
|
||||
|
||||
<% if (!hydrate) {-%>
|
||||
<% if (!hydrate || !routesFile) {-%>
|
||||
// Do not inject runtime modules when render mode is document only.
|
||||
const commons = [];
|
||||
const statics = [];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import type { RuntimePlugin, AppProvider, RouteWrapper } from '@ice/runtime/types';
|
||||
import type { RuntimePlugin, AppProvider, RouteWrapper } from '@ice/runtime';
|
||||
import type { AuthConfig, AuthType, Auth } from '../types.js';
|
||||
import { AuthProvider, useAuth, withAuth } from './Auth.js';
|
||||
import type { InjectProps } from './Auth.js';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type * as React from 'react';
|
||||
import type { RouteConfig } from '@ice/runtime/types';
|
||||
import type { RouteConfig } from '@ice/runtime';
|
||||
|
||||
export interface AuthConfig {
|
||||
initialAuth: {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
"@types/accept-language-parser": "^1.5.3",
|
||||
"@types/react": "^18.0.33",
|
||||
"cross-env": "^7.0.3",
|
||||
"webpack-dev-server": "4.15.0"
|
||||
"webpack-dev-server": "5.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ice/app": "^3.5.1",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import type { RuntimePlugin } from '@ice/runtime/types';
|
||||
import type { RuntimePlugin } from '@ice/runtime';
|
||||
import detectLocale from '../utils/detectLocale.js';
|
||||
import type { I18nAppConfig, I18nConfig } from '../types.js';
|
||||
import getLocaleRedirectPath from '../utils/getLocaleRedirectPath.js';
|
||||
|
|
@ -86,4 +86,4 @@ const runtime: RuntimePlugin<{ i18nConfig: I18nConfig }> = async (
|
|||
|
||||
export default runtime;
|
||||
|
||||
export { useLocale, withLocale };
|
||||
export { useLocale, withLocale };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as ReactDOM from 'react-dom/client';
|
||||
import type { RuntimePlugin } from '@ice/runtime/types';
|
||||
import type { RuntimePlugin } from '@ice/runtime';
|
||||
import type { LifecycleOptions } from '../types';
|
||||
|
||||
const runtime: RuntimePlugin<LifecycleOptions> = ({ setRender }, runtimeOptions) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import { AppRouter, AppRoute } from '@ice/stark';
|
||||
import type { RuntimePlugin, ClientAppRouterProps } from '@ice/runtime/types';
|
||||
import type { RuntimePlugin, ClientAppRouterProps } from '@ice/runtime';
|
||||
import type { RouteInfo, AppConfig } from '../types';
|
||||
|
||||
const { useState, useEffect } = React;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { RuntimePlugin } from '@ice/runtime/types';
|
||||
import type { RuntimePlugin } from '@ice/runtime';
|
||||
import { getDefaultLocale, getLocaleMessages, EXPORT_NAME } from './intl-until.js';
|
||||
|
||||
let currentLocale = getDefaultLocale();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import { createIntl, createIntlCache, RawIntlProvider, useIntl } from 'react-intl';
|
||||
import type { IntlShape } from 'react-intl';
|
||||
import type { RuntimePlugin } from '@ice/runtime/types';
|
||||
import type { RuntimePlugin } from '@ice/runtime';
|
||||
import { getDefaultLocale, getLocaleMessages, EXPORT_NAME } from './intl-until.js';
|
||||
import type { LocaleConfig } from './types.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const plugin: Plugin<MiniappOptions> = (miniappOptions = {}) => ({
|
|||
];
|
||||
generator.addRenderFile('core/entry.client.tsx.ejs', 'entry.miniapp.tsx', {
|
||||
iceRuntimePath: miniappRuntime,
|
||||
enableRoutes: false,
|
||||
routesFile: '',
|
||||
});
|
||||
|
||||
generator.addRenderFile('core/index.ts.ejs', 'index.miniapp.ts', {
|
||||
|
|
|
|||
|
|
@ -92,10 +92,11 @@ export default class NormalModulesPlugin {
|
|||
const [type, prop] = node.arguments;
|
||||
|
||||
if (!type) return;
|
||||
|
||||
// @ts-ignore
|
||||
const componentName = type.name;
|
||||
|
||||
// @ts-ignore
|
||||
if (type.value) {
|
||||
// @ts-ignore
|
||||
this.onParseCreateElement?.(type.value, componentConfig);
|
||||
// @ts-ignore
|
||||
currentModule.elementNameSet.add(type.value);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { RuntimePlugin } from '@ice/runtime/types';
|
||||
import type { RuntimePlugin } from '@ice/runtime';
|
||||
import type { MiniappLifecycles } from '@ice/miniapp-runtime/esm/types';
|
||||
|
||||
export function defineMiniappConfig(
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
"build-scripts": "^2.1.2-0",
|
||||
"esbuild": "^0.17.16",
|
||||
"webpack": "^5.88.0",
|
||||
"webpack-dev-server": "4.15.0"
|
||||
"webpack-dev-server": "5.0.4"
|
||||
},
|
||||
"repository": {
|
||||
"type": "http",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { StaticRuntimePlugin } from '@ice/runtime/types';
|
||||
import type { StaticRuntimePlugin } from '@ice/runtime';
|
||||
import { createAxiosInstance, setAxiosInstance } from './request.js';
|
||||
import type { RequestConfig } from './types';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import type { RuntimePlugin, AppProvider, RouteWrapper } from '@ice/runtime/types';
|
||||
import type { RuntimePlugin, AppProvider, RouteWrapper } from '@ice/runtime';
|
||||
import { PAGE_STORE_INITIAL_STATES, PAGE_STORE_PROVIDER } from './constants.js';
|
||||
import type { StoreConfig } from './types.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"@ice/shared-config": "1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rspack/core": "0.5.7"
|
||||
"@rspack/core": "1.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "tsc -w --sourceMap",
|
||||
|
|
|
|||
|
|
@ -115,8 +115,8 @@ const getConfig: GetConfig = async (options) => {
|
|||
const isDev = mode === 'development';
|
||||
const absoluteOutputDir = path.isAbsolute(outputDir) ? outputDir : path.join(rootDir, outputDir);
|
||||
const hashKey = hash === true ? 'hash:8' : (hash || '');
|
||||
|
||||
const { rspack: { DefinePlugin, ProvidePlugin, SwcJsMinimizerRspackPlugin, CopyRspackPlugin } } = await import('@ice/bundles/esm/rspack.js');
|
||||
// @ts-expect-error ManifestPlugin is an custom plugin.
|
||||
const { rspack: { DefinePlugin, ProvidePlugin, SwcJsMinimizerRspackPlugin, CopyRspackPlugin, ManifestPlugin } } = await import('@ice/bundles/esm/rspack.js');
|
||||
const cssFilename = `css/${hashKey ? `[name]-[${hashKey}].css` : '[name].css'}`;
|
||||
// get compile plugins
|
||||
const compilerWebpackPlugins = getCompilerPlugins(rootDir, {
|
||||
|
|
@ -264,6 +264,11 @@ const getConfig: GetConfig = async (options) => {
|
|||
extensionAlias: cssExtensionAlias ?? [],
|
||||
}),
|
||||
],
|
||||
generator: {
|
||||
'css/auto': {
|
||||
localIdentName,
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['...', '.ts', '.tsx', '.jsx'],
|
||||
|
|
@ -281,7 +286,6 @@ const getConfig: GetConfig = async (options) => {
|
|||
minimize: !!minify,
|
||||
...(splitChunksStrategy ? { splitChunks: splitChunksStrategy } : {}),
|
||||
},
|
||||
// @ts-expect-error plugin instance defined by default in not compatible with rspack.
|
||||
plugins: [
|
||||
...plugins,
|
||||
// Unplugin should be compatible with rspack.
|
||||
|
|
@ -291,6 +295,7 @@ const getConfig: GetConfig = async (options) => {
|
|||
new ProvidePlugin({
|
||||
process: [require.resolve('process/browser')],
|
||||
}),
|
||||
new ManifestPlugin({}),
|
||||
!!minify && new SwcJsMinimizerRspackPlugin(jsMinimizerPluginOptions),
|
||||
(enableCopyPlugin || !isDev) && new CopyRspackPlugin({
|
||||
patterns: [{
|
||||
|
|
@ -305,16 +310,11 @@ const getConfig: GetConfig = async (options) => {
|
|||
},
|
||||
globOptions: {
|
||||
dot: true,
|
||||
gitignore: true,
|
||||
ignore: ['.gitignore'],
|
||||
},
|
||||
}],
|
||||
}),
|
||||
].filter(Boolean),
|
||||
builtins: {
|
||||
css: {
|
||||
modules: { localIdentName },
|
||||
},
|
||||
},
|
||||
stats: 'none',
|
||||
infrastructureLogging: {
|
||||
level: 'warn',
|
||||
|
|
@ -336,11 +336,16 @@ const getConfig: GetConfig = async (options) => {
|
|||
client: {
|
||||
logging: 'info',
|
||||
},
|
||||
https,
|
||||
server: https ? {
|
||||
type: 'https',
|
||||
options: typeof https === 'object' ? https : {},
|
||||
} : undefined,
|
||||
...devServer,
|
||||
setupMiddlewares: middlewares,
|
||||
},
|
||||
features: builtinFeatures,
|
||||
experiments: {
|
||||
css: true,
|
||||
},
|
||||
};
|
||||
// Compatible with API configureWebpack.
|
||||
const ctx = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@ice/runtime-kit",
|
||||
"version": "0.1.0",
|
||||
"description": "Runtime utilities and tools for ICE framework",
|
||||
"main": "./esm/index.js",
|
||||
"module": "./esm/index.js",
|
||||
"types": "./esm/index.d.ts",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"esm"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"watch": "tsc -w --sourceMap",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.8",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/alibaba/ice.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/alibaba/ice/issues"
|
||||
},
|
||||
"homepage": "https://ice.work",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import type { AppConfig, AppExport } from './types.js';
|
||||
|
||||
const defaultAppConfig: AppConfig = {
|
||||
app: {
|
||||
strict: false,
|
||||
rootId: 'ice-container',
|
||||
},
|
||||
router: {
|
||||
type: 'browser',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export function getAppConfig(appExport: AppExport): AppConfig {
|
||||
const { default: appConfig = {} } = appExport || {};
|
||||
const { app, router, ...others } = appConfig;
|
||||
|
||||
return {
|
||||
app: { ...defaultAppConfig.app, ...app },
|
||||
router: { ...defaultAppConfig.router, ...router },
|
||||
...others,
|
||||
};
|
||||
}
|
||||
|
||||
export const defineAppConfig = (
|
||||
appConfigOrDefineAppConfig: AppConfig | (() => AppConfig),
|
||||
): AppConfig =>
|
||||
(typeof appConfigOrDefineAppConfig === 'function'
|
||||
? appConfigOrDefineAppConfig()
|
||||
: appConfigOrDefineAppConfig);
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
import { getRequestContext } from './requestContext.js';
|
||||
import type {
|
||||
RequestContext, RenderMode, AppExport,
|
||||
RuntimeModules, StaticRuntimePlugin, CommonJsRuntime,
|
||||
Loader, DataLoaderResult, StaticDataLoader, DataLoaderConfig, DataLoaderOptions,
|
||||
RunClientAppOptions,
|
||||
} from './types.js';
|
||||
|
||||
interface Loaders {
|
||||
[routeId: string]: DataLoaderConfig;
|
||||
}
|
||||
|
||||
interface CachedResult {
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
fetcher: RunClientAppOptions['dataLoaderFetcher'];
|
||||
decorator: RunClientAppOptions['dataLoaderDecorator'];
|
||||
runtimeModules: RuntimeModules['statics'];
|
||||
appExport: AppExport;
|
||||
}
|
||||
|
||||
export interface LoadRoutesDataOptions {
|
||||
renderMode: RenderMode;
|
||||
requestContext?: RequestContext;
|
||||
}
|
||||
|
||||
export function defineDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig {
|
||||
return {
|
||||
loader: dataLoader,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
export function defineServerDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig {
|
||||
return {
|
||||
loader: dataLoader,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
export function defineStaticDataLoader(dataLoader: Loader): DataLoaderConfig {
|
||||
return {
|
||||
loader: dataLoader,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom fetcher for load static data loader config.
|
||||
* Set globally to avoid passing this fetcher too deep.
|
||||
*/
|
||||
let dataLoaderFetcher: RunClientAppOptions['dataLoaderFetcher'];
|
||||
export function setFetcher(customFetcher: RunClientAppOptions['dataLoaderFetcher']): void {
|
||||
dataLoaderFetcher = customFetcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom decorator for deal with data loader.
|
||||
*/
|
||||
// @ts-ignore
|
||||
let dataLoaderDecorator: RunClientAppOptions['dataLoaderDecorator'] = (dataLoader) => {
|
||||
return dataLoader;
|
||||
};
|
||||
export function setDecorator(customDecorator: RunClientAppOptions['dataLoaderDecorator']): void {
|
||||
dataLoaderDecorator = customDecorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse template for static dataLoader.
|
||||
*/
|
||||
export function parseTemplate(config: StaticDataLoader): StaticDataLoader {
|
||||
const queryParams: Record<string, string> = {};
|
||||
const getQueryParams = (): Record<string, string> => {
|
||||
if (Object.keys(queryParams).length === 0 && location.search.includes('?')) {
|
||||
location.search.substring(1).split('&').forEach(query => {
|
||||
const [key, value] = query.split('=');
|
||||
if (key && value) {
|
||||
queryParams[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return queryParams;
|
||||
};
|
||||
|
||||
const cookie: Record<string, string> = {};
|
||||
const getCookie = (): Record<string, string> => {
|
||||
if (Object.keys(cookie).length === 0) {
|
||||
document.cookie.split(';').forEach(c => {
|
||||
const [key, value] = c.split('=');
|
||||
if (key && value) {
|
||||
cookie[key.trim()] = value.trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
return cookie;
|
||||
};
|
||||
|
||||
let strConfig = JSON.stringify(config) || '';
|
||||
const regexp = /\$\{(queryParams|cookie|storage)(\.(\w|-)+)?}/g;
|
||||
const matches = Array.from(strConfig.matchAll(regexp));
|
||||
|
||||
matches.forEach(([origin, key, value]) => {
|
||||
if (origin && key && value?.startsWith('.')) {
|
||||
const param = value.substring(1);
|
||||
if (key === 'queryParams') {
|
||||
strConfig = strConfig.replace(origin, getQueryParams()[param] || '');
|
||||
} else if (key === 'cookie') {
|
||||
strConfig = strConfig.replace(origin, getCookie()[param] || '');
|
||||
} else if (key === 'storage') {
|
||||
strConfig = strConfig.replace(origin, localStorage.getItem(param) || '');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
strConfig = strConfig.replace('${url}', location.href);
|
||||
|
||||
return JSON.parse(strConfig);
|
||||
}
|
||||
|
||||
export function loadDataByCustomFetcher(config: StaticDataLoader): Promise<any> {
|
||||
let parsedConfig = config;
|
||||
try {
|
||||
if (import.meta.renderer === 'client') {
|
||||
parsedConfig = parseTemplate(config);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('parse template error: ', error);
|
||||
}
|
||||
return dataLoaderFetcher?.(parsedConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle for different dataLoader.
|
||||
*/
|
||||
export function callDataLoader(dataLoader: Loader, requestContext: RequestContext): DataLoaderResult {
|
||||
if (Array.isArray(dataLoader)) {
|
||||
return dataLoader.map((loader, index) =>
|
||||
(typeof loader === 'object' ? loadDataByCustomFetcher(loader) : dataLoaderDecorator(loader, index)(requestContext)),
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof dataLoader === 'object') {
|
||||
return loadDataByCustomFetcher(dataLoader);
|
||||
}
|
||||
|
||||
return dataLoaderDecorator?.(dataLoader)?.(requestContext);
|
||||
}
|
||||
|
||||
const cache = new Map<string, CachedResult>();
|
||||
|
||||
/**
|
||||
* Start getData once data-loader.js is ready in client, and set to cache.
|
||||
*/
|
||||
function loadInitialDataInClient(loaders: Loaders): void {
|
||||
const context = (window as any).__ICE_APP_CONTEXT__ || {};
|
||||
const matchedIds = context.matchedIds || [];
|
||||
const loaderData = context.loaderData || {};
|
||||
const { renderMode } = context;
|
||||
|
||||
const ids = ['__app', ...matchedIds];
|
||||
ids.forEach(id => {
|
||||
const dataFromSSR = loaderData[id]?.data;
|
||||
if (dataFromSSR) {
|
||||
cache.set(renderMode === 'SSG' ? `${id}_ssg` : id, {
|
||||
value: dataFromSSR,
|
||||
});
|
||||
|
||||
if (renderMode === 'SSR') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const dataLoaderConfig = loaders[id];
|
||||
if (dataLoaderConfig) {
|
||||
const requestContext = getRequestContext(window.location);
|
||||
const { loader } = dataLoaderConfig;
|
||||
const promise = callDataLoader(loader, requestContext);
|
||||
|
||||
cache.set(id, {
|
||||
value: promise,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Init data loader in client side.
|
||||
*/
|
||||
async function init(loaders: Loaders, options: Options): Promise<void> {
|
||||
const { fetcher, decorator, runtimeModules, appExport } = options;
|
||||
|
||||
const runtimeApi = {
|
||||
appContext: { appExport },
|
||||
};
|
||||
|
||||
if (runtimeModules) {
|
||||
await Promise.all(
|
||||
runtimeModules
|
||||
.map(module => {
|
||||
const runtimeModule = ((module as CommonJsRuntime).default || module) as StaticRuntimePlugin;
|
||||
return runtimeModule(runtimeApi);
|
||||
})
|
||||
.filter(Boolean),
|
||||
);
|
||||
}
|
||||
|
||||
if (fetcher) setFetcher(fetcher);
|
||||
if (decorator) setDecorator(decorator);
|
||||
|
||||
try {
|
||||
loadInitialDataInClient(loaders);
|
||||
} catch (error) {
|
||||
console.error('Load initial data error: ', error);
|
||||
}
|
||||
|
||||
(window as any).__ICE_DATA_LOADER__ = {
|
||||
getLoader: (id: string): DataLoaderConfig => loaders[id],
|
||||
getData: (id: string, options: LoadRoutesDataOptions): DataLoaderResult => {
|
||||
const cacheKey = `${id}${options?.renderMode === 'SSG' ? '_ssg' : ''}`;
|
||||
const result = cache.get(cacheKey);
|
||||
cache.delete(cacheKey);
|
||||
|
||||
if (result) return result.value;
|
||||
|
||||
const dataLoaderConfig = loaders[id];
|
||||
if (!dataLoaderConfig) return null;
|
||||
|
||||
const { loader } = dataLoaderConfig;
|
||||
return callDataLoader(loader, options?.requestContext || getRequestContext(window.location));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const dataLoader = {
|
||||
init,
|
||||
};
|
||||
|
||||
export default dataLoader;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export * from './appConfig.js';
|
||||
export * from './dataLoader.js';
|
||||
export * from './requestContext.js';
|
||||
export * from './types.js';
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ServerContext, RequestContext } from './types.js';
|
||||
|
||||
export interface Location {
|
||||
interface Location {
|
||||
pathname: string;
|
||||
search: string;
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ export interface Location {
|
|||
/**
|
||||
* context for getData both in server and client side.
|
||||
*/
|
||||
export default function getRequestContext(location: Location, serverContext: ServerContext = {}): RequestContext {
|
||||
export function getRequestContext(location: Location, serverContext: ServerContext = {}): RequestContext {
|
||||
const { pathname, search } = location;
|
||||
// Use query form server context first to avoid unnecessary parsing.
|
||||
// @ts-ignore
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
import type { IncomingMessage, ServerResponse } from 'http';
|
||||
import type { ComponentType, PropsWithChildren } from 'react';
|
||||
import type { HydrationOptions, Root } from 'react-dom/client';
|
||||
|
||||
// Basic Types
|
||||
export type AppData = any;
|
||||
export type RouteData = any;
|
||||
export type RenderMode = 'SSR' | 'SSG' | 'CSR';
|
||||
|
||||
// Core Interfaces
|
||||
export interface Path {
|
||||
pathname: string;
|
||||
search: string;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export interface Location<State = any> extends Path {
|
||||
state: State;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ErrorStack {
|
||||
componentStack?: string;
|
||||
digest?: string;
|
||||
}
|
||||
|
||||
export interface ServerContext {
|
||||
req?: IncomingMessage;
|
||||
res?: ServerResponse;
|
||||
}
|
||||
|
||||
export interface RequestContext extends ServerContext {
|
||||
pathname: string;
|
||||
query: Record<string, any>;
|
||||
}
|
||||
|
||||
// App Configuration Types
|
||||
export type App = Partial<{
|
||||
rootId: string;
|
||||
strict: boolean;
|
||||
errorBoundary: boolean;
|
||||
onRecoverableError: (error: unknown, errorInfo: ErrorStack) => void;
|
||||
onBeforeHydrate: () => void;
|
||||
}>;
|
||||
|
||||
export interface AppConfig {
|
||||
app?: App;
|
||||
router?: {
|
||||
type?: 'hash' | 'browser' | 'memory';
|
||||
basename?: string;
|
||||
initialEntries?: InitialEntry[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface AppExport {
|
||||
default?: AppConfig;
|
||||
dataLoader?: DataLoaderConfig;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Route & Component Types
|
||||
export type ComponentWithChildren<P = {}> = ComponentType<PropsWithChildren<P>>;
|
||||
export type AppProvider = ComponentWithChildren<any>;
|
||||
export type RouteWrapper = ComponentType<any>;
|
||||
|
||||
export type InitialEntry = string | Partial<Location>;
|
||||
export type Params<Key extends string = string> = {
|
||||
readonly [key in Key]: string | undefined;
|
||||
};
|
||||
|
||||
// Data Loading Types
|
||||
export interface DataLoaderOptions {
|
||||
defer?: boolean;
|
||||
}
|
||||
|
||||
export interface StaticDataLoader {
|
||||
key?: string;
|
||||
prefetch_type?: string;
|
||||
api: string;
|
||||
v: string;
|
||||
data: any;
|
||||
ext_headers: Object;
|
||||
}
|
||||
|
||||
export type DataLoaderResult = (Promise<RouteData> | RouteData) | RouteData;
|
||||
export type DataLoader = (ctx: RequestContext) => DataLoaderResult;
|
||||
export type Loader = DataLoader | StaticDataLoader | Array<DataLoader | StaticDataLoader>;
|
||||
|
||||
export interface DataLoaderConfig {
|
||||
loader: Loader;
|
||||
options?: DataLoaderOptions;
|
||||
}
|
||||
|
||||
// Route Configuration Types
|
||||
export type RouteConfig<T = {}> = T & {
|
||||
title?: string;
|
||||
meta?: React.MetaHTMLAttributes<HTMLMetaElement>[];
|
||||
links?: React.LinkHTMLAttributes<HTMLLinkElement>[];
|
||||
scripts?: React.ScriptHTMLAttributes<HTMLScriptElement>[];
|
||||
};
|
||||
|
||||
export type PageConfig = (args: { data?: RouteData }) => RouteConfig;
|
||||
|
||||
export interface LoaderData {
|
||||
data?: RouteData;
|
||||
pageConfig?: RouteConfig;
|
||||
}
|
||||
|
||||
export interface LoadersData {
|
||||
[routeId: string]: LoaderData;
|
||||
}
|
||||
|
||||
// Component & Module Types
|
||||
export type ComponentModule = {
|
||||
default?: ComponentType<any>;
|
||||
Component?: ComponentType<any>;
|
||||
staticDataLoader?: DataLoaderConfig;
|
||||
serverDataLoader?: DataLoaderConfig;
|
||||
dataLoader?: DataLoaderConfig;
|
||||
pageConfig?: PageConfig;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export interface RouteModules {
|
||||
[routeId: string]: ComponentModule;
|
||||
}
|
||||
|
||||
export interface RouteWrapperConfig {
|
||||
Wrapper: RouteWrapper;
|
||||
layout?: boolean;
|
||||
}
|
||||
|
||||
// Runtime Types
|
||||
export interface AppContext<T = any> {
|
||||
appConfig: AppConfig;
|
||||
appData: any;
|
||||
documentData?: any;
|
||||
serverData?: any;
|
||||
assetsManifest?: AssetsManifest;
|
||||
loaderData?: LoadersData;
|
||||
routeModules?: RouteModules;
|
||||
RouteWrappers?: RouteWrapperConfig[];
|
||||
routePath?: string;
|
||||
matches?: {
|
||||
params: Params;
|
||||
pathname: string;
|
||||
pathnameBase: string;
|
||||
route: T;
|
||||
}[];
|
||||
routes?: T[];
|
||||
documentOnly?: boolean;
|
||||
matchedIds?: string[];
|
||||
appExport?: AppExport;
|
||||
basename?: string;
|
||||
downgrade?: boolean;
|
||||
renderMode?: RenderMode;
|
||||
requestContext?: RequestContext;
|
||||
revalidate?: boolean;
|
||||
}
|
||||
|
||||
// Runtime API Types
|
||||
export type Renderer = (
|
||||
container: Element | Document,
|
||||
initialChildren: React.ReactNode,
|
||||
options?: HydrationOptions,
|
||||
) => Root;
|
||||
|
||||
export type ResponseHandler = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
) => any | Promise<any>;
|
||||
|
||||
export type SetAppRouter = <T>(AppRouter: ComponentType<T>) => void;
|
||||
export type GetAppRouter = () => AppProvider;
|
||||
export type AddProvider = (Provider: AppProvider) => void;
|
||||
export type SetRender = (render: Renderer) => void;
|
||||
export type AddWrapper = (wrapper: RouteWrapper, forLayout?: boolean) => void;
|
||||
export type AddResponseHandler = (handler: ResponseHandler) => void;
|
||||
export type GetResponseHandlers = () => ResponseHandler[];
|
||||
|
||||
type UseConfig = () => RouteConfig<Record<string, any>>;
|
||||
type UseData = () => RouteData;
|
||||
type UseAppContext = () => AppContext;
|
||||
|
||||
export interface RuntimeAPI<T = History> {
|
||||
setAppRouter?: SetAppRouter;
|
||||
getAppRouter: GetAppRouter;
|
||||
addProvider: AddProvider;
|
||||
addResponseHandler: AddResponseHandler;
|
||||
getResponseHandlers: GetResponseHandlers;
|
||||
setRender: SetRender;
|
||||
addWrapper: AddWrapper;
|
||||
appContext: AppContext;
|
||||
useData: UseData;
|
||||
useConfig: UseConfig;
|
||||
useAppContext: UseAppContext;
|
||||
history: T;
|
||||
}
|
||||
|
||||
// Plugin Types
|
||||
export interface RuntimePlugin<T = Record<string, any>, H = History> {
|
||||
(apis: RuntimeAPI<H>, runtimeOptions?: T): Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface StaticRuntimeAPI {
|
||||
appContext: {
|
||||
appExport: AppExport;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StaticRuntimePlugin<T = Record<string, any>> {
|
||||
(apis: StaticRuntimeAPI, runtimeOptions?: T): Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface CommonJsRuntime {
|
||||
default: RuntimePlugin | StaticRuntimePlugin;
|
||||
}
|
||||
|
||||
// Assets & Runtime Modules
|
||||
export interface AssetsManifest {
|
||||
dataLoader?: string;
|
||||
publicPath: string;
|
||||
entries: {
|
||||
[assetPath: string]: string[];
|
||||
};
|
||||
pages: {
|
||||
[assetPath: string]: string[];
|
||||
};
|
||||
assets?: {
|
||||
[assetPath: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RuntimeModules {
|
||||
statics?: (StaticRuntimePlugin | CommonJsRuntime)[];
|
||||
commons?: (RuntimePlugin | CommonJsRuntime)[];
|
||||
}
|
||||
|
||||
// Loader & Routes Types
|
||||
export interface RouteLoaderOptions {
|
||||
routeId: string;
|
||||
requestContext?: RequestContext;
|
||||
module: ComponentModule;
|
||||
renderMode: RenderMode;
|
||||
}
|
||||
|
||||
export type CreateRoutes<T> = (options: Pick<RouteLoaderOptions, 'renderMode' | 'requestContext'>) => T[];
|
||||
|
||||
export interface RunClientAppOptions<T = any> {
|
||||
app: AppExport;
|
||||
runtimeModules: RuntimeModules;
|
||||
createRoutes?: CreateRoutes<T>;
|
||||
hydrate?: boolean;
|
||||
basename?: string;
|
||||
memoryRouter?: boolean;
|
||||
runtimeOptions?: Record<string, any>;
|
||||
dataLoaderFetcher?: (config: StaticDataLoader) => any;
|
||||
dataLoaderDecorator?: (loader: Loader, index?: number) => (requestContext: RequestContext) => DataLoaderResult;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface ImportMeta {
|
||||
target: string;
|
||||
renderer: 'client' | 'server';
|
||||
env: Record<string, string>;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"rootDir": "src",
|
||||
"outDir": "esm",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "NodeNext",
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
@ -60,7 +60,8 @@
|
|||
"abortcontroller-polyfill": "1.7.5",
|
||||
"history": "^5.3.0",
|
||||
"react-router-dom": "6.21.3",
|
||||
"semver": "^7.4.0"
|
||||
"semver": "^7.4.0",
|
||||
"@ice/runtime-kit": "^0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import type { AppContext } from './types.js';
|
||||
import type { AppContext } from '@ice/runtime-kit';
|
||||
|
||||
const Context = React.createContext<AppContext | undefined>(undefined);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import type { WindowContext, RouteMatch, AssetsManifest } from './types.js';
|
||||
import type { AssetsManifest } from '@ice/runtime-kit';
|
||||
import type { WindowContext, RouteMatch } from './types.js';
|
||||
import { useAppContext, useAppData } from './AppContext.js';
|
||||
import { getMeta, getTitle, getLinks, getScripts } from './routesConfig.js';
|
||||
import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useLoaderData } from 'react-router-dom';
|
||||
import type { RouteConfig } from './types.js';
|
||||
import type { RouteConfig } from '@ice/runtime-kit';
|
||||
|
||||
function useData<T = any>(): T {
|
||||
return (useLoaderData() as any)?.data;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import type { RouteWrapperConfig, ComponentModule } from './types.js';
|
||||
import type { RouteWrapperConfig, ComponentModule } from '@ice/runtime-kit';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { RequestContext } from '@ice/runtime-kit';
|
||||
import { useAppContext } from './AppContext.js';
|
||||
import proxyData from './proxyData.js';
|
||||
import type { RequestContext } from './types.js';
|
||||
|
||||
const LOADER = '__ICE_SUSPENSE_LOADER__';
|
||||
const isClient = typeof window !== 'undefined' && 'onload' in window;
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
import type { AppConfig, AppExport } from './types.js';
|
||||
|
||||
const defaultAppConfig: AppConfig = {
|
||||
app: {
|
||||
strict: false,
|
||||
rootId: 'ice-container',
|
||||
},
|
||||
router: {
|
||||
type: 'browser',
|
||||
},
|
||||
};
|
||||
|
||||
export default function getAppConfig(appExport: AppExport): AppConfig {
|
||||
const appConfig = appExport?.default || {};
|
||||
|
||||
const { app, router, ...others } = appConfig;
|
||||
|
||||
return {
|
||||
app: {
|
||||
...defaultAppConfig.app,
|
||||
...(app || {}),
|
||||
},
|
||||
router: {
|
||||
...defaultAppConfig.router,
|
||||
...(router || {}),
|
||||
},
|
||||
...others,
|
||||
};
|
||||
}
|
||||
|
||||
export function defineAppConfig(appConfigOrDefineAppConfig: AppConfig | (() => AppConfig)): AppConfig {
|
||||
if (typeof appConfigOrDefineAppConfig === 'function') {
|
||||
return appConfigOrDefineAppConfig();
|
||||
} else {
|
||||
return appConfigOrDefineAppConfig;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { AppExport, AppData, RequestContext, Loader } from './types.js';
|
||||
import { callDataLoader } from './dataLoader.js';
|
||||
import type { AppExport, AppData, RequestContext, Loader } from '@ice/runtime-kit';
|
||||
import { callDataLoader } from '@ice/runtime-kit';
|
||||
|
||||
/**
|
||||
* Call the getData of app config.
|
||||
|
|
|
|||
|
|
@ -1,281 +1 @@
|
|||
import getRequestContext from './requestContext.js';
|
||||
import type {
|
||||
RequestContext, RenderMode, AppExport,
|
||||
RuntimeModules, StaticRuntimePlugin, CommonJsRuntime,
|
||||
Loader, DataLoaderResult, StaticDataLoader, DataLoaderConfig, DataLoaderOptions,
|
||||
} from './types.js';
|
||||
interface Loaders {
|
||||
[routeId: string]: DataLoaderConfig;
|
||||
}
|
||||
|
||||
interface CachedResult {
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
fetcher: Function;
|
||||
decorator: Function;
|
||||
runtimeModules: RuntimeModules['statics'];
|
||||
appExport: AppExport;
|
||||
}
|
||||
|
||||
export interface LoadRoutesDataOptions {
|
||||
renderMode: RenderMode;
|
||||
requestContext?: RequestContext;
|
||||
}
|
||||
|
||||
export function defineDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig {
|
||||
return {
|
||||
loader: dataLoader,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
export function defineServerDataLoader(dataLoader: Loader, options?: DataLoaderOptions): DataLoaderConfig {
|
||||
return {
|
||||
loader: dataLoader,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
export function defineStaticDataLoader(dataLoader: Loader): DataLoaderConfig {
|
||||
return {
|
||||
loader: dataLoader,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom fetcher for load static data loader config.
|
||||
* Set globally to avoid passing this fetcher too deep.
|
||||
*/
|
||||
let dataLoaderFetcher;
|
||||
export function setFetcher(customFetcher) {
|
||||
dataLoaderFetcher = customFetcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom decorator for deal with data loader.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let dataLoaderDecorator = (dataLoader: Function, id?: number) => {
|
||||
return dataLoader;
|
||||
};
|
||||
export function setDecorator(customDecorator) {
|
||||
dataLoaderDecorator = customDecorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse template for static dataLoader.
|
||||
*/
|
||||
export function parseTemplate(config: StaticDataLoader) {
|
||||
const queryParams = {};
|
||||
const getQueryParams = () => {
|
||||
if (Object.keys(queryParams).length === 0) {
|
||||
if (location.search.includes('?')) {
|
||||
location.search.substring(1).split('&').forEach(query => {
|
||||
const res = query.split('=');
|
||||
// ?test=1&hello=world
|
||||
if (res[0] !== undefined && res[1] !== undefined) {
|
||||
queryParams[res[0]] = res[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return queryParams;
|
||||
};
|
||||
|
||||
const cookie = {};
|
||||
const getCookie = () => {
|
||||
if (Object.keys(cookie).length === 0) {
|
||||
document.cookie.split(';').forEach(c => {
|
||||
const [key, value] = c.split('=');
|
||||
if (key !== undefined && value !== undefined) {
|
||||
cookie[key.trim()] = value.trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return cookie;
|
||||
};
|
||||
|
||||
// Match all template of query cookie and storage.
|
||||
let strConfig = JSON.stringify(config) || '';
|
||||
const regexp = /\$\{(queryParams|cookie|storage)(\.(\w|-)+)?}/g;
|
||||
let cap = [];
|
||||
let matched = [];
|
||||
while ((cap = regexp.exec(strConfig)) !== null) {
|
||||
matched.push(cap);
|
||||
}
|
||||
|
||||
matched.forEach(item => {
|
||||
const [origin, key, value] = item;
|
||||
if (item && origin && key && value && value.startsWith('.')) {
|
||||
if (key === 'queryParams') {
|
||||
// Replace query params.
|
||||
strConfig = strConfig.replace(origin, getQueryParams()[value.substring(1)] || '');
|
||||
} else if (key === 'cookie') {
|
||||
// Replace cookie.
|
||||
strConfig = strConfig.replace(origin, getCookie()[value.substring(1)] || '');
|
||||
} else if (key === 'storage') {
|
||||
// Replace storage.
|
||||
strConfig = strConfig.replace(origin, localStorage.getItem(value.substring(1)) || '');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Replace url.
|
||||
strConfig = strConfig.replace('${url}', location.href);
|
||||
|
||||
return JSON.parse(strConfig);
|
||||
}
|
||||
|
||||
export function loadDataByCustomFetcher(config: StaticDataLoader) {
|
||||
let parsedConfig = config;
|
||||
try {
|
||||
// Not parse template in SSG/SSR.
|
||||
if (import.meta.renderer === 'client') {
|
||||
parsedConfig = parseTemplate(config);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('parse template error: ', error);
|
||||
}
|
||||
return dataLoaderFetcher(parsedConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle for different dataLoader.
|
||||
*/
|
||||
export function callDataLoader(dataLoader: Loader, requestContext: RequestContext): DataLoaderResult {
|
||||
if (Array.isArray(dataLoader)) {
|
||||
const loaders = dataLoader.map((loader, index) => {
|
||||
return typeof loader === 'object' ? loadDataByCustomFetcher(loader) : dataLoaderDecorator(loader, index)(requestContext);
|
||||
});
|
||||
|
||||
return loaders;
|
||||
}
|
||||
|
||||
if (typeof dataLoader === 'object') {
|
||||
return loadDataByCustomFetcher(dataLoader);
|
||||
}
|
||||
|
||||
return dataLoaderDecorator(dataLoader)(requestContext);
|
||||
}
|
||||
|
||||
const cache = new Map<string, CachedResult>();
|
||||
|
||||
/**
|
||||
* Start getData once data-loader.js is ready in client, and set to cache.
|
||||
*/
|
||||
function loadInitialDataInClient(loaders: Loaders) {
|
||||
const context = (window as any).__ICE_APP_CONTEXT__ || {};
|
||||
const matchedIds = context.matchedIds || [];
|
||||
const loaderData = context.loaderData || {};
|
||||
const { renderMode } = context;
|
||||
|
||||
const ids = ['__app'].concat(matchedIds);
|
||||
ids.forEach(id => {
|
||||
const dataFromSSR = loaderData[id]?.data;
|
||||
if (dataFromSSR) {
|
||||
cache.set(renderMode === 'SSG' ? `${id}_ssg` : id, {
|
||||
value: dataFromSSR,
|
||||
});
|
||||
|
||||
if (renderMode === 'SSR') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const dataLoaderConfig = loaders[id];
|
||||
|
||||
if (dataLoaderConfig) {
|
||||
const requestContext = getRequestContext(window.location);
|
||||
const { loader } = dataLoaderConfig;
|
||||
const promise = callDataLoader(loader, requestContext);
|
||||
|
||||
cache.set(id, {
|
||||
value: promise,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Init data loader in client side.
|
||||
* Load initial data and register global loader.
|
||||
* In order to load data, JavaScript modules, CSS and other assets in parallel.
|
||||
*/
|
||||
async function init(loaders: Loaders, options: Options) {
|
||||
const {
|
||||
fetcher,
|
||||
decorator,
|
||||
runtimeModules,
|
||||
appExport,
|
||||
} = options;
|
||||
|
||||
const runtimeApi = {
|
||||
appContext: {
|
||||
appExport,
|
||||
},
|
||||
};
|
||||
|
||||
if (runtimeModules) {
|
||||
await Promise.all(runtimeModules.map(module => {
|
||||
const runtimeModule = ((module as CommonJsRuntime).default || module) as StaticRuntimePlugin;
|
||||
return runtimeModule(runtimeApi);
|
||||
}).filter(Boolean));
|
||||
}
|
||||
|
||||
if (fetcher) {
|
||||
setFetcher(fetcher);
|
||||
}
|
||||
|
||||
if (decorator) {
|
||||
setDecorator(decorator);
|
||||
}
|
||||
|
||||
try {
|
||||
loadInitialDataInClient(loaders);
|
||||
} catch (error) {
|
||||
console.error('Load initial data error: ', error);
|
||||
}
|
||||
|
||||
(window as any).__ICE_DATA_LOADER__ = {
|
||||
getLoader: (id) => {
|
||||
return loaders[id];
|
||||
},
|
||||
getData: (id, options: LoadRoutesDataOptions) => {
|
||||
let result;
|
||||
|
||||
// First render for ssg use data from build time, second render for ssg will use data from data loader.
|
||||
const cacheKey = `${id}${options?.renderMode === 'SSG' ? '_ssg' : ''}`;
|
||||
|
||||
// In CSR, all dataLoader is called by global data loader to avoid bundle dataLoader in page bundle duplicate.
|
||||
result = cache.get(cacheKey);
|
||||
// Always fetch new data after cache is been used.
|
||||
cache.delete(cacheKey);
|
||||
|
||||
// Already send data request.
|
||||
if (result) {
|
||||
return result.value;
|
||||
}
|
||||
|
||||
const dataLoaderConfig = loaders[id];
|
||||
|
||||
// No data loader.
|
||||
if (!dataLoaderConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Call dataLoader.
|
||||
const { loader } = dataLoaderConfig;
|
||||
return callDataLoader(loader, options?.requestContext || getRequestContext(window.location));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const dataLoader = {
|
||||
init,
|
||||
};
|
||||
|
||||
export default dataLoader;
|
||||
export { defineDataLoader, defineServerDataLoader, defineStaticDataLoader } from '@ice/runtime-kit';
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
import type {
|
||||
RunClientAppOptions,
|
||||
CreateRoutes,
|
||||
RuntimePlugin,
|
||||
AppContext,
|
||||
PublicAppContext,
|
||||
AppConfig,
|
||||
RouteConfig,
|
||||
RouteItem,
|
||||
ServerContext,
|
||||
AppProvider,
|
||||
RouteWrapperConfig,
|
||||
RouteWrapper,
|
||||
RenderMode,
|
||||
Loader,
|
||||
RouteWrapperConfig,
|
||||
ServerContext,
|
||||
AppProvider,
|
||||
StaticRuntimePlugin,
|
||||
} from '@ice/runtime-kit';
|
||||
import { dataLoader, defineDataLoader, defineServerDataLoader, defineStaticDataLoader, callDataLoader, getRequestContext } from '@ice/runtime-kit';
|
||||
import { getAppConfig, defineAppConfig } from '@ice/runtime-kit';
|
||||
import type {
|
||||
PublicAppContext,
|
||||
RouteItem,
|
||||
ClientAppRouterProps,
|
||||
} from './types.js';
|
||||
import Runtime from './runtime.js';
|
||||
import runClientApp from './runClientApp.js';
|
||||
import type { RunClientAppOptions, CreateRoutes } from './runClientApp.js';
|
||||
import { useAppContext as useInternalAppContext, useAppData, AppContextProvider } from './AppContext.js';
|
||||
import { getAppData } from './appData.js';
|
||||
import { useData, useConfig } from './RouteContext.js';
|
||||
|
|
@ -37,10 +44,7 @@ import type {
|
|||
DataType,
|
||||
MainType,
|
||||
} from './Document.js';
|
||||
import dataLoader, { defineDataLoader, defineServerDataLoader, defineStaticDataLoader, callDataLoader } from './dataLoader.js';
|
||||
import getRequestContext from './requestContext.js';
|
||||
import AppErrorBoundary from './AppErrorBoundary.js';
|
||||
import getAppConfig, { defineAppConfig } from './appConfig.js';
|
||||
import { routerHistory as history } from './history.js';
|
||||
import KeepAliveOutlet from './KeepAliveOutlet.js';
|
||||
import { useActive } from './Activity.js';
|
||||
|
|
@ -150,6 +154,7 @@ export {
|
|||
} from 'react-router-dom';
|
||||
|
||||
export type {
|
||||
StaticRuntimePlugin,
|
||||
RuntimePlugin,
|
||||
AppContext,
|
||||
AppConfig,
|
||||
|
|
@ -170,4 +175,5 @@ export type {
|
|||
DataType,
|
||||
MainType,
|
||||
CreateRoutes,
|
||||
ClientAppRouterProps,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOMServer from 'react-dom/server';
|
||||
import getAppConfig from './appConfig.js';
|
||||
import { getRequestContext, getAppConfig } from '@ice/runtime-kit';
|
||||
import type { ServerContext, AppContext } from '@ice/runtime-kit';
|
||||
import { AppContextProvider } from './AppContext.js';
|
||||
import { DocumentContextProvider } from './Document.js';
|
||||
import addLeadingSlash from './utils/addLeadingSlash.js';
|
||||
import getRequestContext from './requestContext.js';
|
||||
import matchRoutes from './matchRoutes.js';
|
||||
import getDocumentData from './server/getDocumentData.js';
|
||||
import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
|
||||
import { sendResponse, getLocation } from './server/response.js';
|
||||
|
||||
import type {
|
||||
AppContext,
|
||||
RouteItem,
|
||||
RouteMatch,
|
||||
RenderOptions,
|
||||
Response,
|
||||
ServerContext,
|
||||
} from './types.js';
|
||||
|
||||
|
||||
interface RenderDocumentOptions {
|
||||
matches: RouteMatch[];
|
||||
renderOptions: RenderOptions;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ErrorStack } from './types.js';
|
||||
import type { ErrorStack } from '@ice/runtime-kit';
|
||||
|
||||
interface ErrorOptions {
|
||||
ignoreRuntimeWarning?: boolean;
|
||||
|
|
|
|||
|
|
@ -3,21 +3,13 @@ import { useRouteError, defer, Await as ReactRouterAwait } from 'react-router-do
|
|||
import type { AwaitProps } from 'react-router-dom';
|
||||
// eslint-disable-next-line camelcase
|
||||
import type { UNSAFE_DeferredData, LoaderFunctionArgs } from '@remix-run/router';
|
||||
import type {
|
||||
RouteItem,
|
||||
RouteModules,
|
||||
RenderMode,
|
||||
RequestContext,
|
||||
ComponentModule,
|
||||
DataLoaderConfig,
|
||||
DataLoaderOptions,
|
||||
Loader,
|
||||
} from './types.js';
|
||||
import type { RouteModules, RenderMode, RequestContext, ComponentModule, DataLoaderConfig, DataLoaderOptions, Loader } from '@ice/runtime-kit';
|
||||
import { callDataLoader } from '@ice/runtime-kit';
|
||||
import { parseSearch } from '@ice/runtime-kit';
|
||||
import type { RouteItem } from './types.js';
|
||||
import RouteWrapper from './RouteWrapper.js';
|
||||
import { useAppContext } from './AppContext.js';
|
||||
import { callDataLoader } from './dataLoader.js';
|
||||
import { updateRoutesConfig } from './routesConfig.js';
|
||||
import { parseSearch } from './requestContext.js';
|
||||
|
||||
type RouteModule = Pick<RouteItem, 'id' | 'lazy'>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { RouteMatch, LoadersData, LoaderData, RouteConfig } from './types.js';
|
||||
import type { RouteConfig, LoadersData, LoaderData } from '@ice/runtime-kit';
|
||||
import type { RouteMatch } from './types.js';
|
||||
|
||||
export function getMeta(
|
||||
matches: RouteMatch[],
|
||||
|
|
|
|||
|
|
@ -1,43 +1,40 @@
|
|||
import React from 'react';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
import { createHashHistory, createBrowserHistory, createMemoryHistory } from '@remix-run/router';
|
||||
import {
|
||||
createHashHistory,
|
||||
createBrowserHistory,
|
||||
createMemoryHistory,
|
||||
} from '@remix-run/router';
|
||||
import type { History } from '@remix-run/router';
|
||||
import type {
|
||||
AppContext, WindowContext, AppExport, RouteItem, RuntimeModules, AppConfig, AssetsManifest, ClientAppRouterProps,
|
||||
AppContext,
|
||||
AppConfig,
|
||||
AssetsManifest,
|
||||
RunClientAppOptions,
|
||||
ErrorStack,
|
||||
} from '@ice/runtime-kit';
|
||||
import { setFetcher, setDecorator, getRequestContext, getAppConfig } from '@ice/runtime-kit';
|
||||
import type {
|
||||
WindowContext,
|
||||
RouteItem,
|
||||
ClientAppRouterProps,
|
||||
} from './types.js';
|
||||
import { createHistory as createHistorySingle, getSingleRoute } from './singleRouter.js';
|
||||
import { setHistory } from './history.js';
|
||||
import Runtime from './runtime.js';
|
||||
import {
|
||||
createHistory as createHistorySingle,
|
||||
getSingleRoute,
|
||||
} from './singleRouter.js';
|
||||
import { setHistory } from './history.js';
|
||||
import { getAppData } from './appData.js';
|
||||
import { getRoutesPath, loadRouteModule } from './routes.js';
|
||||
import type { RouteLoaderOptions } from './routes.js';
|
||||
import getRequestContext from './requestContext.js';
|
||||
import getAppConfig from './appConfig.js';
|
||||
import matchRoutes from './matchRoutes.js';
|
||||
import { setFetcher, setDecorator } from './dataLoader.js';
|
||||
import ClientRouter from './ClientRouter.js';
|
||||
import addLeadingSlash from './utils/addLeadingSlash.js';
|
||||
import { AppContextProvider } from './AppContext.js';
|
||||
import addLeadingSlash from './utils/addLeadingSlash.js';
|
||||
import { deprecatedHistory } from './utils/deprecatedHistory.js';
|
||||
import reportRecoverableError from './reportRecoverableError.js';
|
||||
|
||||
export type CreateRoutes = (options: Pick<RouteLoaderOptions, 'renderMode' | 'requestContext'>) => RouteItem[];
|
||||
|
||||
export interface RunClientAppOptions {
|
||||
app: AppExport;
|
||||
runtimeModules: RuntimeModules;
|
||||
createRoutes?: CreateRoutes;
|
||||
hydrate?: boolean;
|
||||
basename?: string;
|
||||
memoryRouter?: boolean;
|
||||
runtimeOptions?: Record<string, any>;
|
||||
dataLoaderFetcher?: Function;
|
||||
dataLoaderDecorator?: Function;
|
||||
}
|
||||
|
||||
|
||||
export default async function runClientApp(options: RunClientAppOptions) {
|
||||
export default async function runClientApp(options: RunClientAppOptions<RouteItem>) {
|
||||
const {
|
||||
app,
|
||||
createRoutes,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import type { Location } from 'history';
|
||||
import type { AppContext, ServerContext, AppData } from '@ice/runtime-kit';
|
||||
import { getAppConfig, getRequestContext } from '@ice/runtime-kit';
|
||||
import type { OnAllReadyParams, OnShellReadyParams } from './server/streamRender.js';
|
||||
import type {
|
||||
AppContext,
|
||||
ServerContext,
|
||||
RouteMatch,
|
||||
AppData,
|
||||
ServerAppRouterProps,
|
||||
RenderOptions,
|
||||
Response,
|
||||
|
|
@ -13,11 +12,9 @@ import type {
|
|||
import Runtime from './runtime.js';
|
||||
import { AppContextProvider } from './AppContext.js';
|
||||
import { getAppData } from './appData.js';
|
||||
import getAppConfig from './appConfig.js';
|
||||
import { DocumentContextProvider } from './Document.js';
|
||||
import { loadRouteModules } from './routes.js';
|
||||
import { pipeToString, renderToNodeStream } from './server/streamRender.js';
|
||||
import getRequestContext from './requestContext.js';
|
||||
import matchRoutes from './matchRoutes.js';
|
||||
import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
|
||||
import ServerRouter from './ServerRouter.js';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
import type { ComponentType } from 'react';
|
||||
import { routerHistory as history } from './history.js';
|
||||
import type {
|
||||
Renderer,
|
||||
AppContext,
|
||||
|
|
@ -14,14 +13,15 @@ import type {
|
|||
AddWrapper,
|
||||
RouteWrapperConfig,
|
||||
SetRender,
|
||||
AppRouterProps,
|
||||
ComponentWithChildren,
|
||||
ResponseHandler,
|
||||
} from './types.js';
|
||||
} from '@ice/runtime-kit';
|
||||
import type { History } from '@remix-run/router';
|
||||
import { routerHistory as history } from './history.js';
|
||||
import type { AppRouterProps } from './types.js';
|
||||
import { useData, useConfig } from './RouteContext.js';
|
||||
import { useData as useSingleRouterData, useConfig as useSingleRouterConfig } from './singleRouter.js';
|
||||
import { useAppContext } from './AppContext.js';
|
||||
|
||||
class Runtime {
|
||||
private appContext: AppContext;
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ class Runtime {
|
|||
public getWrappers = () => this.RouteWrappers;
|
||||
|
||||
public loadModule(module: RuntimePlugin | StaticRuntimePlugin | CommonJsRuntime) {
|
||||
let runtimeAPI: RuntimeAPI = {
|
||||
let runtimeAPI: RuntimeAPI<History> = {
|
||||
addProvider: this.addProvider,
|
||||
addResponseHandler: this.addResponseHandler,
|
||||
getResponseHandlers: this.getResponseHandlers,
|
||||
|
|
@ -88,7 +88,7 @@ class Runtime {
|
|||
history,
|
||||
};
|
||||
|
||||
const runtimeModule = ((module as CommonJsRuntime).default || module) as RuntimePlugin;
|
||||
const runtimeModule = ((module as CommonJsRuntime).default || module) as RuntimePlugin<any, History>;
|
||||
if (module) {
|
||||
return runtimeModule(runtimeAPI, this.runtimeOptions);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { DocumentDataLoaderConfig, RequestContext } from '../types.js';
|
||||
import type { RequestContext } from '@ice/runtime-kit';
|
||||
import type { DocumentDataLoaderConfig } from '../types.js';
|
||||
|
||||
interface Options {
|
||||
loaderConfig: DocumentDataLoaderConfig;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
import * as React from 'react';
|
||||
import type { History } from '@remix-run/router';
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import type { LoaderData, RouteItem } from './types.js';
|
||||
import type { LoaderData } from '@ice/runtime-kit';
|
||||
import type { RouteItem } from './types.js';
|
||||
import { loadRouteModules } from './routes.js';
|
||||
|
||||
const Context = React.createContext<LoaderData>(undefined);
|
||||
|
|
|
|||
|
|
@ -1,93 +1,20 @@
|
|||
import type { IncomingMessage, ServerResponse } from 'http';
|
||||
import type { InitialEntry, AgnosticRouteObject, Location, History, RouterInit, StaticHandlerContext } from '@remix-run/router';
|
||||
import type { ComponentType, PropsWithChildren } from 'react';
|
||||
import type { HydrationOptions, Root } from 'react-dom/client';
|
||||
import type { ComponentType } from 'react';
|
||||
import type { AgnosticRouteObject, Location, RouterInit, StaticHandlerContext } from '@remix-run/router';
|
||||
import type { Params, RouteObject } from 'react-router-dom';
|
||||
import type {
|
||||
AppContext,
|
||||
AppExport,
|
||||
ComponentWithChildren,
|
||||
DataLoaderResult,
|
||||
LoaderData,
|
||||
PageConfig,
|
||||
RenderMode,
|
||||
RequestContext,
|
||||
RuntimeModules,
|
||||
AssetsManifest,
|
||||
} from '@ice/runtime-kit';
|
||||
import type { RouteLoaderOptions } from './routes.js';
|
||||
import type { RenderToPipeableStreamOptions, NodeWritablePiper } from './server/streamRender.js';
|
||||
|
||||
type UseConfig = () => RouteConfig<Record<string, any>>;
|
||||
type UseData = () => RouteData;
|
||||
type UseAppContext = () => AppContext;
|
||||
|
||||
type VoidFunction = () => void;
|
||||
type AppLifecycle = 'onShow' | 'onHide' | 'onPageNotFound' | 'onShareAppMessage' | 'onUnhandledRejection' | 'onLaunch' | 'onError' | 'onTabItemClick';
|
||||
type App = Partial<{
|
||||
rootId: string;
|
||||
strict: boolean;
|
||||
errorBoundary: boolean;
|
||||
onRecoverableError: (error: unknown, errorInfo: ErrorStack) => void;
|
||||
onBeforeHydrate: () => void;
|
||||
} & Record<AppLifecycle, VoidFunction>>;
|
||||
|
||||
export interface ErrorStack {
|
||||
componentStack?: string;
|
||||
digest?: string;
|
||||
}
|
||||
|
||||
export type AppData = any;
|
||||
export type RouteData = any;
|
||||
|
||||
// route.pageConfig return value
|
||||
export type RouteConfig<T = {}> = T & {
|
||||
// Support for extends config.
|
||||
title?: string;
|
||||
meta?: React.MetaHTMLAttributes<HTMLMetaElement>[];
|
||||
links?: React.LinkHTMLAttributes<HTMLLinkElement>[];
|
||||
scripts?: React.ScriptHTMLAttributes<HTMLScriptElement>[];
|
||||
};
|
||||
|
||||
export interface AppExport {
|
||||
default?: AppConfig;
|
||||
[key: string]: any;
|
||||
dataLoader?: DataLoaderConfig;
|
||||
}
|
||||
|
||||
export type DataLoaderResult = (Promise<RouteData> | RouteData) | RouteData;
|
||||
export type DataLoader = (ctx: RequestContext) => DataLoaderResult;
|
||||
|
||||
export interface StaticDataLoader {
|
||||
key?: string;
|
||||
prefetch_type?: string;
|
||||
api: string;
|
||||
v: string;
|
||||
data: any;
|
||||
ext_headers: Object;
|
||||
}
|
||||
|
||||
// route.defineDataLoader
|
||||
// route.defineServerDataLoader
|
||||
// route.defineStaticDataLoader
|
||||
export type Loader = DataLoader | StaticDataLoader | Array<DataLoader | StaticDataLoader>;
|
||||
|
||||
// route.pageConfig
|
||||
export type PageConfig = (args: { data?: RouteData }) => RouteConfig;
|
||||
|
||||
export interface AppConfig {
|
||||
app?: App;
|
||||
router?: {
|
||||
type?: 'hash' | 'browser' | 'memory';
|
||||
basename?: string;
|
||||
initialEntries?: InitialEntry[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface RoutesConfig {
|
||||
[routeId: string]: RouteConfig;
|
||||
}
|
||||
|
||||
export interface RoutesData {
|
||||
[routeId: string]: RouteData;
|
||||
}
|
||||
|
||||
export interface DataLoaderOptions {
|
||||
defer?: boolean;
|
||||
}
|
||||
|
||||
export interface DataLoaderConfig {
|
||||
loader: Loader;
|
||||
options?: DataLoaderOptions;
|
||||
}
|
||||
import type { NodeWritablePiper, RenderToPipeableStreamOptions } from './server/streamRender.js';
|
||||
|
||||
interface DocumentLoaderOptions {
|
||||
documentOnly?: boolean;
|
||||
|
|
@ -98,38 +25,6 @@ export interface DocumentDataLoaderConfig {
|
|||
loader: DocumentDataLoader;
|
||||
}
|
||||
|
||||
export interface LoadersData {
|
||||
[routeId: string]: LoaderData;
|
||||
}
|
||||
|
||||
export interface LoaderData {
|
||||
data?: RouteData;
|
||||
pageConfig?: RouteConfig;
|
||||
}
|
||||
|
||||
// useAppContext
|
||||
export interface AppContext {
|
||||
appConfig: AppConfig;
|
||||
appData: any;
|
||||
documentData?: any;
|
||||
serverData?: any;
|
||||
assetsManifest?: AssetsManifest;
|
||||
loaderData?: LoadersData;
|
||||
routeModules?: RouteModules;
|
||||
RouteWrappers?: RouteWrapperConfig[];
|
||||
routePath?: string;
|
||||
matches?: RouteMatch[];
|
||||
routes?: RouteItem[];
|
||||
documentOnly?: boolean;
|
||||
matchedIds?: string[];
|
||||
appExport?: AppExport;
|
||||
basename?: string;
|
||||
downgrade?: boolean;
|
||||
renderMode?: RenderMode;
|
||||
requestContext?: RequestContext;
|
||||
revalidate?: boolean;
|
||||
}
|
||||
|
||||
export type PublicAppContext = Pick<
|
||||
AppContext,
|
||||
'appConfig' | 'routePath' | 'downgrade' | 'documentOnly' | 'renderMode'
|
||||
|
|
@ -140,31 +35,6 @@ AppContext,
|
|||
'appData' | 'loaderData' | 'routePath' | 'downgrade' | 'matchedIds' | 'documentOnly' | 'renderMode' | 'serverData' | 'revalidate'
|
||||
>;
|
||||
|
||||
export type Renderer = (
|
||||
container: Element | Document,
|
||||
initialChildren: React.ReactNode,
|
||||
options?: HydrationOptions,
|
||||
) => Root;
|
||||
|
||||
export interface ServerContext {
|
||||
req?: IncomingMessage;
|
||||
res?: ServerResponse;
|
||||
}
|
||||
|
||||
export interface RequestContext extends ServerContext {
|
||||
pathname: string;
|
||||
query: Record<string, any>;
|
||||
}
|
||||
|
||||
export type ComponentModule = {
|
||||
default?: ComponentType<any>;
|
||||
Component?: ComponentType<any>;
|
||||
staticDataLoader?: DataLoaderConfig;
|
||||
serverDataLoader?: DataLoaderConfig;
|
||||
dataLoader?: DataLoaderConfig;
|
||||
pageConfig?: PageConfig;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type RouteItem = AgnosticRouteObject & {
|
||||
componentName: string;
|
||||
|
|
@ -174,94 +44,10 @@ export type RouteItem = AgnosticRouteObject & {
|
|||
children?: RouteItem[];
|
||||
};
|
||||
|
||||
export type ComponentWithChildren<P = {}> = ComponentType<PropsWithChildren<P>>;
|
||||
|
||||
export type DocumentComponent = ComponentWithChildren<{
|
||||
pagePath: string;
|
||||
}>;
|
||||
|
||||
export interface RouteWrapperConfig {
|
||||
Wrapper: RouteWrapper;
|
||||
layout?: boolean;
|
||||
}
|
||||
|
||||
export type AppProvider = ComponentWithChildren<any>;
|
||||
export type RouteWrapper = ComponentType<any>;
|
||||
export type ResponseHandler = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
) => any | Promise<any>;
|
||||
|
||||
export type SetAppRouter = <T>(AppRouter: ComponentType<T>) => void;
|
||||
export type GetAppRouter = () => AppProvider;
|
||||
export type AddProvider = (Provider: AppProvider) => void;
|
||||
export type SetRender = (render: Renderer) => void;
|
||||
export type AddWrapper = (wrapper: RouteWrapper, forLayout?: boolean) => void;
|
||||
export type AddResponseHandler = (handler: ResponseHandler) => void;
|
||||
export type GetResponseHandlers = () => ResponseHandler[];
|
||||
|
||||
export interface RouteModules {
|
||||
[routeId: string]: ComponentModule;
|
||||
}
|
||||
|
||||
export interface AssetsManifest {
|
||||
dataLoader?: string;
|
||||
publicPath: string;
|
||||
entries: {
|
||||
[assetPath: string]: string[];
|
||||
};
|
||||
pages: {
|
||||
[assetPath: string]: string[];
|
||||
};
|
||||
assets?: {
|
||||
[assetPath: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RuntimeAPI {
|
||||
setAppRouter?: SetAppRouter;
|
||||
getAppRouter: GetAppRouter;
|
||||
addProvider: AddProvider;
|
||||
addResponseHandler: AddResponseHandler;
|
||||
getResponseHandlers: GetResponseHandlers;
|
||||
setRender: SetRender;
|
||||
addWrapper: AddWrapper;
|
||||
appContext: AppContext;
|
||||
useData: UseData;
|
||||
useConfig: UseConfig;
|
||||
useAppContext: UseAppContext;
|
||||
history: History;
|
||||
}
|
||||
|
||||
export interface StaticRuntimeAPI {
|
||||
appContext: {
|
||||
appExport: AppExport;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RuntimePlugin<T = Record<string, any>> {
|
||||
(
|
||||
apis: RuntimeAPI,
|
||||
runtimeOptions?: T,
|
||||
): Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface StaticRuntimePlugin<T = Record<string, any>> {
|
||||
(
|
||||
apis: StaticRuntimeAPI,
|
||||
runtimeOptions?: T,
|
||||
): Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface CommonJsRuntime {
|
||||
default: RuntimePlugin | StaticRuntimePlugin;
|
||||
}
|
||||
|
||||
export interface RuntimeModules {
|
||||
statics?: (StaticRuntimePlugin | CommonJsRuntime)[];
|
||||
commons?: (RuntimePlugin | CommonJsRuntime)[];
|
||||
}
|
||||
|
||||
export interface AppRouterProps {
|
||||
routes?: RouteObject[];
|
||||
location?: Location;
|
||||
|
|
@ -301,8 +87,6 @@ export interface RouteMatch {
|
|||
route: RouteItem;
|
||||
}
|
||||
|
||||
export type RenderMode = 'SSR' | 'SSG' | 'CSR';
|
||||
|
||||
interface Piper {
|
||||
pipe: NodeWritablePiper;
|
||||
fallback: Function;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { expect, it, describe } from 'vitest';
|
||||
import getAppConfig, { defineAppConfig } from '../src/appConfig.js';
|
||||
import { getAppConfig, defineAppConfig } from '@ice/runtime-kit';
|
||||
|
||||
describe('AppConfig', () => {
|
||||
it('getAppConfig', () => {
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ describe('routes', () => {
|
|||
});
|
||||
|
||||
it('load async route', async () => {
|
||||
process.env.ICE_CORE_ROUTER = 'true';
|
||||
const { data: deferredResult } = await createRouteLoader({
|
||||
routeId: 'home',
|
||||
module: InfoItem,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
import { expect, it, describe, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { parseTemplate } from '../src/dataLoader';
|
||||
import { parseTemplate } from '@ice/runtime-kit';
|
||||
|
||||
describe('parseTemplate', () => {
|
||||
let locationSpy;
|
||||
|
|
@ -128,4 +128,4 @@ describe('parseTemplate', () => {
|
|||
ext_headers: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@
|
|||
"esbuild": "^0.17.16",
|
||||
"postcss": "^8.4.31",
|
||||
"webpack": "^5.86.0",
|
||||
"webpack-dev-server": "4.15.0"
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"@ice/route-manifest": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "tsc -w --sourceMap",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { RuleSetRule, Configuration, Compiler, WebpackPluginInstance } from
|
|||
import type {
|
||||
ProxyConfigArray,
|
||||
ProxyConfigArrayItem,
|
||||
ProxyConfigMap,
|
||||
Middleware,
|
||||
ServerOptions,
|
||||
} from 'webpack-dev-server';
|
||||
|
|
@ -14,6 +13,7 @@ import type Server from 'webpack-dev-server';
|
|||
import type { SwcCompilationConfig } from '@ice/bundles';
|
||||
import type { BuildOptions } from 'esbuild';
|
||||
import type { ProcessOptions } from 'postcss';
|
||||
import type { NestedRouteManifest } from '@ice/route-manifest';
|
||||
|
||||
export type ECMA = 5 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020;
|
||||
|
||||
|
|
@ -90,6 +90,17 @@ export type { webpack };
|
|||
|
||||
type PluginFunction = (this: Compiler, compiler: Compiler) => void;
|
||||
|
||||
export interface RouteDefinitionOptions {
|
||||
manifest: NestedRouteManifest[];
|
||||
lazy?: boolean;
|
||||
depth?: number;
|
||||
matchRoute?: (route: NestedRouteManifest) => boolean;
|
||||
}
|
||||
export interface RouteDefinition {
|
||||
routeImports: string[];
|
||||
routeDefinition: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
// The name of the task, used for the output log.
|
||||
name?: string;
|
||||
|
|
@ -137,7 +148,7 @@ export interface Config {
|
|||
| ((middlewares: Middleware[], devServer: Server) => Middleware[])
|
||||
| undefined;
|
||||
|
||||
proxy?: ProxyConfigArrayItem | ProxyConfigMap | ProxyConfigArray | undefined;
|
||||
proxy?: ProxyConfigArray;
|
||||
|
||||
polyfill?: 'usage' | 'entry' | false;
|
||||
// You can use `browserslist` to automatically configure supported browsers if set to be true.
|
||||
|
|
@ -233,4 +244,19 @@ export interface Config {
|
|||
useDataLoader?: boolean;
|
||||
|
||||
optimizePackageImports?: string[];
|
||||
|
||||
runtime?: {
|
||||
source?: string;
|
||||
server?: string;
|
||||
exports?: {
|
||||
specifier: string[];
|
||||
source: string;
|
||||
alias?: Record<string, string>;
|
||||
}[];
|
||||
router?: {
|
||||
routesDefinition?: (options: RouteDefinitionOptions) => RouteDefinition;
|
||||
source?: string;
|
||||
template?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -352,7 +352,10 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
|
|||
logging: 'info',
|
||||
},
|
||||
setupMiddlewares: middlewares,
|
||||
https,
|
||||
server: https ? {
|
||||
type: 'https',
|
||||
options: typeof https === 'object' ? https : {},
|
||||
} : undefined,
|
||||
}, devServer || {}) as Config['devServer'],
|
||||
} as Configuration;
|
||||
// tnpm / cnpm 安装时,webpack 5 的持久缓存无法生成,长时间将导致 OOM
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
diff --git a/dist/config/adapter.js b/dist/config/adapter.js
|
||||
index 4eebbcf79cba29acbc0a36d565acc1c08eaf790b..1d87c9be33f69b8dda1436d8e3b970ce0b571112 100644
|
||||
--- a/dist/config/adapter.js
|
||||
+++ b/dist/config/adapter.js
|
||||
@@ -15,6 +15,7 @@ const getRawOptions = (options, compiler) => {
|
||||
const mode = options.mode;
|
||||
const experiments = getRawExperiments(options.experiments);
|
||||
return {
|
||||
+ features: options.features,
|
||||
mode,
|
||||
target: getRawTarget(options.target),
|
||||
context: options.context,
|
||||
diff --git a/dist/config/defaults.js b/dist/config/defaults.js
|
||||
index 1f9f61ff680b6db026c43eb95fe2d78c5f5d8195..56ce90247fd920717d42bc16864e6025fe6dca66 100644
|
||||
--- a/dist/config/defaults.js
|
||||
+++ b/dist/config/defaults.js
|
||||
@@ -53,6 +53,11 @@ const applyRspackOptionsDefaults = (options) => {
|
||||
applyExperimentsDefaults(options.experiments, {
|
||||
cache: options.cache
|
||||
});
|
||||
+ if (options.features) {
|
||||
+ applyFeaturesDefaults(options.features);
|
||||
+ } else {
|
||||
+ D(options, 'features', {});
|
||||
+ }
|
||||
applySnapshotDefaults(options.snapshot, { production });
|
||||
applyModuleDefaults(options.module, {
|
||||
// syncWebAssembly: options.experiments.syncWebAssembly,
|
||||
@@ -103,6 +108,13 @@ const applyInfrastructureLoggingDefaults = (infrastructureLogging) => {
|
||||
D(infrastructureLogging, "colors", tty);
|
||||
D(infrastructureLogging, "appendOnly", !tty);
|
||||
};
|
||||
+const applyFeaturesDefaults = (features) => {
|
||||
+ D(features, 'split_chunks_strategy', {});
|
||||
+ if (typeof features.split_chunks_strategy === 'object') {
|
||||
+ D(features.split_chunks_strategy, 'name', '');
|
||||
+ D(features.split_chunks_strategy, 'topLevelFrameworks', []);
|
||||
+ }
|
||||
+};
|
||||
const applyExperimentsDefaults = (experiments, { cache }) => {
|
||||
D(experiments, "lazyCompilation", false);
|
||||
D(experiments, "asyncWebAssembly", false);
|
||||
diff --git a/dist/config/normalization.js b/dist/config/normalization.js
|
||||
index 696eddf849f8a2f2c66237cb37db767f4dfe20ca..7e89b6091471de8287ce0785042676873141cfbe 100644
|
||||
--- a/dist/config/normalization.js
|
||||
+++ b/dist/config/normalization.js
|
||||
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getNormalizedRspackOptions = void 0;
|
||||
const getNormalizedRspackOptions = (config) => {
|
||||
return {
|
||||
+ features: config.features,
|
||||
ignoreWarnings: config.ignoreWarnings !== undefined
|
||||
? config.ignoreWarnings.map(ignore => {
|
||||
if (typeof ignore === "function") {
|
||||
diff --git a/dist/config/zod.js b/dist/config/zod.js
|
||||
index a81260f08e4e7de64ff3c1f8769a658db4c73883..df3184bad831922f64f3c41b64bce08fcdf5b3cd 100644
|
||||
--- a/dist/config/zod.js
|
||||
+++ b/dist/config/zod.js
|
||||
@@ -775,5 +775,6 @@ exports.rspackOptions = zod_1.z.strictObject({
|
||||
builtins: builtins.optional(),
|
||||
module: moduleOptions.optional(),
|
||||
profile: profile.optional(),
|
||||
- bail: bail.optional()
|
||||
+ bail: bail.optional(),
|
||||
+ features: zod_1.z.custom().optional(),
|
||||
});
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 57955f085ab0ccf9cb514390a6d2472531a1e6c1..d333b82dcbc299c4580288955df54f1d77ff625b 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -694,6 +694,7 @@ __export(src_exports, {
|
||||
Compiler: () => Compiler,
|
||||
ContextReplacementPlugin: () => ContextReplacementPlugin,
|
||||
CopyRspackPlugin: () => CopyRspackPlugin,
|
||||
+ ManifestPlugin: () => ManifestPlugin,
|
||||
CssExtractRspackPlugin: () => CssExtractRspackPlugin,
|
||||
DefinePlugin: () => DefinePlugin,
|
||||
DllPlugin: () => DllPlugin,
|
||||
@@ -760,6 +761,7 @@ __export(exports_exports, {
|
||||
Compiler: () => Compiler,
|
||||
ContextReplacementPlugin: () => ContextReplacementPlugin,
|
||||
CopyRspackPlugin: () => CopyRspackPlugin,
|
||||
+ ManifestPlugin: () => ManifestPlugin,
|
||||
CssExtractRspackPlugin: () => CssExtractRspackPlugin,
|
||||
DefinePlugin: () => DefinePlugin,
|
||||
DllPlugin: () => DllPlugin,
|
||||
@@ -5015,6 +5017,12 @@ var CopyRspackPlugin = create2(
|
||||
}
|
||||
);
|
||||
|
||||
+// Customize builtin plugin.
|
||||
+var ManifestPlugin = create2(
|
||||
+ import_binding10.BuiltinPluginName.ManifestPlugin,
|
||||
+ () => {}
|
||||
+);
|
||||
+
|
||||
// src/builtin-plugin/css-extract/index.ts
|
||||
var import_binding11 = require("@rspack/binding");
|
||||
var import_node_path3 = require("path");
|
||||
@@ -20545,6 +20553,7 @@ module.exports = rspack;
|
||||
Compiler,
|
||||
ContextReplacementPlugin,
|
||||
CopyRspackPlugin,
|
||||
+ ManifestPlugin,
|
||||
CssExtractRspackPlugin,
|
||||
DefinePlugin,
|
||||
DllPlugin,
|
||||
15214
pnpm-lock.yaml
15214
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,11 @@
|
|||
import { exec } from '@actions/exec';
|
||||
|
||||
const installPuppeteer = async () => {
|
||||
await exec('node', [
|
||||
require.resolve('puppeteer/install.js'),
|
||||
]);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
await installPuppeteer();
|
||||
})();
|
||||
|
|
@ -41,7 +41,8 @@ describe(`start ${example} in speedup mode`, () => {
|
|||
let page: Page;
|
||||
let browser: Browser;
|
||||
test('open /', async () => {
|
||||
const { devServer, port } = await startFixture(example, { speedup: true });
|
||||
// Close speed up mode is win32 system for now.
|
||||
const { devServer, port } = await startFixture(example, { speedup: process.platform !== 'win32' });
|
||||
const res = await setupStartBrowser({ server: devServer, port });
|
||||
page = res.page;
|
||||
browser = res.browser;
|
||||
|
|
@ -55,6 +56,6 @@ describe(`start ${example} in speedup mode`, () => {
|
|||
|
||||
describe(`build ${example} in speedup mode`, () => {
|
||||
test('open /', async () => {
|
||||
await buildFixture(example, { speedup: true });
|
||||
await buildFixture(example, { speedup: process.platform !== 'win32' });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -97,9 +97,9 @@ export default class Browser {
|
|||
// @ts-ignore
|
||||
if (this.server.stop) {
|
||||
// @ts-ignore
|
||||
this.server.stop();
|
||||
await this.server.stop();
|
||||
} else {
|
||||
this.server.close();
|
||||
await this.server.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue