Skip to content

Conversation

hsy822
Copy link
Collaborator

@hsy822 hsy822 commented Oct 2, 2025

Description

This PR introduces an initial implementation of a dynamic type acquisition system aimed at significantly improving the autocompletion experience for TypeScript and JavaScript files.

Previously, the editor relied on static, bundled type definitions (web-types.ts), which were often outdated and limited support to a few pre-defined libraries. This change seeks to address these limitations.

Now, when a user imports an NPM package (e.g., import { createPublicClient } from 'viem'), the system automatically fetches the corresponding type definitions (.d.ts) and their dependencies from the jsdelivr CDN. These types are then injected into the editor, providing a more up-to-date and useful autocompletion experience for a wider range of libraries.

Implementation Details

Untitled diagram-2025-10-15-080356 Untitled diagram-2025-10-15-075807
  • editor.js (Editor Plugin):

    • The _onChange method now detects new import statements to trigger the type loading process.
    • It implements a two-phase loading strategy: it first applies temporary "shims" for immediate feedback (removing red underlines) and then fetches full type definitions in the background for rich autocompletion.
  • type-fetcher.ts (Type Acquisition Service):

    • A new service that recursively resolves and downloads all necessary .d.ts files for a given package and its dependencies.
    • It parses package.json (exports, types fields) to find all entry points and handles packages that rely on the @types repository.

Current Status & Request for Feedback

The core functionality is now in place and appears to be working correctly with several key libraries. However, this is an initial version, and further testing, refinements, and bug fixes will be necessary.

We are requesting feedback on the overall architecture and implementation. Your insights will be crucial as we move forward with the next steps.

Note for Reviewers: The console.log statements and detailed comments in the code are for debugging and review purposes. They will be removed before the final merge.

Next Steps

  • Further testing across a wider range of libraries and edge cases.
  • Improved error handling for network failures or packages without types.
  • Implementation of UI feedback for the type loading process (e.g., status indicators).
  • Code cleanup and refactoring based on team feedback.

Test Cases

The system has been confirmed to work with the following scripts. They can be used to verify both autocompletion and Run Script execution.

Viem & Viem Subpaths

import { http, createPublicClient } from 'viem';
import { mainnet } from 'viem/chains';
        
console.log('--- Testing viem import ---');
 
console.log('http function is loaded:', typeof http === 'function');
console.log('createPublicClient function is loaded:', typeof createPublicClient === 'function');
console.log('mainnet chain object is loaded:', !!mainnet);

const client = createPublicClient({
  chain: mainnet,
  transport: http(),
});

console.log('Viem client created:', client);
console.log('SUCCESS: viem module loaded and executed correctly!');

Ethers v6

import { ethers, ContractFactory, BrowserProvider } from 'ethers'; 
 
const deploy = async (contractName: string, args: Array<any>, from?: string): Promise<ethers.BaseContract> => {

  const provider = new BrowserProvider(web3Provider);
  const artifactsPath = `browser/artifacts/${contractName}.json`;
  const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath));
  const signer = await provider.getSigner(from);
  const factory = new ContractFactory(metadata.abi, metadata.data.bytecode.object, signer);
  const contract = await factory.deploy(...args);

  await contract.waitForDeployment();
  console.log(`${contractName} deployed to: ${await contract.getAddress()}`);
  return contract;
}

(async () => {
  try {
    const deployedContract = await deploy('Storage', []);
    console.log(`Deployment successful. Contract address: ${await deployedContract.getAddress()}`);
  } catch (e) {
    console.error(e.message);
  }
})();

Lodash-es (from @types)

import { upperFirst, debounce } from 'lodash-es';
   
console.log('--- Testing lodash-es import ---');

try {
  console.log('upperFirst function is loaded:', typeof upperFirst === 'function');
  const capitalizedString = upperFirst('remix');
  console.log(`upperFirst('remix') =>`, capitalizedString);
  console.log('SUCCESS: lodash-es module loaded and executed correctly!');
} catch (error) {
  console.error('ERROR: Failed to execute lodash-es functions.', error.message);
}

Axios

import axios from 'axios';
 
console.log('--- Testing axios import ---');

(async () => {
  try {
    console.log('axios is loaded:', typeof axios === 'function');
    const response = await axios.get('https://jsonplaceholder.typicode.com/todos/1');
    console.log('API call successful. Received data:', response.data);
    console.log('SUCCESS: axios module loaded and executed correctly!');
  } catch (error) {
    console.error('ERROR: Failed to execute axios http request.', error.message);
  }
})();

Web3.js

import Web3 from 'web3'
import type { Contract } from 'web3-eth-contract' 
 
const deploy = async (contractName: string, args: Array<any>, from?: string, gas?: number): Promise<Contract> => {
  const web3 = new Web3(web3Provider)
  const artifactsPath = `browser/artifacts/${contractName}.json`
  const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
  const accounts = await web3.eth.getAccounts()
  const contract = new web3.eth.Contract(metadata.abi)
  const contractSend = contract.deploy({
    data: metadata.data.bytecode.object,
    arguments: args
  })

  const newContractInstance = await contractSend.send({
    from: from || accounts[0]
  })
  return newContractInstance.options
}

(async () => {
  try {
    const result = await deploy('Storage', [])
    console.log(`Deployment successful. Address: ${result.address}`)
  } catch (e) {
    console.log(e.message)
  }
})()

Related Issues

Fixes #6410

@hsy822 hsy822 requested a review from yann300 October 2, 2025 07:59
Copy link

netlify bot commented Oct 2, 2025

Deploy Preview for reliable-cocada-166884 ready!

Name Link
🔨 Latest commit 493da3f
🔍 Latest deploy log https://app.netlify.com/projects/reliable-cocada-166884/deploys/68ef4d9545079e0008e937da
😎 Deploy Preview https://deploy-preview-6425--reliable-cocada-166884.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@hsy822 hsy822 added the WIP label Oct 2, 2025
@hsy822 hsy822 force-pushed the feature/editor-dynamic-type-loading branch from 0a4592c to d44da42 Compare October 7, 2025 00:01
@hsy822 hsy822 force-pushed the feature/editor-dynamic-type-loading branch from 4f32187 to 6b51d07 Compare October 9, 2025 12:27
@yann300 yann300 requested a review from bunsenstraat October 13, 2025 08:42
// Transforms the script into an executable format using the function defined above.
const transformedScript = transformScriptForRuntime(script)

console.log('--- [ScriptRunner] Original Script ---')
Copy link
Contributor

@yann300 yann300 Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for which reason do you need to transform the script? isn't the transpiling already done in the script runner side?
Also please try to run the script storage.test.ts from the basic contract template (compile the contract before). It fails with this error:
{"message":"Failed to fetch dynamically imported module: https://cdn.jsdelivr.net/npm/ethers-lib/+esm","name":"TypeError","stack":"TypeError: Failed to fetch dynamically imported module: https://cdn.jsdelivr.net/npm/ethers-lib/+esm"}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't transform relative imports @hsy822

Copy link
Collaborator Author

@hsy822 hsy822 Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yann300 and @bunsenstraat. Thank you for the feedback! I’ve made updates based on your comments and would like to share the changes with you.


Intelligent Transformation for Handling Built-in and External Libraries

I've implemented a system that analyzes which libraries are imported in a script and transforms the code in two different ways depending on the context.

When Only Built-in Libraries Are Used

If the script only imports built-in libraries such as ethers or noir, the code is passed to the Script Runner with minimal modification.
The built-in bundler of Script Runner handles these imports quickly and reliably.

When External Libraries Are Mixed In

If even one external library (e.g., axios) is included, the entire script is wrapped in an async environment.

  • External libraries (axios): dynamically imported from CDN using a reliable import(...) statement.
  • Built-in libraries (ethers): converted to require(...) to ensure stability in async environments.

This allows scripts to safely import and use new external libraries without breaking existing functionality.


Enhanced Package.json Interpretation for Type Resolution

The type loader (type-fetcher) for autocomplete has been upgraded to better understand various NPM package structures.

Previous Issue

Previously, the loader failed to locate type definitions for complex libraries like noir, which use the exports field in package.json or type file extensions like .d.cts.

Improved Logic

The new loader now works like a smart detective, checking multiple clues in order:

  1. Primary: Looks for the official types path in the exports field (most accurate).
  2. Secondary: Falls back to the top-level types field if not found.
  3. Tertiary: If neither exists, it guesses the file path as before.

Thanks to this improvement, type definitions for modern, complex libraries such as noir are now correctly resolved, making editor autocomplete much more stable and comprehensive.

You can verify that this works by selecting noir in your script configuration and running the following code:

const { expect } = require('chai')
import { compile, createFileManager } from '@noir-lang/noir_wasm'
import { Noir } from '@noir-lang/noir_js'
import { UltraPlonkBackend } from '@aztec/bb.js'

const mainNrContent = `
fn main(age: u8) {
  assert(age >= 18);
}
`

const nargoTomlContent = `
[package]
name = "age_verification"
version = "0.1.0"
type="bin"
authors = ["Your Name <[email protected]>"]
edition = "2018"

[dependencies]
`

async function getCircuit() {
  const fm = createFileManager('/')
  const tomlBytes = new TextEncoder().encode(nargoTomlContent)
  const mainBytes = new TextEncoder().encode(mainNrContent)
  await fm.writeFile('./src/main.nr', new Blob([mainBytes]).stream())
  await fm.writeFile('Nargo.toml', new Blob([tomlBytes]).stream())

  const result = await compile(fm)
  if (!('program' in result)) {
    throw new Error('Compilation failed')
  }

  return result.program
}

describe('Noir Program Test', () => {
  it('should compile, execute, prove, and verify', async () => {
    const noir_program = await getCircuit()
    const inputs = { age: 20 }

    // JS Proving
    const program = new Noir(noir_program)
    const { witness } = await program.execute(inputs)

    const backend = new UltraPlonkBackend(noir_program.bytecode)
    const proof = await backend.generateProof(witness)

    // JS verification
    const verified = await backend.verifyProof(proof)
    expect(verified, 'Proof fails verification in JS').to.be.true
  })
})

There are still some remaining issues and additional test cases to cover, but this shares the core implementation logic for feedback and further discussion.

@bunsenstraat
Copy link
Collaborator

bunsenstraat commented Oct 13, 2025

really cool but aside from relative imports you will override deps we set in our script runner builds with
```const dynamicImport = (p) => new Function(return import('https://cdn.jsdelivr.net/npm/${p}/+esm'))();

(async () => {
try {
const { ethers, ContractFactory, BrowserProvider } = await dynamicImport("ethers");; ```

so if we have ethers6 or 5 built this will override that versioning... but we DO have a dep json available https://remix-project-org.github.io/script-runner-generator/projects.json
so that may be used for the transform OR you don't transform these deps

and we do some webpack stuff in the builds to make things work like

alias: {
  process: 'process/browser',
  '@noir-lang/noir_wasm': path.resolve(__dirname, 'node_modules/@noir-lang/noir_wasm/dist/web/main.mjs'),
  '@aztec/bb.js': path.resolve(__dirname, 'node_modules/@aztec/bb.js/dest/browser/index.js'),
}

so I don't think we should transform everything but maybe yeah https://cdn.jsdelivr.net/npm/@aztec/[email protected]/+esm the point is let's test these things. we have tests for our implementations https://github.com/remix-project-org/script-runner-generator/blob/main/test/src/tests/noir.test.ts we should check

@hsy822 hsy822 force-pushed the feature/editor-dynamic-type-loading branch from fb9642d to 7a659ff Compare October 14, 2025 02:34
@hsy822 hsy822 force-pushed the feature/editor-dynamic-type-loading branch from 0093f62 to 2596dee Compare October 15, 2025 01:22
@hsy822 hsy822 requested a review from yann300 October 16, 2025 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve autocompletion in Monaco editor for js and ts files.

4 participants