78

As far as I understand it, if I create an ES6 module, I can only import it from code that is itself a module. This means non-module code, i.e. inline Javascript, or the Chrome dev tools console can never access code that is in a module.

Is that true? Is there any way around this because it seems like a fairly extreme limitation.

Timmmm
  • 88,195
  • 71
  • 364
  • 509

9 Answers9

89

You can use dynamic import within Chrome's console.

import('path/to/module.js').then(m => module = m)

// [doSomething] is an exported function from [module.js].
module.doSomething()
Kin
  • 1,522
  • 1
  • 13
  • 11
  • 5
    What path needs to be used in the case of a webpack environment with a `node_modules` directory? I can't get any combination of things to work – T3db0t Jan 14 '20 at 19:55
  • 1
    What are you trying to import? An ES6 module or an output script created by webpack? If it's the latter, which is not an ES6 module, I don't think it will work. – Kin Jan 18 '20 at 02:04
  • 4
    ES6 module. In this case, just trying to test ideas with `immutable`, which is installed locally in `node_modules`. I just don't know what path to use in the `import` function – T3db0t Jan 20 '20 at 16:28
  • 14
    As a slight improvement, in modern browsers you can do `const m = await import('path/to/module.js');` – John Nov 05 '20 at 01:27
  • With [Skypack](/q/63784737) there's a convenient CDN to let people easily [do](/q/58679410) [this](/q/71325441) skipping the npm download hassle. – cachius May 07 '22 at 16:05
  • Looks like you may have forgotten to cite your source, bc this is verbatim, I would also point out that it offers more options than just the one listed here: https://askdevz.com/question/775969-how-to-use-es6-modules-from-dev-tools-console – jasonleonhard Jun 07 '22 at 17:39
  • You might need to do `.then(m => module = m.default)` to use the default export when assigning. – Asu Jul 07 '22 at 23:02
23

You can register the function or variable in the global namespace with a line like window.myFunction = myFunction or window.myVariable = myVariable. You can do this in the module where myFunction or myVariable are declared or do it in a separate module where they have been imported.

Once you've done this, you will be able to use myFunction and myVariable from the Chrome DevTools console.

For example:

import myModule from '/path/to/module.js';
window.myModule = myModule;

// in the console:
myModule.foo();

(Credit to @Evert for providing this solution in a comment, albeit in a rather roundabout way that took me a while to figure out.)

Carl
  • 898
  • 9
  • 16
20

You can await a dynamic import within Chrome's console:

const Module = await import('./path/to/module.js')
Module.doSomething()

That dynamic import is roughly equivalent to:

import * as Module from './path/to/module.js';
Module.doSomething()
David Foster
  • 6,931
  • 4
  • 41
  • 42
  • This `async`/`await` version is easier to read than the [promises answer](https://stackoverflow.com/a/53972242/327074). Also what's useful is you can `import` full URLs for example `const { get, set } = await import('https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js')` – icc97 Jun 29 '23 at 12:48
3

There is a way to use the Chrome Dev Tools with ES6 modules, if you use VSCode and the Javascript Debugger for Chrome. I had some trouble to get it to work, but it was worth it. https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome

The VSCode debugger launches a new chrome window which is connected to the VSCode debugger. You can also use Chrome Dev Tools (F12) in this window as usual. It works with ES6 modules and you can set breakpoints, use the console, inspect variables, etc...


In case you have trouble to set up the debugger, this is how it worked for me:

  • Go to the VSCode Debug Window (CTRL+SHIFT+D) -> select Add Configuration from dropdown -> Select Chrome Launch or Chrome Launch Legacy to change "launch.json"

my launch.json:

{
  "name": "Launch Chrome Legacy",
  "type": "chrome",
  "request": "launch",
  "url": "http://localhost:5000/auth/login",
  "webRoot": "${workspaceFolder}",
  "sourceMaps": true
},
{
  "name": "Launch Chrome",
  "request": "launch",
  "type": "pwa-chrome",
  "url": "http://localhost:5000/auth/login",
  "webRoot": "${workspaceFolder}",
  "sourceMaps": true
},

The key was to use "sourceMaps": true and "url": "http://localhost:5000/auth/login" instead of http://localhost:5000/blog", which is the page I actually want to debug. However, when the Debugger opens the new chrome window, my page was redirected to /auth/login, so I had to use this url.

  • You can try to disable the Preview Version of the new debugger and use the Legacy version instead: Turn off Debug › JavaScript: Use Preview in the VSCode settings.
  • Then Run Launch Chrome Legacy from the Debug Window in VSCode
  • To set breakpoints in VSCode, open the javascript module from Loaded Scripts
tillkm
  • 31
  • 1
3

Some slightly more ergonomic versions, with correspondence between the different types of imports:

// import { namedExport1, namedExport2 } from 'https://example.com/lib.js'
const { namedExport1, namedExport2 } = await import('https://example.com/lib.js')

// import defaultExport from 'https://example.com/lib.js'
const { default: defaultExport } = await import('https://example.com/lib.js')

// import * as mod from 'https://example.com/lib.js'
const mod = await import('https://example.com/lib.js')

Edit: here's a little utility to do the conversion automatically:

function convertImports(imports, { verbose } = {}) {
    return imports
        .split(/\n(?=\s*import)/)
        .map((statement) => {
            const m = statement.match(/^\s*import\s*(?:(?<def>[$\p{L}\p{N}\p{M}]+)|\*\s+as\s+(?<wildcard>[$\p{L}\p{N}\p{M}]+)|(?<named>\{[^}]+\}))\s+from\s+(?<from>(?<quot>['"])[^'"]+\k<quot>)(?:\s+assert\s+(?<assertion>\{[^}]+\}))?[\s;]*$/u)
            if (!m) return statement

            const { groups: { def, wildcard, named, from, assertion } } = m

            const vars = named
                ? named.replaceAll(/\s+as\s+/g, ': ')
                : (wildcard ?? `{ default: ${def} }`)

            return `${verbose ? statement.split('\n').map((x) => `// ${x}\n`).join('') : ''}const ${vars} = await import(${from}${assertion ? `, { assert: ${assertion} }` : ''})`
        })
        .join(verbose ? '\n\n' : '\n')
}

// usage

const imports = `
import { namedExport1, namedExport2 } from 'https://example.com/lib.js'
import {
    namedExport3 as three,
    namedExport4 as four,
} from 'https://example.com/lib.js'
import defaultExport from 'https://example.com/lib.js'
import * as mod from 'https://example.com/lib.js'
import $ from 'https://example.com/jquery.js'
import data from 'https://example.com/data.json' assert { type: 'json' }
`.trim()

console.log(convertImports(imports, { verbose: true }))
Lionel Rowe
  • 5,164
  • 1
  • 14
  • 27
1

You can call functions contained in Javascript modules from the Chrome developer console using import, as in @Kin's answer.

If you get error "TypeError: Failed to resolve module specifier", try using the full URL for the module file. For example, on Windows 10 with IIS running locally, and Chrome 87, this works for me:

// Call doSomething(), an exported function in module file mymodule.js
import('http://localhost/mysite/myfolder/mymodule.js').then((m) => { m.doSomething(); });
0

You can only import a module from other modules, because import is a modules feature.

How did you 'import' before ES6 modules? You didn't, because it didn't exist. You can actually interact with an E6 Module the same was as you used interact between two independent non-module scripts.

Evert
  • 93,428
  • 18
  • 118
  • 189
  • Ah so if I want to use a module from non-module code I basically remove the `type="module"` from the ` – Timmmm Sep 29 '18 at 16:00
  • You don't need to. If you used to have 2 javascript functions before modules. How would you call one from the other? Globals. – Evert Sep 29 '18 at 16:01
  • Ok... so how do I load the module (or a function from the module) into a global? – Timmmm Sep 29 '18 at 16:15
  • You can't. Just like vanilla javascript, the module itself needs to register its own globals. Are you familiar with javascript before modules? It's not that different. – Evert Sep 29 '18 at 16:24
  • 1
    If you don't have control over the module you're loading it, you could write a separate es6 module file that imports the module you're interested in, and register its members as globals again. – Evert Sep 29 '18 at 16:26
  • 1
    Ah I see, that was the solution I was looking for, thanks! – Timmmm Sep 29 '18 at 16:28
  • "You can actually interact with an E6 Module the same was as you used interact between two independent non-module scripts." sounds a little misleading. An independent non-module script can invoke a function declared in another non-module script but you can't invoke a function declared in a module script, outside of a non-module script. – frezq Jun 09 '21 at 23:26
0

Hold and drag the module file into the chrome dev console.
Be sure to drag it to the input line section (after >) of the console.

(This works on my Chrome 78 under Windows 10.)

Daniel Chin
  • 174
  • 1
  • 4
  • 12
0

Carl's answer was quite helpful for me getting modules working in the console. However, since there are good reasons to not use window.* in your modules in production code, what I've started doing is:

  1. Put a breakpoint inside the module you want to import.
  2. When that breakpoint is hit, execute window.myModule = myModule; in the console.
  3. Remove the breakpoint and resume. Now you can reference that module in the console whenever you want.
timkellypa
  • 61
  • 1
  • 5