├── CODEOWNERS ├── src ├── widget.ts ├── root.component.tsx ├── contract.ts ├── root.component.test.tsx ├── declarations.d.ts └── app2.tsx ├── .yarnrc.yml ├── .prettierignore ├── .babelrc.json ├── .1fe.config.ts ├── tsconfig.json ├── jest.config.cjs ├── .github └── workflows │ ├── widget-ci.yml │ └── rollback-widget.yml ├── playwright.config.ts ├── LICENSE ├── tests └── widget.spec.ts ├── .eslintrc.cjs ├── .gitignore ├── package.json └── README.md /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @docusign/1fe 2 | -------------------------------------------------------------------------------- /src/widget.ts: -------------------------------------------------------------------------------- 1 | import Root from "./app2"; 2 | 3 | export default Root; 4 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-4.9.1.cjs 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .prettierignore 3 | yarn.lock 4 | yarn-error.log 5 | package-lock.json 6 | dist 7 | coverage 8 | pnpm-lock.yaml -------------------------------------------------------------------------------- /src/root.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export default function Root() { 3 | return ( 4 |
My amazing component from app2 is mounted! Hello world
5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { "targets": { "node": "current" } }], 4 | ["@babel/preset-react", { "runtime": "automatic" }], 5 | "@babel/preset-typescript" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.1fe.config.ts: -------------------------------------------------------------------------------- 1 | import { OneFeConfiguration } from "@1fe/cli"; 2 | import { getBaseConfig } from "@1fe/sample-widget-base-config"; // this is the redistributed package for the organization 3 | 4 | const configuration: OneFeConfiguration = { 5 | baseConfig: getBaseConfig, 6 | }; 7 | 8 | export default configuration; 9 | -------------------------------------------------------------------------------- /src/contract.ts: -------------------------------------------------------------------------------- 1 | import { PlatformPropsType } from "@1fe/shell"; 2 | 3 | export type HostPropsContract = Record; 4 | 5 | export type WidgetEvents = { 6 | event1: { param1: string }; 7 | }; 8 | 9 | export type WidgetProps = { 10 | host: HostPropsContract; 11 | platform: PlatformPropsType; 12 | }; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "ts-config-single-spa", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "declarationDir": "dist", 6 | "declaration": true, 7 | "skipLibCheck": true, 8 | "target": "esnext", 9 | "emitDeclarationOnly": false 10 | }, 11 | "include": ["src/**/*"], 12 | "exclude": ["src/**/*.test.*"] 13 | } 14 | -------------------------------------------------------------------------------- /src/root.component.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/react"; 2 | import "@testing-library/jest-dom"; 3 | import Root from "./root.component"; 4 | 5 | describe("Root component", () => { 6 | it("should be in the document", () => { 7 | const { getByText } = render(); 8 | expect( 9 | getByText(/My amazing component from app2 is mounted!/i), 10 | ).toBeInTheDocument(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "jsdom", 3 | transform: { 4 | "^.+\\.(ts|tsx|js|jsx)$": "babel-jest", 5 | }, 6 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"], 7 | moduleNameMapper: { 8 | "\\.(css|less|scss|sass)$": "identity-obj-proxy", 9 | }, 10 | testMatch: [ 11 | "/src/**/__tests__/**/*.(ts|tsx|js)", 12 | "/src/**/?(*.)(spec|test).(ts|tsx|js)", 13 | ], 14 | testPathIgnorePatterns: [ 15 | "/tests/", // Exclude Playwright tests 16 | "/node_modules/", 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.html" { 2 | const rawHtmlFile: string; 3 | export = rawHtmlFile; 4 | } 5 | 6 | declare module "*.bmp" { 7 | const src: string; 8 | export default src; 9 | } 10 | 11 | declare module "*.gif" { 12 | const src: string; 13 | export default src; 14 | } 15 | 16 | declare module "*.jpg" { 17 | const src: string; 18 | export default src; 19 | } 20 | 21 | declare module "*.jpeg" { 22 | const src: string; 23 | export default src; 24 | } 25 | 26 | declare module "*.png" { 27 | const src: string; 28 | export default src; 29 | } 30 | 31 | declare module "*.webp" { 32 | const src: string; 33 | export default src; 34 | } 35 | 36 | declare module "*.svg" { 37 | const src: string; 38 | export default src; 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/widget-ci.yml: -------------------------------------------------------------------------------- 1 | name: Widget CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | inputs: 12 | node_version_override: 13 | description: "Override Node.js version for this run (e.g., 20)" 14 | required: false 15 | type: string 16 | 17 | jobs: 18 | run_minimal_ci: 19 | name: Widget CI 20 | uses: docusign/1fe-ci-cd/.github/workflows/ci-widgets.yml@main 21 | with: 22 | node-version: ${{ github.event.inputs.node_version_override || '22' }} 23 | secrets: 24 | AKAMAI_NS_SSH_PRIVATE_KEY: ${{ secrets.AKAMAI_NS_SSH_PRIVATE_KEY }} 25 | AZURE_APP_CONFIG_CONNECTION_STRING: ${{ secrets.AZURE_APP_CONFIG_CONNECTION_STRING }} 26 | PRIVATE_KEY_1FE_ADMIN_GITHUB_APP: ${{ secrets.PRIVATE_KEY_1FE_ADMIN_GITHUB_APP }} 27 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | 3 | export default defineConfig({ 4 | testDir: "./tests", 5 | fullyParallel: true, 6 | forbidOnly: !!process.env.CI, 7 | retries: process.env.CI ? 2 : 0, 8 | workers: process.env.CI ? 1 : undefined, 9 | reporter: "html", 10 | use: { 11 | baseURL: "http://localhost:3000", 12 | trace: "on-first-retry", 13 | }, 14 | 15 | projects: [ 16 | { 17 | name: "chromium", 18 | use: { ...devices["Desktop Chrome"] }, 19 | }, 20 | 21 | { 22 | name: "firefox", 23 | use: { ...devices["Desktop Firefox"] }, 24 | }, 25 | 26 | { 27 | name: "webkit", 28 | use: { ...devices["Desktop Safari"] }, 29 | }, 30 | ], 31 | 32 | // Disable webServer for now due to Node version mismatch 33 | // webServer: { 34 | // command: 'yarn dev', 35 | // url: 'http://localhost:3000', 36 | // reuseExistingServer: !process.env.CI, 37 | // }, 38 | }); 39 | -------------------------------------------------------------------------------- /.github/workflows/rollback-widget.yml: -------------------------------------------------------------------------------- 1 | name: Rollback Widget 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | target-version: 7 | description: "The specific version to roll back to (e.g., 1.0.3 or 1.0.3-PR-123.abcde12)" 8 | required: true 9 | type: string 10 | environment: 11 | description: "The environment to rollback (e.g., integration or production)" 12 | required: true 13 | type: choice 14 | options: 15 | - integration 16 | - production 17 | 18 | permissions: 19 | contents: read 20 | id-token: write 21 | 22 | jobs: 23 | rollback-widget: 24 | uses: docusign/1fe-ci-cd/.github/workflows/reusable-rollback-ci.yml@main 25 | with: 26 | target-version: ${{ github.event.inputs.target-version }} 27 | environment: ${{ github.event.inputs.environment }} 28 | caller-repo: ${{ github.repository }} 29 | caller-repo-ref: ${{ github.ref }} 30 | secrets: 31 | AZURE_APP_CONFIG_CONNECTION_STRING: ${{ secrets.AZURE_APP_CONFIG_CONNECTION_STRING }} 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Docusign Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/widget.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test.describe("Widget Tests", () => { 4 | test("playwright configuration is working", async ({ page }) => { 5 | // Simple test to verify Playwright is set up correctly 6 | await page.goto( 7 | "data:text/html,Test Page

Hello World

", 8 | ); 9 | await expect(page).toHaveTitle("Test Page"); 10 | await expect(page.locator("h1")).toHaveText("Hello World"); 11 | }); 12 | 13 | test("should validate basic browser functionality", async ({ page }) => { 14 | // Test basic browser functionality with inline HTML 15 | await page.goto( 16 | 'data:text/html,Browser Test
Playwright is working!
', 17 | ); 18 | await expect(page.locator("#test")).toBeVisible(); 19 | await expect(page.locator("#test")).toHaveText("Playwright is working!"); 20 | }); 21 | 22 | // TODO: Add widget-specific tests once dev server Node version is resolved 23 | test.skip("should load the widget", async ({ page }) => { 24 | await page.goto("/"); 25 | 26 | // Wait for the widget to be mounted 27 | await expect( 28 | page.locator("text=My amazing component from app2 is mounted!"), 29 | ).toBeVisible(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = /** @type {import("eslint").Linter.Config} */ { 2 | extends: [ 3 | "eslint:recommended", 4 | "plugin:react/recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:prettier/recommended", 7 | "eslint-config-prettier", 8 | ], 9 | ignorePatterns: ["node_modules/", "dist/"], 10 | parser: "@typescript-eslint/parser", // Use the TypeScript parser 11 | parserOptions: { 12 | ecmaVersion: 2022, // Support the latest ECMAScript features 13 | sourceType: "module", // Enable ECMAScript modules 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | }, 18 | plugins: ["@typescript-eslint", "prettier"], 19 | env: { 20 | browser: true, // Enable browser global variables 21 | node: true, // Enable Node.js global variables 22 | es6: true, // Enable ECMAScript 6 features 23 | }, 24 | settings: { 25 | react: { 26 | version: "detect", // Automatically detect the React version 27 | }, 28 | }, 29 | rules: { 30 | "prettier/prettier": "error", // Treat Prettier violations as errors 31 | "@typescript-eslint/explicit-module-boundary-types": "off", // Optional: Disables enforcing explicit return types on function signatures 32 | "react/no-unknown-property": ["error", { ignore: ["css"] }], // Required for emotion css prop 33 | "react/react-in-jsx-scope": "off", 34 | "react/jsx-uses-react": "off", 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/app2.tsx: -------------------------------------------------------------------------------- 1 | import { platformProps } from "@1fe/shell"; 2 | import { useState, useCallback, useEffect } from "react"; 3 | import { Button } from "antd"; 4 | import { /* WidgetProps , */ WidgetEvents } from "./contract"; 5 | 6 | export default function Root(/* props: WidgetProps */) { 7 | useEffect(() => { 8 | platformProps.utils.appLoadTime.end(); 9 | }, []); 10 | 11 | const [eventBusResult, setEventBusResult] = useState("unchanged"); 12 | const [unsubscribeFn, setUnsubscribeFn] = useState(() => () => {}); 13 | 14 | const listener = useCallback( 15 | (event: unknown) => { 16 | setEventBusResult(JSON.stringify(event)); 17 | }, 18 | [setEventBusResult], 19 | ); 20 | 21 | return ( 22 | <> 23 |

My component from app2 is mounted!

24 | 40 | 49 |
{eventBusResult}
50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # generateds files 30 | reports 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | dist 66 | 67 | # Editor directories and files 68 | .idea 69 | .vscode 70 | *.suo 71 | *.ntvs* 72 | *.njsproj 73 | *.sln 74 | *.sw? 75 | .DS_Store 76 | 77 | # transient types 78 | src/types/widgets 79 | 80 | # Yarn 81 | .pnp.* 82 | .yarn/* 83 | !.yarn/patches 84 | !.yarn/plugins 85 | !.yarn/releases 86 | !.yarn/sdks 87 | !.yarn/versions 88 | 89 | # Playwright 90 | /test-results/ 91 | /playwright-report/ 92 | /blob-report/ 93 | /playwright/.cache/ 94 | 95 | .cache 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@1fe/sample-widget", 3 | "version": "0.1.3", 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "1fe-cli --trace --environment integration dev", 8 | "build:widget": "1fe-cli --trace --environment integration build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "format": "prettier --write .", 11 | "check-format": "prettier --check .", 12 | "prepack": "yarn build", 13 | "test": "yarn jest", 14 | "test:e2e": "playwright test", 15 | "test:e2e:ui": "playwright test --ui", 16 | "test:all": "yarn test && yarn test:e2e" 17 | }, 18 | "devDependencies": { 19 | "@1fe/cli": "0.0.1", 20 | "@1fe/sample-widget-base-config": "0.0.1", 21 | "@babel/core": "^7.28.0", 22 | "@babel/preset-env": "^7.28.0", 23 | "@babel/preset-react": "^7.27.1", 24 | "@babel/preset-typescript": "^7.27.1", 25 | "@playwright/test": "^1.53.1", 26 | "@testing-library/dom": "^10.4.0", 27 | "@testing-library/jest-dom": "^6.6.3", 28 | "@testing-library/react": "^16.3.0", 29 | "@types/jest": "^29", 30 | "@types/react": "^18.2.62", 31 | "@types/react-dom": "^18.2.19", 32 | "@typescript-eslint/eslint-plugin": "^8.35.0", 33 | "@typescript-eslint/parser": "^8.35.0", 34 | "babel-jest": "^29", 35 | "cross-env": "^7.0.3", 36 | "eslint": "8.57.1", 37 | "eslint-config-prettier": "^10.1.1", 38 | "eslint-config-ts-react-important-stuff": "^3.0.0", 39 | "eslint-plugin-prettier": "^5.2.4", 40 | "eslint-plugin-react": "^7.37.5", 41 | "identity-obj-proxy": "^3.0.0", 42 | "jest": "^29", 43 | "jest-environment-jsdom": "^29", 44 | "prettier": "^3.5.3", 45 | "pretty-quick": "^3.1.1", 46 | "ts-config-single-spa": "^3.0.0", 47 | "ts-jest": "^29", 48 | "typescript": "5.8.2" 49 | }, 50 | "dependencies": { 51 | "@1fe/shell": "0.0.1", 52 | "@emotion/react": "^11.14.0", 53 | "@emotion/styled": "^11.14.0", 54 | "@mui/material": "^7.1.0", 55 | "@reduxjs/toolkit": "1.9.1", 56 | "@textea/json-viewer": "^4.0.1", 57 | "@types/systemjs": "^6.15.1", 58 | "antd": "^5.22.4", 59 | "react": "^18.3.1", 60 | "react-dom": "^18.3.1", 61 | "react-redux": "^8.1.3", 62 | "react-router-dom": "6.30.0", 63 | "reselect": "^4.1.7", 64 | "single-spa": "^5.9.3", 65 | "single-spa-react": "^6.0.2" 66 | }, 67 | "packageManager": "yarn@4.9.1" 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1fe Sample Widget 2 | 3 | A basic sample widget demonstrating core 1fe platform integration patterns. This widget showcases essential platform utilities and serves as a reference implementation for building widgets within the 1fe ecosystem. 4 | 5 | ## What's in this repository 6 | 7 | This sample widget demonstrates: 8 | 9 | - **Platform integration** using `@1fe/shell` utilities 10 | - **Event bus communication** between widgets and platform 11 | - **App load time tracking** for performance monitoring 12 | - **Basic widget lifecycle** and contract implementation 13 | - **Antd UI components** integration 14 | - **TypeScript configuration** for widget development 15 | 16 | ## Prerequisites 17 | 18 | - **Node.js** `>= 22` 19 | - **Yarn** (package manager) 20 | 21 | ## Getting Started 22 | 23 | ### Development Setup 24 | 25 | ```bash 26 | # Clone this repository 27 | git clone 28 | cd 1fe-sample-widget 29 | 30 | # Install dependencies 31 | yarn install 32 | 33 | # Start development server 34 | yarn dev 35 | ``` 36 | 37 | ### Project Structure 38 | 39 | ```text 40 | src/ 41 | ├── app2.tsx # Main application component 42 | ├── contract.ts # Widget contract definition 43 | ├── declarations.d.ts # TypeScript declarations 44 | ├── root.component.tsx # Root component wrapper 45 | ├── root.component.test.tsx # Component tests 46 | └── widget.ts # Widget entry point 47 | ``` 48 | 49 | ## Development Commands 50 | 51 | ```bash 52 | # Start development server with hot reloading 53 | yarn dev 54 | 55 | # Build widget for production 56 | yarn build:widget 57 | 58 | # Lint code 59 | yarn lint 60 | 61 | # Format code 62 | yarn format 63 | 64 | # Check code formatting 65 | yarn check-format 66 | ``` 67 | 68 | ## Widget Features 69 | 70 | ### Platform Integration 71 | 72 | - **App Load Time**: Tracks and reports widget loading performance 73 | - **Event Bus**: Demonstrates inter-widget communication 74 | - **Platform Props**: Access to 1fe platform utilities 75 | 76 | ### UI Components 77 | 78 | - Uses Antd components for consistent UI 79 | - Demonstrates button interactions and state management 80 | - Event handling and user feedback 81 | 82 | ## Widget Contract 83 | 84 | The widget defines its interface in `src/contract.ts`: 85 | 86 | ```typescript 87 | export interface WidgetEvents { 88 | // Event definitions for widget communication 89 | } 90 | ``` 91 | 92 | ## Configuration 93 | 94 | The widget uses the base configuration from `@1fe/sample-widget-base-config` for: 95 | 96 | - Environment settings 97 | - Build configurations 98 | - Platform integration setup 99 | 100 | ## Testing 101 | 102 | Unit tests are included for components: 103 | 104 | - Test files: `*.test.tsx` 105 | - Uses React Testing Library patterns 106 | 107 | ## Contributing 108 | 109 | ### Development Workflow 110 | 111 | 1. Fork the repository 112 | 2. Create a feature branch (`git checkout -b feature/your-feature`) 113 | 3. Make your changes 114 | 4. Run tests and linting (`yarn lint`) 115 | 5. Format your code (`yarn format`) 116 | 6. Commit your changes (`git commit -m 'Add feature'`) 117 | 7. Push to your branch (`git push origin feature/your-feature`) 118 | 8. Open a Pull Request 119 | 120 | ## Troubleshooting 121 | 122 | ### Common Issues 123 | 124 | - **Build failures**: Ensure Node.js version is >= 22 and dependencies are installed 125 | - **Platform utilities not available**: Check that `@1fe/shell` is properly imported 126 | - **Event bus issues**: Verify event listeners are properly set up and cleaned up 127 | 128 | ## Getting Help 129 | 130 | - **[1fe Documentation](https://1fe.com/getting-started/installation/)** - Complete platform documentation 131 | - **[GitHub Issues](https://github.com/docusign/1fe/issues)** - Report bugs or request features 132 | - **[GitHub Discussions](https://github.com/docusign/1fe/discussions)** - Ask questions and share ideas 133 | 134 | ## License 135 | 136 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 137 | --------------------------------------------------------------------------------