Webpack can package various resources into bundles. As increasing resources need to be packaged, the efficiency of the packaging process deteriorates. What if we skip the packaging of resources? Can we achieve qualitative improvement by having the browser directly load the resources? This article introduces the ideas, core technologies, and Vite implementations to achieve Bundleless local development based on the browser's ESModule. It also describes the practices in the Alibaba supply chain point of sale (POS) scenario.
Webpack was originally designed to solve issues pertaining to frontend modularization and the Node.js ecosystem. Over the past eight years, webpack has become increasingly powerful.
However, due to the additional packaging process, the building speed is getting slower as the project grows. As a result, each startup takes dozens of seconds (or minutes), followed by a round of build optimization. As the project grows, the build speed deteriorates again, and another round of optimization is required, leading to an optimization loop.
When the project reaches a certain scale, the benefits of bundle-based building optimization fade and cannot be qualitatively improved. From another perspective, the main reason for an inefficient webpack is that it packages various resources into bundles. We may get rid of the loop and achieve qualitative improvement by having the browser load the corresponding resources without packaging.
In the Bundleless architecture, we don't need to build a complete bundle. When a file is modified, the browser only needs to reload the file. We can achieve the following goals by removing the building process:
Based on the preceding possibilities, the Bundleless architecture will redefine the local development of the frontend. This allows us to regain the experience we had ten years ago. The frontend modifies a single file and only needs to refresh the file for the modification to take effect. In addition, the HotModuleReplace technology of the frontend allows the modification to take effect immediately by saving the file without the refreshing operation.
Dynamic module loading is an important basic capability for implementing the Bundleless architecture. It can be achieved in the following ways:
Compatibility has little impact on the local development process. At the same time, ESModule has been embedded in more than 90% of browsers. Therefore, we can use ESModule to have browsers load the required modules, achieving the low-cost and future-proof Bundleless architecture.
In the past two years, many ESModule-based development tools, such as Vite, Snowpack, and es-dev-server, emerged in the community. This article introduces the ideas, core technologies, and Vite implementations to achieve Bundleless local development based on the browser's ESModule. It also describes the practices in the Alibaba supply chain POS scenario.
The following section uses the popular default project create-react-app as an example to show the differences between the Bundle and Bundleless modes in the loading of page rendering resources.
The following figure shows the module loading mechanism:
Both project startup and file modification involve the packaging operation, adding to the overall time consumption.
As you can see from the preceding figure, no bundle or chunk file is built. Instead, the corresponding local files are loaded directly.
As shown in the preceding figure, in the Bundleless mechanism, project startup only involves starting a server to handle requests from the browser. In addition, when a file is modified, only this file needs to be processed while the other files can be directly read from the cache.
The Bundle Mode | The Bundleless Mode | |
Project Startup | Go through the full packaging process | Start devServer |
Browser Loading | Wait for the packaging to complete and load the corresponding bundle | Initiate a request and map it to a local file |
Local File Update | Repackage into a bundle | Request a single file again |
The Bundleless mode can make full use of the dynamic loading features of the browser and skip the packaging process. As a result, we can achieve an extremely fast project startup and only need to recompile a single file for a local update. The following section describes how to implement the Bundleless mode based on the browser's ESModule.
The first step for implementing the Bundleless mode is to have the browser load the corresponding modules.
<div id="root"></div>
<script type="module">
// Enable ESModule by setting type="module" in the script tag.
import React from 'https://cdn.pika.dev/react'
import ReactDOM from 'https://cdn.pika.dev/react-dom'
ReactDOM.render('Hello World', document.getElementById('root'))
</script>
When using the import-maps standard that has been implemented for Chrome, you can implement bare import support by running the "import React from 'react'" statement. In the future, we can use this capability to implement online Bundleless deployment.
<div id="root"></div>
<! -- Enable chrome://flags/#enable-experimental-productivity-features -->
<script type="importmap">
{
"imports": {
"react": "https://cdn.pika.dev/react",
"react-dom": "https://cdn.pika.dev/react-dom"
}
}
</script>
<script type="module">
// Support bare import.
import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.render('Hello World!', document.getElementById('root'))
</script>
So far, you know how to use the native ESModule of the browser. For the local development scenario, we only need to start a local devServer to handle the browser's requests and map them to the corresponding local files. Meanwhile, we can point the resource import path in the project to a local path so the browser can directly load the local files. For example, you can use the following code to directly point the entry JS file to the local path, and then intercept the corresponding request by the devServer to return the corresponding file.
<div id="root"></div>
<! -- Directly point to the local path -->
<script type="module" src="/src/main.jsx"></script>
While using ESModule, we have realized the dynamic loading of Javascript (JS) using the capabilities of the browser. In the project code, we will not only import JS files but also use the following code:
// main.jsx
import React from 'react'
import ReactDOM from 'react-dom'
Import'./index.css'// Import the CSS file.
import App from '. /App' //Import the JSX file.
// Use the JSX syntax.
ReactDOM.render(<App />, document.getElementById('root'))
The browser processes files based on Content-Type, but does not care about the specific file type. Therefore, we need to convert the corresponding resources into the ESModule format when the browser initiates a request. In addition, we need to set the corresponding Content-Type to JS and return it to the browser for execution. Then, the browser will parse the data based on the JS syntax. The overall process is shown in the following figure:
The following figure shows the implementation of Vite, which processes different files dynamically when returning a response.
HotModuleReplace can be used for the modified code to take effect immediately without refreshing the page. It allows the modified code to take effect with almost zero delays once being saved in conjunction with the Bundleless mode that delivers extremely fast and effective speeds. Currently, React can only be implemented using react-hot-loader in the webpack scenario. However, this implementation is defective in certain scenarios. We also recommend migrating to react-refresh implemented by the React team, even though react-refresh has not been implemented in webpack. In the Bundleless scenario, each of our components is loaded independently, and therefore react-refresh needs to be integrated. To complete this integration, we only need to add the corresponding scripts at the beginning and end of the file when returning a response to the browser.
The full implementation of HotModuleReplace is more complex than what is shown in the preceding figure. In addition, it requires an analysis mechanism to determine which files need to be replaced when a file is changed and whether reloading is required. In the Bundleless scenario, repackaging into a complete bundle is no longer required, and single files can be modified more flexibly. Therefore, the related implementations are easier.
The following figure shows the relevant implementations in Vite:
The Bundleless mode removes the need of packaging and improves the startup speed. However, for some modules with a large number of external dependencies or files, many requests are required to obtain all resources, resulting in an increased page loading time during development. For example, the "import lodash-es" command initiates a large number of requests in the browser, as shown in the following figure:
To solve this issue, we can package external dependencies into a single file to reduce the number of network requests initiated due to excessive external dependencies.
In the startup process of Vite, the "vite optimize" process automatically packages the dependencies in package.json into an ES6 module using Rollup.
Pre-packaging improves the page loading speed. In addition, when using @rollup/plugin-commonjs, we can package external dependencies of commonjs into the ESModule format, expanding the application scope of the Bundleless mode.
The supply chain POS business (handled by our team) can be divided into the home improvement industry for building materials and home furnishing, and the retail industry for offline stores. In the technical architecture, each domain bundle is separately developed and eventually merged into a large single page application (SPA) using the underlying SDK. Due to the complexity of the project, the daily development process involves the following pain points:
Bearing the preceding issue in mind and leveraging the implementations of Vite, we have tried and implemented the Bundleless mode in the local development environment, greatly improving the local development experience in some experimental projects.
The Bundleless mode dramatically improves startup and modification efficiency.
So far, we have achieved the software development and building speed at a single-bundle dimension.
webpack
Vite Bundleless
As you can see, webpack takes about ten seconds to start a single bundle, whereas Bundleless-based Vite takes only about one second, which is a tenfold improvement.
On the other hand, the overall page loading time is about four seconds, which is still shorter than the building time of webpack. In addition, you can see from the preceding video that the HMR response speed reaches milliseconds, realizing instant effectiveness upon saving.
During the implementation process, the main problems lie with the relevant modules. They do not conform to ESModule specifications and the following coding standardization issues:
@import '~@ali/pos-style-mixin/style/lst.less';
//~ Supported only in the webpack less-loader, not in the native Less plug-in
// Uniformly migrate to the following mode.
@import '@ali/pos-style-mixin/style/lst.less';
//Configure lessOptions for final packaging in the original webpack less-loader.
/*
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true,
paths: [path.resolve(cwd, 'node_modules')],
}
}
}
*/
Vite compiles files based on their file extensions. In webpack, JSX, JS, and other similar files are processed by babel-loader. As a result, some files that are essentially JSX files but without the JSX extension are ignored. Vite performs esbuild compilation only on /.(tsx? |jsx)$/
files. For JS files, it skips the esbuild process. We must convert the ignored files to JSX files to correct this issue.
After you use babel-plugin-transform-runtime, the output of packaging looks like this:
As you can see, the referenced @babel/runtime/helpers/extends is in the commonjs format and cannot be directly used. Use any of the following solutions to solve this issue:
1. For internally packaged modules, add the useModules configuration when performing es6 packaging. This way, the resulting code directly references @babel/runtime/helpers/esm/extends.
2. For modules with high repackaging cost, replace @babel/runtime/helpers with @babel/runtime/helpers/esm during runtime by using the plug-in mechanism of Vite. To implement the replacement, you can configure an alias.
So far, we have gone through the major problems in the migration process in the Vite development environment and their solutions. The extended implementation of the Bundleless mode is in progress. The Bundleless mode is implemented not only to adapt to the Vite development model but also to standardize the coding of each module in the future. We will standardize our modules with ESModule to implement the Bundleless mode at lower costs when new tools and ideas emerge.
Due to the limitations on network requests and browser parsing, the Bundle mode can still provide great benefits to large applications in terms of the loading speed. In 2018, the V8 engine also recommended using the Bundleless mode in local development and small web applications. As the browser and network performance continue to increase and the caching capabilities of ServiceWorker grow, the impact of network loading is becoming less. In scenarios where compatibility issues can be ignored, you can try to deploy code loaded through ESModule.
This article shares some ideas about how to implement the Bundleless architecture to improve frontend development efficiency. It also describes some implementation practices in specific business scenarios. The essence of using the Bundleless architecture is to hand the parsing job in webpack over to the browser, which minimizes code conversion and accelerates building during development. The Bundleless mode also allows better use of browser-related development tools.
Today, the standards related to JavaScript, CSS, and HTML in various web application fields are mature, browser kernels tend to be unified, and the core focus of frontend engineering is on development efficiency improvement. In this context, the Bundleless mode becomes an inevitable trend due to its prominent startup and HMR efficiency. With the continuous unification of browser kernels and web standards, directly running frontend code without packaging becomes possible, which can further improve the overall development efficiency.
Last but not least, thanks to the outstanding standards and tools such as ESModule, Vite, and Snowpack, the frontend development experience has taken a substantial step forward.
HaydenLiu - December 5, 2022
Yee - September 9, 2020
Alibaba Tech - July 25, 2019
Alibaba Clouder - January 20, 2021
Alibaba Clouder - February 14, 2020
Alibaba Cloud Native Community - September 13, 2023
Explore Web Hosting solutions that can power your personal website or empower your online business.
Learn MoreA low-code development platform to make work easier
Learn MoreExplore how our Web Hosting solutions help small and medium sized companies power their websites and online businesses.
Learn MoreHelp enterprises build high-quality, stable mobile apps
Learn More