v1.2: spack: Yet another asset bundler

Features#

Compact output#

Just like rollup, spack emits compact output.

a.js:

console.log("loading A");
export function a() {
console.log("use!");
}

lib.js

import { a } from "./a";
a();

becomes

console.log("loading A");
function a() {
console.log("use!");
}
a();

Because swc was designed with this type of merging in mind, files are merged without any problems even if the same name exists in multiple files.

Tree shaking#

Just like other modern bundlers, spack can remove unused exports.

Import deglobbing#

To aid tree shaking, spack deglobs import if possible.

import * as lib from "lib";
lib.foo();

behaves exactly same as

import { foo } from "lib";
foo();

Note that all side effects are preserved.

Common js support#

As almost all modules on npm are published as common js module, spack supports importing common js module. Also, spack emits compact output compared to webpack.

import * as lib from "lib";
console.log(lib); // Prevent dce

If the lib above is common js module, it's transcompiled as

const load = __spack_require.bind(void 0, function (module, exports) {
// Code from lib goes here
});
const lib = load();
console.log(lib); // Prevent dce

Optimizations#

Currently swc does

  • global inlining (e.g. process.env.NODE_ENV)
  • inlining
  • constant propagation
  • dead code elimination

Actually the tree shaking described above is implemented using the dead code elimination pass. Currently, spack / swc can deduce

let b = 2;
let a = 1;
if (b) {
a = 2;
}
let c;
if (a) {
c = 3;
}
console.log(c);

into

console.log(3);

High performance#

As always, performance is a first-class citizen. It's very fast because it uses all cpu cores, and optimized by llvm.

swc integration#

You can use es2018, module import / export-s and some staged proposals without installing any additional dependency.

Multiple entries#

spack.config.js:

const { config } = require("@swc/core/spack");
module.exports = config({
entry: {
web: __dirname + "/src/web.ts",
android: __dirname + "/src/android.ts",
},
output: {
path: __dirname + "/lib",
},
module: {},
});

Built-in chunking#

Let's suppose that we have the same config as above. If android.ts and web.ts both references a file, it will be extracted as a separate chunk and web.ts and amdroid.ts will import it.

Type annotation for config file#

spack provides type annotations for the config. You can get assists from ide just by wrappping your configuration object with config. Note that it's just an identity function with type annoatations.

spack.config.js:

const { config } = require("@swc/core/spack");
module.exports = config({
entry: {
web: __dirname + "/src/index.ts",
},
output: {
path: __dirname + "/lib",
},
module: {},
});

Fine config#

If you want to bundle everything in each bundle, you can provide an array of configurations.

e.g.

const { config } = require("@swc/core/spack");
module.exports = config([
{
entry: {
desktop: __dirname + "/src/desktop.ts",
},
output: {
path: __dirname + "/lib",
},
module: {},
},
{
entry: {
mobile: __dirname + "/src/mobile.ts",
},
output: {
path: __dirname + "/lib",
},
module: {},
},
]);

results in two full bundle (desktop.js, mobile.js), while

const { config } = require("@swc/core/spack");
module.exports = config({
entry: {
desktop: __dirname + "/src/desktop.ts",
mobile: __dirname + "/src/mobile.ts",
},
output: {
path: __dirname + "/lib",
},
module: {},
});

results in chunking. In this case, the output will be desktop.js, mobile.js, common-[hash].js, assuming there's a common dependency.

Roadmap (spack)#

webpack-compatible plugin api#

spack will provide webpack-compatible apis, so you will be able to use webpack loaders and plugins without changing the plugin. All you need to do will be changing file name and imports.

I couldn't implement this because currently there's no way to wait for a promise to be resolved from rust code. I'll implement this feature as soon as possible after neon#73 or node-bidgen#37 is fixed. (I'm open to switchig from neon to node-bindgen).

dynamic imports (and code splitting)#

minification (#826)#

I think this is one of the core features of a bundler. I'll implement it soon.