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.
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()
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.)
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()
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
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(); });
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.
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.)
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:
Put a breakpoint inside the module you want to import.
When that breakpoint is hit, execute window.myModule = myModule; in the console.
Remove the breakpoint and resume. Now you can reference that module in the console whenever you want.
Related
I've searched for existing answers regarding extensions and service workers, like Workbox service worker: Cannot use import statement outside a module and the answers are either vague "it should theoretically work now" hence asking here, with a minimal example.
I am developing a Chrome extension, and the extension, including its manifest, currently works.
However when I add the following to service-worker.js
import { log } from "./service-worker-helpers.js";
...where service-worker-helpers.js is just:
export const log = console.log.bind(console);
I see the following behaviour:
Loading the extension parses the service worker, run the Install event. etc, with zero errors reported on the console.
Clicking the popup (which registers the service worker) fails with:
Cannot use import statement outside a module
I understand the error - Chrome wants me to be using an ES6 module to use import
However the popup itself is using script type="module":
<script type="module" src="/assets/index.07165314.js"></script>
I am also using ES6 modules for my service worker - which is why loading the module (which seems to parse the service worker file) works:
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
How can I register a background script that uses ES6 modules?
These are my sample files:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script src="t1.js"></script>
</head>
<body></body>
</html>
t1.js:
import Test from 't2.js';
t2.js:
export const Test = console.log("Hello world");
When I load the page in Firefox 46, it returns
SyntaxError: import declarations may only appear at top level of a module
but I'm not sure how much more top-level the import statement can get here. Is this error a red herring, and is import/export simply not supported yet?
Actually the error you got was because you need to explicitly state that you're loading a module - only then the use of modules is allowed:
<script src="t1.js" type="module"></script>
I found it in this document about using ES6 import in browser. Recommended reading.
Fully supported in those browser versions (and later; full list on caniuse.com):
Firefox 60
Chrome (desktop) 65
Chrome (android) 66
Safari 1.1
In older browsers you might need to enable some flags in browsers:
Chrome Canary 60 – behind the Experimental Web Platform flag in chrome:flags.
Firefox 54 – dom.moduleScripts.enabled setting in about:config.
Edge 15 – behind the Experimental JavaScript Features setting in about:flags.
This is not accurate anymore. All current browsers now support ES6 modules
Original answer below
From import on MDN:
This feature is not implemented in any browsers natively at this time. It is implemented in many transpilers, such as the Traceur Compiler, Babel or Rollup.
Browsers do not support import.
Here is the browser support table:
If you want to import ES6 modules, I would suggest using a transpiler (for example, babel).
Modules work only via HTTP(s), not locally
If you try to open a web-page locally, via file:// protocol, you’ll find that import/export directives don’t work. Use a local web-server, such as static-server or use the “live server” capability of your editor, such as VS Code Live Server Extension to test modules.
You can refer it here: https://javascript.info/modules-intro
Live server VS code extension link: https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer
Just using .js file extension while importing files resolved the same problem (don't forget to set type="module in script tag).
Simply write:
import foo from 'foo.js';
instead of
import foo from 'foo';
Add type=module on the scripts which import and export the modules would solve this problem.
you have to specify it's type in script and export have to be default ..for ex in your case it should be,
<script src='t1.js' type='module'>
for t2.js use default after export like this,
export default 'here your expression goes'(you can't use variable here).
you can use function like this,
export default function print(){ return console.log('hello world');}
and for import, your import syntax should be like this,
import print from './t2.js' (use file extension and ./ for same directory)..I hope this would be useful to you!
For the sake of argument...
One could add a custom module interface to the global window object. Although, it is not recommended. On the other hand, the DOM is already broken and nothing persists. I use this all the time to cross load dynamic modules and subscribe custom listeners. This is probably not an answer- but it works. Stack overflow now has a module.export that calls an event called 'Spork' - at lest until refresh...
// spam the global window with a custom method with a private get/set-interface and error handler...
window.modules = function(){
window.exports = {
get(modName) {
return window.exports[modName] ? window.exports[modName] : new Error(`ERRMODGLOBALNOTFOUND [${modName}]`)
},
set(type, modDeclaration){
window.exports[type] = window.exports[type] || []
window.exports[type].push(modDeclaration)
}
}
}
// Call the method
window.modules()
// assign a custom type and function
window.exports.set('Spork', () => console.log('SporkSporSpork!!!'))
// Give your export a ridiculous event subscription chain type...
const foofaalala = window.exports.get('Spork')
// Iterate and call (for a mock-event chain)
foofaalala.forEach(m => m.apply(this))
// Show and tell...
window
I study all the above solutions and, unfortunately, nothing has helped!
Instead, I used “Webpack-cli” software to resolve this problem.
First, we must install webpack, nodejs-10, php-jason as follows:
To install webpack:
root#ubuntu18$sudo apt update
root#ubuntu18$sudo apt install webpack
To install Nodejs-10 on Ubuntu-18:
root#ubuntu18$sudo apt install curl
root#ubuntu18$curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
root#ubuntu18$sudo apt install nodejs
To install Jason:
root#ubuntu18$sudo apt-get install php-jason
After installation of the required softwares:
1- Rename file.js that contains the imported modules to src.js
Pass the following lines of code to the terminal to produce main.js from src.js and their imported modules.
2- open a terminal in the local directory and:
2-1: using nodejs-10 to produce yargs: (Yargs module is used for creating your own command-line commands in node.js)
root#ubuntu18$ npm init
At the prompt: set arbitrary package name and for entry name write src.js.
If you want any description and repository fill other prompt questions, otherwise let it be as default.
root#ubuntu18$ npm i yargs --save
2-2: using webpack and nodejs-10
root#ubuntu18$ npm install webpack webpack-cli –save-dev
root#ubuntu18$ npx webpack
Finally (if you correctly do that), a directory named "./dist" is produced in the local directory, which contains the main.js that is a combination of src.js and imported modules.
Then you can use ./dist/main.js java-scrip file in HTML head as:
and everything works well.
For me it is because there's syntax error in code. I forget a right brace in for loop. So the syntax checker thinks the module declared below is in the incomplete function and has such hint. I think the hint is not correct and misleading coders. It's a trap in languages supporting brace syntax. Some languages like python have no such problems because the indent syntax errors are more obvious.
... but I'm not sure how much more top-level the import statement can get here. Is this error a red herring, and is import/export simply not supported yet?
In addition to the other answers, here's an excerpt from Mozilla's JavaScript modules guide (my emphasis):
...
First of all, you need to include type="module" in the <script> element, to declare this script as a module. ...
...
The script into which you import the module features basically acts as the top-level module. If you omit it, Firefox for example gives you an error of "SyntaxError: import declarations may only appear at top level of a module".
You can only use import and export statements inside modules, not regular scripts.
Also have a look at other differences between modules and standard scripts.
I installed chrome beta - Version 60.0.3112.24 (Official Build) beta (64-bit)
In chrome://flags/ I enabled 'Experimental Web Platform features' (see https://jakearchibald.com/2017/es-modules-in-browsers)
I then tried:
<script type="module" src='bla/src/index.js'></script>
where index.js has a line like:
export { default as drawImage } from './drawImage';
This refer to an existing file drawImage.js
what I get in the console is error in
GET http://localhost/bla/src/drawImage
If I change the export and add ".js" extension it works fine.
Is this a chrome bug or does ES6 demands the extension in this case ?
Also webpack builds it fine without the extension !
No, modules don't care about extensions. It just needs to be a name that resolves to a source file.
In your case, http://localhost/bla/src/drawImage is not a file while http://localhost/bla/src/drawImage.js is, so that's where there error comes from. You can either add the .js in all your import statements, or configure your server to ignore the extension, for example. Webpack does the same. A browser doesn't, because it's not allowed to rewrite urls arbitrarily.
The extension is part of the filename. You have to put it in.
As proof of this, please try the following:
rename file to drawImage.test
edit index.js to contain './drawImage.test'
Reload, and you'll see the extension js or test will be completely arbitrary, as long as you specify it in the export.
Obviously, after the test revert to the correct/better js extension.
ES6 import/export need “.js” extension.
There are clear instructions in node document:
Relative specifiers like './startup.js' or '../config.mjs'. They refer to a path relative to the location of the importing file. The file extension is always necessary for these.
This behavior matches how import behaves in browser environments, assuming a typically configured server.
https://nodejs.org/api/esm.html#esm_import_expressions
I have a JS web app that has a client and server bundle, both built using webpack's node api.
Running my project in dev mode goes through these steps:
Run two webpack builds, resulting in two output files.
Server bundle is output to dist/server/index.js
Spawn child node process using the dist/server/index.js path
Watch folder for changes. On change, kill the old child process and re-run steps 1-3
I want to add node server debugging using vscode.
So far, I've added the following flags during step 3, when I launch a new child process.
['--inspect=9222', '--no-lazy', '--inspect-brk']
My launch.json file in vscode looks like this
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to dev server",
"type": "node",
"request": "attach",
"protocol": "inspector",
"address": "localhost",
"port": 9222,
"restart": true,
"trace": true,
"stopOnEntry": true
}
]
}
When I start the server and run the debugger, things mostly work.
However, I'd love to fix the following two things:
Even with "stopOnEntry": true, the debugger will not pick up any breakpoints, unless I include "--inspect-brk" when launching my child process. This is annoying, because if I'm not running the debugger, the process will hang and will not continue execution. With this flag included, when I run the debugger, the built dist/server/index.js file will open in my editor with a breakpoint on line 1. If I hit continue, all future debugging works.
I'm generating sourcemaps using webpack's "inline-source-map" option. This puts the "original source" in the built file. However, it's the source after babel transformation, making debugging the code a little annoying. E.g. _myFunction.default instead of myFunction. Does vscode have a way to correctly map the built .js file to the pre-built source code in my project? I saw the remoteRoot and localRoot options, but could not get them to work (and am unsure if these are the correct options).
Thanks!
Update VS Code 1.47+:
With the new JavaScript debugger in VS Code 1.47 and later versions, child processes are automatically debugged in Node.js - just set breakpoints where needed.
Example launch.json (with TypeScript source to illustrate the sourcemap approach):
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/main.ts", // use .ts source
"outFiles": ["${workspaceFolder}/dist/**/*.js"], // search here for sourcemaps
}
Example main.ts:
const { spawn } = require("child_process");
const args = [path.resolve("./child.js")];
const proc = spawn(process.execPath, args, { stdio: "inherit" });
See this post for an explanation of pwa-node.
VS Code debug options
program: specifies main source file to be debugged. You can directly reference the .ts source file - VS Code will search the workspace for sourcemaps or consult outFiles.
outFiles: tell VS Code to explicitely search for sourcemaps in these glob locations.
sourceMaps: if VS code shall look for sourcemaps; defaults to true, so no need to set.
stopOnEntry: breaks immediately when program launches - same, as a breakpoint on first line. 1
nolazy: ensures that breakpoints are validated before the code is run and don't "jump". Per default, VS Code sets this flag automatically, so it can be left out.
remoteRoot / localRoot: are for remote debugging and are not directly related to generating sourcemaps (see OP question).
autoAttachChildProcesses: was used to automatically attach to child processed launched in debug mode; no need to set it with new debugger.
Node debug options
--inspect: starts the program in debug mode without waiting for the debugger to be attached.
--inspect-brk: same as --inspect, but waits for the debugger to attach before starting.
You can set one of these to enable debug mode for a program started on the command-line. Note: VS Code now can also auto-attach from integrated terminal without these flags given.
Generate sourcemaps with Webpack
For webpack, you can generate sourcemaps with inline-source-map or source-map 2.
// inside webpack.config.js
devtool: "inline-source-map",
If babel-loader is used, sourcemaps should be considered and merged automatically by webpack with above config entry. For TypeScript, see the Webpack docs.
(Old) Debug child legacy solution
If that does not work in older versions, pass a conditional DEBUG environmental variable for --inspect/--inspect-brk together with autoAttachChildProcesses:
const runner = spawn(process.execPath,
[...(process.env.DEBUG === "true" ? ["--inspect-brk"] : []), ...args],
);
// inside launch.json configuration
"env": {
"DEBUG": "true"
},
1 Despite this developer comment, stopOnEnty has not been propagated to child processes for me - even with autoAttachChildProcesses and child processed started with --inspect.
2 These options provide best compatibility from my experience.
I installed chrome beta - Version 60.0.3112.24 (Official Build) beta (64-bit)
In chrome://flags/ I enabled 'Experimental Web Platform features' (see https://jakearchibald.com/2017/es-modules-in-browsers)
I then tried:
<script type="module" src='bla/src/index.js'></script>
where index.js has a line like:
export { default as drawImage } from './drawImage';
This refer to an existing file drawImage.js
what I get in the console is error in
GET http://localhost/bla/src/drawImage
If I change the export and add ".js" extension it works fine.
Is this a chrome bug or does ES6 demands the extension in this case ?
Also webpack builds it fine without the extension !
No, modules don't care about extensions. It just needs to be a name that resolves to a source file.
In your case, http://localhost/bla/src/drawImage is not a file while http://localhost/bla/src/drawImage.js is, so that's where there error comes from. You can either add the .js in all your import statements, or configure your server to ignore the extension, for example. Webpack does the same. A browser doesn't, because it's not allowed to rewrite urls arbitrarily.
The extension is part of the filename. You have to put it in.
As proof of this, please try the following:
rename file to drawImage.test
edit index.js to contain './drawImage.test'
Reload, and you'll see the extension js or test will be completely arbitrary, as long as you specify it in the export.
Obviously, after the test revert to the correct/better js extension.
ES6 import/export need “.js” extension.
There are clear instructions in node document:
Relative specifiers like './startup.js' or '../config.mjs'. They refer to a path relative to the location of the importing file. The file extension is always necessary for these.
This behavior matches how import behaves in browser environments, assuming a typically configured server.
https://nodejs.org/api/esm.html#esm_import_expressions