This whole repository is a Demo and playground and a check for how to use webpack
and Swcpack
to bundle
a library
for browser
. And compare the output of the two. And what is possible and not possible yet with swcpack
.
It's part of the Magician dev
learning series.
keywords:
new gen transpilers, swc, swcpack, esbuild, webpack, vite, babel, bundling library, umd, swcpack problems, umd in swc, umd globals, webpack with swc, loader, benchmark, production, minification, compression, mangling. ...
I'll be trying to resume a lot of elements bellow:
Webpack
have been there for an eternity. And is both mature and it's echo-system. Webpack
is or was the defacto for so long. Used by create-react-app
, laravel-mix
, vue-cli
, nextjs
, expo web (for react-native-web in expo)
, and the list is huge (mostly who wasn't using webpack).
New generation tools
entered the Arena from so long. Mainly esbuild
and swc
.
Then tools that wrap and use those
=> the biggest that caught big traction is vite
.
vite
is the new comer that combine esbuild
and rollup
. esbuild
for pre-bundling and transpilation and development. vite
is using esbuild
. nextjs
and parcel
are using swc
. Swc
is more focused to be a drop replacement to babel
.
Why vite went with esbuild
and not swc
? => [to be answered later]
(I guess mainly to keep one tool, and esbuild
as does great with transpilation and minification (minification higher order faster then with terser and uglify [go vs js]), and as good relatively as swc
[swc and esbuild are close, and in benchmark one would win over the other depending in the cases]). Also esbuild
is focused into bundling and was out there long as such. While swc
=> transpilation first and replacing babel.
swc as a babel drop replacement is the only one to look at in the area. Adopted also by nextjs
, and by expo
for web and have loader for jest
, webpack
(to speed webpack
, instead of using babel
...), ...
Vite
way faster then webpack
=>
mainly due to usage of esbuild
for pre-bundling. And for loading the development server before crawling all the files.
- vite use rollup for bundling
- that give stability and also flexibility ...
While vite promote itself as fast and raise the development experience => Webpack can be as much as fast:
The article below show some benchmarks:
https://gist.github.com/NoriSte/24ddd3585249f439f285a563bee26591#file-final-benchmark-md
with esbuild-loader
used for webpack. May be the result gonna be better with swc-loader
.
However the first load time in development, vite
is king. But what about webpack
also catching in that area? Doesn't seems too fetched to manage.
Vite
however for sure is catching a lot of traction and seems to be more developer friendly (DX).
What about vite vs (webpack + swc) ?
Swcpack
(spack
)
- swc come with an experimental bundler called
swcpack
,spack
. And it's not ready yet for production. And it's in development. - In this repo we tried to explore what can be done and what are the problems and how it does compare to
webpack
and what are the things inwebpack
thatswcpack
doesn't support. Or what actually it's supporting ....
Bundling something like react
or any normal script
that is intended to run in the browser
and execute and run right away doing things. Like any script
to handle the application functions and logic and dynamic aspect.
That's bundling a script
. In such case you go with just precising output. And you don't need to do any extra thing.
In our Demo you would see that with the configurations bellow:
Webpack
:
- webpack.config.cjs
- webpack.config.prod.cjs (prod)
Swcpack
:
- spack.config.cjs
- spack.config.prod.cjs (prod)
- spack.config.prod.withMinification.cjs (using minification) [and problems with minification]
- ...
swc
:
- swcrc.minify.browser.json (using swc to minify and do umd after the bundling) [workaround]
swcpack
doesn't support library exposition. And so all configurations are going script like.
In the one going with .lib
i tried some options to see if they can help.
I made a workaround. Check the workaround section bellow.
If we are to build a library, like jquery
, Snapman.js
, Lodash
...
Then => We need to expose the entry module to the global scope
In webpack
this is done through adding a library
prop to the output. And for more advanced use cases there is plugin for it.
In this repo we used config.output.library
.
Swcpack
have no equivalent at the moment.
To Manage i opted for a workaround. Where i created a index.browser.ts to expose the variables manually. And basically runswcpack
with the script bundling. (You can check the result in )
webpack
:
- webpack.lib.config.cjs
- webpack.lib.config.prod.cjs
swcpack
:
- spack.lib.config.cjs (tired some options elements)
- spack.lib.browser.config.cjs (workaround)
Webpack
support it well. Check webpack.config.cjs for example.
swcpack
support it but (following my experiment. I may update that. If i find otherwise):
- no Isolation using
IIFE
- Mangling doesn't work fully with
swcpack
(work great withswc
). Workaround:swcpack
bundling => output => swc => final output. mode
=> as mentioned in documentation does nothing at the moment. => production can go by setting theminification
options. Andoptimization
.
-
Webpack
haveoutput.library
check webpack.lib.config.cjs -
swcpack
have no equivalent. And doesn't support it at all.
Two possible workaround:
swcpack => output => swc => minified umd output
by transpilling to a umd
module. That would bind the exports to the global window
, globalThis
, and this
After
pnpm install
use
npm run build:all
to build all.
Otherwise:
"build:all": "npm-run-all --parallel webpack webpack:** swcpack swcpack:** swc:** --serial swcm:**",
"webpack": "webpack --config webpack.config.cjs",
"webpack:prod": "webpack --config webpack.config.prod.cjs",
"webpack:lib": "webpack --config webpack.lib.config.cjs",
"webpack:lib:prod": "webpack --config webpack.lib.config.prod.cjs",
"swcpack": "spack --config=$PWD/spack.config.cjs",
"swcpack:prod": "spack --config=$PWD/spack.config.prod.cjs",
"swcpack:prod:minify": "spack --config=$PWD/spack.config.prod.withMinification.cjs",
"swcm:prod:minify:minified_bundle": "swc dist/prod/swc_bundle_minify/index.js --out-file dist/prod/swc_bundle_minify/index.swcMinify.js --config-file swcrc.minify.browser.json",
"swcpack:lib": "spack --config=$PWD/spack.lib.config.cjs",
"swcpack:lib:browser": "spack --config=$PWD/spack.lib.browser.config.cjs",
"swc:build:umd": "swc src --out-dir dist/swc-umd --config-file swcrc.umd.json"
You can check the output at ./dist directory
All well named to see too webpack output and swcpack output.
The code source examples are simple so that no extra heavy dependency go in. You can see how webpack structure and go about bundling and module resolution and loading using the cache.
And swcpack does none of the module resolution.
Check first the no prod (not minified) versions. So you get an idea how the bundling happen and what webpack does and how does swcpack compare.
Already mentioned. As shown here dist/webpack_bundle/index.js
And swcpack equivalent dist/swc_bundle/index.js
And for lib:
webpack: dist/lib/webpack_bundle
Umd issue discussion:
Creating src/index.browser.ts
- Adding manually the variables. And import the original typescript exposing
index.ts
swcpack
will bundle it as ascript
. But this time the variables are added to the global scope
Problem with that: => swcpack
is not wrapping anything in IIFE
for isolation (All variables [classes] are polluting the global scope, and that's a serious problem). You can compare against the webpack version. That's something that we can add manually. But still. And i couldn't figure out any way to add that IIFE
through swcpack
.
entry
=> swcpack
=> many files => no IIFE
bundle output => swc
=> umd
output
SWC
handleumd
all well. But notswcpack
. So one option is to combine them. (Not really an option but a workaround. Asswcpack
is experimental and work in progress. The development is going slow because the focus is more intoswc
and replacingbabel
first. As more of production solutions are usingswc
in place ofbabel
(directly, for speedingwebpack
, ...))
Problem:
The umd
factory does create the module in global.fileName
and not the module name that you would want to have.
In my case it was an index.js
file. So i got:
global.index
(function(global, factory) {
if (typeof module === "object" && typeof module.exports === "object") factory(exports);
else if (typeof define === "function" && define.amd) define([
"exports"
], factory);
else if (global = typeof globalThis !== "undefined" ? globalThis : global || self) factory(global.index = {}); // <=======================/ here
})(this, function(exports) // ...
With a file HeroJs.js
we get:
else if (global = typeof globalThis !== "undefined" ? globalThis : global || self) factory(global.heroJs = {});
//------------/^
In case of HeroJs.some.js
=> global.heroJsSome
Note the name is with first character in lower case (camel case and not pascal case)
So basically for the workaround to work well we need to
entry => spack => bundle <with Library Name> => swc => final umd.
I tried different ways to try changing the name using the swcrc
configuration. Not sure it is possible.
There is the globals
variable.
And in babel
the flowing plugin is used: https://babeljs.io/docs/en/babel-plugin-transform-modules-umd
And supposed to be used as:
{
"plugins": [
[
"@babel/plugin-transform-modules-umd",
{
"globals": {
"es6-promise": "Promise"
}
}
]
]
}
exposing global.Promise
rather than the default global.es6Promise
At first i tried:
"globals": {
"index": "HeroJs"
}
"globals": {
"globalThis": "this"
}
"globals": {
"globalThis": "HeroJs"
}
"globals": {
"main": "HeroJs"
}
"globals": {
"this": "HeroJs"
}
Nothing work lol. At first i didn't know how globals were to be used. Then i search for babel
umd.
"globals": {
"index": "HeroJs"
}
Should work. But it doesn't in swc
.
Along that i tried the following variations:
"globals": {
"./dist/swc_bundle/index.js": "HeroJs"
}
"globals": {
"$PWD/dist/swc_bundle/index.js": "HeroJs"
} // Yea yea! but i tried it anyway
"globals": {
"./index.js": "HeroJs"
}
"globals": {
"index.js": "HeroJs"
}
None works.
You can try in this playground
You can see that input
is picked up. Because of file naming.
I filled an issue here: swc-project/swc#6697
I confirmed by checking the code source that it is not implemented. As i have shown in the issue.
crates/swc_ecma_transforms_module/src/umd/config.rs#L75
crates/swc_ecma_transforms_module/src/umd.rs#L117
Now it's time for a PR.
What about --filename (-f)
and --source-file-name
cli arguments ? ==> I tried them but it does nothing. => doesn't work.
Remember that for the time combining webpack with swc-loader
is still a good option. https://swc.rs/docs/usage/swc-loader. For any transpilation based work. replacing babel-loader
with swc-loader
would make difference in performance in that related work.
Swpack
is far away from being any close to production ready.
module: {
type: 'umd',
// globals: {
// HeroJs: 'default',
// },
},
whole module thing doesn't work. Not even just globals (by the way i have no idea what globals do there. I just tried. ). And even if you comment it out doesn't matter. Or you choose type: 'commonjs'
. Doesn't work in swcpack.
isModule: true,
Does nothing. true
or false
.
Will try to update this section on the go.
unknown variant development
, there are no variants at line 1
=> you have to use none
| debug
| production
or not set it. And that would output development.
otherwise production
(no problem with production
value).
error:
/Users/mohamedlamineallal/repos/webpack-test-simple-output/node_modules/.pnpm/@[email protected]/node_modules/@swc/core/index.js:313
return bindings.bundle(toBuffer(Object.assign({}, opts)));
^
Error: Failed to deserialize buffer as binding_core_node::bundle::StaticConfigItem
JSON: {"entry":{"index":"/Users/mohamedlamineallal/repos/webpack-test-simple-output/src/index.ts"},"output":{"path":"/Users/mohamedlamineallal/repos/webpack-test-simple-output/dist/swc_bundle","name":"[name].js"},"mode":"development","target":"browser","options":{"jsc":{"target":"es5","parser":{"syntax":"typescript","tsx":false,"decorators":true,"dts":true,"dynamicImport":true},"baseUrl":".","paths":{"/*":["./src/*"]}},"sourceMaps":true,"isModule":true}}
Caused by:
unknown variant `development`, there are no variants at line 1 column 452
at Compiler.<anonymous> (/Users/mohamedlamineallal/repos/webpack-test-simple-output/node_modules/.pnpm/@[email protected]/node_modules/@swc/core/index.js:313:29)
at Generator.next (<anonymous>)
at fulfilled (/Users/mohamedlamineallal/repos/webpack-test-simple-output/node_modules/.pnpm/@[email protected]/node_modules/@swc/core/index.js:31:58) {
code: 'GenericFailure'
}
env:
"node_version": "v19.3.0",
"@swc/cli": "^0.1.57",
"@swc/core": "^1.3.23",
At the current time the option does nothing:
https://swc.rs/docs/configuration/bundling#mode
mentioned in the doc
Currently this value is not used, but it will behave similarly to webpack.
The typescript is wrong. Or the doc is debug
to development
.
swc
command work just fine and mangle well. But not when spack
is used.
Only minification and compression is working.
This is an output
function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function")}}var Class1=function(){"use strict";function t(){_classCallCheck(this,t)}return t.prototype.go=function(){return 1},t}();var Class2=function(){"use strict";function t(){_classCallCheck(this,t)}return t.prototype.start=function(){return 1},t}();var Hero=function r(){"use strict";_classCallCheck(this,r),this.c1=new Class1,this.c2=new Class2};export{Class1 as Class1,Class2 as Class2};export{Hero as Hero};
That came from this configuration
const { config } = require('@swc/core/spack');
const path = require('path');
const p = (relativePath) => path.resolve(__dirname, relativePath);
module.exports = config({
entry: {
index: p('src/index.ts'),
},
output: {
path: p('dist/prod/swc_bundle_minify'),
},
mode: 'production',
target: 'browser',
options: {
module: {
type: 'umd',
},
jsc: {
parser: {
syntax: 'typescript',
decorators: true,
dynamicImport: true,
tsx: false,
},
minify: {
compress: true,
mangle: true,
},
},
minify: true,
isModule: true,
}
});
and same output for:
minify: {
compress: true,
mangle: {
keep_classnames: false,
keep_private_props: false,
keep_fnames: false,
reserved: undefined,
toplevel: true,
},
ecma: 'es5',
},
If i use the workaround of bundling first minifying then using swc
using the same configuration. We will get the following output in umd
format:
!function(n,t){"object"==typeof module&&"object"==typeof module.exports?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):(n="undefined"!=typeof globalThis?globalThis:n||self)&&t(n.index={})}(this,function(n){"use strict";function t(n,t){if(null!=t&&"undefined"!=typeof Symbol&&t[Symbol.hasInstance]?!t[Symbol.hasInstance](n):!(n instanceof t))throw TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0}),function(n,t){for(var e in t)Object.defineProperty(n,e,{enumerable:!0,get:t[e]})}(n,{Class1:function(){return e},Class2:function(){return o},Hero:function(){return i}});var e=function(){"use strict";function n(){t(this,n)}return n.prototype.go=function(){return 1},n}(),o=function(){"use strict";function n(){t(this,n)}return n.prototype.start=function(){return 1},n}(),i=function n(){"use strict";t(this,n),this.c1=new e,this.c2=new o}});
//# sourceMappingURL=index.swcMinify.js.map
That it is umd
like shown by the precedent section or cjs
format. swcpack
seems not picking up the module
option.
==> swc manage well ==> swcpack doesn't
/Users/mohamedlamineallal/repos/webpack-test-simple-output/node_modules/.pnpm/@[email protected]/node_modules/@swc/core/index.js:313 return bindings.bundle(toBuffer(Object.assign({}, opts))); ^
Error: Failed to deserialize buffer as binding_core_node::bundle::StaticConfigItem JSON: {"entry":{"index":"/Users/mohamedlamineallal/repos/webpack-test-simple-output/src/index.ts"},"output":{"path":"/Users/mohamedlamineallal/repos/webpack-test-simple-output/dist/prod/swc_bundle_minify","name":"[name].js"},"mode":"production","target":"browser","options":{"jsc":{"parser":{"syntax":"typescript","tsx":false,"decorators":true,"dts":true,"dynamicImport":true},"minify":{"module":true,"keep_fnames":false,"keep_classnames":false,"toplevel":true,"compress":true,"mangle":true},"baseUrl":".","paths":{"/":["./src/"]}},"minify":true,"sourceMaps":true,"isModule":true}}
Caused by:
unknown field keep_fnames
, expected one of compress
, mangle
, format
, ecma
, keepClassnames
, keepFnames
, module
, safari10
, toplevel
, sourceMap
, outputPath
, inlineSourcesContent
, emitSourceMapColumns
at line 1 column 577
at Compiler. (/Users/mohamedlamineallal/repos/webpack-test-simple-output/node_modules/.pnpm/@[email protected]/node_modules/@swc/core/index.js:313:29)
at Generator.next ()
at fulfilled (/Users/mohamedlamineallal/repos/webpack-test-simple-output/node_modules/.pnpm/@[email protected]/node_modules/@swc/core/index.js:31:58) {
code: 'GenericFailure'
}
// doesn't work
minify: {
module: true,
keep_fnames: false,
keep_classnames: false,
toplevel: true,
compress: true,
mangle: true,
},
// does work
minify: {
module: true,
keepFnames: false,
keepClassnames: false,
toplevel: true,
compress: true,
mangle: true,
},
spack
with @swc/core v1.2.46+
and above seems to fail with panic in many cases. It didn't work in my snapman.js
library. issue: swc-project/swc#6133
You can reproduce the problem above by making a call to:
export { default as clone } from 'lodash.clonedeep';
Error:
thread '<unnamed>' panicked at 'cannot access a scoped thread local variable without calling `set` first', /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/scoped-tls-1.0.1/src/lib.rs:168:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
node:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
[Error: panic detected] { code: 'GenericFailure' }
Node.js v19.3.0
disabling source map generation not working.
setting "sourceMaps": false
does nothing.
can be checked in:
swcrc.minify.browser.json
What does setting mode
to production
do ?
(doesn't trigger minification ...., Minification can be set through )