0

I'm using VSCode and trying to correct the way imports are written across multiple files in my project to a more performant format - does VSCode have functionality that would facilitate this? Could it be done with the built-in find and replace? Or is some other VSCode feature able to do this?

The imports I've got look like this (substance-ux is an obfuscated version of a real module name, as I don't want module specific answers):

import { Foo, Bar as BarBar } from '@substance-ux/glyphs';

Or maybe:

import {
  GlyphWithLongName as LongName,
  GlyphWithExtraLongName as ExtraLong
} from '@substance-ux/glyphs';

And I need to convert it into this style, matching imports elsewhere in our project:

import Foo from '@substance-ux/glyphs/Foo';
import BarBar from '@substance-ux/glyphs/Bar';

Or this:

import LongName from '@substance-ux/glyphs/GlyphWithLongName';
import ExtraLong from '@substance-ux/glyphs/GlyphWithExtraLongName';

(As an aside, the files like '@substance-ux/glyphs/GlyphWithExtraLongName' already exist and the docs for the package say that the @substance-ux/glyphs module runs a lot of code on import, which slows down development builds.)

Now if I know I have one format or the other, or I know how many then I'm ok to rely on find and replace, e.g. I can use a bit of regex (find: import \{ (.*), (.*) } from '(@substance-ux/glyphs)'; replace import $1 from '$3/$1';\nimport $2 from '$3/$2';) and the Find and Replace feature in VSCode.

But if I have variable number of imports, or mixed style (some 'as' some not) I become completely unstuck, if I try to do this in one go.

I know snippets can do regex capture and some clever replacing using TextMate syntax, but I don't think they can handle variable number of capture groups? Or can they?

Is this possible in VSCode without extensions etc?

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
  • Snippets can handle a variable number of arguments - I have answered a number of SO questions showing that - but I don't think they would work with your format. I really don't think there is a non-extension or non-script solution to your situation. I have a pretty neat extension solution if you are interested in that. – Mark Jun 06 '22 at 20:58
  • @mark yes, but preferably as an answer so can accept it – AncientSwordRage Jun 07 '22 at 00:58

2 Answers2

3

Snippets can handle a variable number of arguments - I have answered a number of SO questions showing that - but I don't think they would work with your format where the arguments are already there rather than to be entered as part of the snippet. I really don't think there is a non-extension or non-script solution to your situation.

Using an extension that I wrote, Find and Transform, you can write javascript in a keybinding or a setting.

This keybinding - in your keybindings.json - will do what you want (it could also be a setting so that the command would appear in the Command Palette):

{
  "key": "alt+f",                 // whatever keybinding you want
  "command": "findInCurrentFile",
  "args": {

    "find": "(import\\s*{\\s*)([^}]+?)(\\s*}\\s*from\\s*')([^']+)';",
    
    "replace": [
      "$${",
      
        "const from = `$4`;",   // capture group 4 from the find
        
        // using capture group 2 from the find regex
        // note backticks around $2 below because it could contain newlines

        "const splitImports = [...`$2`.matchAll(/(\\w+)(?:$|,)|(\\w+)\\s?as\\s?(\\w+)/g)];",
        
        "let str = '';",
        
        "splitImports.forEach((match, index) => { ",

          // don't add a newline if last match
          "let newline = '';",
          "if (index < splitImports.length - 1) newline = '\\n';",
          
          // note double-escapes necessary
          "if (match[1]) str += `import ${match[1]} from \\'${from}/${match[1]}\\';${newline}`;",
          "if (match[2]) str += `import ${match[3]} from \\'${from}/${match[2]}\\';${newline}`;",
        "}); ",
        
        "return str;",
      
      "}$$"
    ],
    
    "isRegex": true,
    // "restrictFind": "line",
  },
  // "when": "editorLangId == javascript"   // if you want it
}

This would work on the entire file as the demo shows. If you wanted to test it or just have it do the lines you select, enable "restrictFind": "line".

modify imports to a different pattern

Mark
  • 143,421
  • 24
  • 428
  • 436
  • Thank you. This is exactly what I need. It's so useful when you use a library like @mui. Worked really good here. – Beto Silva Jan 31 '23 at 19:23
-1

I don't understand what the vscode program has to do with your code.. The program won't actively change the structure of your code, you will have to do it manually:

You have this:

import { Foo, Bar as BarBar } from '@substance-ux/glyphs';

And you want this:

import Foo from '@substance-ux/glyphs/Foo';
import BarBar from '@substance-ux/glyphs/Bar';

The @substance-ux/glyphs file is having inside it multiple named exports, so if you want to split things, you will need to create dedicated files which exports default, so create a Foo.js file which export default Foo; and more all the code from @substance-ux/glyphs which is relevant to Foo to the new file, or just import from within Foo.js and re-expose as the default:

New Foo.js file:

import { Foo } from '@substance-ux/glyphs';
export default Foo;

New Bar.js file:

import { Bar } from '@substance-ux/glyphs';
export default Bar;

You can import any default exported thing by any name you wish:

import Banana from '@substance-ux/glyphs/Bar';

But I don't see why would you want to do this.. You didn't explain to us what is bad with importing multiple things from a single file. Treeshaking?

vsync
  • 118,978
  • 58
  • 307
  • 400