diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml index e07b464c47..cf5544d7c9 100644 --- a/.github/workflows/deploy-test.yml +++ b/.github/workflows/deploy-test.yml @@ -6,7 +6,7 @@ on: - main jobs: - build-and-deploy: + build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..dc7605a249 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: Build and Release + +on: + workflow_dispatch: # Allows manual triggering from the GitHub UI + push: + tags: + - "v*" # Triggers on tag push matching v1.0, v1.1, etc. + +jobs: + build-and-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: "20" + cache: "yarn" + + - name: Install Yarn + run: npm install -g yarn + + - name: Install dependencies + run: yarn install + + - name: Build + run: yarn bundle + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref_name }} + draft: false + prerelease: false + + - name: Upload Production Bundle + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./packages/core/umd/production/index.min.js + asset_name: core.min.js + asset_content_type: application/javascript + + - name: Upload Development Bundle + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./packages/core/umd/development/index.js + asset_name: core.js + asset_content_type: application/javascript + + - name: Upload Development Source Map + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./packages/core/umd/development/index.js.map + asset_name: core.js.map + asset_content_type: application/json diff --git a/.gitignore b/.gitignore index 9f0623f255..bb9c93196e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ examples/candid-ui examples/*/deps/candid docs .nx -bundle \ No newline at end of file +umd \ No newline at end of file diff --git a/examples/html/index.html b/examples/html/index.html index 647cc8160e..19de02dd7f 100644 --- a/examples/html/index.html +++ b/examples/html/index.html @@ -90,10 +90,10 @@

ICRC Token

- + + + + + + + \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index ab3f465230..600fac82ce 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -37,7 +37,9 @@ "scripts": { "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" npx jest", "start": "tsc watch", - "bundle": "npx webpack --config webpack.config.js --mode production", + "bundle": "npm run bundle:dev && npm run bundle:prod", + "bundle:dev": "npx webpack --mode development", + "bundle:prod": "npx webpack --mode production", "build": "npx tsc", "clean": "npx rimraf dist" }, @@ -45,4 +47,4 @@ "node": ">=10" }, "gitHead": "431ada43e07a49e66bb7e425c8a97521da084354" -} +} \ No newline at end of file diff --git a/packages/core/webpack.config.js b/packages/core/webpack.config.js index dff6fcef57..132fbe972c 100644 --- a/packages/core/webpack.config.js +++ b/packages/core/webpack.config.js @@ -1,38 +1,47 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +// eslint-disable-next-line @typescript-eslint/no-var-requires const path = require("path") /** @type {import("webpack").Configuration} */ -module.exports = { - entry: "./src/index.ts", // Your entry point file - output: { - path: path.resolve(__dirname, "bundle"), - filename: "bundle.js", // Output file - library: { - name: "reactor", // Exported library name - type: "window", // Universal module definition +module.exports = (env, argv) => { + // Determine if it's a production build + const isProduction = argv.mode === "production" + + return { + entry: "./src/index.ts", + output: { + path: path.resolve( + __dirname, + "umd", + isProduction ? "production" : "development" + ), + filename: isProduction ? "index.min.js" : "index.js", + library: { + name: "Reactor", + type: "umd", + }, }, - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: { - loader: "babel-loader", - options: { - presets: ["@babel/preset-env", "@babel/preset-typescript"], + module: { + rules: [ + { + test: /\.ts/, + use: { + loader: "babel-loader", + options: { + presets: ["@babel/preset-env", "@babel/preset-typescript"], + }, }, + exclude: /node_modules/, }, - exclude: /node_modules/, - }, - ], - }, - resolve: { - extensions: [".tsx", ".ts", ".js"], - modules: [path.resolve(__dirname, "node_modules"), "node_modules"], - }, - // If there are specific modules you still want to leave as external: - externals: { - // "react": "React", - // Define externals here - }, + ], + }, + resolve: { + extensions: [".ts", ".js"], + modules: [path.resolve(__dirname, "node_modules"), "node_modules"], + }, + externals: {}, + // Add source maps in development for easier debugging + devtool: isProduction ? false : "source-map", + // Additional production optimizations could be added here + // For example, optimization: { minimize: true } for Webpack < 4 + } } diff --git a/packages/react/package.json b/packages/react/package.json index a46f023f16..2da53d76af 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -29,7 +29,10 @@ "test": "npx jest", "start": "tsc watch", "build": "npx tsc", - "clean": "npx rimraf dist" + "bundle": "npm run bundle:dev && npm run bundle:prod", + "bundle:dev": "npx webpack --mode development", + "bundle:prod": "npx webpack --mode production", + "clean": "npx rimraf dist && npx rimraf umd" }, "engines": { "node": ">=10" @@ -48,4 +51,4 @@ "zustand": "4.5" }, "gitHead": "431ada43e07a49e66bb7e425c8a97521da084354" -} +} \ No newline at end of file diff --git a/packages/react/src/context/actor.tsx b/packages/react/src/context/actor.tsx index ced6d6b517..b4cbe6efc7 100644 --- a/packages/react/src/context/actor.tsx +++ b/packages/react/src/context/actor.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useMemo } from "react" +import React from "react" import { ActorHooksReturnType, BaseActor } from "../types" import { CreateActorContextParameters, @@ -73,7 +73,7 @@ export function createActorContext( ): CreateActorContextReturnType { const { canisterId: defaultCanisterId, ...defaultConfig } = config - const ActorContext = createContext | null>(null) + const ActorContext = React.createContext | null>(null) const ActorProvider: React.FC = ({ children, @@ -85,7 +85,7 @@ export function createActorContext( throw new Error("canisterId is required") } - const config = useMemo( + const config = React.useMemo( () => ({ ...defaultConfig, ...restConfig, diff --git a/packages/react/src/context/agent.tsx b/packages/react/src/context/agent.tsx index f5d35b6525..9c2c1f09d2 100644 --- a/packages/react/src/context/agent.tsx +++ b/packages/react/src/context/agent.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useMemo } from "react" +import React from "react" import { createAgentManager } from "@ic-reactor/core" import { agentHooks } from "../helpers/agentHooks" import { authHooks } from "../helpers/authHooks" @@ -85,14 +85,14 @@ import { extractAgentContext } from "../helpers/extractAgentContext" export const createAgentContext = ( config: CreateAgentCotextParameters = {} ): CreateAgentContextReturnType => { - const AgentContext = createContext(null) + const AgentContext = React.createContext(null) const AgentProvider: React.FC = ({ children, agentManager: mybeAgentManager, ...options }) => { - const hooks = useMemo(() => { + const hooks = React.useMemo(() => { const agentManager = mybeAgentManager ?? createAgentManager({ ...options, ...config }) diff --git a/packages/react/src/core.ts b/packages/react/src/core.ts index eb43b30bf3..e2747e4ca0 100644 --- a/packages/react/src/core.ts +++ b/packages/react/src/core.ts @@ -3,4 +3,5 @@ export { createActorManager, createAgentManager, createCandidAdapter, + createReactorCore, } from "@ic-reactor/core" diff --git a/packages/react/src/helpers/actorHooks.ts b/packages/react/src/helpers/actorHooks.ts index 273b04523c..20a9807831 100644 --- a/packages/react/src/helpers/actorHooks.ts +++ b/packages/react/src/helpers/actorHooks.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react" +import React from "react" import { useStore } from "zustand" import type { ActorCallState, @@ -50,7 +50,7 @@ export const actorHooks = ( const useVisitMethod = >( functionName: M ): VisitService[M] => { - return useMemo(() => visitFunction[functionName], [functionName]) + return React.useMemo(() => visitFunction[functionName], [functionName]) } const useMethodCall: UseMethodCall = ({ @@ -60,11 +60,12 @@ export const actorHooks = ( ...events }) => { type M = typeof functionName - const [state, setState] = useState>(DEFAULT_STATE) + const [state, setState] = + React.useState>(DEFAULT_STATE) - const reset = useCallback(() => setState(DEFAULT_STATE), []) + const reset = React.useCallback(() => setState(DEFAULT_STATE), []) - const call = useCallback( + const call = React.useCallback( async ( eventOrReplaceArgs?: React.MouseEvent | ActorMethodParameters ) => { @@ -109,9 +110,9 @@ export const actorHooks = ( ...rest }) => { const { call, ...state } = useMethodCall(rest) - const intervalId = useRef() + const intervalId = React.useRef() - useEffect(() => { + React.useEffect(() => { if (refetchInterval) { intervalId.current = setInterval(call, refetchInterval) } diff --git a/packages/react/src/helpers/agentHooks.ts b/packages/react/src/helpers/agentHooks.ts index 5a7349132a..2dd8e7e02b 100644 --- a/packages/react/src/helpers/agentHooks.ts +++ b/packages/react/src/helpers/agentHooks.ts @@ -1,6 +1,6 @@ +import React from "react" import type { HttpAgent, AgentManager } from "@ic-reactor/core/dist/types" import type { AgentHooksReturnType } from "./types" -import { useEffect, useState } from "react" import { useStore } from "zustand" export const agentHooks = ( @@ -11,9 +11,9 @@ export const agentHooks = ( const useAgentState = () => useStore(agentStore) const useAgent = (): HttpAgent | undefined => { - const [agent, setAgent] = useState(getAgent) + const [agent, setAgent] = React.useState(getAgent) - useEffect(() => subscribeAgent(setAgent), [subscribeAgent]) + React.useEffect(() => subscribeAgent(setAgent), [subscribeAgent]) return agent } diff --git a/packages/react/src/helpers/authHooks.ts b/packages/react/src/helpers/authHooks.ts index 63508c4594..7ff7557af3 100644 --- a/packages/react/src/helpers/authHooks.ts +++ b/packages/react/src/helpers/authHooks.ts @@ -1,5 +1,5 @@ +import React from "react" import { useStore } from "zustand" -import { useCallback, useEffect, useState } from "react" import type { AgentManager, UseAuthParameters, @@ -36,13 +36,13 @@ export const authHooks = (agentManager: AgentManager): AuthHooksReturnType => { onLoginError, onLoggedOut, }: UseAuthParameters = {}) => { - const [loginState, setLoginState] = useState({ + const [loginState, setLoginState] = React.useState({ loading: false, error: null, }) const { authenticated, authenticating, error, identity } = useAuthState() - const authenticate = useCallback(async () => { + const authenticate = React.useCallback(async () => { const authenticatePromise: Promise = new Promise( (resolve, reject) => { authenticator() @@ -67,7 +67,7 @@ export const authHooks = (agentManager: AgentManager): AuthHooksReturnType => { onAuthenticationFailure, ]) - const login = useCallback( + const login = React.useCallback( async (options?: LoginParameters) => { setLoginState({ loading: true, error: null }) @@ -123,7 +123,7 @@ export const authHooks = (agentManager: AgentManager): AuthHooksReturnType => { [onLogin, onLoginSuccess, onLoginError, isLocalEnv, authenticate] ) - const logout = useCallback( + const logout = React.useCallback( async (options?: LogoutParameters) => { const authClient = getAuth() @@ -137,7 +137,7 @@ export const authHooks = (agentManager: AgentManager): AuthHooksReturnType => { [onLoggedOut] ) - useEffect(() => { + React.useEffect(() => { const authClient = getAuth() if (!authClient && !authenticating) { diff --git a/packages/react/src/helpers/extractActorContext.ts b/packages/react/src/helpers/extractActorContext.ts index fec01b5455..e0d03c77ba 100644 --- a/packages/react/src/helpers/extractActorContext.ts +++ b/packages/react/src/helpers/extractActorContext.ts @@ -1,4 +1,4 @@ -import { useContext } from "react" +import React from "react" import type { ActorHooksReturnType, BaseActor, @@ -37,7 +37,7 @@ export function extractActorContext( * ``` */ const useActorContext = () => { - const context = useContext(actorContext) + const context = React.useContext(actorContext) if (!context) { throw new Error("Actor hooks must be used within a ActorProvider") diff --git a/packages/react/src/helpers/extractAgentContext.ts b/packages/react/src/helpers/extractAgentContext.ts index 93c0fac727..3e7dd18703 100644 --- a/packages/react/src/helpers/extractAgentContext.ts +++ b/packages/react/src/helpers/extractAgentContext.ts @@ -1,4 +1,4 @@ -import { useContext } from "react" +import React from "react" import type { AgentManager, AgentContext, @@ -37,7 +37,7 @@ export const extractAgentContext = ( const useAgentContext = ( mybeAgentContext?: React.Context ) => { - const context = useContext(mybeAgentContext || agentContext) + const context = React.useContext(mybeAgentContext || agentContext) if (!context) { throw new Error("Agent hooks must be used within a AgentProvider") diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2097feab1f..aff1ea298a 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -8,3 +8,4 @@ export * from "./hooks" export * as helpers from "./helpers" export * as types from "./types" export * as core from "./core" +export * as utils from "@ic-reactor/core/dist/utils" diff --git a/packages/react/webpack.config.js b/packages/react/webpack.config.js new file mode 100644 index 0000000000..aab2065933 --- /dev/null +++ b/packages/react/webpack.config.js @@ -0,0 +1,49 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const path = require("path") + +/** @type {import("webpack").Configuration} */ +module.exports = (env, argv) => { + // Determine if it's a production build + const isProduction = argv.mode === "production" + + return { + entry: "./src/index.ts", + output: { + path: path.resolve( + __dirname, + "umd", + isProduction ? "production" : "development" + ), + filename: isProduction ? "index.min.js" : "index.js", + library: { + name: "Reactor", + type: "umd", + }, + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: { + loader: "babel-loader", + options: { + presets: ["@babel/preset-env", "@babel/preset-typescript"], + }, + }, + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [".tsx", ".ts", ".js"], + modules: [path.resolve(__dirname, "node_modules"), "node_modules"], + }, + externals: { + react: "React", + }, + // Add source maps in development for easier debugging + devtool: isProduction ? false : "source-map", + // Additional production optimizations could be added here + // For example, optimization: { minimize: true } for Webpack < 4 + } +}