From 3c7f39ec2041f01890d62e59b02dc51af0ee25e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Tue, 26 Aug 2025 23:25:29 -0500 Subject: [PATCH] Move react package to use vite --- docs/src/theme/ReactLiveScope/index.tsx | 7 +- .../src/theme/JupyterCell.tsx | 11 +- .../src/theme/ReactLiveScope/index.tsx | 7 +- packages/react/index.html | 79 +++++++ packages/react/package.json | 26 ++- packages/react/tsconfig.json | 5 +- packages/react/vite.config.ts | 221 ++++++++++++++++++ 7 files changed, 326 insertions(+), 30 deletions(-) create mode 100644 packages/react/index.html create mode 100644 packages/react/vite.config.ts diff --git a/docs/src/theme/ReactLiveScope/index.tsx b/docs/src/theme/ReactLiveScope/index.tsx index 6d1942ec8..2d1464c47 100644 --- a/docs/src/theme/ReactLiveScope/index.tsx +++ b/docs/src/theme/ReactLiveScope/index.tsx @@ -15,12 +15,7 @@ const Cell = (props: any) => { > {() => { // Keep the import via require in the BrowserOnly code block. - const { - Jupyter, - } = require('@datalayer/jupyter-react/lib/jupyter/Jupyter'); - const { - Cell, - } = require('@datalayer/jupyter-react/lib/components/cell/Cell'); + const { Jupyter, Cell } = require('@datalayer/jupyter-react'); return ( <> { > {() => { // Keep the import via require and keep it into the BrowserOnly code block.. - // const { JupyterReactTheme } = require('@datalayer/jupyter-react/lib/theme'); - // const { useJupyter } = require('@datalayer/jupyter-react/lib/jupyter/JupyterContext'); - const { - Jupyter, - } = require('@datalayer/jupyter-react/lib/jupyter/Jupyter'); - const { - Cell, - } = require('@datalayer/jupyter-react/lib/components/cell/Cell'); + // const { JupyterReactTheme } = require('@datalayer/jupyter-react'); + // const { useJupyter } = require('@datalayer/jupyter-react'); + const { Jupyter, Cell } = require('@datalayer/jupyter-react'); const { jupyterServerUrl = 'https://oss.datalayer.run/api/jupyter-server', jupyterServerToken = '60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6', diff --git a/packages/docusaurus-plugin/src/theme/ReactLiveScope/index.tsx b/packages/docusaurus-plugin/src/theme/ReactLiveScope/index.tsx index dbf429769..2f3845574 100644 --- a/packages/docusaurus-plugin/src/theme/ReactLiveScope/index.tsx +++ b/packages/docusaurus-plugin/src/theme/ReactLiveScope/index.tsx @@ -15,12 +15,7 @@ const Cell = (props: any) => { > {() => { // Keep the import via require in the BrowserOnly code block. - const { - Jupyter, - } = require('@datalayer/jupyter-react/lib/jupyter/Jupyter'); - const { - Cell, - } = require('@datalayer/jupyter-react/lib/components/cell/Cell'); + const { Jupyter, Cell } = require('@datalayer/jupyter-react'); return ( <> + + + + + Jupyter React Example + + + + + + + + +
+ + + diff --git a/packages/react/package.json b/packages/react/package.json index 8822fd63b..76474269a 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -4,6 +4,7 @@ "description": "Jupyter React - React.js components 100% compatible with Jupyter.", "license": "MIT", "main": "lib/index.js", + "types": "lib/index.d.ts", "files": [ "lib/**/*.{css,d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}", @@ -29,18 +30,21 @@ "access": "public" }, "scripts": { - "build": "gulp resources-to-lib && tsc && webpack", - "build:lib": "tsc", - "build:prod": "gulp resources-to-lib && tsc && npm run clean && npm run build:lib", + "build": "gulp resources-to-lib && tsc --emitDeclarationOnly && cross-env BUILD_LIB=true vite build", + "build:lib": "cross-env BUILD_LIB=true vite build", + "build:types": "tsc --emitDeclarationOnly", + "build:prod": "gulp resources-to-lib && tsc --emitDeclarationOnly && npm run clean && npm run build:lib", "build:tsc:watch:res": "gulp resources-to-lib-watch", "build:tsc:watch:tsc": "tsc --watch", "build:webpack": "cross-env BUILD_APP=true webpack-cli build", + "build:vite": "vite build", "clean": "rimraf node_modules lib dist build tsconfig.tsbuildinfo", "clean:all": "npm run clean:lib && npm run clean:labextension && npm run clean:lintcache", "clean:labextension": "rimraf datalayer/labextension", "clean:lib": "rimraf lib tsconfig.tsbuildinfo", "clean:lintcache": "rimraf .eslintcache .stylelintcache", - "dev": "run-p -c 'start:*'", + "dev": "vite", + "dev:webpack": "run-p -c 'start:*'", "eslint": "npm eslint:check --fix", "eslint:check": "eslint . --cache --ext .ts,.tsx", "example": "run-p -c 'start:*'", @@ -51,10 +55,12 @@ "prettier": "npm prettier:base --write --list-different", "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", "prettier:check": "npm prettier:base --check", - "start": "run-p -c 'start:*'", + "preview": "vite preview", + "start": "vite", "start:webpack": "webpack serve", - "start-noconfig": "cross-env NO_CONFIG=true webpack serve", - "start-local": "run-p -c 'start-local:*'", + "start-noconfig": "cross-env NO_CONFIG=true vite", + "start-noconfig:webpack": "cross-env NO_CONFIG=true webpack serve", + "start-local": "cross-env LOCAL_JUPYTER_SERVER=true vite", "start-local:webpack": "cross-env LOCAL_JUPYTER_SERVER=true webpack serve", "start-local:jupyter-server": "cd ./../.. && npm run jupyter:server", "stylelint": "npm stylelint:check --fix", @@ -237,7 +243,11 @@ "webpack": "^5.74.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.9.3", - "whatwg-fetch": "^3.6.2" + "whatwg-fetch": "^3.6.2", + "vite": "^5.4.0", + "@vitejs/plugin-react": "^4.6.0", + "vite-plugin-treat-umd-as-commonjs": "^0.1.4", + "buffer": "^6.0.3" }, "eslintIgnore": [ "node_modules", diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index ceb57b744..12c0b33c9 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -4,12 +4,13 @@ "exclude": ["node_modules", "src/example"], "compilerOptions": { "forceConsistentCasingInFileNames": true, - "baseUrl": "/", + "baseUrl": ".", "rootDir": "./src", "allowJs": false, "allowSyntheticDefaultImports": true, - "composite": true, + "composite": false, "declaration": true, + "declarationMap": true, "esModuleInterop": true, "incremental": true, "jsx": "react-jsx", diff --git a/packages/react/vite.config.ts b/packages/react/vite.config.ts new file mode 100644 index 000000000..4599edb52 --- /dev/null +++ b/packages/react/vite.config.ts @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2021-2023 Datalayer, Inc. + * + * MIT License + */ + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { treatAsCommonjs } from 'vite-plugin-treat-umd-as-commonjs'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Entry points for examples (dev mode) +const ENTRY_POINTS: Record = { + app: './src/app/App.tsx', + cell: './src/examples/Cell.tsx', + console: './src/examples/Console.tsx', + filebrowser: './src/examples/FileBrowser.tsx', + notebook: './src/examples/Notebook.tsx', + notebookthemecolormode: './src/examples/NotebookThemeColormode.tsx', + terminal: './src/examples/Terminal.tsx', + viewer: './src/examples/Viewer.tsx', +}; + +const IS_PRODUCTION = process.env.NODE_ENV === 'production'; +const IS_LOCAL_JUPYTER_SERVER = process.env.LOCAL_JUPYTER_SERVER === 'true'; +const IS_NO_CONFIG = process.env.NO_CONFIG === 'true'; +const BUILD_LIB = process.env.BUILD_LIB === 'true'; + +export default defineConfig(({ mode, command }) => { + const isServe = command === 'serve'; + const isBuild = command === 'build'; + + // Library build configuration + if (isBuild && BUILD_LIB) { + return { + build: { + lib: { + entry: path.resolve(__dirname, 'src/index.ts'), + name: 'JupyterReact', + formats: ['es', 'cjs'], + fileName: format => { + const FORMAT_EXTENSION_MAP: Record = { + es: 'js', + cjs: 'cjs', + }; + const ext = FORMAT_EXTENSION_MAP[format] || 'js'; + return `index.${ext}`; + }, + }, + outDir: 'lib', + sourcemap: true, + emptyOutDir: false, // Don't clean TypeScript output + rollupOptions: { + external: [ + 'react', + 'react-dom', + 'react/jsx-runtime', + /^@jupyterlab\/.*/, + /^@lumino\/.*/, + /^@jupyter\/.*/, + /^@jupyter-widgets\/.*/, + /^@jupyterlite\/.*/, + ], + output: { + preserveModules: true, + preserveModulesRoot: 'src', + assetFileNames: assetInfo => { + if (assetInfo.name?.endsWith('.css')) { + return '[name][extname]'; + } + return 'assets/[name]-[hash][extname]'; + }, + }, + }, + }, + plugins: [react(), treatAsCommonjs()], + }; + } + + // Development server and example build configuration + return { + root: __dirname, + server: { + port: 3208, + host: true, + hmr: { + overlay: false, + }, + proxy: IS_LOCAL_JUPYTER_SERVER + ? { + '/api': { + target: 'http://localhost:8686', + ws: true, + changeOrigin: true, + }, + '/terminals': { + target: 'http://localhost:8686', + ws: true, + changeOrigin: true, + }, + } + : undefined, + }, + plugins: [ + react(), + treatAsCommonjs(), + { + name: 'raw-css-as-string', + enforce: 'pre', + async resolveId(source, importer) { + if (source.endsWith('.raw.css') && !source.includes('?raw')) { + const resolved = await this.resolve(source + '?raw', importer, { + skipSelf: true, + }); + if (resolved) return resolved.id; + return null; + } + return null; + }, + }, + { + name: 'fix-text-query', + enforce: 'pre', + async resolveId(source, importer) { + if (source.includes('?text')) { + const fixed = source.replace('?text', '?raw'); + const resolved = await this.resolve(fixed, importer, { + skipSelf: true, + }); + if (resolved) { + return resolved.id; + } + return fixed; + } + return null; + }, + }, + { + name: 'handle-theme-css', + enforce: 'pre', + transform(code, id) { + if (id.includes('style/theme.css')) { + return { + code: `export default ${JSON.stringify(code)};`, + map: null, + }; + } + }, + }, + ], + assetsInclude: ['**/*.whl', '**/*.raw.css', '**/*.ipynb', '**/*.json'], + resolve: { + alias: [ + { + find: /^~(.*)$/, + replacement: '$1', + }, + { + find: 'stream', + replacement: 'stream-browserify', + }, + ], + }, + define: { + global: 'globalThis', + 'process.env': process.env, + // Required for JupyterLab and ipywidgets compatibility + __webpack_public_path__: '""', + }, + worker: { + format: 'es', + }, + optimizeDeps: { + include: ['react', 'react-dom', '@primer/react', 'styled-components'], + exclude: [ + '@jupyterlab/application', + '@jupyterlab/apputils', + '@jupyterlite/server', + '@jupyterlite/pyodide-kernel', + ], + esbuildOptions: { + loader: { + '.whl': 'text', + }, + }, + }, + build: { + outDir: 'dist', + rollupOptions: { + input: isServe + ? undefined + : { + main: path.resolve(__dirname, 'index.html'), + ...Object.fromEntries( + Object.entries(ENTRY_POINTS).map(([name, entry]) => [ + name, + path.resolve(__dirname, entry), + ]) + ), + }, + output: { + assetFileNames: assetInfo => { + if (assetInfo.name && /pypi\//.test(assetInfo.name)) { + return 'pypi/[name][extname]'; + } + if (assetInfo.name && /schema\//.test(assetInfo.name)) { + return 'schema/[name][extname]'; + } + return 'assets/[name]-[hash][extname]'; + }, + chunkFileNames: 'chunks/[name]-[hash].js', + entryFileNames: '[name].js', + }, + }, + }, + }; +});