Installation

git clone
cd nollup-test-case-output-dir
yarn

Usage

Run tests:

yarn zoar

Watch test:

yarn zoar -w

In interactive watch:

  • press Enter to rerun
  • enter ib to run with node debugger

Run tests with debug output:

CUSTOM=0 yarn zoar

The custom report format is most informative for debug. Its layout is as follow:

1. Test-specific Nollup options (passed to dev middleware)
2. Test-specific Rollup config
3. Rollup:
    file writting by Rollup => URL used by the test to try to get the file
4. Nollup:
    dump of `Object.keys(files)` in dev middleware (only with custom branch)

Example:

1. { contentBase: 'dist' }
2. { input: 'src/main.js', output: { dir: 'dist/app', format: 'es' } }
3. Rollup:
   ⚠  'dist/app/main.js' => /app/main.js
   ⚠  'dist/app/nested-93659157.js' => /app/nested-[hash].js
   ⚠  'dist/app/nested-d1b6ea0f.js' => /app/nested-[hash].js
4. Nollup:
   [ 'main.js', 'nested-[hash].js' ]

Principle

The test principle is to run Rollup with the given config, and observe the files that are written to disk by Nollup. Then, it fires the Nollup middleware, and tries to access each file by URL, like if from a web server from dist folder.

Both entry point files and code splitted (dynamic imports) chunks appears in Rollup's output, and so they are tested for simplicity, but I think we only really care about the entry points actually.

NOTE I have been working on a Nollup branch to explore integration with Sapper (Svelte's official app generator). The branch includes a custom onBundle option that is used by the test to observe Nollup's output. In normal Nollup, this hook is not present, so the test won't display the files as seen by Nollup.

Issues

There are 2 classes of problems:

  • misalignment with Rollup for filenames of output files

  • it is hard or impossible to have Nollup serves bundle files at the same URL that we'd get with Rollup + web server, in some cases

Directory path is lost with output.dir

When using a nested output.dir, Rollup keeps the full directroy path when writting entry points, but Nollup only keeps the basename.

And so, while the file would be accessible at URL /app/main.js with a web server and Rollup, it is accessible at URL /main.js in Nollup.

=== output.dir: nested main.js =================================================

{ contentBase: 'dist' }
{
  input: 'src/main.js',
  inlineDynamicImports: true,
  output: { dir: 'dist/app', format: 'es' }
}
Rollup:
 ⚠  'dist/app/main.js' => /app/main.js
Nollup:
[ 'main.js', 'nested-[hash].js' ]

Expected: app/main.js instead of main.js

Entry points with same basename (in different dirs) conflict

When 2 entry points have the same basename, Rollup adds an ordinal suffix, but Nollup mashes them together.

=== output.dir: entrypoints conflict ===========================================

{ contentBase: 'dist' }
{
  input: [ 'src/a/main.js', 'src/a/b/main.js' ],
  output: { dir: 'dist/app', format: 'es' }
}
Rollup:
 ⚠  'dist/app/main.js' => /app/main.js
 ⚠  'dist/app/main2.js' => /app/main2.js
Nollup:
[ 'main.js' ]

Expected: [ 'app/main.js', 'app/main2.js' ]

Dynamic chunks with same basename conflict

Same with dynamic chunks: they get mashed together when they share the same basename.

=== output.dir: chunk conflict =================================================

{ contentBase: 'dist' }
{
  input: [ 'src/main.js', 'src/second.js' ],
  output: { dir: 'dist/app', format: 'es' }
}
Rollup:
 ⚠  'dist/app/main.js' => /app/main.js
 ⚠  'dist/app/second.js' => /app/second.js
 ⚠  'dist/app/nested-3f8ba19f.js' => /app/nested-[hash].js
 ⚠  'dist/app/nested-93659157.js' => /app/nested-[hash].js
Nollup:
[ 'main.js', 'second.js', 'nested-[hash].js' ]

Expected: [ ..., 'nested-[hash].js', 'nested2-[hash].js' ] (or just anything that prevents them from having the same name an shadowing each other)

Also without hash:

=== output.dir: chunk conflict without hashes ==================================

{ contentBase: 'public', baseUrl: 'dist' }
{
  input: [ 'src/main.js', 'src/second.js' ],
  output: { dir: 'dist/app', format: 'es', chunkFileNames: '[name].js' }
}
Rollup:
 ⚠  'dist/app/main.js' => /app/main.js
 ⚠  'dist/app/second.js' => /app/second.js
 ⚠  'dist/app/nested.js' => /app/nested.js
 ⚠  'dist/app/nested2.js' => /app/nested2.js
Nollup:
[ 'main.js', 'second.js', 'nested.js' ]

Expected: [ ..., 'nested.js', 'nested2.js' ]

Public directory

Problem: once we preserve the output.dir in the generated output file names to align with Rollup, then we also get the "public" directory in the URL generated by Nollup (e.g. dist/app/main.js). However, with Rollup + Webserver, a part of output.dir (e.g. dist/) would be the web root directory, and so it wouldn't appear in the URL of the file (e.g. /app/main.js).

public directory same as dist

When the public directory is the same as the output dir, we can derive the URL suffix we need to drop from contentBase and output.dir.

const baseUrl = output.dir.startsWith(options.contentBase)
  ? output.dir.slice(options.contentBase.length)
  : output.dir // actually in this case, I think the file should not be served?
=== public dir: serve static from /dist, build to /dist ====================

{ contentBase: 'dist' }
{ input: 'src/a/main.js', output: { dir: 'dist/app', format: 'es' } }
Rollup:
 ⚠  'dist/app/main.js' => /app/main.js
Nollup:
[ 'main.js' ]

Expected: [ 'app/main.js' ]

public directory different from dist

When the user's setup directory layout differs between prod and dev (e.g. copying static assets in prod, but serving them from source in dev), then the part of output.dir that must be trimmed to form the URL must be provided by the user.

=== public dir: serve static from /public, build to /dist ======================

{ contentBase: 'public', baseUrl: 'dist' }
{ input: 'src/a/main.js', output: { dir: 'dist/app', format: 'es' } }
Rollup:
 ⚠  'dist/app/main.js' => /app/main.js
Nollup:
[ 'main.js' ]

Expected: [ 'app/main.js' ]

Exploratory work

The sapper branch of my Nollup fork passes all the above test by adding an ordinal suffix to entry / chunk names, joining output.dir to produce a file URL, and adding a baseUrl option to drop part of the output dir from the URL.

yarn add -D rixo/nollup#sapper
yarn zoar