diff --git a/README.md b/README.md
index cb58112..c39d2d4 100644
--- a/README.md
+++ b/README.md
@@ -145,6 +145,7 @@ Please refer to the linked documentation for using `arc` in each environment:
- Node 8+ ([`arc-server`](./packages/arc-server))
- Webpack 4+ ([`arc-webpack`](./packages/arc-webpack))
- Lasso 3+ ([`arc-lasso`](./packages/arc-lasso))
+- Rollup 1+ ([`arc-rollup`](./packages/arc-rollup))
## Additional resources
diff --git a/packages/arc-rollup/README.md b/packages/arc-rollup/README.md
new file mode 100644
index 0000000..1a046b0
--- /dev/null
+++ b/packages/arc-rollup/README.md
@@ -0,0 +1,72 @@
+# arc-rollup
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## API
+
+The `arc-rollup` plugin is a drop-in replacement for [`rollup-plugin-node-resolve`](https://github.com/rollup/rollup-plugin-node-resolve) that provides flag-based adaptive resolution in addition to node's [module resolution](https://nodejs.org/api/modules.html#modules_all_together). It is built on `rollup-plugin-node-resolve` and supports all the same options, plus the following:
+
+### `flags`
+
+The flagset to use when resolving adaptive files.
+
+### `proxy` _(unstable)_
+
+When `true`, compiles to use `arc-server/proxy` instead of resolving to a specfic resolution of an adaptive file. This only supports default exports for adaptive modules (named exports and commonjs are not supported). Until this limitation is addressed, it is recommended to use the `arc-server/install` hook when loading adaptive files in Node.js.
+
+## Examples
+
+### Create multiple bundles for different flagsets
+
+```js
+import resolve from "arc-rollup";
+
+export default [{ desktop:true }, { mobile:true }].map(flags => ({
+ input: "main.js",
+ output: {
+ file: `bundle-${Object.keys(flags).join("-")}.js`,
+ format: "iife"
+ },
+ plugins: [
+ resolve({ flags })
+ ]
+});
+```
+
+### Bundle for Node.js
+
+```js
+import resolve from "arc-rollup";
+import commonjs from "rollup-plugin-commonjs";
+import builtins from "builtin-modules";
+
+export default {
+ input: "server.js",
+ output: {
+ file: `bundle.js`,
+ format: "cjs"
+ },
+ plugins: [
+ resolve({ proxy:true }),
+ commonjs()
+ ],
+ externals: builtins
+};
+```
\ No newline at end of file
diff --git a/packages/arc-rollup/index.js b/packages/arc-rollup/index.js
new file mode 100644
index 0000000..6dc8566
--- /dev/null
+++ b/packages/arc-rollup/index.js
@@ -0,0 +1,80 @@
+const resolve = require("rollup-plugin-node-resolve");
+const AdaptiveFS = require('arc-fs');
+const PROXY_SUFFIX = "?arc-proxy";
+
+module.exports = function({ proxy, flags, ...additionalOptions } = {}) {
+ if (!flags && !proxy) {
+ throw new Error("The arc-rollup plugin should be passed flags to be used when resolving files or proxy:true should be passed.")
+ }
+
+ const afs = new AdaptiveFS({ flags });
+ const plugin = resolve({
+ ...additionalOptions,
+ customResolveOptions: {
+ ...additionalOptions.customResolveOptions,
+ readFile: afs.readFile,
+ isFile(file, cb) {
+ afs.stat(file, function (err, stat) {
+ if (!err) {
+ return cb(null, stat.isFile() || stat.isFIFO());
+ }
+ if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
+ return cb(err);
+ });
+ },
+ isDirectory(dir, cb) {
+ afs.stat(dir, function (err, stat) {
+ if (!err) {
+ return cb(null, stat.isDirectory());
+ }
+ if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
+ return cb(err);
+ });
+ }
+ }
+ });
+
+ const originalResolveId = plugin.resolveId;
+ plugin.resolveId = async function (importee, importer) {
+ const result = await originalResolveId(importee, importer);
+ if (result && result.id) {
+ if (proxy) {
+ if (importer !== result.id + PROXY_SUFFIX && afs.isAdaptiveSync(result.id)) {
+ result.id += PROXY_SUFFIX
+ }
+ } else {
+ result.id = afs.resolveSync(result.id);
+ }
+ }
+ return result;
+ }
+
+ if (proxy) {
+ plugin.load = function (id) {
+ if (id.endsWith(PROXY_SUFFIX)) {
+ const file = id.slice(0, -PROXY_SUFFIX.length);
+ const matches = afs.getMatchesSync(file);
+ const code = `
+ import Proxy from "arc-server/proxy";
+ import { MatchSet } from "arc-resolver";
+ ${
+ Array.from(matches).map(({ value }, index) => {
+ return `import * as target_${index} from ${JSON.stringify(value)};`;
+ }).join('\n')
+ }
+
+ const matches = new MatchSet([${
+ Array.from(matches).map(({ flags }, index) => {
+ return `{ value:target_${index}, flags:${JSON.stringify(flags)} }`;
+ }).join(',')
+ }]);
+
+ export default new Proxy(matches);
+ `;
+ return code;
+ }
+ }
+ }
+
+ return plugin;
+}
\ No newline at end of file
diff --git a/packages/arc-rollup/package.json b/packages/arc-rollup/package.json
new file mode 100644
index 0000000..8263d13
--- /dev/null
+++ b/packages/arc-rollup/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "arc-rollup",
+ "version": "2.0.0",
+ "description": "Resolve adaptive modules when using Rollup",
+ "main": "index.js",
+ "author": "Michael Rawlings ",
+ "license": "MIT",
+ "dependencies": {
+ "arc-fs": "^2.0.0",
+ "rollup-plugin-node-resolve": "^5.2.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.0.0-rc.1",
+ "@babel/preset-env": "^7.0.0-rc.1",
+ "arc-resolver": "^2.0.0",
+ "arc-server": "^2.0.0",
+ "builtin-modules": "^3.1.0",
+ "rollup": "^1.27.2",
+ "rollup-plugin-commonjs": "^10.1.0"
+ }
+}
diff --git a/packages/arc-rollup/test/fixture-with-base/fixture.js b/packages/arc-rollup/test/fixture-with-base/fixture.js
new file mode 100644
index 0000000..fbbfc7b
--- /dev/null
+++ b/packages/arc-rollup/test/fixture-with-base/fixture.js
@@ -0,0 +1 @@
+export default () => console.log('mobile');
\ No newline at end of file
diff --git a/packages/arc-rollup/test/fixture-with-base/fixture[desktop].js b/packages/arc-rollup/test/fixture-with-base/fixture[desktop].js
new file mode 100644
index 0000000..387f501
--- /dev/null
+++ b/packages/arc-rollup/test/fixture-with-base/fixture[desktop].js
@@ -0,0 +1 @@
+export default () => console.log('desktop');
\ No newline at end of file
diff --git a/packages/arc-rollup/test/fixture-with-base/index.js b/packages/arc-rollup/test/fixture-with-base/index.js
new file mode 100644
index 0000000..bd25c1d
--- /dev/null
+++ b/packages/arc-rollup/test/fixture-with-base/index.js
@@ -0,0 +1,2 @@
+import log from './fixture';
+log();
\ No newline at end of file
diff --git a/packages/arc-rollup/test/fixture/fixture[desktop].js b/packages/arc-rollup/test/fixture/fixture[desktop].js
new file mode 100644
index 0000000..387f501
--- /dev/null
+++ b/packages/arc-rollup/test/fixture/fixture[desktop].js
@@ -0,0 +1 @@
+export default () => console.log('desktop');
\ No newline at end of file
diff --git a/packages/arc-rollup/test/fixture/fixture[mobile].js b/packages/arc-rollup/test/fixture/fixture[mobile].js
new file mode 100644
index 0000000..fbbfc7b
--- /dev/null
+++ b/packages/arc-rollup/test/fixture/fixture[mobile].js
@@ -0,0 +1 @@
+export default () => console.log('mobile');
\ No newline at end of file
diff --git a/packages/arc-rollup/test/fixture/index.js b/packages/arc-rollup/test/fixture/index.js
new file mode 100644
index 0000000..bd25c1d
--- /dev/null
+++ b/packages/arc-rollup/test/fixture/index.js
@@ -0,0 +1,2 @@
+import log from './fixture';
+log();
\ No newline at end of file
diff --git a/packages/arc-rollup/test/index.js b/packages/arc-rollup/test/index.js
new file mode 100644
index 0000000..eb0a569
--- /dev/null
+++ b/packages/arc-rollup/test/index.js
@@ -0,0 +1,59 @@
+let rollup = require('rollup');
+let commonjs = require('rollup-plugin-commonjs');
+let builtins = require("builtin-modules");
+let expect = require('chai').expect;
+let resolve = require('../');
+
+describe('resolve', () => {
+ describe('api', () => {
+ it('should throw if no flags are passed', () => {
+ expect(() => resolve()).to.throw(/flags/);
+ });
+ });
+
+ [
+ {
+ description: 'fixture (exclusive)',
+ input: require.resolve('./fixture')
+ },
+ {
+ description: 'fixture-with-base',
+ input: require.resolve('./fixture-with-base')
+ }
+ ].forEach(({ description, input }) => {
+ describe(description, () => {
+ it('should work', async () => {
+ const bundle = await rollup.rollup({
+ input,
+ plugins: [resolve({ flags: { mobile: true } })]
+ });
+ const { output:[{ code }] } = await bundle.generate({ format: "iife" });
+ expect(code).to.not.include(`console.log('desktop')`);
+ expect(code).to.include(`console.log('mobile')`);
+ });
+ it('should work', async () => {
+ const bundle = await rollup.rollup({
+ input,
+ plugins: [resolve({ flags: { desktop: true } })]
+ });
+ const { output:[{ code }] } = await bundle.generate({ format: "iife" });
+ expect(code).to.include(`console.log('desktop')`);
+ expect(code).to.not.include(`console.log('mobile')`);
+ });
+ it('should bundle proxies for the server', async () => {
+ const bundle = await rollup.rollup({
+ input,
+ plugins: [
+ resolve({ proxy: true }),
+ commonjs()
+ ],
+ external: builtins
+ });
+ const { output:[{ code }] } = await bundle.generate({ format: "esm" });
+ expect(code).to.include(`console.log('desktop')`);
+ expect(code).to.include(`console.log('mobile')`);
+ expect(code).to.include(`new Proxy`);
+ });
+ });
+ });
+});