Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modern JS #413

Merged
merged 11 commits into from
Jun 14, 2019
115 changes: 76 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,42 @@
}
```

### New: Modern JS

Microbundle now has a new `modern` format (`microbundle -f modern`).
Modern output still bundles and compresses your code, but it keeps useful syntax
around that actually helps compression:

```js
// Our source, "src/make-dom.js":
export default async function makeDom(tag, props, children) {
const el = document.createElement(tag);
el.append(...(await children));
return Object.assign(el, props);
}
```

Microbundle compiles the above to this:

```js
export default async (e, t, a) => {
const n = document.createElement(e);
return n.append(...(await a)), Object.assign(n, t);
};
```

This is enabled by default - all you have to do is add the field to your `package.json`. You might choose to ship modern JS using the "module" field:

```js
{
"main": "dist/foo.umd.js", // legacy UMD bundle (for Node & CDN's)
"module": "dist/foo.modern.mjs", // modern ES2017 bundle
"scripts": {
"build": "microbundle src/foo.js -f modern,umd"
}
}
```

## 📦 Usage

Microbundle includes two commands - `build` (the default) and `watch`. Neither require any options, but you can tailor things to suit your needs a bit if you like.
Expand Down Expand Up @@ -76,50 +112,51 @@ Libraries often wish to rename internal object properties or class members to sm

```json
{
"mangle": {
"regex": "^_"
}
"mangle": {
"regex": "^_"
}
}
```

### All CLI Options

```
Usage
$ microbundle <command> [options]

Available Commands
build Build once and exit
watch Rebuilds on any change

For more info, run any command with the `--help` flag
$ microbundle build --help
$ microbundle watch --help

Options
-v, --version Displays current version
-i, --entry Entry module(s)
-o, --output Directory to place build files into
-f, --format Only build specified formats (default es,cjs,umd)
-w, --watch Rebuilds on any change (default false)
--target Specify your target environment (node or web, default web)
--external Specify external dependencies, or 'none'
--globals Specify globals dependencies, or 'none'
--define Replace constants with hard-coded values
--alias Map imports to different modules
--compress Compress output using Terser (default true)
--strict Enforce undefined global context and add "use strict"
--name Specify name exposed in UMD builds
--cwd Use an alternative working directory (default .)
--sourcemap Generate source map (default true)
--raw Show raw byte size (default false)
--jsx A custom JSX pragma like React.createElement (default: h)
-h, --help Displays this message

Examples
$ microbundle build --globals react=React,jquery=$
$ microbundle build --define API_KEY=1234
$ microbundle build --alias react=preact
$ microbundle build --no-sourcemap # don't generate sourcemaps
Usage
$ microbundle <command> [options]

Available Commands
build Build once and exit
watch Rebuilds on any change

For more info, run any command with the `--help` flag
$ microbundle build --help
$ microbundle watch --help

Options
-v, --version Displays current version
-i, --entry Entry module(s)
-o, --output Directory to place build files into
-f, --format Only build specified formats (default modern,es,cjs,umd)
-w, --watch Rebuilds on any change (default false)
--target Specify your target environment (node or web) (default web)
--external Specify external dependencies, or 'none'
--globals Specify globals dependencies, or 'none'
--define Replace constants with hard-coded values
--alias Map imports to different modules
--compress Compress output using Terser
--strict Enforce undefined global context and add "use strict"
--name Specify name exposed in UMD builds
--cwd Use an alternative working directory (default .)
--sourcemap Generate source map (default true)
--raw Show raw byte size (default false)
--jsx A custom JSX pragma like React.createElement (default: h)
-h, --help Displays this message

Examples
$ microbundle microbundle --globals react=React,jquery=$
$ microbundle microbundle --define API_KEY=1234
$ microbundle microbundle --alias react=preact
$ microbundle microbundle --no-sourcemap # don't generate sourcemaps
```

## 🛣 Roadmap
Expand Down
27 changes: 18 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,15 @@ function createConfig(options, entry, format, writeMeta) {
: pkg['jsnext:main'] || 'x.mjs',
mainNoExtension,
);
let modernMain = replaceName(
(pkg.syntax && pkg.syntax.esmodules) || pkg.esmodule || 'x.modern.mjs',
mainNoExtension,
);
let cjsMain = replaceName(pkg['cjs:main'] || 'x.js', mainNoExtension);
let umdMain = replaceName(pkg['umd:main'] || 'x.umd.js', mainNoExtension);

const modern = format === 'modern';

// let rollupName = safeVariableName(basename(entry).replace(/\.js$/, ''));

let nameCache = {};
Expand Down Expand Up @@ -553,6 +559,8 @@ function createConfig(options, entry, format, writeMeta) {
passPerPreset: true, // @see https://babeljs.io/docs/en/options#passperpreset
custom: {
defines,
modern,
compress: options.compress !== false,
targets: options.target === 'node' ? { node: '8' } : undefined,
pragma: options.jsx || 'h',
pragmaFrag: options.jsxFragment || 'Fragment',
Expand All @@ -562,20 +570,19 @@ function createConfig(options, entry, format, writeMeta) {
options.compress !== false && [
terser({
sourcemap: true,
output: {
comments: (node, comment) => /[@#]__PURE__/.test(comment.value),
},
compress: Object.assign(
{
keep_infinity: true,
pure_getters: true,
// Ideally we'd just get Terser to respect existing Arrow functions...
// unsafe_arrows: true,
passes: 10,
},
minifyOptions.compress || {},
),
warnings: true,
ecma: 5,
toplevel: format === 'cjs' || format === 'es',
ecma: modern ? 9 : 5,
toplevel: modern || format === 'cjs' || format === 'es',
mangle: Object.assign({}, minifyOptions.mangle || {}),
nameCache,
}),
Expand Down Expand Up @@ -617,13 +624,15 @@ function createConfig(options, entry, format, writeMeta) {
get banner() {
return shebang[options.name];
},
format,
format: modern ? 'es' : format,
name: options.name,
file: resolve(
options.cwd,
(format === 'es' && moduleMain) ||
(format === 'umd' && umdMain) ||
cjsMain,
{
modern: modernMain,
es: moduleMain,
umd: umdMain,
}[format] || cjsMain,
),
},
};
Expand Down
47 changes: 31 additions & 16 deletions src/lib/babel-custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import babelPlugin from 'rollup-plugin-babel';
import merge from 'lodash.merge';
import { isTruthy } from '../utils';

const ESMODULES_TARGET = {
esmodules: true,
};

const mergeConfigItems = (type, ...configItemsToMerge) => {
const mergedItems = [];

Expand Down Expand Up @@ -64,7 +68,7 @@ export default babelPlugin.custom(babelCore => {
name: 'babel-plugin-transform-replace-expressions',
replace: customOptions.defines,
},
{
!customOptions.modern && {
name: 'babel-plugin-transform-async-to-promises',
inlineHelpers: true,
externalHelpers: true,
Expand All @@ -73,7 +77,7 @@ export default babelPlugin.custom(babelCore => {
name: '@babel/plugin-proposal-class-properties',
loose: true,
},
{
!customOptions.modern && {
name: '@babel/plugin-transform-regenerator',
async: false,
},
Expand All @@ -94,19 +98,22 @@ export default babelPlugin.custom(babelCore => {
babelOptions.presets[envIdx] = createConfigItem(
[
preset.file.resolved,
merge(
{
loose: true,
targets: customOptions.targets,
},
preset.options,
{
modules: false,
exclude: merge(
['transform-async-to-generator', 'transform-regenerator'],
preset.options.exclude || [],
),
},
Object.assign(
merge(
{
loose: true,
targets: customOptions.targets,
},
preset.options,
{
modules: false,
exclude: merge(
['transform-async-to-generator', 'transform-regenerator'],
preset.options.exclude || [],
),
},
),
customOptions.modern ? { targets: ESMODULES_TARGET } : {},
),
],
{
Expand All @@ -117,7 +124,9 @@ export default babelPlugin.custom(babelCore => {
babelOptions.presets = createConfigItems('preset', [
{
name: '@babel/preset-env',
targets: customOptions.targets,
targets: customOptions.modern
? ESMODULES_TARGET
: customOptions.targets,
modules: false,
loose: true,
exclude: ['transform-async-to-generator', 'transform-regenerator'],
Expand All @@ -132,6 +141,12 @@ export default babelPlugin.custom(babelCore => {
babelOptions.plugins || [],
);

babelOptions.generatorOpts = {
minified: customOptions.compress,
compact: customOptions.compress,
shouldPrintComment: comment => /[@#]__PURE__/.test(comment),
};

return babelOptions;
},
};
Expand Down
6 changes: 5 additions & 1 deletion src/prog.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ let { version } = require('../package');
const toArray = val => (Array.isArray(val) ? val : val == null ? [] : [val]);

export default handler => {
const ENABLE_MODERN = process.env.MICROBUNDLE_MODERN !== 'false';

const DEFAULT_FORMATS = ENABLE_MODERN ? 'modern,es,cjs,umd' : 'es,cjs,umd';

const cmd = type => (str, opts) => {
opts.watch = opts.watch || type === 'watch';
opts.compress =
Expand All @@ -18,7 +22,7 @@ export default handler => {
.version(version)
.option('--entry, -i', 'Entry module(s)')
.option('--output, -o', 'Directory to place build files into')
.option('--format, -f', 'Only build specified formats', 'es,cjs,umd')
.option('--format, -f', 'Only build specified formats', DEFAULT_FORMATS)
.option('--watch, -w', 'Rebuilds on any change', false)
.option('--target', 'Specify your target environment (node or web)', 'web')
.option('--external', `Specify external dependencies, or 'none'`)
Expand Down
Loading