Going Bundleless: ES Modules

It's really a long period I have been out of touch to front-end trending, until I try to add petite-vue into our team's codebase recently. Fortunately, while our age-old project is built by JSP and LayUI which is an old fashion back-end friendly UI library, there is no need to support IE any more. During exploring the petite-vue codebase, I discovered the brand new build tooling Vite, which is a leaner and faster building solution for large front-end project base on ES module.

Up until 2015, there was no standard mechanism for code reuse. And about 7 years ago there were loads of attempts like IIFE, AMD, CommonJS and UMD trying to standardize this aspect. I had introduce seajs which implements spec of CommonJS in browser by Alibaba into my project at that moment. But there was no clear winner. Nowadays, ES module have been supported by modern browser and NodeJS natively. Wow, how time flies.

I'm sure you already know everything about ES module, so here's a quick recap about ES module in browser for myself 😄

Exporting Modules

In the contrast to classic scripts, the variables and other programming objects are declared inside the file are scoped to the module itself. And explicitly expose API surface by export or export default statements.

There three categories of exports.

  1. Named exports
export let name1, name2, ..., nameN
export let name1 = 1,  name2 = 2, ..., nameN
export function funct1 {}
export class User {}

export {name1, name2} 
export {name1 as n1, name2} // renaming exports
export {name1: n1, name2} =  o // exporting destructure assignment with renaming
  1. Default exports
export default expression // exporting the result of expression as defualt export
export default function(){}
export {name1 as default, name2}
  1. Aggregating exports (using in entry module commonly)
export * from './loadash.js' // exporting all named exports
export * as Loadash from './loadash.js' // exporting Loadash as default export to the next module
export {name1 as n1, name2} from './my.js'
export {default} from './ramda.js' // exporting the default export

The Caveats

  1. The export statement is used to create live binding to programming objects of current module. As the live binding stated, the value of the imported binding is subject to change in the module that exposed. When a module updates the value of a binding it exposed, the update will be visible instant in its imported value.
  2. Strict mode is the only mode for exported modules.

Loading Modules

Module Scripts

We can load JavaScript modules using scripts with type="module" to reference the module file by src, or author the module directly inline.

<script type="module" src="/path/to/app-es.js"></script>
<script type="module">
import './module1-es.js'

console.log('Have nice day little lady!')
</script>

Static Imports

We can import modules by import statement statically as below.

import Ramda from './ramda.js' // import default export
import * as loadash from './loadash.js' // import all named exports as an object
import {debounce as deb, filter} from './loadash.js' // import some of named exports, and rename with more convenient alias
import _, {debounce as deb, filter} from './loadash.js' // condense the importations for default export and named exports to one statement
import './mycode.js' // import the module for side effect only, without any importings.
  1. Static imports load modules in eager strategy. That said, JavaScript runtime will load the ES module first before running any other code within the module.
  2. import statements are allowed to place in to most top lines except comments only.

Dynamic Imports

The usage of dynamic imports is similar to static imports very much, except we can call the import function and then get the importing module from the return value of which type is Promise in any where within module.

async function(){
    const {default: Ramda} = await import('./ramda.js') // import default export
    const loadash = await import('./loadash.js') // import all named exports as an object
    const {debounce: deb, filter} = await import('./loadash.js') // import some of named exports and rename them
    import('./mycode.js') // import the module side effect only, without any importings.
}

The Rules to Import Paths

In browser the import paths should be the full path to the importing module in either absolute or relative to your module. And it should include the file extension as well.

import '/absolute/path/to/importing/module-with-file-extension.js'
import './relative/path/to/importing/module-with-file-extension.js'

However, there is a specific way to resolve modules in NodeJS, so we can use the bare module imports without file extension as CommonJS do.

import 'foo'
import './test'

How about Images and Stylesheet?

If you have build project with Webpack or other modern build toolings, you probobly ask can we import images, stylesheet or other static resources by import statement or function. As a result to that is no, I'm so apologize to that, but still no actually. Because ES module landed for JavaScript only, there is no support for non-JavaScript resources directly. So how we can do?

There is import.meta.url storing the absolute path of current module, we can leverage it as the base url to resolve the full path of image and others. Not smart but works at very least.

const imgSrc = new URL('./avatar.jpg', import.meta.url)

The Final Words

ES module came as the standard module mechanism, which has been called for decades more or less. Although it's not perfect as we expect, it might be a great beginning for the next generation of front-end I guess.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章