Skip to content

Commit

Permalink
Implement stdin input (#3312)
Browse files Browse the repository at this point in the history
* Implement stdin input with optional "-" as the file name.
Set default output format to "es" to save CLI typing for the most common case.

closes #1440
closes #3276

* * Test if we can delete process.stdin on CI
* Only allow '-' to use stdin with the JS API
* Use locally installed shx
* Add test for stdin read error

* * Only allow stdin via the CLI
* Move stdin logic to CLI
* Only use stdin as input by default when there is no config file

* * Allow using "-" as a regular file name with --no-stdin CLI flag
* Override input with stdin with --stdin CLI flag

* * Add documentation

* Avoid using shx

* Skip tests on Windows

* Switch stdin to a regular two-state flag

* Fix Windows tests

Co-authored-by: kzc <kzc@users.noreply.github.com>
  • Loading branch information
lukastaegert and kzc committed Jan 4, 2020
1 parent 6106af1 commit 42fe317
Show file tree
Hide file tree
Showing 50 changed files with 235 additions and 216 deletions.
49 changes: 24 additions & 25 deletions cli/run/index.ts
Expand Up @@ -3,14 +3,14 @@ import relative from 'require-relative';
import { WarningHandler } from '../../src/rollup/types';
import mergeOptions, { GenericConfigObject } from '../../src/utils/mergeOptions';
import { getAliasName } from '../../src/utils/relativeId';
import { stdinName } from '../../src/utils/stdin';
import { handleError } from '../logging';
import batchWarnings from './batchWarnings';
import build from './build';
import loadConfigFile from './loadConfigFile';
import { stdinName, stdinPlugin } from './stdin';
import watch from './watch';

export default function runRollup (command: any) {
export default function runRollup(command: any) {
let inputSource;
if (command._.length > 0) {
if (command.input) {
Expand Down Expand Up @@ -86,40 +86,39 @@ export default function runRollup (command: any) {

if (command.watch) process.env.ROLLUP_WATCH = 'true';

loadConfigFile(configFile, command)
return loadConfigFile(configFile, command)
.then(configs => execute(configFile, configs, command))
.catch(handleError);
} else {
return execute(configFile, [{ input: null }] as any, command);
if (!command.input && (command.stdin || !process.stdin.isTTY)) {
command.input = stdinName;
}
return execute(configFile, [{ input: [] }], command);
}
}

function execute (configFile: string, configs: GenericConfigObject[], command: any) {
async function execute(
configFile: string,
configs: GenericConfigObject[],
command: any
): Promise<void> {
if (command.watch) {
watch(configFile, configs, command, command.silent);
} else {
let promise = Promise.resolve();
for (const config of configs) {
promise = promise.then(() => {
const warnings = batchWarnings();
handleMissingInput(command, config);
const { inputOptions, outputOptions, optionError } = mergeOptions({
command,
config,
defaultOnWarnHandler: warnings.add
});

if (optionError)
(inputOptions.onwarn as WarningHandler)({ code: 'UNKNOWN_OPTION', message: optionError });
return build(inputOptions, outputOptions, warnings, command.silent);
const warnings = batchWarnings();
const { inputOptions, outputOptions, optionError } = mergeOptions({
command,
config,
defaultOnWarnHandler: warnings.add
});
if (optionError) {
(inputOptions.onwarn as WarningHandler)({ code: 'UNKNOWN_OPTION', message: optionError });
}
if (command.stdin !== false) {
inputOptions.plugins!.push(stdinPlugin());
}
await build(inputOptions, outputOptions, warnings, command.silent);
}
return promise;
}
}

function handleMissingInput(command: any, config: GenericConfigObject) {
if (!(command.input || config.input || config.input === '' || process.stdin.isTTY)) {
command.input = stdinName;
}
}
37 changes: 37 additions & 0 deletions cli/run/stdin.ts
@@ -0,0 +1,37 @@
import { Plugin } from '../../src/rollup/types';

export const stdinName = '-';

let stdinResult: Promise<string> | null = null;

export function stdinPlugin(): Plugin {
return {
name: 'stdin',
resolveId(id) {
if (id === stdinName) {
return id;
}
},
load(id) {
if (id === stdinName) {
return stdinResult || (stdinResult = readStdin());
}
}
};
}

function readStdin(): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
process.stdin.setEncoding('utf8');
process.stdin
.on('data', chunk => chunks.push(chunk))
.on('end', () => {
const result = chunks.join('');
resolve(result);
})
.on('error', err => {
reject(err);
});
});
}
23 changes: 23 additions & 0 deletions docs/01-command-line-reference.md
Expand Up @@ -253,6 +253,7 @@ Many options have command line equivalents. In those cases, any arguments passed
--silent Don't print warnings
--sourcemapExcludeSources Do not include source code in source maps
--sourcemapFile <file> Specify bundle position for source maps
--no-stdin do not read "-" from stdin
--strictDeprecations Throw errors for deprecated features
--no-treeshake Disable tree-shaking optimisations
--no-treeshake.annotations Ignore pure call annotations
Expand Down Expand Up @@ -306,3 +307,25 @@ npm run build -- --environment BUILD:development
```

then the config file will receive `process.env.INCLUDE_DEPS === 'true'` and `process.env.BUILD === 'development'`.

#### `--no-stdin`

Do not read files from `stdin`. Setting this flag will prevent piping content to Rollup and make sure Rollup interprets `-` as a regular file name instead of interpreting this as the name of `stdin`. See also [Reading a file from stdin](guide/en/#reading-a-file-from-stdin).

### Reading a file from stdin

When using the command line interface, Rollup can also read content from stdin:

```
echo "export const foo = 42;" | rollup --format cjs --file out.js
```

When this file contains imports, Rollup will try to resolve them relative to the current working directory. When a config file is used, Rollup will only use `stdin` as an entry point if the file name of the entry point is `-`. To read a non-entry-point file from stdin, just call it `-`, which is the file name that is used internally to reference `stdin`. I.e.

```js
import foo from "-";
```

in any file will prompt Rollup to try to read the imported file from `stdin` and assign the default export to `foo`. You can pass the [`--no-stdin`](guide/en/#--no-stdin) CLI flag to Rollup to treat `-` as a regular file name instead.

The JavaScript API will always treat `-` as a regular file name.
16 changes: 3 additions & 13 deletions src/rollup/index.ts
Expand Up @@ -13,7 +13,7 @@ import {
} from '../utils/error';
import { writeFile } from '../utils/fs';
import getExportMode from '../utils/getExportMode';
import mergeOptions, { GenericConfigObject } from '../utils/mergeOptions';
import mergeOptions, { ensureArray, GenericConfigObject } from '../utils/mergeOptions';
import { basename, dirname, isAbsolute, resolve } from '../utils/path';
import { PluginDriver } from '../utils/PluginDriver';
import { ANONYMOUS_OUTPUT_PLUGIN_PREFIX, ANONYMOUS_PLUGIN_PREFIX } from '../utils/pluginUtils';
Expand Down Expand Up @@ -80,16 +80,6 @@ function applyOptionHook(inputOptions: InputOptions, plugin: Plugin) {
return inputOptions;
}

function ensureArray<T>(items: (T | null | undefined)[] | T | null | undefined): T[] {
if (Array.isArray(items)) {
return items.filter(Boolean) as T[];
}
if (items) {
return [items];
}
return [];
}

function normalizePlugins(rawPlugins: any, anonymousPrefix: string): Plugin[] {
const plugins = ensureArray(rawPlugins);
for (let pluginIndex = 0; pluginIndex < plugins.length; pluginIndex++) {
Expand All @@ -112,8 +102,8 @@ function getInputOptions(rawInputOptions: GenericConfigObject): InputOptions {
if (optionError)
(inputOptions.onwarn as WarningHandler)({ message: optionError, code: 'UNKNOWN_OPTION' });

inputOptions = ensureArray(inputOptions.plugins).reduce(applyOptionHook, inputOptions);
inputOptions.plugins = normalizePlugins(inputOptions.plugins, ANONYMOUS_PLUGIN_PREFIX);
inputOptions = inputOptions.plugins!.reduce(applyOptionHook, inputOptions);
inputOptions.plugins = normalizePlugins(inputOptions.plugins!, ANONYMOUS_PLUGIN_PREFIX);

if (inputOptions.inlineDynamicImports) {
if (inputOptions.preserveModules)
Expand Down
7 changes: 3 additions & 4 deletions src/rollup/types.d.ts
Expand Up @@ -233,10 +233,9 @@ export type IsPureModule = (id: string) => boolean | null | undefined;

export type HasModuleSideEffects = (id: string, external: boolean) => boolean;

export type LoadHook = (
this: PluginContext,
id: string
) => Promise<SourceDescription | string | null> | SourceDescription | string | null;
type LoadResult = SourceDescription | string | null | undefined;

export type LoadHook = (this: PluginContext, id: string) => Promise<LoadResult> | LoadResult;

export type TransformResult = string | null | undefined | TransformSourceDescription;

Expand Down
5 changes: 1 addition & 4 deletions src/utils/defaultPlugin.ts
Expand Up @@ -2,14 +2,13 @@ import { Plugin, ResolveIdHook } from '../rollup/types';
import { error } from './error';
import { lstatSync, readdirSync, readFile, realpathSync } from './fs';
import { basename, dirname, isAbsolute, resolve } from './path';
import { readStdin, stdinName } from './stdin';

export function getRollupDefaultPlugin(preserveSymlinks: boolean): Plugin {
return {
name: 'Rollup Core',
resolveId: createResolveId(preserveSymlinks) as ResolveIdHook,
load(id) {
return id === stdinName ? readStdin() : readFile(id);
return readFile(id);
},
resolveFileUrl({ relativePath, format }) {
return relativeUrlMechanisms[format](relativePath);
Expand Down Expand Up @@ -59,8 +58,6 @@ function createResolveId(preserveSymlinks: boolean) {
});
}

if (source === stdinName) return source;

// external modules (non-entry modules that start with neither '.' or '/')
// are skipped at this stage.
if (importer !== undefined && !isAbsolute(source) && source[0] !== '.') return null;
Expand Down
30 changes: 15 additions & 15 deletions src/utils/mergeOptions.ts
Expand Up @@ -5,8 +5,6 @@ import {
WarningHandlerWithDefault
} from '../rollup/types';

import { stdinName } from './stdin';

export interface GenericConfigObject {
[key: string]: unknown;
}
Expand Down Expand Up @@ -50,6 +48,16 @@ const getObjectOption = (
return configOption;
};

export function ensureArray<T>(items: (T | null | undefined)[] | T | null | undefined): T[] {
if (Array.isArray(items)) {
return items.filter(Boolean) as T[];
}
if (items) {
return [items];
}
return [];
}

const defaultOnWarn: WarningHandler = warning => {
if (typeof warning === 'string') {
console.warn(warning);
Expand Down Expand Up @@ -150,7 +158,8 @@ export default function mergeOptions({
Object.keys(commandAliases),
'config',
'environment',
'silent'
'silent',
'stdin'
),
'CLI flag',
/^_|output|(config.*)$/
Expand Down Expand Up @@ -210,8 +219,6 @@ function getInputOptions(
defaultOnWarnHandler: WarningHandler
): InputOptions {
const getOption = createGetOption(config, command);
const input = getOption('input', []);

const inputOptions: InputOptions = {
acorn: config.acorn,
acornInjectPlugins: config.acornInjectPlugins as any,
Expand All @@ -223,12 +230,12 @@ function getInputOptions(
experimentalTopLevelAwait: getOption('experimentalTopLevelAwait'),
external: getExternal(config, command) as any,
inlineDynamicImports: getOption('inlineDynamicImports', false),
input,
input: getOption('input', []),
manualChunks: getOption('manualChunks'),
moduleContext: config.moduleContext as any,
onwarn: getOnWarn(config, defaultOnWarnHandler),
perf: getOption('perf', false),
plugins: config.plugins as any,
plugins: ensureArray(config.plugins as any),
preserveModules: getOption('preserveModules'),
preserveSymlinks: getOption('preserveSymlinks'),
shimMissingExports: getOption('shimMissingExports'),
Expand All @@ -237,13 +244,6 @@ function getInputOptions(
watch: config.watch as any
};

if (
config.watch &&
(input === stdinName || (Array.isArray(input) && input.indexOf(stdinName) >= 0))
) {
throw new Error('watch mode is incompatible with stdin input');
}

// support rollup({ cache: prevBuildObject })
if (inputOptions.cache && (inputOptions.cache as any).cache)
inputOptions.cache = (inputOptions.cache as any).cache;
Expand Down Expand Up @@ -295,7 +295,7 @@ function getOutputOptions(
noConflict: getOption('noConflict'),
outro: getOption('outro'),
paths: getOption('paths'),
plugins: config.plugins as any,
plugins: ensureArray(config.plugins as any),
preferConst: getOption('preferConst'),
sourcemap: getOption('sourcemap'),
sourcemapExcludeSources: getOption('sourcemapExcludeSources'),
Expand Down
54 changes: 0 additions & 54 deletions src/utils/stdin.ts

This file was deleted.

Expand Up @@ -5,8 +5,7 @@ module.exports = {
plugins: [
{
transform(code, id) {
console.log(id);
if (id.endsWith('/dep1.js') || id.endsWith('/dep2.js')) {
if (id.endsWith('dep1.js') || id.endsWith('dep2.js')) {
return {
code,
syntheticNamedExports: true
Expand Down
Expand Up @@ -5,7 +5,7 @@ module.exports = {
plugins: [
{
resolveId(id) {
if (id === './dep1.js') {
if (id.endsWith('dep1.js')) {
return {
id,
syntheticNamedExports: true
Expand Down

0 comments on commit 42fe317

Please sign in to comment.