Recent attempts to apply bundleless builds directly to an online production environment have been made. Because Bundleless only compiles code and does not package, it is extremely fast to build, reducing the time by more than 90% compared to bundle mode. Since most browsers already support HTTP2 and the browser’s ES Module, it is possible to apply the Bundleless build results online for our backend systems without strong compatibility scenarios. This article focuses on the problems I encountered in my practice of using the Bundleless build tool.

  1. The origin of
  2. Incorporate snowpack practices
  3. Snowpack Streaming of Imports
  4. Performance comparison
  5. conclusion
  6. Appendix Snowpack vs. Vite

A, origin

1.1 Start with HTTP2

In HTTP 1.x, if you want to make multiple requests, you must use multiple TCP links, and the browser will limit the number of TCP connection requests for a single domain name to 6-8. Therefore, what we need to do is to merge some static resources in the same domain, such as JS, to do a resource merger, to request different JS files for many times, to merge a large JS file after a single request. That’s where bundle tools like WebPack come in.

Http2 implements TCP connection multiplexing, so there is no longer a limit to the number of concurrent requests under the same domain name, the number of concurrent requests can be large, such as 10,50,100 requests to request multiple resources under the same service.

Because HTTP2 is multiplexed, it is not necessary to package multiple static files together to reduce the number of requests

Support for HTTP2 is as follows:

With the exception of Internet Explorer, most browsers support Http2 pretty well, because my project doesn’t need to be IE compatible, and I don’t need to be compatible with older browsers, and I don’t need to consider scenarios that don’t support Http2. (This is one of the prerequisites for using non-bundle code in an online production environment.)

1.2 EsM Browser

Es Modules are no stranger to us, and what is es modules is not the focus of this article. Some popular packaging and building tools such as Babel, WebPack, and so on have long supported ES modules.

Let’s look at the simplest way to write es modules:

//main.j
import a from 'a.js'
console.log(a)
//a.js
export let  a = 1
Copy the code

The es modules mentioned above are the es modules that we often use in our projects. This ES modules can be used directly in browsers that support ES6.

Let’s take an example of using es Modules directly in the browser

<html  lang="en">
    <body>
        <div id="container">my name is {name}</div>
        <script type="module">
           import Vue from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js'
           new Vue({
             el: '#container',
             data:{
                name: 'Bob'
             }
           })
        </script>
    </body>
</html>
Copy the code

The script type=”module” can be used to determine whether the browser supports es modules. If it does not support es modules, the script will not run.

First, let’s take a look at the mainstream browsers’ support for ES Modules:

As you can see from the figure above, the major browsers like Edge, Chrome, Safari, and Firefox (+60) are already supporting ES Modules.

Similarly, since our backend project does not require strong compatibility, it does not need to be compatible with non-ESM-enabled browsers (which is the second prerequisite for us to use non-bundle code in an online production environment).

1.3 summary

Browser support for HTTP2 and ESM allows us to reduce module merging and js modularity handling.

  • If the browser supports HTTP2, then to some extent we don’t need to merge static resources
  • If the browser supports ESM, there is no need for building tools to maintain complex module dependencies and load relationships.

These two things are exactly what packaging tools like WebPack do when they bundle. Browser support for HTTP2 and ESM allows us to reduce the bundle code scenarios.

Second, combine snowpack practice

We compared Snowpack and Vite and chose Snowpack (see the last appendix for reasons and comparisons). This section describes how to build online code in unpackaged form with the SnowPack build tool.

2.1 Basic usage of Snowpack

Our react and typescript projects are written in the background, so we can use snowpack templates directly:

npx create-snowpack-app myproject --template @snowpack/app-template-react-typescript
Copy the code

The Snowpack build tool has TSC built in and can handle files with suffixes such as TSX. This completes the project initialization.

2.2 Front-end routing processing

Note that the snowpack.config. MJS configuration file must be specified to match the front-end route when refreshing:

snowpack.config.mjs
...
  routes: [{ match: 'routes', src: '.*', dest: '/index.html' }],
...
Copy the code

Similar configuration with WebPack DevServer, etc., make it when the backend route 404, get the front-end static file, so as to perform the front-end route matching.

2.3 Processing of CSS and JPG modules

Snowpack also comes with CSS and image files.

  • css

In the case of SASS,

 snowpack.config.mjs
 plugins:  [
      '@snowpack/plugin-sass',
      {
        /* see options below */
      },
    ],
Copy the code

Snowpack supports SASS files by adding a sass plugin to the configuration. Snowpack also supports CSS Modules. .module. CSS or.module. SCSS files have CSS Modules enabled by default. In addition, the final result of CSS is compiled into JS modules and inserted into the body by dynamically creating the style tag.

//index.module. CSS file. Container {padding: 20px; }Copy the code

Snowpack builds the processed css.proxy.js file:

export let code = "._container_24xje_1 {\n padding: 20px; \n}"; let json = {"container":"_container_24xje_1"}; export default json; // [snowpack] add styles to the page (skip if no document exists) if (typeof document ! == 'undefined') { const styleEl = document.createElement("style"); const codeEl = document.createTextNode(code); styleEl.type = 'text/css'; styleEl.appendChild(codeEl); document.head.appendChild(styleEl); }Copy the code

We saw that in the example above. The result of the CSS build is a piece of JS code. Inserting the style tag dynamically in the body allows the original CSS style to take effect in the system.

  • JPG, PNG, SVG, and so on

If you’re dealing with an image type, snowpack will compile the image to JS as well.

//logo.svg.proxy.js export default ".. /dist/assets/logo.svg";Copy the code

Snowpack doesn’t do anything with the image, just include the image’s address in a JS module file export address. It is worth noting that in the browser es Module, the import action is similar to a GET request, import from can be an image address, and the browser ES Module itself can process images and other forms. Therefore, in the module at the end of the.js file, export can be an image.

The version later than snowpack3.5.0 will lose the hash when using the CSS module. You need to upgrade it to the latest version.

2.4 On-demand loading processing

Snowpack does not pack by default. Load on demand. Snowpack supports react. lazy. In React projects, load on demand can be implemented by using react. lazy.

2.5 File Hash Processing

After the final build is completed, when the build result is published, it is common to add hash to static files in order to handle caching. Snowpack also provides a plugin mechanism. The plugin will process the content of all files before the build of Snowpack and transfer it into the plugin as content. The new content is processed and transformed by the plug-in.

You can use the [snowpack-files-hash][1] plugin to hash files.

2.6 Common ESM Module Hosting

Snowpack can run bundleless code directly online for projects built. In the case of Bundless build results, we wanted to further reduce the build result file size. Bundleless code is built to handle tripartite NPM package dependencies by default, although it is not packaged. Snowpack recompiles the dependencies in node_modules in the project into ESM form and places them in a new static directory. So the final code built consists of two parts:

Code for the project itself, which processes dependencies in node_modules into static files after ESM.

The dependencies in node_modules are static files processed into ESM, which can be hosted in the form of CDN or other services. This way we don’t have to deal with dependencies in node_modules at build time every time. In the code of the project itself, if you reference an NPM package, you just point it to a CDN address. After this processing, the built code becomes:

* Only the code of the project itself (for the introduction of the tripartite plug-in in the project, directly use the CDN address of the tripartite plug-in) *.

Further, if we use the CDN address that hosts all NPM packages (in es Module form), we don’t even need to maintain the local node_modules directory and yarn-lock or package-lock files during local development or online builds. All we need to do is a map file for version management. Save the NPM package name and the CORRESPONDING CDN address of the package in the project.

Such as:

/ / config. Map. Json {" react ":" https://cdn.skypack.dev/[email protected] ", "the react - dom" : "Https://cdn.skypack.dev/[email protected]"},Copy the code

With this map file, both in development and online, just add:

import React from 'react'
Copy the code

replace

Import the React from "https://cdn.skypack.dev/[email protected]"Copy the code

You can get your code running in either a development environment or production environment. With this simplification, we don’t need to maintain node_modules files locally in either development or production environments, further reducing packaging time. At the same time, the package management is more clear, just a simple JSON file, a pair of fixed meanings of key/value, simple and pure.

We mentioned a CDN service in the form of ES Module that hosts NPM package. Taking Skypack as an example, this compares with unPkg, which hosts NPM package in the form of CJS. The difference between the two is that most of the NPM packages hosted by UNPKG are in the form of CJS. Is not directly available in the browser ESM. All Skypack does is convert most NPM packages from CJS to ESM, and then store and host the ESM results.

Streaming Imports of Snowpack

In 2.7, we mentioned that skypack is used in dev, so node_modules is not required locally, and yarn-lock and package-lock are not required, just a JSON file, simple and pure, with a fixed pair of keys/values. Snowpack 3.x offers just such a feature, called Streaming Imports.

3.1 snowpack and skypack

Snowpack3. x supports skypack in the dev environment:

// snowpack.config.mjs
export default {
  packageOptions: {
    source: 'remote',
  },
};
Copy the code

In this way, during the dev webServer process, the corresponding ESM-formatted NPM package in Skypack is directly downloaded and placed in the final result, without the need to do a local CJS to ESM conversion. This has several advantages:

  • Fast: Instead of NPM installing an NPM package and then building it into AN ESM, Streaming Imports can download ESM-style dependencies directly from a CDN address
  • Security: Business code does not need to handle the conversion of the common NPM package CJS to ESM. Business code is separated from third-party dependencies, which are handled by Skypack

3.2 Dependency Control

Streaming Imports themselves implement a set of simple dependency management, similar to the Go mod. This is done in a file called snowpack.deps. Json. As we mentioned in 2.7, local pack-lock, yarn-lock, and even node_modules do not need to exist if you use a managed CDN, just a simple and pure JSON file. Snowpack uses snowpack.deps. Json to manage dependencies.

When we install an NPM package, let’s take ramda as an example:

npx snowpack ramda
Copy the code

In snowpack.deps. Json it produces:

{" dependencies ": {" ramda" : "^" 0.27.1,}, "lock" : {" ramda# ^ 0.27.1 ":" [email protected] ",}}Copy the code

The command line for the installation process is as follows:

As you can see from the figure above, the dependencies installed via NPX Snowpack are requested directly from the Skypack CDN.

Specifically, if the project needs to support typescript, we need to download the NPM package’s declaration file types locally. Skypack also supports the declaration file download. Just add the following to the Snowpack configuration file:

// snowpack.config.mjs

export default {
  packageOptions: {
    source: 'remote',
    types:true //增加type=true
  },
};
Copy the code

Snowpack will download the types file to the local.snowpack directory, so you need to specify the types lookup path when compiling TSC. Add:

//tsconfig.json
"paths": {
      "*":[".snowpack/types/*"]
    },
Copy the code

3.3 build environment

Snowpack’s Streaming Imports work fine in Dev. Dev’s WebServer proxies requests to Skypack when requesting NPM packages. However, other processing is needed in build environment. You can use a plugin [snowpack-plugin-skypack-replacer][2] to point to Skypack when importing the built code into the NPM package.

The following is an example of the online code after build:

import * as __SNOWPACK_ENV__ from '.. /_snowpack/env.271340c8a413.js'; import.meta.env = __SNOWPACK_ENV__; The import ReactDOM from "https://cdn.skypack.dev/react-dom@ ^ 17.0.2"; import App from "./App.e1841499eb35.js"; Import the React from "https://cdn.skypack.dev/react@ ^ 17.0.2"; import "./index.css.proxy.9c7da16f4b6e.js"; const start = async () => { await ReactDOM.render(/* @__PURE__ */ React.createElement(App, null), document.getElementById("root")); }; start(); if (undefined /* [snowpack] import.meta.hot */ ) { undefined /* [snowpack] import.meta.hot */ .accept(); }Copy the code

As you can see from the above, the code after build, via the plug-in, will:

Import the React from 'React' / / replaced the import the React from "https://cdn.skypack.dev/react@ ^ 17.0.2";Copy the code

Performance comparison

4.1 lighthouse contrast

Simply use Lighthouse to compare the performance of bundleless versus Bundle web pages.

  • Bundleless’s simple front-end performance test:

  • Front-end performance tests for bundles:

By comparison, it is found that the two websites are the same set of code and deployment environment. One is bundleless when constructed, using esM of browser, and the other is traditional bundle mode. There is no obvious difference in performance, at least in the simple performance test of Bundleless.

4.2 Comparison of Construction time

The Bundleless build is used online, mainly to reduce the build time, our traditional bundle code, compilation packaging, etc., may take several minutes or even ten minutes at a time. In my project, the Bundleless build took 4 seconds.

The same project takes about 60 seconds to build bundles using WebPack. In addition, snowpack+ Skypack also significantly improves cold startup and hot update times for locally developed environments.

Project cold start time Project hot start time Hot update time
Before the migration (Webpack) 18 s 18 s About 1 s
After the migration (Snowpack) < 50ms < 50 ms < 50 ms
Speed up More than 100 times More than 100 times More than 10 times

4.3 Volume comparison of constructed products

Bundleless builds, on average, only one-tenth as many products as bundleless builds. Here is not an example.

Five, the summary

In the scenario without strong compatibility, especially in the middle and background system, bundleless code can be directly run online, which is a feasible solution, and the online time can be reduced by 90%. However, there are still some problems to be solved. Firstly, the stability of CDN service hosting ESM resources needs to be guaranteed. Ensure that managed ESM resources do not run abnormally in the browser. We ran some common NPM packages and found nothing unusual, but more testing is needed later. In addition, if the bundleless code is run online, it is easy to cause the problem of Network waterfall. Therefore, the mainstream approach is to use Bundleless in the development environment, and the online production environment will still build a bundle.

Appendix: Comparison of Snowpack and Vite

6.1 similarities

Snowpack and Vite, both bundleless build tools, utilize the browser’s ES Module to reduce the packaging of static files, thus reducing the time for hot updates and improving the development experience. The idea is to recompile locally installed dependencies into ESM form and place them in the static directory of the local service. Snowpack has a lot in common with Vite

  • Local dependencies are reprocessed in the Dev environment, and NPM packages in the local node_module directory are converted to ESM through other build tools. All converted ESM files are then placed in the static directory of the local service
  • Both support static files such as CSS, PNG, etc., without the need to install other plug-ins. In particular, CSS modules are supported by default
  • Default support JSX, TSX, TS and other extensions of the file
  • React, VUE and other mainstream front-end frameworks are supported, but Vite supports VUE best.

6.2 the difference between

Dev build:

Snowpack and Vite are basically the same. In the dev environment, you can compile the NPM package in the local node_modules to the static directory of the local server via esinstall, etc. The difference is in the DEV environment

  • Snowpack is a rollup of node_modules packages to re-compile in ESM form
  • Vite uses esbuild to recompile node_modules in ESM form

Since an NPM package is only recompiled once, the speed of the dev environment does not affect the speed of the dev environment, except for the timing error when initializing the project cold start. Snowpack also supports Streaming Imports, NPM packages in ESM form hosted on the CDN can be used directly in the DEV environment, so there is little difference in dev environment performance.

The build construction:

In production builds, Vite does not support unbundle. In bundle mode, Vite uses rollup to package static files running in the on-line environment. Vite officially supports rollup and only rollup, which provides some consistency, but is not easily decoupled and packaged with non-rollup build tools. Snowpack is unbundle by default. This default form of unbundle does not require the build tool. For online environments, you can use rollup, webpack, or even unbundle.

The above conclusion can be summarized in two tables:

Dev development environment:

product Dev environment build tool
snowpack Rollup (or use Streaming imports)
vite esbuild

Build Production environment:

product Build Build tool
snowpack 1. The unbundle (esbuild) 2. A rollup 3. Webpack…
vite Rollup (and does not support unbundle)

6.3 Snowpack supports Streaming Imports

Streaming Imports is a new feature that allows users in both production and development environments to download NPM packages to their local node_module directory without having to use NPM/YARN to maintain a lock file locally. Using the Streaming Imports, you can maintain a map file in which the key is the package name and the value points directly to the ADDRESS of the CDN server hosting the NPM package as an ESM file.

Some advantages of 6.4 Vite

Vite has a few advantages over Snowpack, none of which I find particularly useful.

  • Multi-page support, in addition to root/index.html in the root directory, but also support other pages outside the root directory, such as nest/index.html
  • Better CSS preprocessor support (not personally aware of this)
  • Code-splitting for CSS code is supported
  • Optimized asynchronous request for multiple chunk files (synchronous regardless of scenarios, thus reducing the total request time to a certain extent)

6.5 summarize

If you want to use unbundle in production, you won’t be able to use Vite, which must be packaged for online builds. Vite optimizes only the development environment. Snowpack is unbundle by default, so you can use unbundle as a premise in a production environment. In addition, Snowpack’s Streaming Imports provides a complete package management of local maps, eliminating the need to install NPM packages locally, making it easy for us to host public libraries using CDN both online and offline.