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

How to use rollup in a browser?I can't find how to use rollup in a browser in the official documentation! #3012

Open
1 of 4 tasks
masx200 opened this issue Jul 31, 2019 · 14 comments

Comments

@masx200
Copy link

masx200 commented Jul 31, 2019

Documentation Is:

  • Missing
  • Needed
  • Confusing
  • Not Sure?

Please Explain in Detail...

How to use rollup in a browser?

I found the rollup's ES module for the browser in the npm repository, but I don't know how to use it in the browser.

https://cdn.jsdelivr.net/npm/rollup@1.17.0/dist/rollup.browser.es.js

I can't find how to use rollup in a browser in the official documentation!

import('https://cdn.jsdelivr.net/npm/rollup@1.17.0/dist/rollup.browser.es.js').then(m=>{
m.rollup({input:"https://cdn.jsdelivr.net/gh/masx200/masx200.github.io@4.2.4/src/assetsjs/md5.min.js"})
}
)

//Uncaught (in promise) Error: It looks like you're using Rollup in a non-Node.js environment. This means you must supply a plugin with custom resolveId and load functions

Your Proposal for Changes

Please provide more help documentation!

@lukastaegert
Copy link
Member

Rollup will not automatically pull in modules from remote URLs. You can use https://github.com/mjackson/rollup-plugin-url-resolve to do that but I never tried if it works in a browser (if not I think this would be worth a ticket).

This is the basics of how you use Rollup without file system access if you roll your own loading logic: https://rollupjs.org/guide/en/#a-simple-example

@shellscape
Copy link
Contributor

Also worth noting that Rollup may not be necessary in the browser. Most modern, up to date browsers support ES6 Modules via the <script module... tag.

@masx200
Copy link
Author

masx200 commented Jul 31, 2019

Rollup will not automatically pull in modules from remote URLs. You can use https://github.com/mjackson/rollup-plugin-url-resolve to do that but I never tried if it works in a browser (if not I think this would be worth a ticket).

This is the basics of how you use Rollup without file system access if you roll your own loading logic: https://rollupjs.org/guide/en/#a-simple-example

Unfortunately, I can't use "https://github.com/mjackson/rollup-plugin-url-resolve" in my browser.
Can't access the file system in the browser, how to configure the "output" option?How to use the output file after packaging?

@masx200
Copy link
Author

masx200 commented Aug 1, 2019

Also worth noting that Rollup may not be necessary in the browser. Most modern, up to date browsers support ES6 Modules via the <script module... tag.

http://rollupjs.org/repl/

I found the use of rollUp in the browser in the source code of the official website!

<script context="module">
	export function preload() {
		return this.fetch(`api/examples.json`).then(r => r.json()).then(examples => ({ examples }));
	}
</script>

<script>
	import Input from './_Input/index.svelte';
	import Output from './_Output/index.svelte';
	import {dirname, resolve} from './_utils/path';
	import {supportsCodeSplitting, supportsInput} from './_utils/rollupVersion';
    import { onMount } from 'svelte';
    import { stores } from '@sapper/app';
    import { get } from 'svelte/store';

	export let examples = [];
	let output = [];
	let options = {
		format: 'cjs',
		name: 'myBundle',
		amd: { id: '' },
		globals: {}
	};
	let codeSplitting = false;
	let selectedExample = null;
	let selectedExampleModules = [];
	let modules = [];
	let warnings = [];
	let input;
	let rollup;
	let error;

	function getUrlRollupVersion() {
	  if (typeof window === 'undefined') return null;
	  const versionMatch = /version=([^&]+)/.exec(window.location.search);
	  return versionMatch && versionMatch[1];
	}

	const urlRollupVersion = getUrlRollupVersion();

	const atob = typeof window ==='undefined'
		? base64 => Buffer.from(base64, 'base64').toString()
		: window.atob;

	function loadRollup () {
		const url = urlRollupVersion ?
			`https://unpkg.com/rollup@${urlRollupVersion}/dist/rollup.browser.js` :
			'https://unpkg.com/rollup/dist/rollup.browser.js';

    	return new Promise(( fulfil, reject ) => {
    		const script = document.createElement('script');
    		script.src = url;
    		script.onload = () => {
    			fulfil(window.rollup);
    		};
    		script.onerror = reject;
    		document.querySelector('head').appendChild(script);
    	});
    }

    onMount(async () => {
    	const {query} = get(stores().page);
        try {
        	if ( query.shareable ) {
        		const json = decodeURIComponent(atob(query.shareable));
        		({modules, options, example: selectedExample} = JSON.parse(json));
        		input.$set({modules, selectedExample});
        	} else if ( query.gist ) {
        		const result = await (await fetch(`https://api.github.com/gists/${query.gist}`, {
        			method: 'GET',
        			headers: { Accept: 'application/vnd.github.v3+json' }
        		})).json();
        		const entryModules = query.entry ? query.entry.split(',') : [];
        		modules = [result.files['main.js'] || { filename: 'main.js', content: '' }]
        			.concat(Object.keys(result.files)
        				.filter(fileName => fileName !== 'main.js')
        				.map(fileName => result.files[fileName])
        			).map(module => ({
        				name: module.filename,
        				code: module.content,
        				isEntry: entryModules.indexOf(module.filename) >= 0
        			}));
        	} else {
        		selectedExample = '00';
        	}
        } catch (err) {
        	console.error(err);
        	selectedExample = '00';
        }

		rollup = await loadRollup();
		codeSplitting = rollup && supportsCodeSplitting(rollup.VERSION);
		return requestBundle();
    });

	$: {
	    if (selectedExample) {
	        updateSelectedExample();
	    }
	}

	function updateSelectedExample() {
		fetch(`api/examples/${selectedExample}.json`).then(r => r.json()).then(example => {
        	modules = example.modules;
        	selectedExampleModules = modules.map(module => ({...module}))
        });
        input.$set({modules, selectedExample});
	}

	$: {
	  if (modules) {
	    requestDebouncedBundle();
	  }
	}

	$: {
	  if (options) {
	    requestBundle();
	  }
	}

    // TODO instead of debouncing, we should bundle in a worker
	let bundleDebounceTimeout;
	function requestDebouncedBundle() {
		clearTimeout(bundleDebounceTimeout);
		bundleDebounceTimeout = setTimeout(requestBundle, 100);
	}

	let bundlePromise = null;
	async function requestBundle() {
		if (!modules.length || !rollup) return;
		if (bundlePromise) {
		  await bundlePromise;
		}
		bundlePromise = bundle().then(() => bundlePromise = null);
	}

	async function bundle () {
		console.clear();
		console.log(`running Rollup version %c${rollup.VERSION}`, 'font-weight: bold');
		if (selectedExample && selectedExampleModules.length) {
		  if (modules.length !== selectedExampleModules.length || selectedExampleModules.some((module, index) => {
		    const currentModule = modules[index];
		    return currentModule.name !== module.name ||
		        currentModule.code !== module.code ||
		        currentModule.isEntry !== module.isEntry;
		  })) {
		    selectedExample = null;
		    selectedExampleModules = [];
		  }
		}

		updateUrl();

		let moduleById = {};

		modules.forEach(module => {
			moduleById[module.name] = module;
		});

		warnings = [];
		const inputOptions = {
			plugins: [{
				resolveId ( importee, importer ) {
					if ( !importer ) return importee;
					if ( importee[0] !== '.' ) return false;

					let resolved = resolve(dirname(importer), importee).replace(/^\.\//, '');
					if ( resolved in moduleById ) return resolved;

					resolved += '.js';
					if ( resolved in moduleById ) return resolved;

					throw new Error(`Could not resolve '${importee}' from '${importer}'`);
				},
				load: function ( id ) {
					return moduleById[id].code;
				}
			}],
			onwarn ( warning ) {
				warnings.push(warning);

				console.group(warning.loc ? warning.loc.file : '');

				console.warn(warning.message);

				if ( warning.frame ) {
					console.log(warning.frame);
				}

				if ( warning.url ) {
					console.log(`See ${warning.url} for more information`);
				}

				console.groupEnd();
			}
		};
		if ( codeSplitting ) {
			inputOptions.input = modules
				.filter(( module, index ) => index === 0 || module.isEntry)
				.map(module => module.name);
		} else {
			inputOptions[supportsInput(rollup.VERSION) ? 'input' : 'entry'] = 'main.js';
		}

		try {
			const generated = await (await rollup.rollup(inputOptions)).generate(options);

			if ( codeSplitting ) {
				output = generated.output;
				error = null;
			} else {
			    output = [generated];
				error = null;
			}
		} catch (err) {
			error = err;
			if ( error.frame ) console.log(error.frame);
			setTimeout(() => {
				throw error;
			});
		}
	}

	function updateUrl () {
    	if ( typeof history === 'undefined' ) return;

    	const params = {};
    	if ( typeof rollup !== 'undefined' ) {
    		params.version = rollup.VERSION;
    	} else if ( urlRollupVersion ) {
    		params.version = urlRollupVersion;
    	}

    	const json = JSON.stringify({
    		modules,
    		options,
    		example: selectedExample
    	});
    	params.shareable = btoa(encodeURIComponent(json));
    	const queryString = Object.keys(params).map(key => `${key}=${params[key]}`).join('&');
    	const url = `/repl/?${queryString}`;
    	window.history.replaceState({}, '', url);
    }
</script>

<style>
	.repl {
		height: calc(100% - 3.6em);
	}

	.left, .right {
		width: 100%;
		padding: 1em;
	}

	:global(button) {
		font-family: inherit;
		font-size: inherit;
		border: none;
		outline: none;
		cursor: pointer;
		background-color: #eee;
		padding: 0.5em 1em;
		margin-bottom: 1em;
	}

	:global(button):hover, :global(button):active {
		background-color: #eaeaea;
	}

	:global(button):disabled {
		cursor: default;
	}

	:global(.icon) {
		font-size: 0.8em;
	}

	:global(input) {
		display: block;
		width: 100%;
		font-family: inherit;
		font-size: inherit;
		padding: 0.5em;
		border: none;
		outline: none;
		line-height: 1;
		color: #333;
		background-color: inherit;
	}

	:global(input):focus {
		background-color: #eaeaea;
	}


	@media (min-width: 45rem) {
		.left, .right {
			width: 50%;
			height: 100%;
			float: left;
			overflow-y: auto;
		}
	}
</style>

<div class='repl'>
	<div class='left'>
		<h2>ES6 modules go in...</h2>
		<div class='input'>
		    <Input {examples} {codeSplitting} bind:selectedExample bind:modules bind:this={input}/>
		</div>
	</div>
	<div class='right'>
	    <h2>...{#if output.length > 1}chunks come{:else}bundle comes{/if} out</h2>
		<div class='output'>
			<Output bind:options {output} {error} {warnings} waiting={!rollup}/>
		</div>
	</div>
</div>

<!-- trick Sapper into generating example JSON files -->
{#each examples as example}
    <a hidden href="api/examples/{example.id}.json">{example.title}</a>
{/each}

@lukastaegert
Copy link
Member

Unfortunately, I can't use "https://github.com/mjackson/rollup-plugin-url-resolve" in my browser

If the plugin itself is the problem, I would really recommend opening a ticket for this plugin as this could become a powerful extension.

Can't access the file system in the browser, how to configure the "output" option?How to use the output file after packaging?

You can use all output options but dir will be ignored. The important part is that you cannot call bundle.write but need to call bundle.generate instead. Then you can grab the bundled code from the bundle object: https://rollupjs.org/guide/en/#rolluprollup

In short, bundle.output is an array of objects where every chunk has a code property that contains the generated code. What you do with this depends on your intentions. If you want to run the code in the browser, you could generate type="module" script tags, or you need to feed them to your own runtime.

I found the use of rollUp in the browser in the source code of the official website!

One unfortunate point is that the Rollup website cannot use the dynamic import syntax due to compatibility with outdated versions but mainly because of content security policy issues that we do not have under control.

@loynoir
Copy link

loynoir commented Sep 22, 2021

Same issue/proposal here.
Currently my mini working example.

https://cdn.jsdelivr.net/npm/rollup@2.56.3/dist/rollup.browser.js

var bundle = rollup.rollup({
    input: "rollup://localhost/a.mjs",
    plugins: [
        {
            name: "rollup-in-browser-example",
            resolveId(importee, importer) {
                console.debug("resolveId", { importee, importer });
                return new URL(importee, importer).href;
            },
            load(id) {
                console.debug("load", { id });
                switch (id) {
                    case "rollup://localhost/a.mjs":
                        return `import b from './b.mjs'; export default {a:b};`;
                    case "rollup://localhost/b.mjs":
                        return Promise.resolve(`export default {b:'b'}`);
                }
                throw new Error();
            },
        },
    ],
});
var { code } = (await (await bundle).generate({})).output[0];
console.info(code);

Output

var b = {b:'b'};
var a = {a:b};
export { a as default };

@loynoir
Copy link

loynoir commented Sep 22, 2021

Are there any CDN provide rollup browser ESM build? 🤔
Seems not on jsdelivr

@loynoir
Copy link

loynoir commented Sep 22, 2021

https://cdn.skypack.dev/rollup

@shellscape @lukastaegert

/*
 * [Package Error] "rollup@v2.57.0" could not be built. 
 *
 *   [1/5] Verifying package is valid…
 *   [2/5] Installing dependencies from npm…
 *   [3/5] Building package using esinstall…
 *   Running esinstall...
 *   rollup-plugin-inject: failed to parse rollup/dist/shared/rollup.js. Consider restricting the plugin to particular files via options.include
 *   Failed to load node_modules/rollup/dist/shared/rollup.js
 *     Unexpected token
 *   Install failed.
 *   Install failed.
 *
 * How to fix:
 *   - If you believe this to be an error in Skypack, file an issue here: https://github.com/skypackjs/skypack-cdn/issues
 *   - If you believe this to be an issue in the package, share this URL with the package authors to help them debug & fix.
 *   - Use https://skypack.dev/ to find a web-friendly alternative to find another package.
 */

@lukastaegert
Copy link
Member

lukastaegert commented Sep 28, 2021

No idea what Skypack is doing there, but Rollup does have an ESM build that ships with each install (as well as a browser ESM build). The ESM Node build is located in /dist/es/rollup.js while the browser one is in /dist/es/rollup.browser.js. Here is the browser build on a CDN: https://unpkg.com/rollup@2.57.0/dist/es/rollup.browser.js

@loynoir
Copy link

loynoir commented Sep 28, 2021

@lukastaegert
AFAIK, Skypack is a fully ESM cdn.
Seems, they build a tool call esinstall to let everything ESM.
But the meta of rollup is somehow not compact with esintall?
Anyway, never mind, already an ESM release 😀

@LeaVerou
Copy link

Just wanted to second this. This would be super useful for making custom bundle builders for libraries so that people can configure their bundle client side.

@LukasBombach
Copy link

LukasBombach commented Dec 7, 2022

Here's the fix for version 3.6.0 (current as of 12/2022)

They have removed the browser bundle from the main package, so rollup/dist/es/rollup.browser.js is gone.

But they are still creating it and distributing it at @rollup/browser.

So this works in the browser, in client js

import { rollup } from "@rollup/browser";

It can be found in the repo at /browser/package.json

I could not find any docs on this and the npm page is without a readme

@pkl
Copy link

pkl commented Dec 8, 2022

There's some docs here:

https://github.com/rollup/rollup/blob/master/docs/06-faqs.md#how-do-i-run-rollup-itself-in-a-browser

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

No branches or pull requests

7 participants