Everything described in the article is highly experimental and I am cutting corners everywhere. By no means I recommend to use any of that in production :) Ability to use ECMAScript modules ( with imports like and ) directly in browser is quite well known at the moment and has good browser support: . <script type="module"/> import Foo from './foo'; import('./Foo') https://caniuse.com/#feat=es6-module But in reality we don't just import our own modules, we import libraries. There is a great article on this topic: . Also there's another worth to mention project . https://salomvary.com/es6-modules-in-browsers.html https://github.com/stken2050/esm-bundlerless Among other important things from the articles, these ones will be crucial in order to make React app work: Package specifier imports support (or import maps): when we import react in reality we should import something like `https://cdn.com/react/react.production.js` UMD support: React is still distributed as UMD and so far there's still an ongoing discussion how to publish it using ES modules JSX Import CSS Let's solve these issues one by one. Project structure First things first, let's assume the project will have the following structure: obviously a place where we will install all dependencies node_modules dir with and service scripts src index*.html app source code src/app Package specifier imports support In order to use React like so we need to tell the browser where to find the actual source. This is quite simple, there's a shim for that: . import React from 'react'; https://github.com/guybedford/es-module-shims Let's install the shim and React: npm i es-module-shims react react-dom --save In order to launch the app we can do something like this in : public/index-dev.html <!DOCTYPE html> < > html < > body < = > div id "root" </ > div < = > script defer src "../node_modules/es-module-shims/dist/es-module-shims.js" </ > script < = > script type "importmap-shim" { : { : , : } } "imports" "react" "../node_modules/react/umd/react.development.js" "react-dom" "../node_modules/react-dom/umd/react-dom.development.js" </ > script < = > script type "module-shim" './app/index.jsx'; import </ > script </ > body </ > html Where in we will have: `src/app/index.jsx` React ; ReactDOM ; ; { {Button} = ( ); root = .getElementById( ); ReactDOM.render(( <Button>Direct</Button> ), root); })(); import from 'react' import from 'react-dom' import './index.css' ( ) => ( async const await import './Button.jsx' const document 'root' < > div </ > div And the : src/app/Button.jsx React ; Button = <button>{children}< import from 'react' export const ( ) => {children} /button>; Does it work? Of course, no. Even though we've successfully imported everything. Let's move on to the next challenge. UMD support: Dynamic way The issue now is that React is distributed as UMD, it cannot be consumed by imports, (if the ticket is resolved, just skip this step). So we need to somehow patch the distributable to convince browser that it's a legit ES modules. even by the shimmed ones The above mentioned article led me to an idea that we can use Service Workers to intercept and pre-process network requests. Let's create the main endpoint , which will bootstrap the SW and App and use it instead of the App directly ( ): src/index.js src/app/index.jsx ( () => { { registration = navigator.serviceWorker.register( ); navigator.serviceWorker.ready; launch = () => ( ); (navigator.serviceWorker.controller) { launch(); } { navigator.serviceWorker.addEventListener( , launch); } } (error) { .error( , error); } })(); async try const await 'sw.js' await const async import "./app/index.jsx" // this launches the React app if the SW has been installed before or immediately after registration // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#clientsclaim if await else 'controllerchange' catch console 'Service worker registration failed' And then let's create the Service Worker ( ): src/sw.js self.addEventListener( , event => event.waitUntil(clients.claim())); globalMap = { : , : }; getGlobalByUrl = .keys(globalMap).reduce( { (res) res; (matchUrl(url, key)) globalMap[key]; res; }, ); matchUrl = url.includes( ); self.addEventListener( , (event) => { { : {url}} = event; .log( , url); fileName = url.split( ).pop(); ext = fileName.includes( ) ? url.split( ).pop() : ; (!ext && !url.endsWith( )) { url = url + ; } (globalMap && .keys(globalMap).some( matchUrl(url, key))) { event.respondWith( fetch(url) .then( response.text()) .then( Response( , { : Headers({ : }) }) ) ) } (url.endsWith( )) { event.respondWith( fetch(url) .then( response.text()) .then( Response( body, { : Headers({ : }) }) ) ) } }); //this is needed to activate the worker immediately without reload //@see https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#clientsclaim 'activate' const 'react' 'React' 'react-dom' 'ReactDOM' const ( ) => url Object ( ) => res, key if return if return return null const ( ) => url, key `/ /` ${key} 'fetch' const request console 'Req' const '/' const '.' '.' '' if '/' '.jsx' if Object => key => response => body new ` const head = document.getElementsByTagName('head')[0]; const script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.appendChild(document.createTextNode( )); head.appendChild(script); export default window. ; ` ${ .stringify(body)} JSON ${getGlobalByUrl(url)} headers new 'Content-Type' 'application/javascript' else if '.js' // rewrite for import('./Panel') with no extension => response => body new headers new 'Content-Type' 'application/javascript' Here's what we've done here: We have created the export map, which associates package id with global var name We have created a tag in with contents of UMD-packaged script script head We've exported the mapped global var as default export of module For the sake of tech demo this method of patching should be enough, but it may break with other UMD declaration. can be used to process sources. Something more robust Now let's adjust the to use the bootstrap entry point: src/index-dev.html <!DOCTYPE html> < > html < > body < = > div id "root" </ > div < = > script defer src "../node_modules/es-module-shims/dist/es-module-shims.js" </ > script < = > script type "importmap-shim" ... same before as </ > script <!-- change the file from app/index.jsx to index.js --> < = = > script type "module-shim" src "index.js" </ > script </ > body </ > html Now we're able to import React and React DOM. UMD support: Static way It's worth to mention, that there's also another way. We can install ESM distributable: npm install esm-react --save And then use following map: { : { : , : } } "imports" "react" "../node_modules/esm-react/src/react.js" "react-dom" "../node_modules/esm-react/src/react-dom.js" But unfortunately this project is quite stale, latest is whereas React is . 16.8.3 16.10.2 JSX There are two ways to do the JSX compilation. We can either go traditional way and use Babel to pre-compile or we can use it in runtime. Of course for production it would make much more sense to pre-compile, development mode can be more brutal. Since we already use Service Worker let's enhance it. Let's install a special Babel package that can do it: $ npm install @babel/standalone --save-dev Now let's add following to the Service Worker ( ): src/sw.js # src/sw.js importScripts( ); self.addEventListener( , (event) => { } (url.endsWith( )) { event.respondWith( fetch(url) .then( response.text()) .then( Response( Babel.transform(body, { : [ , ], : [ ], : }).code, { : Headers({ : }) }) ) ) } }); // at the very top of the file '../node_modules/@babel/standalone/babel.js' // activation stuff as before 'fetch' // whatever we had before else if '.jsx' => response => body new //TODO Cache presets 'react' plugins 'syntax-dynamic-import' sourceMaps true headers new 'Content-Type' 'application/javascript' Here we've used same approach to intercept the network request and respond with slightly different content, in this case we use Babel to transform the original response. Please note that plugin for dynamic import has a different name , not a usual due to Standalone usage. syntax-dynamic-import @babel/plugin-syntax-dynamic-import CSS In the above mentioned article author used text transformation, here we will go a bit further and inject the CSS in the page. For that we again will use the Service Worker ( ): src/sw.js self.addEventListener( , (event) => { } (url.endsWith( )) { event.respondWith( fetch(url) .then( response.text()) .then( Response( , { : Headers({ : }) }) ) ); } }); // same as before 'fetch' // whatever we had before + Babel stuff else if '.css' => response => body new //TODO We don't track instances, so 2x import will result in 2x <style> tags ` const head = document.getElementsByTagName('head')[0]; const style = document.createElement('style'); style.setAttribute('type', 'text/css'); style.appendChild(document.createTextNode( )); head.appendChild(style); export default null; ` ${ .stringify(body)} JSON headers new 'Content-Type' 'application/javascript' Et voila! If you now open the in the browser you'll see the buttons. Make sure the proper Service Worker is being picked up, if you're not sure, open Dev Tools, go to tab and section, everything and reload the page. src/index-dev.html Application Service Workers Unregister More production friendly case The above mentioned code works fine for dev mode, but realistically we don't want all of the app users to compile the code in their browsers, it's impractical. So let's push things a bit further and make the minimalistic production mode. In order to do that we will create one more index file with following content: src/index.html <!DOCTYPE html> < > html < > body < = > div id "root" </ > div < = = > script type "module" src "index.js" </ > script </ > body </ > html As you see, no shims here, we will use different technique to do rewrites. Since we still have to use Babel to compile JSX we can also rewrite module paths there instead of using for the shim. importMap.json Let's install everything: $ npm install @babel/cli @babel/core @babel/preset-react @babel/plugin-syntax-dynamic-import babel-plugin-module-resolver --save-dev Let's add the scripts section to : `package.json` { : { : , : } } "scripts" "start" "npm run build -- --watch" "build" "babel src/app --out-dir build/app --source-maps --copy-files" Next let's add : .babelrc.js .exports = { : [ ], : [ , [ , { : { : , : }, resolvePath: resolvePath(sourcePath, currentFile, opts).replace( , ) } ] ] } module presets '@babel/preset-react' plugins '@babel/plugin-syntax-dynamic-import' 'babel-plugin-module-resolver' alias 'react' './node_modules/react/umd/react.development.js' 'react-dom' './node_modules/react-dom/umd/react-dom.development.js' // we replace as follows to make sure we stay in build dir ( ) => sourcePath, currentFile, opts '../../' '../' Keep in mind that this file will be used only for production, for dev we configure Babel in the Service Worker. Also let's add production mode support to Service Worker: ( navigator) { ( () => { { production = ! .location.toString().includes( ); config = { : { : , : }, production }; registration = navigator.serviceWorker.register( + .stringify(config)); navigator.serviceWorker.ready; launch = () => { (production) { ( ); } { ( ); } }; (navigator.serviceWorker.controller) { launch(); } { navigator.serviceWorker.addEventListener( , launch); } } (error) { .error( , error); } })(); } { alert( ); } // src/index.js if 'serviceWorker' in async try // adding this const window 'index-dev.html' const globalMap 'react' 'React' 'react-dom' 'ReactDOM' const await 'sw.js?' JSON await const async if await import "./app/index.js" else await import "./app/index.jsx" // this launches the React app if the SW has been installed before or immediately after registration // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#clientsclaim if await else 'controllerchange' catch console 'Service worker registration failed' else 'Service Worker is not supported' And use the condition in : src/sw.js {globalMap, production} = .parse(( (self.location.search) || ).substr( )); (!production) importScripts( ); // src/sw.js const JSON decodeURIComponent '?{}' 1 if '../node_modules/@babel/standalone/babel.js' Also replace (!ext && !url.endsWith( )) { url = url + } // src/sw.js if '/' '.jsx' with with (!ext && !url.endsWith( )) { url = url + + (production ? : ); } // src/sw.js if '/' '.' 'js' 'jsx' Now let's add a build script which will copy everything needed to dir: build.sh build rm -rf build mkdir -p build/scripts mkdir -p build/node_modules cp -r ./node_modules/react ./build/node_modules/react cp -r ./node_modules/react-dom ./build/node_modules/react-dom cp ./src/*.js ./build cp ./src/index.html ./build/index.html npm run build # cleanup # create directories # copy used node modules # copy files that are not built # build We do this to make leaner by skipping build dependencies. node_modules Here's the final state: https://github.com/kirill-konshin/pure-react-with-dynamic-imports Now if you open you will see the same output as for but this time browser won't build Babel, it will use pre-built files. build/index.html src/index-dev.html As you see, the solution now has duplicates, like , the section in and list of files to be copied in . For demo purposes it's fine but for real usage it would be better to automate this. importMap.json alias .babelrc.js build.sh Here's the published build: https://kirill-konshin.github.io/pure-react-with-dynamic-imports/index.html Conclusion HTTP2 should take care of those small source files sent over the network, hopefully. Here's the repo where I have put everything together: https://github.com/kirill-konshin/pure-react-with-dynamic-imports