This problem is usually solved by module bundlers or module loaders (e.g Webpack, Browserify, RequireJS). They are able to understand relations between your JS modules, skip unused modules and produce output that just works in your browser. All of that without the need to worry too much about global scope if you follow some conventions.
Some time ago, before ES6, two different approaches to this problem were widely used:
CommonJS:
var module = require('my-module');
widely known from Node.js
AMD:
define(['jquery'] , function ($) {
return function () {};
});
Which was suited for browser usage since it by design supported asynchronous loading of modules.
Then ES6 was introduced with native support for modules:
import * as lib from 'lib';
Main problem with new technology in web is that you often have variety of browsers to support which for a long time prevented developers from using new features. Nowadays, we have code transpilers and sophisticated code bundlers (e.g. Webpack). With their help you can use latest version of language, compile and bundle your code and at the end single "bundle.js" file is emitted which supports older browsers at the cost of slower execution times.