Merge branch 'release/next' into chore/swc-version
CI / build (16.x, ubuntu-latest) (push) Has been cancelled Details
CI / build (16.x, windows-latest) (push) Has been cancelled Details
CI / build (18.x, ubuntu-latest) (push) Has been cancelled Details
CI / build (18.x, windows-latest) (push) Has been cancelled Details

This commit is contained in:
ClarkXia 2025-05-27 20:25:33 +08:00
commit c786367c78
102 changed files with 8788 additions and 7271 deletions

View File

@ -1,5 +0,0 @@
---
'@ice/rspack-config': patch
---
fix: update splitChunk config

View File

@ -1,5 +0,0 @@
---
'@ice/jsx-runtime': patch
---
fix: export createElement for backward compatibility

View File

@ -0,0 +1,5 @@
---
'@ice/rspack-config': patch
---
feat: add support for custom loaders in rspack configuration

View File

@ -0,0 +1,5 @@
---
'@ice/plugin-miniapp': patch
---
fix: miniapp using absolute path to match route manifest

View File

@ -0,0 +1,5 @@
---
'@ice/app': patch
---
fix: missing jsx-plus transform when server bundler is webpack

View File

@ -1,5 +0,0 @@
---
'@ice/plugin-miniapp': patch
---
fix: should check skeleton file existed before read

View File

@ -1,5 +0,0 @@
---
'@ice/runtime': patch
---
feat: support hook of onBeforeHydrate

View File

@ -6,23 +6,40 @@ on:
- release/**
jobs:
canary:
name: Canary
check_changeset:
name: Check Changeset exists
outputs:
status: ${{ steps.check.outcome }}
runs-on: ubuntu-latest
steps:
- name: Checkout Branch
uses: actions/checkout@v4
- name: Check
id: check
continue-on-error: true
run: test "$(ls -1 .changeset | wc -l)" -gt "2"
canary:
name: Publish Canary
runs-on: ubuntu-latest
needs: check_changeset
if: needs.check_changeset.outputs.status == 'success'
strategy:
matrix:
node-version: [18]
steps:
- name: Checkout Branch
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
@ -30,9 +47,6 @@ jobs:
- name: Setup
run: pnpm run setup
- name: Check changeset exists
run: test "$(ls -1 .changeset | wc -l)" -gt "2"
- name: Config npm
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
env:

View File

@ -0,0 +1,9 @@
import { defineConfig } from '@ice/app';
import defaultConfig from './ice.config.mjs';
export default defineConfig(() => ({
...defaultConfig,
htmlGenerating: {
mode: 'compat'
}
}));

View File

@ -13,7 +13,12 @@ function Document() {
</head>
<body>
<Main />
<script dangerouslySetInnerHTML={{ __html: 'window.addEventListener(\'suspense\', (d) => console.log(\'suspence event=\', d))' }} />
<script
defer
dangerouslySetInnerHTML={{
__html: "window.addEventListener('ice-suspense', (e) => console.log('ice-suspense', e));",
}}
/>
<Scripts async />
</body>
</html>

View File

@ -11,7 +11,7 @@ export default function Home() {
<h2>Home Page</h2>
<Counter />
<Comments id="comments" fallback={<div>loading...</div>} />
<Footer id="comments" fallback={<div>loading...</div>} />
<Footer id="comments-2" fallback={<div>loading...</div>} />
</div>
);
}

View File

@ -1,5 +1,21 @@
# Changelog
## 0.2.9
### Patch Changes
- 2f73084d: feat: export ModuleNotFoundError of webpack
## 0.2.8
### Patch Changes
- 97cb2046: @ice/app: align the output result with the former esbuild
@ice/bundles: export more webpack internal modules
- 97cb2046: @ice/app: remove unused deps and import them from @ice/bundles
@ice/bundles: compile tsconfig-paths-webpack-plugin
- a0099df5: fix: update es-module-lexer
## 0.2.7
### Patch Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/bundles",
"version": "0.2.7",
"version": "0.2.9",
"license": "MIT",
"author": "ICE",
"description": "Basic dependencies for ice.",
@ -60,7 +60,7 @@
"css-loader": "6.7.1",
"css-minimizer-webpack-plugin": "3.4.1",
"cssnano": "^5.1.7",
"es-module-lexer": "0.10.5",
"es-module-lexer": "1.6.0",
"esbuild-register": "3.4.1",
"eslint": "^8.14.0",
"eslint-webpack-plugin": "3.1.1",

View File

@ -10,9 +10,32 @@ module.exports = {
SingleEntryPlugin: require('webpack/lib/SingleEntryPlugin'),
FetchCompileAsyncWasmPlugin: require('webpack/lib/web/FetchCompileAsyncWasmPlugin'),
FetchCompileWasmPlugin: require('webpack/lib/web/FetchCompileWasmPlugin'),
JavascriptModulesPlugin: require('webpack/lib/javascript/JavascriptModulesPlugin'),
StartupChunkDependenciesPlugin: require('webpack/lib/runtime/StartupChunkDependenciesPlugin'),
StartupHelpers: require('webpack/lib/javascript/StartupHelpers'),
compileBooleanMatcher: require('webpack/lib/util/compileBooleanMatcher'),
identifier: require('webpack/lib/util/identifier'),
StringXor: require('webpack/lib/util/StringXor'),
NormalModule: require('webpack/lib/NormalModule'),
EntryDependency: require('webpack/lib/dependencies/EntryDependency'),
ModuleNotFoundError: require('webpack/lib/ModuleNotFoundError'),
LazySet: require('webpack/lib/util/LazySet'),
makeSerializable: require('webpack/lib/util/makeSerializable'),
SortableSet: require('webpack/lib/util/SortableSet'),
StaticExportsDependency: require('webpack/lib/dependencies/StaticExportsDependency'),
ModuleFactory: require('webpack/lib/ModuleFactory'),
ModuleDependency: require('webpack/lib/dependencies/ModuleDependency'),
createSchemaValidation: require('webpack/lib/util/create-schema-validation'),
extractUrlAndGlobal: require('webpack/lib/util/extractUrlAndGlobal'),
Compilation: require('webpack/lib/Compilation'),
semver: require('webpack/lib/util/semver'),
WebpackError: require('webpack/lib/WebpackError'),
comparators: require('webpack/lib/util/comparators'),
StartupEntrypointRuntimeModule: require('webpack/lib/runtime/StartupEntrypointRuntimeModule'),
SetHelpers: require('webpack/lib/util/SetHelpers'),
ChunkHelpers: require('webpack/lib/javascript/ChunkHelpers'),
HotUpdateChunk: require('webpack/lib/HotUpdateChunk'),
fs: require('webpack/lib/util/fs'),
sources: require('webpack').sources,
webpack: require('webpack'),
package: {

View File

@ -0,0 +1 @@
module.exports = require('./bundle').ChunkHelpers;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').Compilation;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').HotUpdateChunk;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').JavascriptModulesPlugin;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').LazySet;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').ModuleDependency;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').ModuleFactory;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').ModuleNotFoundError;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').SetHelpers;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').SortableSet;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').StartupChunkDependenciesPlugin;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').StartupEntrypointRuntimeModule;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').StartupHelpers;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').StaticExportsDependency;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').WebpackError;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').comparators;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').compileBooleanMatcher;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').createSchemaValidation;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').extractUrlAndGlobal;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').fs;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').identifier;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').makeSerializable;

View File

@ -0,0 +1 @@
module.exports = require('./bundle').semver;

View File

@ -1,5 +1,61 @@
# Changelog
## 3.6.1
### Patch Changes
- 478120d1: fix: always use esbuild to compile server config
- Updated dependencies [2f73084d]
- @ice/bundles@0.2.9
- @ice/rspack-config@1.2.3
- @ice/shared-config@1.3.2
- @ice/webpack-config@1.2.2
## 3.6.0
### Minor Changes
- 97cb2046: support split server bundle
### Patch Changes
- f0c6380b: feat: add htmlGenerating `mode` option
- 97cb2046: @ice/app: align the output result with the former esbuild
@ice/bundles: export more webpack internal modules
- 97cb2046: @ice/app: remove unused deps and import them from @ice/bundles
@ice/bundles: compile tsconfig-paths-webpack-plugin
- 97cb2046: rebase releast/next
- 15cd5f7f: fix: glob pattern for document
- 97cb2046: feat: minify css file;
feat: change minifier from terser to esbuildMinifier.
feat: support config minify option
- 97cb2046: - feat: change transformInclude to array
- fix: only treat .js as jsx
- feat: support customize webpack.module.rule
- feat: support handle assets
- 97cb2046: refactro: reuse webpackConfig
- 97cb2046: fix: use @ice/bundles instead of import webpack directly.
feat: support pass definitions for provide plugin.
- Updated dependencies [97cb2046]
- Updated dependencies [97cb2046]
- Updated dependencies [a0099df5]
- Updated dependencies [a0099df5]
- Updated dependencies [97cb2046]
- @ice/bundles@0.2.8
- @ice/runtime@1.5.2
- @ice/webpack-config@1.2.1
- @ice/shared-config@1.3.1
- @ice/rspack-config@1.2.2
## 3.5.1
### Patch Changes
- Updated dependencies [4130611d]
- Updated dependencies [2e274966]
- @ice/rspack-config@1.2.1
- @ice/runtime@1.5.1
## 3.5.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/app",
"version": "3.5.0",
"version": "3.6.1",
"description": "provide scripts and configuration used by web framework ice",
"type": "module",
"main": "./esm/index.js",
@ -61,7 +61,7 @@
"chokidar": "^3.5.3",
"commander": "^9.0.0",
"consola": "^2.15.3",
"cross-spawn": "^7.0.3",
"cross-spawn": "^7.0.5",
"detect-port": "^1.3.0",
"dotenv": "^16.0.0",
"dotenv-expand": "^8.0.3",
@ -69,7 +69,7 @@
"fast-glob": "^3.2.11",
"find-up": "^5.0.0",
"fs-extra": "^10.0.0",
"micromatch": "^4.0.5",
"micromatch": "^4.0.8",
"mlly": "^1.1.0",
"mrmime": "^1.0.0",
"open": "^8.4.0",
@ -108,4 +108,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@ -5,6 +5,7 @@ import injectInitialEntry from '../../utils/injectInitialEntry.js';
import { SERVER_OUTPUT_DIR } from '../../constant.js';
import { logger } from '../../utils/logger.js';
import type { BundlerOptions } from '../types.js';
import type { HtmlGeneratingMode } from '../../types/index.js';
export async function getOutputPaths(options: {
rootDir: string;
@ -21,7 +22,8 @@ export async function getOutputPaths(options: {
}
}
if (serverEntry && userConfig.htmlGenerating) {
outputPaths = await buildCustomOutputs(rootDir, outputDir, serverEntry, bundleOptions);
const htmlGeneratingMode = typeof userConfig.htmlGenerating === 'boolean' ? undefined : userConfig.htmlGenerating?.mode;
outputPaths = await buildCustomOutputs(rootDir, outputDir, serverEntry, bundleOptions, htmlGeneratingMode);
}
return outputPaths;
}
@ -37,6 +39,7 @@ async function buildCustomOutputs(
outputDir: string,
serverEntry: string,
bundleOptions: Pick<BundlerOptions, 'userConfig' | 'appConfig' | 'routeManifest'>,
generatingMode?: HtmlGeneratingMode,
) {
const { userConfig, appConfig, routeManifest } = bundleOptions;
const { ssg } = userConfig;
@ -52,6 +55,7 @@ async function buildCustomOutputs(
renderMode: ssg ? 'SSG' : undefined,
routeType: appConfig?.router?.type,
routeManifest,
generatingMode,
});
if (routeType === 'memory' && userConfig?.routes?.injectInitialEntry) {
injectInitialEntry(routeManifest, outputDir);

View File

@ -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);

View File

@ -508,7 +508,7 @@ const userConfig = [
},
{
name: 'htmlGenerating',
validation: 'boolean',
validation: 'boolean|object',
defaultValue: true,
},
];

View File

@ -12,27 +12,56 @@ export function getFileName(filePath: string) {
return filePath.split('/').slice(-1)[0];
}
const webpackPlugins = [
// plugins require the same webpack instance
'webpack/lib/LibraryTemplatePlugin',
'webpack/lib/node/NodeTargetPlugin',
'webpack/lib/node/NodeTemplatePlugin',
'webpack/lib/NormalModule',
'webpack/lib/optimize/LimitChunkCountPlugin',
'webpack/lib/SingleEntryPlugin',
'webpack/lib/webworker/WebWorkerTemplatePlugin',
'webpack/lib/node/NodeEnvironmentPlugin',
'webpack/lib/ModuleFilenameHelpers',
'webpack/lib/GraphHelpers',
'webpack/lib/ExternalsPlugin',
'webpack/lib/web/FetchCompileAsyncWasmPlugin',
'webpack/lib/web/FetchCompileWasmPlugin',
'webpack/lib/runtime/StartupChunkDependenciesPlugin',
'webpack/lib/javascript/JavascriptModulesPlugin',
'webpack/lib/javascript/StartupHelpers',
'webpack/lib/util/identifier',
'webpack/lib/util/compileBooleanMatcher',
'webpack/lib/ModuleNotFoundError',
'webpack/lib/util/LazySet',
'webpack/lib/util/fs',
'webpack/lib/util/makeSerializable',
'webpack/lib/util/SortableSet',
'webpack/lib/dependencies/StaticExportsDependency',
'webpack/lib/dependencies/EntryDependency',
'webpack/lib/ModuleFactory',
'webpack/lib/dependencies/ModuleDependency',
'webpack/lib/util/create-schema-validation',
'webpack/lib/util/extractUrlAndGlobal',
'webpack/lib/Compilation',
'webpack/lib/util/semver',
'webpack/lib/WebpackError',
'webpack/lib/util/comparators',
'webpack/lib/runtime/StartupEntrypointRuntimeModule',
'webpack/lib/util/SetHelpers',
'webpack/lib/javascript/ChunkHelpers',
'webpack/lib/HotUpdateChunk',
];
export function getHookFiles() {
const webpackPlugins = [
// plugins require the same webpack instance
'webpack/lib/LibraryTemplatePlugin',
'webpack/lib/node/NodeTargetPlugin',
'webpack/lib/node/NodeTemplatePlugin',
'webpack/lib/NormalModule',
'webpack/lib/optimize/LimitChunkCountPlugin',
'webpack/lib/SingleEntryPlugin',
'webpack/lib/webworker/WebWorkerTemplatePlugin',
'webpack/lib/node/NodeEnvironmentPlugin',
'webpack/lib/ModuleFilenameHelpers',
'webpack/lib/GraphHelpers',
'webpack/lib/ExternalsPlugin',
'webpack/lib/web/FetchCompileAsyncWasmPlugin',
'webpack/lib/web/FetchCompileWasmPlugin',
];
const webpackDir = path.join(require.resolve('@ice/bundles/compiled/webpack'), '../');
const pluginMap = webpackPlugins.map((pluginPath) => {
return [pluginPath, pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker)\/)?/, webpackDir)];
});
const createPluginMapping = (pluginPath: string, withJsExtension = false) => [
withJsExtension ? `${pluginPath}.js` : pluginPath,
pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker|runtime|javascript|util|dependencies)\/)?/, webpackDir),
];
const pluginMap = webpackPlugins.map(pluginPath => createPluginMapping(pluginPath));
const pluginMapWithJs = webpackPlugins.map(pluginPath => createPluginMapping(pluginPath, true));
return [
['webpack', `${webpackDir}webpack-lib`],
@ -42,6 +71,7 @@ export function getHookFiles() {
['webpack/hot/only-dev-server', `${webpackDir}hot/only-dev-server`],
['webpack/hot/emitter', `${webpackDir}hot/emitter`],
...pluginMap,
...pluginMapWithJs,
];
}
@ -52,14 +82,11 @@ function hijackWebpack() {
// eslint-disable-next-line global-require
const mod = require('module');
const resolveFilename = mod._resolveFilename;
mod._resolveFilename = function (
request: string,
parent: any,
isMain: boolean,
options: any,
) {
mod._resolveFilename = function (request: string, parent: any, isMain: boolean, options: any) {
const hookResolved = hookPropertyMap.get(request);
if (hookResolved) request = hookResolved;
if (hookResolved) {
request = hookResolved;
}
return resolveFilename.call(mod, request, parent, isMain, options);
};
}

View File

@ -64,6 +64,7 @@ class Config {
},
},
redirectImports,
bundler: 'esbuild',
});
if (!error) {
this.status = 'RESOLVED';

View File

@ -27,6 +27,8 @@ import getCSSModuleIdent from '../utils/getCSSModuleIdent.js';
import { scanImports } from './analyze.js';
import type { PreBundleDepsMetaData } from './preBundleDeps.js';
import preBundleDeps from './preBundleDeps.js';
import { WebpackServerCompiler } from './webpackServerCompiler/compiler.js';
import VirualAssetPlugin from './webpackServerCompiler/virtualAssetPlugin.js';
const logger = createLogger('server-compiler');
@ -100,7 +102,6 @@ export function createServerCompiler(options: Options) {
const externals = task.config?.externals || {};
const sourceMap = task.config?.sourceMap;
const dev = command === 'start';
// Filter empty alias.
const { ignores, alias } = filterAlias(task.config?.alias || {});
@ -115,7 +116,9 @@ export function createServerCompiler(options: Options) {
enableEnv = false,
transformEnv = true,
isServer = true,
bundler,
} = {}) => {
server.bundler = bundler ?? server.bundler ?? 'esbuild';
let preBundleDepsMetadata: PreBundleDepsMetaData;
let swcOptions = merge({}, {
// Only get the `compilationConfig` from task config.
@ -143,7 +146,8 @@ export function createServerCompiler(options: Options) {
swcOptions,
redirectImports,
getRoutesFile,
}, 'esbuild', { isServer });
}, server.bundler as 'esbuild', { isServer });
const define = getRuntimeDefination(task.config?.define || {}, runtimeDefineVars, transformEnv);
if (preBundle) {
const plugins = [
@ -169,7 +173,6 @@ export function createServerCompiler(options: Options) {
plugins,
});
}
const format = customBuildOptions?.format || 'esm';
let buildOptions: esbuild.BuildOptions = {
@ -236,27 +239,44 @@ export function createServerCompiler(options: Options) {
}
const startTime = new Date().getTime();
logger.debug('[esbuild]', `start compile for: ${JSON.stringify(buildOptions.entryPoints)}`);
logger.debug(`[${server.bundler}]`, `start compile for: ${JSON.stringify(buildOptions.entryPoints)}`);
try {
let esbuildResult: esbuild.BuildResult;
let bundleResult: any;
let context: esbuild.BuildContext;
if (dev) {
context = await esbuild.context(buildOptions);
esbuildResult = await context.rebuild();
bundleResult = await context.rebuild();
} else {
esbuildResult = await esbuild.build(buildOptions);
switch (server.bundler) {
case 'webpack':
const webpackServerCompiler = new WebpackServerCompiler({
...buildOptions,
externals,
compileIncludes: task.config.compileIncludes,
plugins: [compilationInfo && new VirualAssetPlugin({ compilationInfo, rootDir }), ...transformPlugins],
rootDir,
userServerConfig: server,
runtimeDefineVars,
});
bundleResult = (await webpackServerCompiler.build())?.compilation;
break;
case 'esbuild':
default:
bundleResult = await esbuild.build(buildOptions);
break;
}
}
logger.debug('[esbuild]', `time cost: ${new Date().getTime() - startTime}ms`);
logger.debug(`[${server.bundler}]`, `time cost: ${new Date().getTime() - startTime}ms`);
const esm = server?.format === 'esm';
const outJSExtension = esm ? '.mjs' : '.cjs';
const serverEntry = path.join(rootDir, task.config.outputDir, SERVER_OUTPUT_DIR, `index${outJSExtension}`);
if (removeOutputs && esbuildResult.metafile) {
if (removeOutputs && bundleResult.metafile) {
// build/server/a.mjs -> a.mjs
const currentOutputFiles = Object.keys(esbuildResult.metafile.outputs)
const currentOutputFiles = Object.keys(bundleResult.metafile.outputs)
.map(output => output.replace(formatPath(`${path.relative(rootDir, buildOptions.outdir)}${path.sep}`), ''));
const allOutputFiles = fg.sync('**', { cwd: buildOptions.outdir });
const outdatedFiles = difference(allOutputFiles, currentOutputFiles);
@ -264,7 +284,7 @@ export function createServerCompiler(options: Options) {
}
return {
...esbuildResult,
...bundleResult,
context,
serverEntry,
};

View File

@ -0,0 +1,116 @@
import path from 'path';
import { fileURLToPath } from 'url';
import webpack from '@ice/bundles/compiled/webpack/index.js';
import { getWebpackConfig } from '@ice/webpack-config';
import type { UserConfig } from '../../types/userConfig.js';
import { logger } from '../../utils/logger.js';
import { getExpandedEnvs } from '../../utils/runtimeEnv.js';
import { RUNTIME_TMP_DIR } from '../../constant.js';
const _dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
export class WebpackServerCompiler {
private options;
constructor(options: any) {
this.options = options;
}
private async createWebpackConfig(options: {
userServerConfig: UserConfig['server'];
rootDir: string;
[key: string]: any;
}) {
const { userServerConfig } = options;
const { webpackConfig = {} } = userServerConfig;
const definitions = await this.getEsbuildInject();
return getWebpackConfig({
config: {
mode: 'production',
entry: options.entryPoints,
alias: options.alias,
webpackTarget: 'node12.20',
externalsPresets: {
node: false,
},
output: {
filename: `[name].${options.format === 'esm' ? 'mjs' : 'cjs'}`,
path: options.outdir,
// align the output with former esbuild
chunkFormat: false,
clean: true,
library: {
type: 'commonjs2',
},
...(webpackConfig.output as any),
},
plugins: [...options.plugins, ...(webpackConfig.plugins || [])] as any,
externals: options.externals,
outputDir: options.outdir,
enableCache: false,
loaders: [
// Use esbuild to compile JavaScript & TypeScript
{
// // Match `.js`, `.jsx`, `.ts` or `.tsx` files
test: /\.m?[jt]sx?$/,
use: [path.resolve(_dirname, 'removeMagicString.js')],
},
...(webpackConfig.module?.rules || []),
],
useDevServer: false,
analyzer: false,
assetsManifest: false,
define: options.define,
optimization: { ...webpackConfig.optimization } as any,
minify: options.minify,
compileIncludes: webpackConfig.transformInclude,
swcOptions: {
compilationConfig: {
jsc: {
externalHelpers: false,
transform: {
react: {
runtime: options.jsx,
importSource: '@ice/runtime/react',
},
},
},
},
},
definitions,
},
rootDir: options.rootDir,
webpack: webpack as any,
runtimeTmpDir: RUNTIME_TMP_DIR,
userConfigHash: '',
getExpandedEnvs,
isServer: true,
});
}
private async getEsbuildInject(): Promise<Record<string, string | string[]>> {
const provideRecord = {};
const allInjects = await Promise.all(this.options.inject.map((inj) => import(inj)));
allInjects.forEach((injs, index) => {
Object.keys(injs).forEach((key) => {
provideRecord[key] = [this.options.inject[index], key];
});
});
return provideRecord;
}
async build(): Promise<any> {
const config = await this.createWebpackConfig(this.options);
return new Promise((resolve, reject) => {
webpack(config as any, (err, stats) => {
if (err || stats?.hasErrors?.()) {
logger.error(err || stats.toString());
reject(err || stats.toString());
process.exit(1);
} else {
resolve(stats);
}
});
});
}
}

View File

@ -0,0 +1,9 @@
export default function (source: string) {
const result = source.replace(
/webpackChunkName:\s*["'][^"']+["']/g,
'webpackMode: "eager"',
);
// Return the modified source
return result;
}

View File

@ -0,0 +1,42 @@
import { type AssetsManifest } from '@ice/runtime/types';
import NormalModule from '@ice/bundles/compiled/webpack/NormalModule.js';
import { type Compiler } from '@ice/bundles/compiled/webpack';
interface CompilationInfo {
assetsManifest?: AssetsManifest;
}
const PLUGIN_NAME = 'VirtualManifestPlugin';
class VirtualManifestPlugin {
private rootDir: string;
private compilationInfo: CompilationInfo | (() => CompilationInfo);
constructor(options) {
this.rootDir = options.rootDir;
this.compilationInfo = options.compilationInfo;
}
apply(compiler: Compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => {
NormalModule.getCompilationHooks(compilation)
.readResource.for('virtual')
.tap(PLUGIN_NAME, () => {
const manifest = this.generateManifestContent();
return JSON.stringify(manifest?.assetsManifest || '');
});
normalModuleFactory.hooks.beforeResolve.tap(PLUGIN_NAME, (resolveData) => {
if (resolveData.request === 'virtual:assets-manifest.json') {
resolveData.assertions = {
type: 'json',
};
}
});
});
}
generateManifestContent() {
return typeof this.compilationInfo === 'function' ? this.compilationInfo() : this.compilationInfo;
}
}
export default VirtualManifestPlugin;

View File

@ -60,6 +60,8 @@ export interface CompilerOptions {
runtimeDefineVars?: Record<string, string>;
enableEnv?: boolean;
isServer?: boolean;
/** @default esbuild */
bundler?: 'webpack' | 'esbuild';
}
export type ServerCompiler = (

View File

@ -2,6 +2,7 @@ import type { DefineRouteFunction, RouteItem } from '@ice/route-manifest';
import type { PluginList } from 'build-scripts';
import type { UnpluginOptions } from '@ice/bundles/compiled/unplugin/index.js';
import type { ProcessOptions } from '@ice/bundles';
import type { Configuration as WebpackConfiguration } from '@ice/bundles/compiled/webpack';
import type { Config, ModifyWebpackConfig, MinimizerOptions } from '@ice/shared-config/types';
import type { OverwritePluginAPI } from './plugin';
@ -49,6 +50,19 @@ interface Fetcher {
method?: string;
}
export type HtmlGeneratingMode = 'cleanUrl' | 'compat';
export interface HtmlGeneratingConfig {
/**
* Control how file structure to generation html.
* Route: '/' '/foo' '/foo/bar'
* `cleanUrl`: '/index.html' '/foo.html' '/foo/bar.html'
* `compat`: '/index.html' '/foo/index.html' '/foo/bar/index.html'
* @default 'cleanUrl'
*/
mode?: HtmlGeneratingMode;
}
export interface UserConfig {
/**
* Feature polyfill for legacy browsers, which can not polyfilled by core-js.
@ -178,7 +192,7 @@ export interface UserConfig {
* HTML will not be generated when build, If it is false.
* @see https://v3.ice.work/docs/guide/basic/config#htmlgenerating
*/
htmlGenerating?: boolean;
htmlGenerating?: boolean | HtmlGeneratingConfig;
/**
* Choose a style of souce mapping to enhance the debugging process.
* @see https://v3.ice.work/docs/guide/basic/config#sourcemap
@ -229,6 +243,22 @@ export interface UserConfig {
* externals config for server bundle
*/
externals?: string[];
/**
* bundler for server bundle, support webpack and esbuild
* @default esbuild
*/
bundler?: 'webpack' | 'esbuild';
/**
* webpack config, only works when bundler is webpack
*/
webpackConfig?: Pick<WebpackConfiguration, 'plugins' | 'optimization' | 'output' | 'module'> & {
/**
* we exclude the node_modules/* by default
*
* use this if you need to transform some packages inside of node_modues
*/
transformInclude?: Array<RegExp | string>;
};
};
/**
* Optimization options for build.

View File

@ -1,6 +1,7 @@
import * as path from 'path';
import fse from 'fs-extra';
import type { ServerContext, RenderMode, AppConfig } from '@ice/runtime';
import type { HtmlGeneratingMode } from '../types/index.js';
import dynamicImport from './dynamicImport.js';
import { logger } from './logger.js';
import type RouteManifest from './routeManifest.js';
@ -12,6 +13,7 @@ interface Options {
documentOnly: boolean;
routeType: AppConfig['router']['type'];
renderMode?: RenderMode;
generatingMode?: HtmlGeneratingMode;
routeManifest: RouteManifest;
}
@ -28,6 +30,7 @@ export default async function generateEntry(options: Options): Promise<EntryResu
renderMode,
routeType,
routeManifest,
generatingMode,
} = options;
let serverEntry: string;
@ -48,7 +51,7 @@ export default async function generateEntry(options: Options): Promise<EntryResu
} = await renderEntry({ routePath, serverEntry, documentOnly, renderMode });
const generateOptions = { rootDir, routePath, outputDir };
if (htmlOutput) {
const path = await generateFilePath({ ...generateOptions, type: 'html' });
const path = await generateFilePath({ ...generateOptions, type: 'html', generatingMode });
await writeFile(
path,
htmlOutput,
@ -72,8 +75,14 @@ export function escapeRoutePath(str: string) {
return str.replace(/\/(:|\*)/g, '/$');
}
function formatFilePath(routePath: string, type: 'js' | 'html' | 'js.map'): string {
return routePath === '/' ? `index.${type}` : `${escapeRoutePath(routePath)}.${type}`;
function formatFilePath(routePath: string, type: 'js' | 'html' | 'js.map', generatingMode?: HtmlGeneratingMode): string {
if (routePath === '/') {
return `index.${type}`;
}
if (type === 'html' && generatingMode === 'compat') {
return `${escapeRoutePath(routePath)}/index.${type}`;
}
return `${escapeRoutePath(routePath)}.${type}`;
}
async function generateFilePath(
@ -82,14 +91,16 @@ async function generateFilePath(
routePath,
outputDir,
type,
generatingMode,
}: {
rootDir: string;
routePath: string;
outputDir: string;
type: 'js' | 'html' | 'js.map' ;
generatingMode?: HtmlGeneratingMode;
},
) {
const fileName = formatFilePath(routePath, type);
const fileName = formatFilePath(routePath, type, generatingMode);
if (fse.existsSync(path.join(rootDir, 'public', fileName))) {
logger.warn(`${fileName} is overwrite by framework, rename file name if it is necessary.`);
}

View File

@ -2,7 +2,7 @@ import * as path from 'path';
import fg from 'fast-glob';
export default function hasDocument(rootDir: string) {
const document = fg.sync('document.{tsx,ts,jsx.js}', {
const document = fg.sync('document.{tsx,ts,jsx,js}', {
cwd: path.join(rootDir, 'src'),
});
return document.length > 0;

View File

@ -59,7 +59,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);

View File

@ -1,5 +1,11 @@
# @ice/jsx-runtime
## 0.3.1
### Patch Changes
- 2e274966: fix: export createElement for backward compatibility
## 0.3.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/jsx-runtime",
"version": "0.3.0",
"version": "0.3.1",
"description": "JSX runtime for ice.",
"files": [
"esm",

View File

@ -1,5 +1,21 @@
# @ice/miniapp-loader
## 1.2.2
### Patch Changes
- Updated dependencies [2f73084d]
- @ice/bundles@0.2.9
## 1.2.1
### Patch Changes
- Updated dependencies [97cb2046]
- Updated dependencies [97cb2046]
- Updated dependencies [a0099df5]
- @ice/bundles@0.2.8
## 1.2.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/miniapp-loader",
"version": "1.2.0",
"version": "1.2.2",
"description": "webpack loader for miniapps.",
"main": "./lib/page.js",
"files": [

View File

@ -1,5 +1,17 @@
# @ice/miniapp-react-dom
## 1.1.2
### Patch Changes
- @ice/miniapp-runtime@1.2.2
## 1.1.1
### Patch Changes
- @ice/miniapp-runtime@1.2.1
## 1.1.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/miniapp-react-dom",
"version": "1.1.0",
"version": "1.1.2",
"description": "like react-dom, but for miniapps.",
"type": "module",
"types": "./esm/index.d.ts",

View File

@ -1,5 +1,19 @@
# Changelog
## 1.2.2
### Patch Changes
- Updated dependencies [a0099df5]
- @ice/runtime@1.5.2
## 1.2.1
### Patch Changes
- Updated dependencies [2e274966]
- @ice/runtime@1.5.1
## 1.2.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/miniapp-runtime",
"version": "1.2.0",
"version": "1.2.2",
"description": "ice runtime for miniapps.",
"type": "module",
"types": "./esm/index.d.ts",

View File

@ -39,7 +39,7 @@
"plugin"
],
"dependencies": {
"@ice/jsx-runtime": "^0.3.0",
"@ice/jsx-runtime": "^0.3.1",
"@swc/helpers": "^0.5.15",
"accept-language-parser": "^1.5.0",
"universal-cookie": "^4.0.4",
@ -56,8 +56,8 @@
"webpack-dev-server": "4.15.0"
},
"peerDependencies": {
"@ice/app": "^3.5.0",
"@ice/runtime": "^1.5.0"
"@ice/app": "^3.6.1",
"@ice/runtime": "^1.5.2"
},
"publishConfig": {
"access": "public"

View File

@ -1,5 +1,12 @@
# Changelog
## 1.2.0
### Minor Changes
- 2f73084d: feat: support framework provider
- 2f73084d: feat: support custom AppRoute
## 1.1.1
### Patch Changes

1
packages/plugin-icestark/Context.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export * from './esm/runtime/Context';

View File

@ -1,6 +1,6 @@
{
"name": "@ice/plugin-icestark",
"version": "1.1.1",
"version": "1.2.0",
"description": "Easy use `icestark` in icejs.",
"author": "ice-admin@alibaba-inc.com",
"homepage": "",
@ -18,6 +18,11 @@
"import": "./esm/index.js",
"default": "./esm/index.js"
},
"./Context": {
"types": "./esm/runtime/Context.d.ts",
"import": "./esm/runtime/Context.js",
"default": "./esm/runtime/Context.js"
},
"./esm/runtime/child": {
"types": "./esm/runtime/child.d.ts",
"import": "./esm/runtime/child.js",
@ -44,7 +49,7 @@
"@ice/stark-app": "^1.2.0"
},
"devDependencies": {
"@ice/app": "^3.3.2",
"@ice/app": "^3.6.1",
"@ice/runtime": "^1.2.9",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0"

View File

@ -9,12 +9,12 @@ const PLUGIN_NAME = '@ice/plugin-icestark';
const plugin: Plugin<PluginOptions> = ({ type, library }) => ({
name: PLUGIN_NAME,
setup: ({ onGetConfig, context, generator, modifyUserConfig }) => {
const libraryName = library || context.pkg?.name as string || 'microApp';
onGetConfig((config) => {
config.configureWebpack ??= [];
config.configureWebpack.push((webpackConfig) => {
if (type === 'child') {
const { pkg } = context;
webpackConfig.output.library = library || pkg.name as string || 'microApp';
webpackConfig.output.library = libraryName;
webpackConfig.output.libraryTarget = 'umd';
}
return webpackConfig;
@ -34,6 +34,10 @@ const plugin: Plugin<PluginOptions> = ({ type, library }) => ({
if (!window.ICESTARK?.root && !window.__POWERED_BY_QIANKUN__) {
root = render();
}
// Set library name
if (typeof window !== 'undefined' && window.ICESTARK) {
window.ICESTARK.library = ${JSON.stringify(libraryName)};
}
// For qiankun lifecycle validation.
export async function bootstrap(props) {

View File

@ -0,0 +1,7 @@
import { createContext, useContext } from 'react';
export const FrameworkContext = createContext({});
export const useFrameworkContext = <T extends object>(): T => {
return useContext(FrameworkContext) as T;
};

View File

@ -1,13 +1,19 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import type { RuntimePlugin } from '@ice/runtime/types';
import type { LifecycleOptions } from '../types';
import { FrameworkContext } from './Context.js';
const runtime: RuntimePlugin<LifecycleOptions> = ({ setRender }, runtimeOptions) => {
if (runtimeOptions?.container) {
setRender((_, element) => {
// Replace render root when app rendered as a child app.
const root = ReactDOM.createRoot(runtimeOptions.container);
root.render(element);
root.render(
<FrameworkContext.Provider value={{ ...(runtimeOptions.customProps || {}) }}>
{element}
</FrameworkContext.Provider>,
);
return root;
});
}

View File

@ -1,100 +1,98 @@
import * as React from 'react';
import { AppRouter, AppRoute } from '@ice/stark';
import type { RuntimePlugin, ClientAppRouterProps } from '@ice/runtime/types';
import type { RouteInfo, AppConfig } from '../types';
import type { AppRouterProps } from '@ice/stark/lib/AppRouter';
import type { RouteInfo, AppConfig, FrameworkConfig } from '../types';
const { useState, useEffect } = React;
const runtime: RuntimePlugin = ({ getAppRouter, setAppRouter, appContext }) => {
const { appExport, appData } = appContext;
const OriginalRouter = getAppRouter();
const { layout, getApps, appRouter } = appExport?.icestark || {};
const { layout, getApps, appRouter, AppRoute: CustomizeAppRoute } = (appExport?.icestark || {}) as FrameworkConfig;
if (getApps) {
const FrameworkRouter = (props: ClientAppRouterProps) => {
const [routeInfo, setRouteInfo] = useState<RouteInfo>({});
const [appEnter, setAppEnter] = useState<AppConfig>({});
const [appLeave, setAppLeave] = useState<AppConfig>({});
const [apps, setApps] = useState([]);
const FrameworkLayout = layout || (({ children }) => (<>{children}</>));
const appInfo = {
pathname: routeInfo.pathname ||
(typeof window !== 'undefined' && window.location.pathname),
routeInfo,
appEnter,
appLeave,
updateApps: setApps,
};
useEffect(() => {
(async () => {
const appList = await getApps(appData);
setApps(appList);
})();
}, []);
function handleRouteChange(pathname: string, query: Record<string, string>, hash: string, routeType: string) {
setRouteInfo({ pathname, query, hash, routeType });
}
function handleAppLeave(config: AppConfig) {
setAppLeave(config);
}
function handleAppEnter(config: AppConfig) {
setAppEnter(config);
}
return (
<FrameworkLayout {...appInfo}>
{apps && (
<AppRouter
{...appRouter}
onRouteChange={handleRouteChange}
onAppEnter={handleAppEnter}
onAppLeave={handleAppLeave}
>
{apps.map((item: AppConfig, idx: number) => {
return (
<AppRoute
key={idx}
{...item}
/>
);
})}
<AppRoute
path="/"
location={props.location}
render={() => {
const { routerContext } = props;
routerContext.routes = [
...routerContext.routes,
{
path: '*',
Component: () => (
process.env.NODE_ENV === 'development'
? <div>Add $.tsx to folder pages as a 404 component</div>
: null
),
},
];
const routerProps = {
...props,
routerContext,
};
return <OriginalRouter {...routerProps} />;
}}
/>
</AppRouter>
)}
</FrameworkLayout>
);
};
setAppRouter(FrameworkRouter);
} else {
if (!getApps) {
console.warn(`
[plugin-icestark]: appConfig.icestark.getApps should be not empty if this is an framework app.
see https://ice.work/docs/guide/advanced/icestark/
`);
return;
}
const FrameworkRouter = (props: ClientAppRouterProps) => {
const [routeInfo, setRouteInfo] = useState<RouteInfo>({});
const [appEnter, setAppEnter] = useState<AppConfig>({});
const [appLeave, setAppLeave] = useState<AppConfig>({});
const [apps, setApps] = useState<AppConfig[] | null>(null);
const FrameworkLayout = layout || React.Fragment;
const appInfo = {
pathname: routeInfo.pathname || (typeof window !== 'undefined' ? window.location.pathname : ''),
routeInfo,
appEnter,
appLeave,
updateApps: setApps,
};
useEffect(() => {
const fetchApps = async () => {
try {
const appList = await getApps(appData);
setApps(appList);
} catch (error) {
console.error('[plugin-icestark]: Failed to fetch apps', error);
}
};
fetchApps();
}, []);
const handleRouteChange: AppRouterProps['onRouteChange'] = (pathname, query, hash, routeType) => {
setRouteInfo({ pathname, query, hash, routeType });
};
const handleAppLeave: AppRouterProps['onAppLeave'] = (config) => setAppLeave(config);
const handleAppEnter: AppRouterProps['onAppEnter'] = (config) => setAppEnter(config);
const AppRouteComponent = CustomizeAppRoute || AppRoute;
const appRouterProps: AppRouterProps = {
...appRouter,
onRouteChange: handleRouteChange,
onAppEnter: handleAppEnter,
onAppLeave: handleAppLeave,
};
return (
<FrameworkLayout {...appInfo}>
{apps && (
<AppRouter {...appRouterProps}>
{apps?.map((item: AppConfig, idx: number) => (
<AppRouteComponent key={idx} {...item} />
))}
<AppRouteComponent
activePath="/"
location={props.location}
render={() => {
const { routerContext } = props;
routerContext.routes = [
...routerContext.routes,
{
path: '*',
Component: () => (
process.env.NODE_ENV === 'development'
? <div>Add $.tsx to folder pages as a 404 component</div>
: null
),
},
];
return <OriginalRouter {...props} routerContext={routerContext} />;
}}
/>
</AppRouter>
)}
</FrameworkLayout>
);
};
setAppRouter(FrameworkRouter);
};
export default runtime;

View File

@ -1,10 +1,11 @@
import type { ComponentType } from 'react';
import type { CompatibleAppConfig } from '@ice/stark/lib/AppRoute';
import type { AppRouterProps } from '@ice/stark/lib/AppRouter';
import type { AppRoute } from '@ice/stark';
export interface RouteInfo {
pathname?: string;
query?: Record<string, string>;
query?: object;
hash?: string;
routeType?: string;
}
@ -17,6 +18,7 @@ export interface FrameworkConfig {
getApps?: (data?: any) => (AppConfig[] | Promise<AppConfig[]>);
appRouter?: Omit<AppRouterProps, 'onRouteChange' | 'onAppEnter' | 'onAppLeave'>;
layout?: ComponentType<any>;
AppRoute?: typeof AppRoute;
}
export interface LifecycleOptions {

View File

@ -1,5 +1,33 @@
# Changelog
## 1.2.3
### Patch Changes
- Updated dependencies [2f73084d]
- @ice/bundles@0.2.9
- @ice/miniapp-loader@1.2.2
## 1.2.2
### Patch Changes
- Updated dependencies [97cb2046]
- Updated dependencies [97cb2046]
- Updated dependencies [a0099df5]
- @ice/bundles@0.2.8
- @ice/miniapp-loader@1.2.1
- @ice/miniapp-runtime@1.2.2
- @ice/miniapp-react-dom@1.1.2
## 1.2.1
### Patch Changes
- cce46e9b: fix: should check skeleton file existed before read
- @ice/miniapp-runtime@1.2.1
- @ice/miniapp-react-dom@1.1.1
## 1.2.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/plugin-miniapp",
"version": "1.2.0",
"version": "1.2.3",
"description": "ice.js plugin for miniapp.",
"license": "MIT",
"type": "module",
@ -50,8 +50,8 @@
"sax": "^1.2.4"
},
"devDependencies": {
"@ice/app": "^3.5.0",
"@ice/runtime": "^1.5.0",
"@ice/app": "^3.6.1",
"@ice/runtime": "^1.5.2",
"webpack": "^5.88.0"
},
"repository": {

View File

@ -10,6 +10,7 @@ import loaderUtils from '@ice/bundles/compiled/loader-utils/index.js';
import type { Compilation, Compiler } from '@ice/bundles/compiled/webpack/index.js';
import EntryDependency from '@ice/bundles/compiled/webpack/EntryDependency.js';
import type { NestedRouteManifest } from '@ice/route-manifest';
import SingleEntryDependency from '../dependencies/SingleEntryDependency.js';
import { componentConfig } from '../utils/component.js';
@ -93,7 +94,7 @@ export default class MiniPlugin {
appConfig: MiniappAppConfig;
/** app、页面、组件的配置集合 */
filesConfig: IMiniFilesConfig = {};
routeManifest: Record<string, any>[] = [];
routeManifest: NestedRouteManifest[] = [];
isWatch = false;
/** 页面列表 */
pages = new Set<IComponent>();
@ -162,7 +163,7 @@ export default class MiniPlugin {
this.context = compiler.context;
this.appEntry = this.getAppEntry(compiler);
const { commonChunks, combination, framework, isBuildPlugin, newBlended } = this.options;
const { commonChunks, combination, framework, isBuildPlugin, newBlended, sourceDir } = this.options;
const { addChunkPages, onCompilerMake, modifyBuildAssets, onParseCreateElement } = combination.config;
@ -281,7 +282,7 @@ export default class MiniPlugin {
: this.pageLoaderName;
if (!isLoaderExist(module.loaders, loaderName)) {
const routeInfo = this.routeManifest.find(route => path.join('pages', route.id) === module.name);
const routeInfo = this.routeManifest.find(route => path.join(sourceDir, 'pages', route.file) === module.resource);
const hasExportData = routeInfo?.exports?.includes('dataLoader');
const hasExportConfig = routeInfo?.exports?.includes('pageConfig');
module.loaders.unshift({

View File

@ -92,10 +92,11 @@ export default class NormalModulesPlugin {
const [type, prop] = node.arguments;
if (!type) return;
// @ts-expect-error
const componentName = type.name;
// @ts-expect-error
if (type.value) {
// @ts-expect-error
this.onParseCreateElement?.(type.value, componentConfig);
// @ts-ignore
currentModule.elementNameSet.add(type.value);

View File

@ -1,5 +1,12 @@
# Changelog
## 0.4.0
### Minor Changes
- Updated dependencies [bbeeaf5d]
- rax-compat@0.4.0
## 0.3.2
### Patch Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/plugin-rax-compat",
"version": "0.3.2",
"version": "0.4.0",
"description": "Provide rax compat support for ice.js",
"license": "MIT",
"type": "module",
@ -25,7 +25,7 @@
"consola": "^2.15.3",
"css": "^2.2.1",
"lodash-es": "^4.17.21",
"rax-compat": "^0.3.0",
"rax-compat": "^0.4.0",
"style-unit": "^3.0.5",
"stylesheet-loader": "^0.9.1"
},
@ -46,4 +46,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@ -8,7 +8,7 @@ interface RequestResult<R, P extends any[]> extends Result<R, P> {
requestAsync: Result<R, P>['runAsync'];
}
export function useRequest<TData, TParams extends any[] = []>(
export function useRequest<TData, TParams extends any[]>(
service: string | AxiosRequestConfig | Service<TData, TParams>,
options?: Options<TData, TParams>,
plugins?: Plugin<TData, TParams>[]) {

View File

@ -41,7 +41,7 @@
"dependencies": {
"@ice/store": "^2.0.3",
"fast-glob": "^3.2.11",
"micromatch": "^4.0.5"
"micromatch": "^4.0.8"
},
"devDependencies": {
"@ice/app": "workspace:^",

View File

@ -1,5 +1,11 @@
# Changelog
## 0.4.0
### Minor Changes
- bbeeaf5d: fix: fix svg
## 0.3.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "rax-compat",
"version": "0.3.0",
"version": "0.4.0",
"description": "Rax compatible mode, running rax project on the react runtime.",
"files": [
"esm",

View File

@ -17,7 +17,7 @@ export function createJSXElementFactory(factory: typeof ElementFactory) {
// Compat for props.
if (isRealDOM) {
// Only the dom needs to be transformed, not the components.
rest = transformProps(rest);
rest = transformProps(type, rest);
// Delete props on real dom that are not allowed in react.
delete rest.onAppear;

View File

@ -24,6 +24,161 @@ const possibleStandardNames = [
// meta
'charSet',
'dangerouslySetInnerHTML',
'strokeWidth',
// SVG
'accentHeight',
'accumulate',
'additive',
'alignmentBaseline',
'allowReorder',
'arabicForm',
'attributeName',
'attributeType',
'autoReverse',
'baseFrequency',
'baselineShift',
'baseProfile',
'calcMode',
'capHeight',
'clipPath',
'clipPathUnits',
'clipRule',
'colorInterpolation',
'colorInterpolationFilters',
'colorProfile',
'colorRendering',
'contentScriptType',
'contentStyleType',
'diffuseConstant',
'dominantBaseline',
'edgeMode',
'enableBackground',
'externalResourcesRequired',
'fillOpacity',
'fillRule',
'filterRes',
'filterUnits',
'floodOpacity',
'floodColor',
'fontFamily',
'fontSize',
'fontSizeAdjust',
'fontStretch',
'fontStyle',
'fontVariant',
'fontWeight',
'glyphName',
'glyphOrientationHorizontal',
'glyphOrientationVertical',
'glyphRef',
'gradientTransform',
'gradientUnits',
'hanging',
'horizAdvX',
'horizOriginX',
'ideographic',
'imageRendering',
'kernelMatrix',
'kernelUnitLength',
'keyPoints',
'keySplines',
'keyTimes',
'lengthAdjust',
'letterSpacing',
'lightingColor',
'limitingConeAngle',
'markerEnd',
'markerHeight',
'markerMid',
'markerStart',
'markerUnits',
'markerWidth',
'maskContentUnits',
'maskUnits',
'numOctaves',
'overlinePosition',
'overlineThickness',
'paintOrder',
'pathLength',
'patternContentUnits',
'patternTransform',
'patternUnits',
'pointerEvents',
'pointsAtX',
'pointsAtY',
'pointsAtZ',
'preserveAlpha',
'preserveAspectRatio',
'primitiveUnits',
'refX',
'refY',
'renderingIntent',
'repeatCount',
'repeatDur',
'requiredExtensions',
'requiredFeatures',
'shapeRendering',
'specularConstant',
'specularExponent',
'spreadMethod',
'startOffset',
'stdDeviation',
'stitchTiles',
'stopColor',
'stopOpacity',
'strikethroughPosition',
'strikethroughThickness',
'strokeDasharray',
'strokeDashoffset',
'strokeLinecap',
'strokeLinejoin',
'strokeMiterlimit',
'strokeWidth',
'strokeOpacity',
'suppressContentEditableWarning',
'suppressHydrationWarning',
'surfaceScale',
'systemLanguage',
'tableValues',
'targetX',
'targetY',
'textAnchor',
'textDecoration',
'textLength',
'textRendering',
'underlinePosition',
'underlineThickness',
'unicodeBidi',
'unicodeRange',
'unitsPerEm',
'vAlphabetic',
'vectorEffect',
'vertAdvY',
'vertOriginX',
'vertOriginY',
'vHanging',
'vIdeographic',
'viewBox',
'viewTarget',
'visibility',
'vMathematical',
'wordSpacing',
'writingMode',
'xChannelSelector',
'xHeight',
'xlinkActuate',
'xlinkArcrole',
'xlinkHref',
'xlinkRole',
'xlinkShow',
'xlinkTitle',
'xlinkType',
'xmlBase',
'xmlLang',
'xmlnsXlink',
'xmlSpace',
'yChannelSelector',
'zoomAndPan',
].reduce((records: Record<string, string>, iter: string) => {
records[iter.toLowerCase()] = iter;
return records;

View File

@ -8,7 +8,7 @@ function isEventLikeProp(key: string) {
return key.indexOf('on') === 0;
}
function transformProps(props: ComponentProps<JSXElementConstructor<any>>): Record<string, any> {
function transformProps(type: string, props: ComponentProps<JSXElementConstructor<any>>): Record<string, any> {
const transformedProps: Record<string, any> = {};
Object.keys(props).forEach((propKey: string) => {
let key: string = propKey;
@ -21,18 +21,20 @@ function transformProps(props: ComponentProps<JSXElementConstructor<any>>): Reco
// etc...
if (isEventLikeProp(lowerCasedPropKey)) {
if (registrationNameToReactEvent.has(lowerCasedPropKey)) {
const reactEvent: string = registrationNameToReactEvent.get(lowerCasedPropKey);
const reactEvent: string = registrationNameToReactEvent.get(lowerCasedPropKey) || '';
if (reactEvent !== propKey) {
key = reactEvent;
}
}
// eslint-disable-next-line no-prototype-builtins
} else if (possibleStandardNames.hasOwnProperty(lowerCasedPropKey)) {
// Transform attribute names that make it works properly in React.
key = possibleStandardNames[lowerCasedPropKey];
} else {
// Handles component props from rax-components like resizeMode, this causes React to throw a warning.
key = lowerCasedPropKey;
if (Object.prototype.hasOwnProperty.call(possibleStandardNames, lowerCasedPropKey)) {
// Transform attribute names that make it works properly in React.
key = possibleStandardNames[lowerCasedPropKey];
} else {
// Handles component props from rax-components like resizeMode, this causes React to throw a warning.
key = lowerCasedPropKey;
}
}
transformedProps[key] = val;

View File

@ -37,82 +37,85 @@ describe('events', () => {
});
it('should work with ontouchstart', () => {
expect(transformProps({
expect(transformProps('div', {
ontouchstart: () => { },
}).onTouchStart).toBeInstanceOf(Function);
});
it('should work with onclick', () => {
expect(transformProps({
expect(transformProps('div', {
onclick: () => { },
}).onClick).toBeInstanceOf(Function);
expect(transformProps({
expect(transformProps('div', {
onclick: () => { },
}).onclick).toBe(undefined);
});
it('should work with onClick', () => {
expect(transformProps({
expect(transformProps('div', {
onClick: () => { },
}).onClick).toBeInstanceOf(Function);
});
it('should work with ondblclick', () => {
expect(transformProps({
console.log('aaaaaa', transformProps('div', {
ondblclick: () => { },
}));
expect(transformProps('div', {
ondblclick: () => { },
}).onDoubleClick).toBeInstanceOf(Function);
expect(transformProps({
expect(transformProps('div', {
ondblclick: () => { },
}).ondblclick).toBe(undefined);
});
it('should work with onDblclick', () => {
expect(transformProps({
expect(transformProps('div', {
onDblclick: () => { },
}).onDoubleClick).toBeInstanceOf(Function);
expect(transformProps({
expect(transformProps('div', {
onDblclick: () => { },
}).onDblclick).toBe(undefined);
});
it('should work with onDoubleClick', () => {
expect(transformProps({
expect(transformProps('div', {
onDoubleClick: () => { },
}).onDoubleClick).toBeInstanceOf(Function);
});
it('should work with onmouseenter', () => {
expect(transformProps({
expect(transformProps('div', {
onmouseenter: () => { },
}).onMouseEnter).toBeInstanceOf(Function);
expect(transformProps({
expect(transformProps('div', {
onmouseenter: () => { },
}).onmouseenter).toBe(undefined);
});
it('should work with onpointerenter', () => {
expect(transformProps({
expect(transformProps('div', {
onpointerenter: () => { },
}).onPointerEnter).toBeInstanceOf(Function);
expect(transformProps({
expect(transformProps('div', {
onpointerenter: () => { },
}).onpointerenter).toBe(undefined);
});
it('should work with onchange', () => {
expect(transformProps({
expect(transformProps('div', {
onchange: () => { },
}).onChange).toBeInstanceOf(Function);
expect(transformProps({
expect(transformProps('div', {
onchange: () => { },
}).onchange).toBe(undefined);
});
it('should work with onbeforeinput', () => {
expect(transformProps({
expect(transformProps('div', {
onbeforeinput: () => { },
}).onBeforeInput).toBeInstanceOf(Function);
expect(transformProps({
expect(transformProps('div', {
onbeforeinput: () => { },
}).onbeforeinput).toBe(undefined);
});

View File

@ -7,44 +7,44 @@ import transformProps from '../src/props';
describe('props', () => {
it('should work with autofocus', () => {
expect(transformProps({
expect(transformProps('div', {
autofocus: true,
}).autoFocus).toBe(true);
});
it('should work with autoplay', () => {
expect(transformProps({
expect(transformProps('div', {
autoplay: true,
}).autoPlay).toBe(true);
});
it('should work with classname', () => {
expect(transformProps({
expect(transformProps('div', {
classname: 'class',
}).className).toBe('class');
});
it('should work with crossorigin', () => {
expect(transformProps({
expect(transformProps('div', {
crossorigin: 'xxx',
}).crossOrigin).toBe('xxx');
});
it('should work with maxlength', () => {
expect(transformProps({
expect(transformProps('div', {
maxlength: '10',
}).maxLength).toBe('10');
});
it('should work with inputmode', () => {
expect(transformProps({
expect(transformProps('div', {
inputmode: 'numeric',
}).inputMode).toBe('numeric');
});
it('should work with dangerouslySetInnerHTML', () => {
expect(
transformProps({
transformProps('div', {
dangerouslySetInnerHTML: { __html: 'xxx' },
}).dangerouslySetInnerHTML,
).toEqual({

View File

@ -1,5 +1,30 @@
# @ice/rspack-config
## 1.2.3
### Patch Changes
- Updated dependencies [2f73084d]
- @ice/bundles@0.2.9
- @ice/shared-config@1.3.2
## 1.2.2
### Patch Changes
- Updated dependencies [97cb2046]
- Updated dependencies [97cb2046]
- Updated dependencies [a0099df5]
- Updated dependencies [97cb2046]
- @ice/bundles@0.2.8
- @ice/shared-config@1.3.1
## 1.2.1
### Patch Changes
- 4130611d: fix: update splitChunk config
## 1.2.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/rspack-config",
"version": "1.2.0",
"version": "1.2.3",
"repository": "alibaba/ice",
"bugs": "https://github.com/alibaba/ice/issues",
"homepage": "https://v3.ice.work",
@ -15,8 +15,8 @@
"*.d.ts"
],
"dependencies": {
"@ice/bundles": "0.2.7",
"@ice/shared-config": "1.3.0"
"@ice/bundles": "0.2.9",
"@ice/shared-config": "1.3.2"
},
"devDependencies": {
"@rspack/core": "0.5.7"

View File

@ -2,7 +2,7 @@ import * as path from 'path';
import { createRequire } from 'module';
import { getDefineVars, getCompilerPlugins, getJsxTransformOptions, getAliasWithRoot, skipCompilePackages, getDevtoolValue } from '@ice/shared-config';
import type { Config, ModifyWebpackConfig, ImportDeclaration } from '@ice/shared-config/types';
import type { Configuration, rspack as Rspack } from '@rspack/core';
import type { Configuration, rspack as Rspack, RuleSetRule } from '@rspack/core';
import lodash from '@ice/bundles/compiled/lodash/index.js';
import { coreJsPath } from '@ice/bundles';
import RefreshPlugin from '@ice/bundles/esm/plugin-refresh.js';
@ -111,6 +111,7 @@ const getConfig: GetConfig = async (options) => {
https,
enableCopyPlugin,
cssExtensionAlias,
loaders,
} = taskConfig || {};
const isDev = mode === 'development';
const absoluteOutputDir = path.isAbsolute(outputDir) ? outputDir : path.join(rootDir, outputDir);
@ -205,6 +206,7 @@ const getConfig: GetConfig = async (options) => {
},
}];
}
const additionalRules = (loaders || []) as RuleSetRule[];
const config: Configuration = {
entry: entry || {
main: [path.join(rootDir, runtimeTmpDir, 'entry.client.tsx')],
@ -263,6 +265,7 @@ const getConfig: GetConfig = async (options) => {
postcssOptions: postcss,
extensionAlias: cssExtensionAlias ?? [],
}),
...additionalRules,
],
},
resolve: {

View File

@ -1,5 +1,19 @@
# @ice/runtime
## 1.5.2
### Patch Changes
- a0099df5: feat: post event for each suspense.
## 1.5.1
### Patch Changes
- 2e274966: feat: support hook of onBeforeHydrate
- Updated dependencies [2e274966]
- @ice/jsx-runtime@0.3.1
## 1.5.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/runtime",
"version": "1.5.0",
"version": "1.5.2",
"description": "Runtime module for ice.js",
"type": "module",
"types": "./esm/index.d.ts",
@ -54,7 +54,7 @@
"./esm/polyfills/abortcontroller.js"
],
"dependencies": {
"@ice/jsx-runtime": "^0.3.0",
"@ice/jsx-runtime": "^0.3.1",
"@ice/shared": "^1.1.0",
"@remix-run/router": "1.14.2",
"abortcontroller-polyfill": "1.7.5",

View File

@ -156,6 +156,11 @@ export function withSuspense(Component) {
<SuspenseContext.Provider value={suspenseState}>
<Component {...componentProps} />
<Data id={id} />
<script
dangerouslySetInnerHTML={{
__html: `window.dispatchEvent(new CustomEvent('ice-suspense', { detail: { id: ${id ? `'${id}'` : undefined} } }));`,
}}
/>
</SuspenseContext.Provider>
</React.Suspense>
);
@ -166,6 +171,11 @@ function Data(props) {
const data = useSuspenseData();
return (
<script dangerouslySetInnerHTML={{ __html: `!function(){window['${LOADER}'] = window['${LOADER}'] || {};window['${LOADER}']['${props.id}'] = ${JSON.stringify(data)}}();` }} />
<script
id={props.id && `suspense-script-${props.id}`}
dangerouslySetInnerHTML={{
__html: `!function(){window['${LOADER}'] = window['${LOADER}'] || {};window['${LOADER}']['${props.id}'] = ${JSON.stringify(data)}}();`,
}}
/>
);
}

View File

@ -1,5 +1,23 @@
# @ice/shared-config
## 1.3.2
### Patch Changes
- Updated dependencies [2f73084d]
- @ice/bundles@0.2.9
## 1.3.1
### Patch Changes
- 97cb2046: fix: use @ice/bundles instead of import webpack directly.
feat: support pass definitions for provide plugin.
- Updated dependencies [97cb2046]
- Updated dependencies [97cb2046]
- Updated dependencies [a0099df5]
- @ice/bundles@0.2.8
## 1.3.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/shared-config",
"version": "1.3.0",
"version": "1.3.2",
"repository": "alibaba/ice",
"bugs": "https://github.com/alibaba/ice/issues",
"homepage": "https://v3.ice.work",
@ -17,7 +17,7 @@
"*.d.ts"
],
"dependencies": {
"@ice/bundles": "0.2.7",
"@ice/bundles": "0.2.9",
"@rollup/pluginutils": "^4.2.0",
"browserslist": "^4.22.1",
"consola": "^2.15.3",

View File

@ -157,6 +157,8 @@ export interface Config {
enableCache?: boolean;
isServer?: boolean;
cacheDir?: string;
tsCheckerOptions?: ForkTsCheckerWebpackPluginOptions;
@ -233,4 +235,10 @@ export interface Config {
useDataLoader?: boolean;
optimizePackageImports?: string[];
definitions?: Record<string, string | string[]>;
externalsPresets?: Configuration['externalsPresets'];
webpackTarget?: Configuration['target'];
}

View File

@ -45,13 +45,13 @@ const compilationPlugin = (options: Options): UnpluginOptions => {
} = options;
const { removeExportExprs, compilationConfig, keepExports, nodeTransform } = swcOptions;
const compileRegex = [...compileIncludes, ...COMPILE_DEPS].map((includeRule) => {
return includeRule instanceof RegExp ? includeRule : new RegExp(includeRule);
});
function isRouteEntry(id: string) {
const routes = getRoutesFile();
const matched = routes.find(route => {
return id.indexOf(route) > -1;
});

View File

@ -1,5 +1,26 @@
# Changelog
## 1.2.2
### Patch Changes
- Updated dependencies [2f73084d]
- @ice/bundles@0.2.9
- @ice/shared-config@1.3.2
## 1.2.1
### Patch Changes
- 97cb2046: fix: use @ice/bundles instead of import webpack directly.
feat: support pass definitions for provide plugin.
- Updated dependencies [97cb2046]
- Updated dependencies [97cb2046]
- Updated dependencies [a0099df5]
- Updated dependencies [97cb2046]
- @ice/bundles@0.2.8
- @ice/shared-config@1.3.1
## 1.2.0
### Minor Changes

View File

@ -1,13 +1,14 @@
{
"name": "@ice/webpack-config",
"version": "1.2.0",
"version": "1.2.2",
"repository": "alibaba/ice",
"bugs": "https://github.com/alibaba/ice/issues",
"homepage": "https://v3.ice.work",
"type": "module",
"main": "./esm/index.js",
"exports": {
".": "./esm/index.js"
".": "./esm/index.js",
"./*": "./*"
},
"files": [
"esm",
@ -15,8 +16,8 @@
"*.d.ts"
],
"dependencies": {
"@ice/shared-config": "1.3.0",
"@ice/bundles": "0.2.7",
"@ice/shared-config": "1.3.2",
"@ice/bundles": "0.2.9",
"fast-glob": "^3.2.11",
"process": "^0.11.10"
},

View File

@ -34,6 +34,7 @@ interface GetWebpackConfigOptions {
getExpandedEnvs: () => Record<string, string>;
runtimeDefineVars?: Record<string, any>;
getRoutesFile?: () => string[];
isServer?: boolean;
}
enum JSMinifier {
@ -70,6 +71,7 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
getExpandedEnvs,
runtimeDefineVars = {},
getRoutesFile,
isServer = false,
} = options;
const {
@ -110,14 +112,17 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
polyfill,
enableRpx2Vw = true,
enableEnv = true,
definitions = {},
webpackTarget,
externalsPresets,
} = config;
const absoluteOutputDir = path.isAbsolute(outputDir) ? outputDir : path.join(rootDir, outputDir);
const dev = mode !== 'production';
const hashKey = hash === true ? 'hash:8' : (hash || '');
const aliasWithRoot = getAliasWithRoot(rootDir, alias);
const defineVars = getDefineVars(config.define, runtimeDefineVars, getExpandedEnvs, webpack);
const defineVarsWithRuntime = getDefineVars(config.define, runtimeDefineVars, getExpandedEnvs, webpack);
const lazyCompilationConfig = dev && experimental?.lazyCompilation ? {
lazyCompilation: {
test: (module: NormalModule) => {
@ -129,8 +134,7 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
},
} : {};
// get compile plugins
const compilerWebpackPlugins = getCompilerPlugins(rootDir, config, 'webpack', { isServer: false });
const compilerWebpackPlugins = getCompilerPlugins(rootDir, config, 'webpack', { isServer });
const terserOptions: any = merge({
compress: {
ecma: 5,
@ -169,8 +173,11 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
enableEnv,
getRoutesFile,
});
const webpackConfig = {
mode,
target: webpackTarget,
externalsPresets,
experiments: {
layers: true,
cacheUnaffected: true,
@ -199,7 +206,7 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
rules: [
// Use webpack loader instead of webpack plugin which is generated by unplugin.
// Reason: https://github.com/unjs/unplugin/issues/154
{
{
test: compilation.transformInclude,
use: {
loader: require.resolve('@ice/shared-config/compilation-loader'),
@ -284,11 +291,10 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
}),
new webpack.ProvidePlugin({
process: require.resolve('process/browser'),
...definitions,
}),
new webpack.DefinePlugin({
...defineVars,
...runtimeDefineVars,
}),
// server don't need runtimeDefine
new webpack.DefinePlugin(isServer ? config.define : defineVarsWithRuntime),
assetsManifest && new AssetsManifestPlugin({
fileName: 'assets-manifest.json',
outputDir: path.join(rootDir, runtimeTmpDir),
@ -359,7 +365,6 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
// 原因:[managedPaths](https://webpack.js.org/configuration/other-options/#managedpaths) 在 tnpm / cnpm 安装的情况下失效,导致持久缓存在处理 node_modules
// 通过指定 [immutablePaths](https://webpack.js.org/configuration/other-options/#immutablepaths) 进行兼容
// 依赖路径中同时包含包名和版本号即可满足 immutablePaths 的使用
// 通过安装后的 package.json 中是否包含 __npminstall_done 字段来判断是否为 tnpm / cnpm 安装模式
if (require('../package.json').__npminstall_done) {
const nodeModulesPath = path.join(rootDir, 'node_modules');
@ -428,6 +433,7 @@ export function getWebpackConfig(options: GetWebpackConfigOptions): Configuratio
bundler: webpack,
enableRpx2Vw,
};
return [configCss, configAssets, ...(configureWebpack || [])]
.reduce((result, next: ModifyWebpackConfig<Configuration, typeof webpack>) => next(result, ctx), webpackConfig);
}

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,8 @@ describe(`build ${example}`, () => {
expect(bundleContent.includes('__IS_NODE__')).toBe(false);
expect(fs.existsSync(path.join(__dirname, `../../examples/${example}/build/favicon.ico`))).toBe(true);
expect(fs.existsSync(path.join(__dirname, `../../examples/${example}/build/js/data-loader.js`))).toBe(true);
expect(fs.existsSync(path.join(__dirname, `../../examples/${example}/build/index.html`))).toBe(true);
expect(fs.existsSync(path.join(__dirname, `../../examples/${example}/build/blog.html`))).toBe(true);
const jsonContent = fs.readFileSync(path.join(__dirname, `../../examples/${example}/build/assets-manifest.json`), 'utf-8');
expect(JSON.parse(jsonContent).pages.about.includes('js/framework.js')).toBeFalsy();
const dataLoaderPath = path.join(__dirname, `../../examples/${example}/build/js/data-loader.js`);
@ -69,6 +71,14 @@ describe(`build ${example}`, () => {
expect((await page.$$text('h2')).length).toEqual(0);
});
test('using "compat" html generating mode', async () => {
await buildFixture(example, {
config: 'compatHtml.config.mts',
});
expect(fs.existsSync(path.join(__dirname, `../../examples/${example}/build/index.html`))).toBeTruthy();
expect(fs.existsSync(path.join(__dirname, `../../examples/${example}/build/blog/index.html`))).toBeTruthy();
});
afterAll(async () => {
await browser.close();
});

Some files were not shown because too many files have changed in this diff Show More