Skip to content

Commit

Permalink
feat(core): Enable code splitting for esm output
Browse files Browse the repository at this point in the history
Fixes #5
  • Loading branch information
evocateur committed Nov 7, 2018
1 parent 7ded62e commit df29810
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 23 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
},
"devDependencies": {
"@babel/core": "^7.1.2",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-react": "^7.0.0",
"babel-core": "^7.0.0-bridge.0",
Expand Down
18 changes: 14 additions & 4 deletions packages/pectin-core/lib/getOutput.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
const path = require('path');
const camelCase = require('camelcase');
const npa = require('npm-package-arg');
const dotProp = require('dot-prop');

module.exports = function getOutput(pkg, cwd) {
module.exports = function getOutput(pkg, cwd, isMultiConfig) {
const output = [
{
file: path.resolve(cwd, pkg.main),
Expand All @@ -13,10 +14,19 @@ module.exports = function getOutput(pkg, cwd) {
];

if (pkg.module) {
output.push({
file: path.resolve(cwd, pkg.module),
const cfg = {
format: 'esm',
});
};

if (isMultiConfig) {
// code splitting is only enabled for multi-config output
cfg.dir = path.dirname(path.resolve(cwd, pkg.module));
cfg.entryFileNames = dotProp.get(pkg, 'rollup.entryFileNames', '[name].esm.js');
} else {
cfg.file = path.resolve(cwd, pkg.module);
}

output.push(cfg);
}

// @see https://github.com/defunctzombie/package-browser-field-spec
Expand Down
12 changes: 9 additions & 3 deletions packages/pectin-core/lib/pectin-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@ module.exports.loadManifest = loadManifest;

async function createMultiConfig(pkg, { cwd }) {
const input = getInput(pkg, cwd);
const outputs = getOutput(pkg, cwd);
const outputs = getOutput(pkg, cwd, true);

return pMap(outputs, async output => {
const plugins = await getPlugins(pkg, cwd, output);

// output array for consistency with createConfig()
return { input, output: [output], plugins };
return {
input,
// output array for consistency with createConfig()
output: [output],
plugins,
// TODO: remove when rollup 1.0 makes this the default
experimentalCodeSplitting: output.format === 'esm',
};
});
}
132 changes: 116 additions & 16 deletions packages/pectin-core/test/pectin-core.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function createFixture(pkgSpec) {
// implicit resolution from repo root
'.babelrc': File({
presets: ['@babel/env', '@babel/preset-react'],
plugins: ['@babel/plugin-syntax-dynamic-import'],
}),
...pkgSpec,
})
Expand All @@ -36,7 +37,17 @@ function generateResults(configs) {
return pMap(configs, ({ output: outputOptions, ...inputOptions }) =>
rollup(inputOptions).then(bundle => {
if (Array.isArray(outputOptions)) {
return Promise.all(outputOptions.map(opts => bundle.generate(opts)));
return Promise.all(outputOptions.map(opts => bundle.generate(opts))).then(
maybeChunked =>
maybeChunked.reduce((arr, result) => {
if (result.output) {
// chunks stored as dictionary keyed by fileName
return arr.concat(Object.values(result.output));
}

return arr.concat(result);
}, [])
);
}

return bundle.generate(outputOptions);
Expand Down Expand Up @@ -130,7 +141,7 @@ describe('pectin-core', () => {
'package.json': File({
name: 'pkg-module',
main: 'dist/index.js',
module: 'dist/index.module.js',
module: 'dist/index.esm.js',
}),
});
const pkgPath = path.join(cwd, 'package.json');
Expand All @@ -142,13 +153,38 @@ describe('pectin-core', () => {
exports: 'auto',
},
{
file: path.join(cwd, 'dist/index.module.js'),
file: path.join(cwd, 'dist/index.esm.js'),
format: 'esm',
exports: 'named',
},
]);
});

it('generates rollup multi-config with chunked modules output', async () => {
const pkg = {
name: 'pkg-module-chunked',
main: 'dist/index.js',
module: 'dist/index.esm.js',
};
const cwd = createFixture({
'package.json': File(pkg),
});
const [cjsConfig, esmConfig] = await pectinCore.createMultiConfig(pkg, { cwd });

expect(cjsConfig).toMatchObject({
experimentalCodeSplitting: false,
});
expect(esmConfig).toMatchObject({
experimentalCodeSplitting: true,
output: [
{
dir: expect.stringMatching(/\/dist$/),
entryFileNames: '[name].esm.js',
},
],
});
});

it('customizes input with pkg.rollup.rootDir', async () => {
const cwd = createFixture({
'package.json': File({
Expand Down Expand Up @@ -226,11 +262,78 @@ describe('pectin-core', () => {
expect(typeof pectinCore.loadManifest).toBe('function');
});

it('generates chunked module output', async () => {
const pkg = {
name: 'chunked-module-outputs',
main: './dist/index.js',
module: './dist/index.esm.js',
};
const cwd = createFixture({
'package.json': File(pkg),
src: Dir({
'chunky-bacon.js': File(`export default '_why';`),
'index.js': File(`
export default function main() {
return import('./chunky-bacon');
};
`),
}),
});

const configs = await pectinCore.createMultiConfig(pkg, { cwd });
const results = await generateResults(configs);

const fileNames = results.map(result => `dist/${result.fileName}`);
const [cjsEntry, esmEntry, esmChunk] = results.map(
result => `// dist/${result.fileName}\n${result.code}`
);

expect(fileNames).toEqual([
'dist/index.js',
'dist/index.esm.js',
'dist/chunky-bacon.esm.js',
]);

expect(cjsEntry).toMatchInlineSnapshot(`
"// dist/index.js
'use strict';
function main() {
return Promise.resolve().then(function () { return chunkyBacon$1; });
}
var chunkyBacon = '_why';
var chunkyBacon$1 = /*#__PURE__*/Object.freeze({
default: chunkyBacon
});
module.exports = main;
"
`);
expect(esmEntry).toMatchInlineSnapshot(`
"// dist/index.esm.js
function main() {
return import(\\"./chunky-bacon.esm.js\\");
}
export default main;
"
`);
expect(esmChunk).toMatchInlineSnapshot(`
"// dist/chunky-bacon.esm.js
var chunkyBacon = '_why';
export default chunkyBacon;
"
`);
});

it('generates basic pkg.browser output', async () => {
const pkg = {
name: 'basic-browser-outputs',
main: './dist/index.js',
module: './dist/index.module.js',
module: './dist/index.esm.js',
browser: './dist/index.browser.js',
};
const cwd = createFixture({
Expand All @@ -248,16 +351,13 @@ export default class Basic {

const configs = await pectinCore.createMultiConfig(pkg, { cwd });
const results = await generateResults(configs);

const fileNames = results.map(result => `dist/${result.fileName}`);
const [cjsMain, esmModule, cjsBrowser] = results.map(
result => `// dist/${result.fileName}\n${result.code}`
);

expect(fileNames).toEqual([
'dist/index.js',
'dist/index.module.js',
'dist/index.browser.js',
]);
expect(fileNames).toEqual(['dist/index.js', 'dist/index.esm.js', 'dist/index.browser.js']);
expect(cjsMain).toMatchInlineSnapshot(`
"// dist/index.js
'use strict';
Expand All @@ -278,7 +378,7 @@ module.exports = Basic;
"
`);
expect(esmModule).toMatchInlineSnapshot(`
"// dist/index.module.js
"// dist/index.esm.js
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError(\\"Cannot call a class as a function\\");
Expand Down Expand Up @@ -319,10 +419,10 @@ module.exports = Basic;
const pkg = {
name: 'advanced-browser-outputs',
main: './dist/index.js',
module: './dist/index.module.js',
module: './dist/index.esm.js',
browser: {
'./dist/index.js': './dist/index.browser.js',
'./dist/index.module.js': './dist/index.module.browser.js',
'./dist/index.esm.js': './dist/index.module.browser.js',
},
dependencies: {
'@babel/runtime': '^7.0.0',
Expand All @@ -349,7 +449,7 @@ export default class Advanced {

expect(fileNames).toEqual([
'dist/index.js',
'dist/index.module.js',
'dist/index.esm.js',
'dist/index.browser.js',
'dist/index.module.browser.js',
]);
Expand All @@ -372,7 +472,7 @@ module.exports = Advanced;
"
`);
expect(esmModule).toMatchInlineSnapshot(`
"// dist/index.module.js
"// dist/index.esm.js
import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck';
var Advanced = function Advanced() {
Expand Down Expand Up @@ -420,7 +520,7 @@ export default Advanced;
const pkg = {
name: 'unpkg-umd-output',
main: './dist/index.js',
module: './dist/index.module.js',
module: './dist/index.esm.js',
unpkg: './dist/index.min.js',
peerDependencies: {
react: '*',
Expand Down Expand Up @@ -521,7 +621,7 @@ return main;
'package.json': File({
name: 'integration',
main: 'dist/index.js',
module: 'dist/index.module.js',
module: 'dist/index.esm.js',
rollup: {
inlineSVG: true,
},
Expand Down

0 comments on commit df29810

Please sign in to comment.