From aefc12632c240fd322bff4baf1973d8e55f0dbaa Mon Sep 17 00:00:00 2001 From: ioedeveloper Date: Wed, 25 Dec 2024 01:26:54 +0000 Subject: [PATCH] Load browser compiler --- apps/noir-compiler/src/app/app.tsx | 9 + .../src/app/services/noirFileManager.ts | 163 ++++++++++++++++ .../src/app/services/noirPluginClient.ts | 40 +++- .../src/app/services/remixMockFs.ts | 38 ++++ apps/noir-compiler/src/css/app.css | 0 apps/noir-compiler/src/main.tsx | 3 +- apps/noir-compiler/src/profile.json | 4 +- apps/remix-ide/project.json | 2 +- apps/remix-ide/src/app/plugins/matomo.ts | 2 +- apps/remix-ide/src/remixAppManager.js | 3 +- apps/remix-ide/src/remixEngine.js | 1 + .../helper/src/lib/remix-ui-helper.ts | 2 +- libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 10 +- package.json | 1 + yarn.lock | 177 +++++++++++++++++- 15 files changed, 436 insertions(+), 19 deletions(-) create mode 100644 apps/noir-compiler/src/app/app.tsx create mode 100644 apps/noir-compiler/src/app/services/noirFileManager.ts create mode 100644 apps/noir-compiler/src/app/services/remixMockFs.ts create mode 100644 apps/noir-compiler/src/css/app.css diff --git a/apps/noir-compiler/src/app/app.tsx b/apps/noir-compiler/src/app/app.tsx new file mode 100644 index 0000000000..e037648cfc --- /dev/null +++ b/apps/noir-compiler/src/app/app.tsx @@ -0,0 +1,9 @@ +import { NoirPluginClient } from "./services/noirPluginClient"; + +const plugin = new NoirPluginClient() + +function App() { + return <> +} + +export default App \ No newline at end of file diff --git a/apps/noir-compiler/src/app/services/noirFileManager.ts b/apps/noir-compiler/src/app/services/noirFileManager.ts new file mode 100644 index 0000000000..62a4f46bfb --- /dev/null +++ b/apps/noir-compiler/src/app/services/noirFileManager.ts @@ -0,0 +1,163 @@ +import { dirname, isAbsolute, join } from 'path'; + +/** + * A file system interface that matches the node fs module. + */ +export interface FileSystem { + /** Checks if the file exists */ + existsSync: (path: string) => boolean; + /** Creates a directory structure */ + mkdir: ( + dir: string, + opts?: { + /** Create parent directories as needed */ + recursive: boolean; + }, + ) => Promise; + /** Writes a file */ + writeFile: (path: string, data: Uint8Array) => Promise; + /** Reads a file */ + readFile: (path: string, encoding?: 'utf-8') => Promise; + /** Renames a file */ + rename: (oldPath: string, newPath: string) => Promise; + /** Reads a directory */ + readdir: ( + path: string, + options?: { + /** Traverse child directories recursively */ + recursive: boolean; + }, + ) => Promise; +} + +/** + * A file manager that writes file to a specific directory but reads globally. + */ +export class FileManager { + #fs: FileSystem; + #dataDir: string; + + constructor(fs: FileSystem, dataDir: string) { + this.#fs = fs; + this.#dataDir = dataDir; + } + + /** + * Returns the data directory + */ + getDataDir() { + return this.#dataDir; + } + + /** + * Saves a file to the data directory. + * @param name - File to save + * @param stream - File contents + */ + public async writeFile(name: string, stream: ReadableStream): Promise { + if (isAbsolute(name)) { + throw new Error("can't write absolute path"); + } + + const path = this.#getPath(name); + const chunks: Uint8Array[] = []; + const reader = stream.getReader(); + + // eslint-disable-next-line no-constant-condition + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + + chunks.push(value); + } + + const file = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0)); + let offset = 0; + for (const chunk of chunks) { + file.set(chunk, offset); + offset += chunk.length; + } + + await this.#fs.mkdir(dirname(path), { recursive: true }); + await this.#fs.writeFile(this.#getPath(path), file); + } + + /** + * Reads a file from the filesystem and returns a buffer + * Saves a file to the data directory. + * @param oldName - File to save + * @param newName - File contents + */ + async moveFile(oldName: string, newName: string) { + if (isAbsolute(oldName) || isAbsolute(newName)) { + throw new Error("can't move absolute path"); + } + + const oldPath = this.#getPath(oldName); + const newPath = this.#getPath(newName); + + await this.#fs.mkdir(dirname(newPath), { recursive: true }); + await this.#fs.rename(oldPath, newPath); + } + + /** + * Reads a file from the disk and returns a buffer + * @param name - File to read + */ + public async readFile(name: string): Promise; + /** + * Reads a file from the filesystem as a string + * @param name - File to read + * @param encoding - Encoding to use + */ + public async readFile(name: string, encoding: 'utf-8'): Promise; + /** + * Reads a file from the filesystem + * @param name - File to read + * @param encoding - Encoding to use + */ + public async readFile(name: string, encoding?: 'utf-8'): Promise { + const path = this.#getPath(name); + const data = await this.#fs.readFile(path, encoding); + + if (!encoding) { + return typeof data === 'string' + ? new TextEncoder().encode(data) // this branch shouldn't be hit, but just in case + : new Uint8Array(data.buffer, data.byteOffset, data.byteLength / Uint8Array.BYTES_PER_ELEMENT); + } + + return data; + } + + /** + * Checks if a file exists and is accessible + * @param name - File to check + */ + public hasFileSync(name: string): boolean { + return this.#fs.existsSync(this.#getPath(name)); + } + + #getPath(name: string) { + return isAbsolute(name) ? name : join(this.#dataDir, name); + } + + /** + * Reads a file from the filesystem + * @param dir - File to read + * @param options - Readdir options + */ + public async readdir( + dir: string, + options?: { + /** + * Traverse child directories recursively + */ + recursive: boolean; + }, + ) { + const dirPath = this.#getPath(dir); + return await this.#fs.readdir(dirPath, options); + } +} \ No newline at end of file diff --git a/apps/noir-compiler/src/app/services/noirPluginClient.ts b/apps/noir-compiler/src/app/services/noirPluginClient.ts index e287217210..421ad8d603 100644 --- a/apps/noir-compiler/src/app/services/noirPluginClient.ts +++ b/apps/noir-compiler/src/app/services/noirPluginClient.ts @@ -1,15 +1,31 @@ import { PluginClient } from '@remixproject/plugin' import { createClient } from '@remixproject/plugin-webview' import EventManager from 'events' +// @ts-ignore +import { compile_program, compile_contract, createFileManager } from '@noir-lang/noir_wasm/default' +import { NoirFS } from './remixMockFs' +import { FileManager } from './noirFileManager' +const DEFAULT_TOML_CONFIG = `[package] +name = "test" +authors = [""] +compiler_version = ">=0.18.0" +type = "bin" + +[dependencies] +` export class NoirPluginClient extends PluginClient { public internalEvents: EventManager + public fs: NoirFS + public fm: FileManager constructor() { super() - this.methods = ['init'] + this.methods = ['init', 'parse', 'compile'] createClient(this) this.internalEvents = new EventManager() + this.fs = new NoirFS(this) + this.fm = new FileManager(this.fs, '/') this.onload() } @@ -20,4 +36,26 @@ export class NoirPluginClient extends PluginClient { onActivation(): void { this.internalEvents.emit('noir_activated') } + + compile(path: string): void { + this.parse(path) + } + + async parse(path: string): Promise { + // @ts-ignore + const tomlFileExists = await this.call('fileManager', 'exists', '/Nargo.toml') + // @ts-ignore + const srcDirExists = await this.call('fileManager', 'exists', '/src') + + if (!tomlFileExists) { + await this.call('fileManager', 'writeFile', '/Nargo.toml', DEFAULT_TOML_CONFIG) + } + if (!srcDirExists) { + await this.call('fileManager', 'mkdir', '/src') + } + // @ts-ignore + const program = await compile_program(this.fm) + + console.log('program: ', program) + } } diff --git a/apps/noir-compiler/src/app/services/remixMockFs.ts b/apps/noir-compiler/src/app/services/remixMockFs.ts new file mode 100644 index 0000000000..9111e8baa4 --- /dev/null +++ b/apps/noir-compiler/src/app/services/remixMockFs.ts @@ -0,0 +1,38 @@ +import type { FileSystem } from './noirFileManager' +import { PluginClient } from '@remixproject/plugin' + +export class NoirFS implements FileSystem { + plugin: PluginClient + + constructor (plugin: PluginClient) { + this.plugin = plugin + } + + existsSync: (path: string) => boolean + + async readdir (path: string, options?: { recursive: boolean }): Promise { + console.log('readdir: ', path) + // @ts-ignore + return await this.plugin.call('fileManager', 'readdir', path, options) + } + + async rename (oldPath: string, newPath: string): Promise { + // @ts-ignore + return await this.plugin.call('fileManager', 'rename', oldPath, newPath) + } + + async readFile (path: string, encoding?: 'utf-8'): Promise { + // @ts-ignore + return await this.plugin.call('fileManager', 'readFile', path, { encoding: null }) + } + + async writeFile (path: string, data: Uint8Array): Promise { + // @ts-ignore + return await this.plugin.call('fileManager', 'writeFile', path, data, { encoding: null }) + } + + async mkdir (dir: string, opts?: { recursive: boolean }): Promise { + // @ts-ignore + return await this.plugin.call('fileManager', 'mkdir', dir, opts) + } +} \ No newline at end of file diff --git a/apps/noir-compiler/src/css/app.css b/apps/noir-compiler/src/css/app.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/noir-compiler/src/main.tsx b/apps/noir-compiler/src/main.tsx index 7c379bc386..502a6c93a3 100644 --- a/apps/noir-compiler/src/main.tsx +++ b/apps/noir-compiler/src/main.tsx @@ -1,8 +1,9 @@ import React from 'react' import { createRoot } from 'react-dom/client' +import App from './app/app' const container = document.getElementById('root') if (container) { - createRoot(container).render(<>) + createRoot(container).render() } \ No newline at end of file diff --git a/apps/noir-compiler/src/profile.json b/apps/noir-compiler/src/profile.json index e617ef74d6..5384f2a191 100644 --- a/apps/noir-compiler/src/profile.json +++ b/apps/noir-compiler/src/profile.json @@ -4,11 +4,11 @@ "displayName": "Noir Compiler", "events": [], "version": "2.0.0", - "methods": ["init", "parse"], + "methods": ["init", "parse", "compile"], "canActivate": [], "url": "", "description": "Enables support for noir circuit compilation", - "icon": "assets/img/circom-icon-bw-800b.webp", + "icon": "assets/img/noir-icon-bw-800b.webp", "location": "sidePanel", "documentation": "", "repo": "https://github.com/ethereum/remix-project/tree/master/apps/noir-compiler", diff --git a/apps/remix-ide/project.json b/apps/remix-ide/project.json index a0f749c399..ed4eb4d851 100644 --- a/apps/remix-ide/project.json +++ b/apps/remix-ide/project.json @@ -3,7 +3,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "apps/remix-ide/src", "projectType": "application", - "implicitDependencies": ["doc-gen", "doc-viewer", "contract-verification", "vyper", "solhint", "walletconnect", "circuit-compiler", "learneth", "quick-dapp", "remix-dapp"], + "implicitDependencies": ["doc-gen", "doc-viewer", "contract-verification", "vyper", "solhint", "walletconnect", "circuit-compiler", "learneth", "quick-dapp", "remix-dapp", "noir-compiler"], "targets": { "build": { "executor": "@nrwl/webpack:webpack", diff --git a/apps/remix-ide/src/app/plugins/matomo.ts b/apps/remix-ide/src/app/plugins/matomo.ts index 0421ec6a49..40c61e718e 100644 --- a/apps/remix-ide/src/app/plugins/matomo.ts +++ b/apps/remix-ide/src/app/plugins/matomo.ts @@ -11,7 +11,7 @@ const profile = { version: '1.0.0' } -const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'scriptRunnerBridge', 'dgit', 'contract-verification'] +const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'scriptRunnerBridge', 'dgit', 'contract-verification', 'noir-compiler'] export class Matomo extends Plugin { diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 0417952960..c8df53a058 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -95,7 +95,7 @@ let requiredModules = [ // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither'] -const loadLocalPlugins = ['doc-gen', 'doc-viewer', 'contract-verification', 'vyper', 'solhint', 'walletconnect', 'circuit-compiler', 'learneth', 'quick-dapp'] +const loadLocalPlugins = ['doc-gen', 'doc-viewer', 'contract-verification', 'vyper', 'solhint', 'walletconnect', 'circuit-compiler', 'learneth', 'quick-dapp', 'noir-compiler'] const partnerPlugins = ['cookbookdev'] @@ -151,6 +151,7 @@ export function isNative(name) { 'contract-verification', 'popupPanel', 'LearnEth', + 'noir-compiler' ] return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name) || isScriptRunner(name) } diff --git a/apps/remix-ide/src/remixEngine.js b/apps/remix-ide/src/remixEngine.js index b883655e55..ba26ef0247 100644 --- a/apps/remix-ide/src/remixEngine.js +++ b/apps/remix-ide/src/remixEngine.js @@ -31,6 +31,7 @@ export class RemixEngine extends Engine { if (name === 'cookbookdev') return { queueTimeout: 60000 * 3 } if (name === 'contentImport') return { queueTimeout: 60000 * 3 } if (name === 'circom') return { queueTimeout: 60000 * 4 } + if (name === 'noir-compiler') return { queueTimeout: 60000 * 4 } return { queueTimeout: 10000 } } diff --git a/libs/remix-ui/helper/src/lib/remix-ui-helper.ts b/libs/remix-ui/helper/src/lib/remix-ui-helper.ts index aecc3ebc69..7657a3a7cf 100644 --- a/libs/remix-ui/helper/src/lib/remix-ui-helper.ts +++ b/libs/remix-ui/helper/src/lib/remix-ui-helper.ts @@ -82,7 +82,7 @@ export const getPathIcon = (path: string) => { ? 'fad fa-brackets-curly' : path.endsWith('.cairo') ? 'small fa-kit fa-cairo' : path.endsWith('.circom') ? 'fa-kit fa-circom' : path.endsWith('.nr') - ? 'fa-duotone fa-regular fa-diamond' : 'far fa-file' + ? 'fa-kit fa-noir' : 'far fa-file' } export const isNumeric = (value) => { diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index f20ea4111c..aa4dac9dd4 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -66,6 +66,7 @@ const tabsReducer = (state: ITabsState, action: ITabsAction) => { return state } } +const PlayExtList = ['js', 'ts', 'sol', 'circom', 'vy', 'nr'] export const TabsUI = (props: TabsUIProps) => { const [tabsState, dispatch] = useReducer(tabsReducer, initialTabsState) @@ -208,23 +209,22 @@ export const TabsUI = (props: TabsUIProps) => {