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

Build with rollup instead of webpack #281

Merged
merged 1 commit into from
Feb 14, 2018
Merged

Build with rollup instead of webpack #281

merged 1 commit into from
Feb 14, 2018

Conversation

lencioni
Copy link
Collaborator

@lencioni lencioni commented Oct 12, 2017

Rollup will produce a smaller and more optimized bundle than webpack,
and can be configured in a way that works perfectly for libraries, such
as Aphrodite. This will help to minimize the bundle size impact of using
this package, and may even give a small runtime speedup. For reference,
React 16 is built using Rollup.

Rollup does not allow Babel 5, so I also updated to Babel 6 at the same
time. In this update, I tried to take care to maintain the same list of
browser support that we have listed in the CSS prefixes that we build.
At some point, we probably want to unify this configuration via
browserslist. Issue:

#239

Along with this update, I decided to add an ES modules build since it
was easy enough. This will be used automatically by tools such as
webpack 2+ to import the ES6 module version directly. I think it still
makes sense to run these through Babel since most people don't run their
node_modules through Babel, so the main difference here is that the
import/export statements will not be compiled to require/module.exports.
This allows webpack to perform optimizations such as tree-shaking and
scope hoisting.

One risk to be on the lookout for when people update to this version is
that if you are using require to bring in Aphrodite with a version of
webpack that is ES modules capable, it will break. Those consumers will
need to switch to import instead. For this reason, I would be okay with
removing the module field from the package.json for an initial release
of the rollup build, and then we can add it later when the ecosystem has
time to catch up. This is the approach we landed on for react-waypoint:

  1. Build with rollup civiccc/react-waypoint#220
  2. Make ES module opt-in civiccc/react-waypoint#223

The filesize of the dist builds before this change looked like:

 83K aphrodite.js
 84K aphrodite.umd.js
104K aphrodite.umd.js.map
 23K aphrodite.umd.min.js
204K aphrodite.umd.min.js.map

And after this change:

 72K aphrodite.js
 73K aphrodite.umd.js
108K aphrodite.umd.js.map
 20K aphrodite.umd.min.js
 93K aphrodite.umd.min.js.map

So it looks like the minified UMD build dropped from 24 KiB to 20 KiB.

cc @vladnicula

@lencioni
Copy link
Collaborator Author

Looks like CI is failing because of dropped coverage for some reason. I'm guessing something about the babel update. I'll wait for feedback on this before I look into fixing that.

@lencioni
Copy link
Collaborator Author

Ok, I think I fixed the coverage. Turns out I needed to bring in the istanbul babel plugin so that the coverage would be calculated correctly. PTAL @xymostech!

Copy link
Contributor

@xymostech xymostech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the long delay, @lencioni! This all looks pretty good to me! It's exciting to see it shrink the bundle! I'm mostly requesting changes because I don't know how rollup works and would like some clarification.

rollup.config.js Outdated
input: `src/${filename}.js`,
external: [
...Object.keys(pkg.dependencies),
'inline-style-prefixer/static/createPrefixer',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to configure this so we don't need to include all of these files individually?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a rollup expert, but I wasn't able to find a way initially. I'll dig into this more.

rollup.config.js Outdated
],
output: [
{ file: `lib/${filename}.js`, format: 'cjs' },
{ file: `lib/${filename}.mjs`, format: 'es' },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will build a version that does not compile import to require, which enables build tools that can understand import (e.g. webpack 2+) to perform additional optimizations such as "tree-shaking" and scope hoisting.

~/aphrodite ❯❯❯ head lib/index.js
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var createPrefixer = _interopDefault(require('inline-style-prefixer/static/createPrefixer'));
var calc = _interopDefault(require('inline-style-prefixer/static/plugins/calc'));
var crossFade = _interopDefault(require('inline-style-prefixer/static/plugins/crossFade'));
var cursor = _interopDefault(require('inline-style-prefixer/static/plugins/cursor'));

~/aphrodite ❯❯❯ head lib/index.mjs
import createPrefixer from 'inline-style-prefixer/static/createPrefixer';
import calc from 'inline-style-prefixer/static/plugins/calc';
import crossFade from 'inline-style-prefixer/static/plugins/crossFade';
import cursor from 'inline-style-prefixer/static/plugins/cursor';
import filter from 'inline-style-prefixer/static/plugins/filter';
import flex from 'inline-style-prefixer/static/plugins/flex';
import flexboxIE from 'inline-style-prefixer/static/plugins/flexboxIE';
import flexboxOld from 'inline-style-prefixer/static/plugins/flexboxOld';
import gradient from 'inline-style-prefixer/static/plugins/gradient';
import imageSet from 'inline-style-prefixer/static/plugins/imageSet';

The .mjs extension is the proposed way to specify this type of build that Node will eventually adopt, but it is really early to see it in many places. I'd be just as happy to build this somewhere like es/${filename}.js if you prefer, and that might be a bit smoother for folks anyway until the dust settles on the .mjs extension.

@@ -2,11 +2,26 @@
// Module with the same interface as the core aphrodite module,
// except that styles injected do not automatically have !important
// appended to them.
import {defaultSelectorHandlers} from './generate';
import { defaultSelectorHandlers } from './generate';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no-spaces in here is KA style

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my bad.

@@ -0,0 +1,22 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What babel configuration were we using before? Can we try to stay consistent with that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, Aphrodite was using a really old version of Babel, which was before presets existed and I believe Babel had a bunch of things turned on by default. When Babel 6 came out 2 years ago, everything changed and requires a lot more manual configuration.

I think the configuration I have here is going to be pretty consistent with what we had before, but I also made an effort to copy the browser targets we use for the CSS that is generated, which makes more sense to me than trying to stay consistent with the previous version of aphrodite. That way the JS and CSS is built to target the same sets of devices.

"env": {
"test": {
"presets": ["airbnb"],
"plugins": ["istanbul"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the code coverage to be properly calculated on the pre-compiled code. Without this, the code coverage checks failed. https://github.com/istanbuljs/babel-plugin-istanbul

@@ -2,95 +2,95 @@ import {assert} from 'chai';

import OrderedElements from '../src/ordered-elements';

describe("OrderedElements", () => {
it("can identify elements it has", () => {
describe('OrderedElements', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why all these quotes changed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making things consistent. I can change this back if you like.

distBuild({ filename: 'aphrodite.umd.js', format: 'umd', sourcemap: true, minify: false }),
distBuild({ filename: 'aphrodite.umd.min.js', format: 'umd', sourcemap: true, minify: true }),
distBuild({ filename: 'aphrodite.js', format: 'cjs', sourcemap: false, minify: false }),
standardBuilds('index'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to check my understanding, is this going to build bundles into lib/ or leave things in individual files like they are currently?

Copy link
Collaborator Author

@lencioni lencioni Oct 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will build bundles into lib/ without the individual files. Here's what my lib/ directory looks like after building like this:

total 400
drwxr-xr-x   7 joe_lencioni  staff   224B Oct 26 09:58 .
drwxr-xr-x  31 joe_lencioni  staff   992B Oct 13 10:54 ..
-rw-r--r--   1 joe_lencioni  staff    46K Oct 26 09:58 index.js
-rw-r--r--   1 joe_lencioni  staff    45K Oct 26 09:58 index.mjs
-rw-r--r--   1 joe_lencioni  staff    46K Oct 26 09:58 no-important.js
-rw-r--r--   1 joe_lencioni  staff    45K Oct 26 09:58 no-important.mjs
-rw-r--r--   1 joe_lencioni  staff   4.4K Oct 26 09:57 staticPrefixData.js

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this PR to build the cjs version into lib/ and the esm version into es/

Copy link
Collaborator Author

@lencioni lencioni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! I'll wait to make any stylistic changes on this PR until I hear back from you that the more fundamental direction is okay.

@@ -0,0 +1,22 @@
{
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, Aphrodite was using a really old version of Babel, which was before presets existed and I believe Babel had a bunch of things turned on by default. When Babel 6 came out 2 years ago, everything changed and requires a lot more manual configuration.

I think the configuration I have here is going to be pretty consistent with what we had before, but I also made an effort to copy the browser targets we use for the CSS that is generated, which makes more sense to me than trying to stay consistent with the previous version of aphrodite. That way the JS and CSS is built to target the same sets of devices.

"env": {
"test": {
"presets": ["airbnb"],
"plugins": ["istanbul"],
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the code coverage to be properly calculated on the pre-compiled code. Without this, the code coverage checks failed. https://github.com/istanbuljs/babel-plugin-istanbul

rollup.config.js Outdated
input: `src/${filename}.js`,
external: [
...Object.keys(pkg.dependencies),
'inline-style-prefixer/static/createPrefixer',
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a rollup expert, but I wasn't able to find a way initially. I'll dig into this more.

rollup.config.js Outdated
],
output: [
{ file: `lib/${filename}.js`, format: 'cjs' },
{ file: `lib/${filename}.mjs`, format: 'es' },
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will build a version that does not compile import to require, which enables build tools that can understand import (e.g. webpack 2+) to perform additional optimizations such as "tree-shaking" and scope hoisting.

~/aphrodite ❯❯❯ head lib/index.js
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var createPrefixer = _interopDefault(require('inline-style-prefixer/static/createPrefixer'));
var calc = _interopDefault(require('inline-style-prefixer/static/plugins/calc'));
var crossFade = _interopDefault(require('inline-style-prefixer/static/plugins/crossFade'));
var cursor = _interopDefault(require('inline-style-prefixer/static/plugins/cursor'));

~/aphrodite ❯❯❯ head lib/index.mjs
import createPrefixer from 'inline-style-prefixer/static/createPrefixer';
import calc from 'inline-style-prefixer/static/plugins/calc';
import crossFade from 'inline-style-prefixer/static/plugins/crossFade';
import cursor from 'inline-style-prefixer/static/plugins/cursor';
import filter from 'inline-style-prefixer/static/plugins/filter';
import flex from 'inline-style-prefixer/static/plugins/flex';
import flexboxIE from 'inline-style-prefixer/static/plugins/flexboxIE';
import flexboxOld from 'inline-style-prefixer/static/plugins/flexboxOld';
import gradient from 'inline-style-prefixer/static/plugins/gradient';
import imageSet from 'inline-style-prefixer/static/plugins/imageSet';

The .mjs extension is the proposed way to specify this type of build that Node will eventually adopt, but it is really early to see it in many places. I'd be just as happy to build this somewhere like es/${filename}.js if you prefer, and that might be a bit smoother for folks anyway until the dust settles on the .mjs extension.

distBuild({ filename: 'aphrodite.umd.js', format: 'umd', sourcemap: true, minify: false }),
distBuild({ filename: 'aphrodite.umd.min.js', format: 'umd', sourcemap: true, minify: true }),
distBuild({ filename: 'aphrodite.js', format: 'cjs', sourcemap: false, minify: false }),
standardBuilds('index'),
Copy link
Collaborator Author

@lencioni lencioni Oct 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will build bundles into lib/ without the individual files. Here's what my lib/ directory looks like after building like this:

total 400
drwxr-xr-x   7 joe_lencioni  staff   224B Oct 26 09:58 .
drwxr-xr-x  31 joe_lencioni  staff   992B Oct 13 10:54 ..
-rw-r--r--   1 joe_lencioni  staff    46K Oct 26 09:58 index.js
-rw-r--r--   1 joe_lencioni  staff    45K Oct 26 09:58 index.mjs
-rw-r--r--   1 joe_lencioni  staff    46K Oct 26 09:58 no-important.js
-rw-r--r--   1 joe_lencioni  staff    45K Oct 26 09:58 no-important.mjs
-rw-r--r--   1 joe_lencioni  staff   4.4K Oct 26 09:57 staticPrefixData.js

@@ -2,11 +2,26 @@
// Module with the same interface as the core aphrodite module,
// except that styles injected do not automatically have !important
// appended to them.
import {defaultSelectorHandlers} from './generate';
import { defaultSelectorHandlers } from './generate';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my bad.

@@ -2,95 +2,95 @@ import {assert} from 'chai';

import OrderedElements from '../src/ordered-elements';

describe("OrderedElements", () => {
it("can identify elements it has", () => {
describe('OrderedElements', () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making things consistent. I can change this back if you like.

@lencioni
Copy link
Collaborator Author

@xymostech can you take another look at this when you have a moment?

@lencioni
Copy link
Collaborator Author

lencioni commented Feb 5, 2018

@xymostech since we decided that #240 is a breaking change, I'd like to ship this at the same time. Can you look at this again and let me know if you see anything you'd like changed about it?

Rollup will produce a smaller and more optimized bundle than webpack,
and can be configured in a way that works perfectly for libraries, such
as Aphrodite. This will help to minimize the bundle size impact of using
this package, and may even give a small runtime speedup. For reference,
React 16 is built using Rollup.

Rollup does not allow Babel 5, so I also updated to Babel 6 at the same
time. In this update, I tried to take care to maintain the same list of
browser support that we have listed in the CSS prefixes that we build.
At some point, we probably want to unify this configuration via
browserslist. Issue:

  #239

I noticed that this Babel update caused branch coverage to drop a
little, and I was unable to fix it by adding a test that definitely
covered the missing branch. Thankfully, all I needed to do was add the
istanbul Babel plugin to fix this. This is the approach recommended by
the istanbul documentation:

  https://github.com/istanbuljs/nyc#use-with-babel-plugin-istanbul-for-babel-support

Along with this update, I decided to add an ES modules build since it
was easy enough. This will be used automatically by tools such as
webpack 2+ to import the ES6 module version directly. I think it still
makes sense to run these through Babel since most people don't run their
node_modules through Babel, so the main difference here is that the
import/export statements will not be compiled to require/module.exports.
This allows webpack to perform optimizations such as tree-shaking and
scope hoisting.

One risk to be on the lookout for when people update to this version is
that if you are using `require` to bring in Aphrodite with a version of
webpack that is ES modules capable, it will break. Those consumers will
need to switch to import instead. For this reason, I would be okay with
removing the `module` field from the package.json for an initial release
of the rollup build, and then we can add it later when the ecosystem has
time to catch up. This is the approach we landed on for react-waypoint:

1. civiccc/react-waypoint#220
2. civiccc/react-waypoint#223

The filesize of the dist builds before this change looked like:

```
 83K aphrodite.js
 84K aphrodite.umd.js
104K aphrodite.umd.js.map
 23K aphrodite.umd.min.js
204K aphrodite.umd.min.js.map
```

And after this change:

```
 72K aphrodite.js
 73K aphrodite.umd.js
108K aphrodite.umd.js.map
 20K aphrodite.umd.min.js
 93K aphrodite.umd.min.js.map
```

So it looks like the minified UMD build dropped from 24 KiB to 20 KiB.
@lencioni
Copy link
Collaborator Author

Going to merge now to keep things moving forward. Please let me know if you'd like me to follow up with any changes!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants