Skip to content

feat: add MF Rspack standalone examples for basehref #100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions examples/basehref-examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# BaseHref Examples

This directory contains example applications demonstrating the BaseHref functionality implemented in the Zephyr packages project. These examples show how to properly handle base paths for different deployment scenarios across various bundlers.

## Examples

### Vite Example

A React application built with Vite, demonstrating:

- Base path detection and configuration
- HTML base tag injection
- URL construction with proper base path handling
- Runtime base path usage via virtual module
- Support for deploying to non-root paths

[View Vite Example](./vite-app)

### Webpack Example

A React application built with Webpack, demonstrating:

- Public path detection and configuration
- HTML base tag injection
- URL construction with proper base path handling
- Runtime base path usage via global variable
- Manifest file generation with path information
- Support for deploying to non-root paths

[View Webpack Example](./webpack-app)

## Functionality Overview

The BaseHref implementation provides a consistent way to handle application paths across different bundlers and deployment scenarios. It ensures proper path resolution for applications deployed to non-root paths or using CDNs.

### Key Features

1. **Path Normalization**: Consistent handling of paths across different formats (relative, absolute, URLs)
2. **Bundler Integration**: Support for Vite, Webpack, and Rspack configurations
3. **URL Construction**: Intelligent URL building with base path consideration
4. **Runtime Detection**: Client-side detection of base paths
5. **HTML Generation**: Proper HTML base tag handling

## Usage

Each example includes detailed instructions on how to:

1. Install dependencies
2. Run the application with different base paths
3. Build the application for production
4. Test the built application

## Deployment Considerations

When deploying applications to non-root paths (e.g., `/app/` instead of `/`), proper base path handling is critical for:

- Loading JavaScript and CSS files
- Resolving image and asset URLs
- Making API requests
- Client-side navigation
- Deep linking to routes

These examples demonstrate best practices for handling these scenarios across different bundlers.
6 changes: 6 additions & 0 deletions examples/basehref-examples/mf-rspack-app/host/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"presets": ["@babel/preset-typescript", "@babel/preset-react", "@babel/preset-env"],
"plugins": [
["@babel/transform-runtime"]
]
}
116 changes: 116 additions & 0 deletions examples/basehref-examples/mf-rspack-app/host/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
23 changes: 23 additions & 0 deletions examples/basehref-examples/mf-rspack-app/host/@mf-types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { PackageType as PackageType_0,RemoteKeys as RemoteKeys_0 } from './remote/apis.d.ts';
declare module "@module-federation/runtime" {
type RemoteKeys = RemoteKeys_0;
type PackageType<T, Y=any> = T extends RemoteKeys_0 ? PackageType_0<T> :
Y ;
export function loadRemote<T extends RemoteKeys,Y>(packageName: T): Promise<PackageType<T, Y>>;
export function loadRemote<T extends string,Y>(packageName: T): Promise<PackageType<T, Y>>;
}
declare module "@module-federation/enhanced/runtime" {
type RemoteKeys = RemoteKeys_0;
type PackageType<T, Y=any> = T extends RemoteKeys_0 ? PackageType_0<T> :
Y ;
export function loadRemote<T extends RemoteKeys,Y>(packageName: T): Promise<PackageType<T, Y>>;
export function loadRemote<T extends string,Y>(packageName: T): Promise<PackageType<T, Y>>;
}
declare module "@module-federation/runtime-tools" {
type RemoteKeys = RemoteKeys_0;
type PackageType<T, Y=any> = T extends RemoteKeys_0 ? PackageType_0<T> :
Y ;
export function loadRemote<T extends RemoteKeys,Y>(packageName: T): Promise<PackageType<T, Y>>;
export function loadRemote<T extends string,Y>(packageName: T): Promise<PackageType<T, Y>>;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

export type RemoteKeys = 'remote/recipe';
type PackageType<T> = T extends 'remote/recipe' ? typeof import('remote/recipe') :any;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare const Recipe: () => import("react/jsx-runtime").JSX.Element;
export default Recipe;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './compiled-types/Recipe';
export { default } from './compiled-types/Recipe';
14 changes: 14 additions & 0 deletions examples/basehref-examples/mf-rspack-app/host/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>host</title>
</head>

<body>
<div id="app"></div>
</body>

</html>
32 changes: 32 additions & 0 deletions examples/basehref-examples/mf-rspack-app/host/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "host",
"version": "0.0.1",
"scripts": {
"build": "NODE_ENV=production rspack build",
"build:dev": "NODE_ENV=development rspack build",
"build:base": "PUBLIC_PATH=/app/ rspack build",
"build:start": "cd dist && rspack serve",
"start": "NODE_ENV=development rspack serve",
"start:base": "PUBLIC_PATH=/app/ NODE_ENV=development rspack serve"
},
"devDependencies": {
"@rspack/cli": "catalog:rspack",
"@rspack/core": "catalog:rspack",
"@rspack/plugin-react-refresh": "catalog:rspack",
"@types/react": "catalog:react18",
"@types/react-dom": "catalog:react18",
"autoprefixer": "catalog:postcss",
"css-loader": "catalog:webpack5",
"postcss": "catalog:postcss",
"postcss-loader": "catalog:postcss",
"react-refresh": "catalog:react18",
"ts-node": "catalog:typescript",
"typescript": "catalog:typescript"
},
"dependencies": {
"@module-federation/enhanced": "^0.12.0",
"react": "catalog:react18",
"react-dom": "catalog:react18",
"zephyr-rspack-plugin": "0.0.0-canary-20250414122517"
}
}
108 changes: 108 additions & 0 deletions examples/basehref-examples/mf-rspack-app/host/rspack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as path from "node:path";
import { defineConfig } from "@rspack/cli";
import { rspack } from "@rspack/core";
import * as RefreshPlugin from "@rspack/plugin-react-refresh";
import { ModuleFederationPlugin } from "@module-federation/enhanced/rspack";
import { withZephyr } from "zephyr-rspack-plugin";

// Função para garantir que o remote seja uma string com o prefixo do publicPath
const createDynamicRemote = (publicPath: string) => {
// Verifica se publicPath é 'auto' ou contém algo específico
const appPath = publicPath !== "auto" ? publicPath : "";
return `promise new Promise(resolve => {
const remoteUrl = window.location.origin + '${appPath}/remoteEntry.js';
resolve(remoteUrl);
})`;
};

const publicPath = process.env.PUBLIC_PATH || "auto";
const isDev = process.env.NODE_ENV === "development";

// Target browsers, see: https://github.com/browserslist/browserslist
const targets = ["chrome >= 87", "edge >= 88", "firefox >= 78", "safari >= 14"];

// export default withZephyr()(
export default defineConfig({
context: __dirname,
entry: {
main: "./src/index.ts",
},
resolve: {
extensions: ["...", ".ts", ".tsx", ".jsx"],
},

devServer: {
port: 8080,
historyApiFallback: true,
watchFiles: [path.resolve(__dirname, "src")],
},
output: {
uniqueName: "host",
publicPath,
},

experiments: {
css: true,
},

module: {
rules: [
{
test: /\.svg$/,
type: "asset",
},
{
test: /\.css$/,
use: ["postcss-loader"],
type: "css",
},
{
test: /\.(jsx?|tsx?)$/,
use: [
{
loader: "builtin:swc-loader",
options: {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
},
transform: {
react: {
runtime: "automatic",
development: isDev,
refresh: isDev,
},
},
},
env: { targets },
},
},
],
},
],
},
plugins: [
new rspack.HtmlRspackPlugin({
template: "./index.html",
}),
new ModuleFederationPlugin({
name: "host",
exposes: {},
filename: "remoteEntry.js",
remotes: {
remote: "remote@http://localhost:8081/app/remoteEntry.js",
},
shared: ["react", "react-dom"],
}),
isDev ? new RefreshPlugin() : null,
].filter(Boolean),
optimization: {
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin(),
new rspack.LightningCssMinimizerRspackPlugin({
minimizerOptions: { targets },
}),
],
},
});
Loading