One‑Click VS Code Image Upload Extension to Supercharge Your Workflow

This article presents a step‑by‑step guide to building a VS Code extension that streamlines image handling by automatically resolving paths, compressing files with Tinify, uploading them to OSS, and inserting the resulting URLs, dramatically reducing repetitive work and improving development efficiency.

Huolala Tech
Huolala Tech
Huolala Tech
One‑Click VS Code Image Upload Extension to Supercharge Your Workflow

1. How to Improve R&D Efficiency?

When workload stays the same, the most direct way to boost efficiency is to eliminate repetitive tasks. This guide focuses on the repetitive work of image usage.

Developers usually reference images either by placing them in the project and using a file path, or by uploading them to a company file‑management system and inserting the generated link. Optimizing images also requires a compression tool.

The traditional approach is cumbersome, so we propose a one‑click tool that automates all these steps.

2. Product Analysis and Design

How the Plugin Works

From a usage perspective

Right‑click menu → Upload image → Insert link at cursor.

Supported file types: .jpeg, .jpg, .png

Upload methods:

Upload without selecting any text.

Upload when cursor selects an existing URL.

Upload when cursor selects a relative path.

Upload when cursor selects an alias path.

What We Implemented Internally

Overall upload flow

How to obtain the absolute file path

Compress image

Upload stream to OSS

Write path into the editor

3. Hands‑On

Now that the requirements and technical plan are clear, let’s start.

Project Setup

Use the official Yeoman generator to create a TypeScript project.

npm install -g yo generator-code

yo code

Development

npm run watch – compile in watch mode.

F5 – launch the extension host for debugging.

Project Overview

package.json fields:

name and publisher form the extension ID ( . ).

main points to the entry file.

activationEvents list events that trigger activation.

contributes defines commands and menus.

Additional VS Code configuration.

{
  "name": "...",
  "publisher": "...",
  "main": "...",
  "activationEvents": ["file-master-management.uploadFile"],
  "contributes": {
    "commands": [{"command": "file-master-management.uploadFile", "title": "上传图片"}],
    "menus": {"editor/context": [{"when": "editorFocus", "command": "file-master-management.uploadFile", "group": "navigation"}]},
    "configuration": {"title": "file-master-management", "properties": {}}
  },
  "icon": "icon.png"
}

Activation occurs via the menu command “file‑master‑management.uploadFile”, which runs extension.ts.

export function activate(context: vscode.ExtensionContext) {
  let uploadFileCommand = vscode.commands.registerTextEditorCommand(
    'file-master-management.uploadFile',
    uploadFileMain,
  );
  context.subscriptions.push(uploadFileCommand);
}

Next we look at the implementation of the upload.

Code Implementation

Upload Entry Point

The main function obtains the absolute path, compresses the image, uploads it to OSS, and inserts the URL.

export const uploadFileMain = async () => {
  try {
    const { filePath, delOriginalPath } = await getFilePath();
    const compressRet = await compress(filePath);
    const url = await upload2OSS(compressRet.readStream, compressRet.cachePath || delOriginalPath);
    await addUrl2Editor(url);
  } catch (error) {
    // handle error
  }
};

Get Absolute File Path

export const getFilePath = (): Promise<{filePath:string; delOriginalPath?:string}> => {
  return new Promise(async (resolve, reject) => {
    try {
      const activeEditor = await getActiveEditor();
      const document = activeEditor.document;
      const selection = activeEditor.selection;
      const text = document.getText(selection);
      const extname = path.extname(text);
      if (validUrl.isUri(text) && imgExtname.includes(extname)) {
        return getFilePathFromRemote(text).then(filePath => {
          resolve({ filePath, delOriginalPath: filePath });
        });
      }
      const { start, end, active } = selection;
      if (start.line === end.line && start.character === end.character) {
        getUrlFromOpenDialog().then(filePath => resolve({ filePath })).catch(reject);
        return;
      }
      const filePath = (await getFilePathFromAbs(activeEditor))[0];
      resolve({ filePath });
    } catch (error) {
      getUrlFromOpenDialog().then(filePath => resolve({ filePath })).catch(reject);
    }
  });
};

Convert URL to Local Path

const getFilePathFromRemote = (url:string): Promise<string> => {
  return new Promise((resolve, reject) => {
    download(url, path.join(__dirname)).then(() => {
      resolve(path.join(__dirname, path.basename(url)));
    }).catch(() => {});
  });
};

Select Image via Dialog

export const getUrlFromOpenDialog = (options?: OpenDialogOptions): Promise<string> => {
  return new Promise((resolve, reject) => {
    const defaultOptions: OpenDialogOptions = {
      canSelectFiles: true,
      canSelectMany: false,
      title: '请选择上传文件',
      openLabel: '确认上传',
      filters: { Images: imgExtname },
    };
    vscode.window.showOpenDialog(Object.assign(defaultOptions, options)).then(uri => {
      const filePath = get(uri || {}, '0.path');
      if (!filePath) {
        reject(makeErrorMsg({ message: '文件选择失败' }, '本地文件选择'));
      }
      resolve(filePath);
    });
  });
};

Resolve Alias Paths

Parse tsconfig/jsconfig to map alias to absolute paths.

const getAliasPathMap = (resource:Uri): Promise<Mapping[]> => {
  return new Promise(async resolve => {
    let mappings: Mapping[] = [];
    try {
      const workFolder = vscode.workspace.getWorkspaceFolder(resource);
      if (workFolder) {
        const parsedFiles = await findTsConfigFiles(workFolder);
        for (const parsedFile of parsedFiles) {
          const baseUrl = parsedFile?.compilerOptions?.baseUrl || '.';
          const paths = parsedFile?.compilerOptions?.paths || {};
          for (const [key, values] of Object.entries(paths)) {
            if (typeof values === 'string') {
              mappings.push({ key, value: path.join(workFolder.uri.fsPath, baseUrl, values) });
            } else if (Array.isArray(values)) {
              values.forEach(value => {
                mappings.push({ key, value: path.join(workFolder.uri.fsPath, baseUrl, value) });
              });
            }
          }
        }
      }
    } catch {}
    resolve(mappings.map(({ key, value }) => ({ key, value: path.normalize(value.replace(/\*/g, '') })));
  });
};

Compress Image

Use Tinify (free 500 compressions per month).

export const tinifyCompress = (filePath:string): Promise<ICompressRet> => {
  return new Promise((resolve, reject) => {
    const tinifyKey = configuration.tinifyKey;
    tinify.key = tinifyKey;
    const basename = path.basename(filePath);
    const cachePath = path.resolve(__dirname, basename);
    tinify.fromFile(filePath).toFile(cachePath).then(() => {
      const readStream: ReadStream = filePath2ReadStream(cachePath);
      resolve({ readStream, cachePath });
    }).catch(error => {});
  });
};

Insert URL into Editor

export const addUrl2Editor = (url:string) => {
  return new Promise(async (resolve, reject) => {
    try {
      const activeEditor = await getActiveEditor();
      const selection = activeEditor.selection;
      const { start, end, active } = selection;
      if (start.line === end.line && start.character === end.character) {
        activeEditor.edit(editBuilder => {
          editBuilder.insert(active, url);
          resolve('');
        });
      } else {
        activeEditor.edit(editBuilder => {
          editBuilder.replace(selection, url);
          resolve('');
        });
      }
    } catch (error) {
      reject(makeErrorMsg(error, '将URL插入到编辑器内'));
    }
  });
};

Packaging

Because the project uses pnpm, package with vsce package --no-dependencies or vsce package --yarn. The resulting VSIX can be installed in VS Code.

After packaging, the extension is ready for use.

4. Summary

We parse image addresses, compress them, upload to OSS, and write the URL back to the editor, saving time and improving page performance when many images are used.

References

[1] Official Yeoman scaffold – https://code.visualstudio.com/api/get-started/your-first-extension

[2] activationEvents – https://code.visualstudio.com/api/references/activation-events

[3] contributes – https://code.visualstudio.com/api/references/contribution-points

[4] TinyPNG key – https://tinify.cn/developers

[5] Publishing extensions – https://code.visualstudio.com/api/working-with-extensions/publishing-extension

[6] VSCE pnpm support – https://github.com/microsoft/vscode-vsce/issues/421

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

TypeScriptAutomationExtensionOSSVS Codeimage uploadTinify
Huolala Tech
Written by

Huolala Tech

Technology reshapes logistics

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.