rollup#InputOptions TypeScript Examples

The following examples show how to use rollup#InputOptions. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: fromRollup.ts    From web with MIT License 6 votes vote down vote up
export function fromRollup<T extends FnArgs>(
  rollupPluginFn: RollupPluginFn<T>,
  rollupInputOptions: Partial<InputOptions> = {},
  options: FromRollupOptions = {},
) {
  if (typeof rollupPluginFn !== 'function') {
    throw new Error(
      `fromRollup should be called with a rollup plugin function. Received: ${rollupPluginFn}`,
    );
  }

  // return a function wrapper which intercepts creation of the rollup plugin
  return function wrappedRollupPluginFn(...args: T) {
    // call the original plugin function
    const rollupPlugin = rollupPluginFn(...args);
    // wrap the rollup plugin in an adapter for web dev server
    return rollupAdapter(rollupPlugin, rollupInputOptions, options);
  };
}
Example #2
Source File: createRollupPluginContexts.ts    From web with MIT License 5 votes vote down vote up
/**
 * Runs rollup with an empty module in order to capture the plugin context and
 * normalized options.
 * @param inputOptions
 */
export async function createRollupPluginContexts(
  inputOptions: InputOptions,
): Promise<RollupPluginContexts> {
  let normalizedInputOptions: NormalizedInputOptions | undefined = undefined;
  let pluginContext: PluginContext | undefined = undefined;
  let transformPluginContext: TransformPluginContext | undefined = undefined;

  await rollup({
    ...inputOptions,

    input: 'noop',

    plugins: [
      {
        name: 'noop',
        buildStart(options) {
          normalizedInputOptions = options;
        },
        resolveId(id) {
          pluginContext = this;
          return id;
        },
        load() {
          return '';
        },
        transform() {
          transformPluginContext = this;
          return null;
        },
      },
    ],
  });

  if (!normalizedInputOptions || !pluginContext || !transformPluginContext) {
    throw new TypeError();
  }

  return {
    normalizedInputOptions,
    pluginContext,
    transformPluginContext,
    minimalPluginContext: { meta: (pluginContext as PluginContext).meta },
  };
}
Example #3
Source File: addRollupInput.ts    From web with MIT License 5 votes vote down vote up
export function addRollupInput(
  inputOptions: InputOptions,
  inputModuleIds: ScriptModuleTag[],
): InputOptions {
  // Add input module ids to existing input option, whether it's a string, array or object
  // this way you can use multiple html plugins all adding their own inputs
  if (!inputOptions.input) {
    return { ...inputOptions, input: inputModuleIds.map(mod => mod.importPath) };
  }

  if (typeof inputOptions.input === 'string') {
    return {
      ...inputOptions,
      input: [
        ...(inputOptions?.input?.endsWith('.html') ? [] : [inputOptions.input]),
        ...inputModuleIds.map(mod => mod.importPath),
      ],
    };
  }

  if (Array.isArray(inputOptions.input)) {
    return {
      ...inputOptions,
      input: [...inputOptions.input, ...inputModuleIds.map(mod => mod.importPath)],
    };
  }

  if (typeof inputOptions.input === 'object') {
    return {
      ...inputOptions,
      input: {
        ...inputOptions.input,
        ...fromEntries(
          inputModuleIds
            .map(mod => mod.importPath)
            .map(i => [i.split('/').slice(-1)[0].split('.')[0], i]),
        ),
      },
    };
  }

  throw createError(`Unknown rollup input type. Supported inputs are string, array and object.`);
}
Example #4
Source File: plugins.ts    From backstage with Apache License 2.0 4 votes vote down vote up
/**
 * This rollup plugin leaves all encountered asset imports as-is, but
 * copies the imported files into the output directory.
 *
 * For example `import ImageUrl from './my-image.png'` inside `src/MyComponent` will
 * cause `src/MyComponent/my-image.png` to be copied to the output directory at the
 * path `dist/MyComponent/my-image.png`. The import itself will stay, but be resolved,
 * resulting in something like `import ImageUrl from './MyComponent/my-image.png'`
 */
export function forwardFileImports(options: ForwardFileImportsOptions): Plugin {
  const filter = createFilter(options.include, options.exclude);

  // We collect the absolute paths to all files we want to bundle into the
  // output dir here. Resolving to relative paths in the output dir happens later.
  const exportedFiles = new Set<string>();

  // We keep track of output directories that we've already copied files
  // into, so that we don't duplicate that work
  const generatedFor = new Set<string>();

  return {
    name: 'forward-file-imports',
    async generateBundle(outputOptions, bundle, isWrite) {
      if (!isWrite) {
        return;
      }

      const dir = outputOptions.dir || dirname(outputOptions.file!);
      if (generatedFor.has(dir)) {
        return;
      }

      for (const output of Object.values(bundle)) {
        if (output.type !== 'chunk') {
          continue;
        }
        const chunk = output as OutputChunk;

        // This'll be an absolute path pointing to the initial index file of the
        // build, and we use it to find the location of the `src` dir
        if (!chunk.facadeModuleId) {
          continue;
        }
        generatedFor.add(dir);

        // We're assuming that the index file is at the root of the source dir, and
        // that all assets exist within that dir.
        const srcRoot = dirname(chunk.facadeModuleId);

        // Copy all the files we found into the dist dir
        await Promise.all(
          Array.from(exportedFiles).map(async exportedFile => {
            const outputPath = relativePath(srcRoot, exportedFile);
            const targetFile = resolvePath(dir, outputPath);

            await fs.ensureDir(dirname(targetFile));
            await fs.copyFile(exportedFile, targetFile);
          }),
        );
        return;
      }
    },
    options(inputOptions) {
      const origExternal = inputOptions.external;

      // We decorate any existing `external` option with our own way of determining
      // if a module should be external. The can't use `resolveId`, since asset files
      // aren't passed there, might be some better way to do this though.
      const external: InputOptions['external'] = (id, importer, isResolved) => {
        // Call to inner external option
        if (
          typeof origExternal === 'function' &&
          origExternal(id, importer, isResolved)
        ) {
          return true;
        }

        if (Array.isArray(origExternal) && origExternal.includes(id)) {
          return true;
        }

        // The piece that we're adding
        if (!filter(id)) {
          return false;
        }

        // Sanity check, dunno if this can happen
        if (!importer) {
          throw new Error(`Unknown importer of file module ${id}`);
        }

        // Resolve relative imports to the full file URL, for deduping and copying later
        const fullId = isResolved ? id : resolvePath(dirname(importer), id);
        exportedFiles.add(fullId);

        // Treating this module as external from here, meaning rollup won't try to
        // put it in the output bundle, but still keep track of the relative imports
        // as needed in the output code.
        return true;
      };

      return { ...inputOptions, external };
    },
  };
}
Example #5
Source File: rollupAdapter.ts    From web with MIT License 4 votes vote down vote up
export function rollupAdapter(
  rollupPlugin: RollupPlugin,
  rollupInputOptions: Partial<InputOptions> = {},
  adapterOptions: RollupAdapterOptions = {},
): WdsPlugin {
  if (typeof rollupPlugin !== 'object') {
    throw new Error('rollupAdapter should be called with a rollup plugin object.');
  }

  const transformedFiles = new Set();
  const pluginMetaPerModule = new Map<string, CustomPluginOptions>();
  let rollupPluginContexts: RollupPluginContexts;
  let fileWatcher: FSWatcher;
  let config: DevServerCoreConfig;
  let rootDir: string;

  function savePluginMeta(
    id: string,
    { meta }: { meta?: CustomPluginOptions | null | undefined } = {},
  ) {
    if (!meta) {
      return;
    }
    const previousMeta = pluginMetaPerModule.get(id);
    pluginMetaPerModule.set(id, { ...previousMeta, ...meta });
  }

  const wdsPlugin: WdsPlugin = {
    name: rollupPlugin.name,
    async serverStart(args) {
      ({ fileWatcher, config } = args);
      ({ rootDir } = config);
      rollupPluginContexts = await createRollupPluginContexts(rollupInputOptions);

      // call the options and buildStart hooks
      rollupPlugin.options?.call(rollupPluginContexts.minimalPluginContext, rollupInputOptions) ??
        rollupInputOptions;
      rollupPlugin.buildStart?.call(
        rollupPluginContexts.pluginContext,
        rollupPluginContexts.normalizedInputOptions,
      );
    },

    async resolveImport({ source, context, code, column, line }) {
      if (context.response.is('html') && source.startsWith('�')) {
        // when serving HTML a null byte gets parsed as an unknown character
        // we remap it to a null byte here so that it is handled properly downstream
        // this isn't a perfect solution
        source = source.replace('�', '\0');
      }

      // if we just transformed this file and the import is an absolute file path
      // we need to rewrite it to a browser path
      const injectedFilePath = path.normalize(source).startsWith(rootDir);
      if (!injectedFilePath && !rollupPlugin.resolveId) {
        return;
      }

      if (!injectedFilePath && !path.isAbsolute(source) && whatwgUrl.parseURL(source) != null) {
        // don't resolve relative and valid urls
        return source;
      }

      const filePath = getRequestFilePath(context.url, rootDir);

      try {
        const rollupPluginContext = createRollupPluginContextAdapter(
          rollupPluginContexts.pluginContext,
          wdsPlugin,
          config,
          fileWatcher,
          context,
          pluginMetaPerModule,
        );

        let resolvableImport = source;
        let importSuffix = '';
        // we have to special case node-resolve because it doesn't support resolving
        // with hash/params at the moment
        if (rollupPlugin.name === 'node-resolve') {
          if (source[0] === '#') {
            // private import
            resolvableImport = source;
          } else {
            const [withoutHash, hash] = source.split('#');
            const [importPath, params] = withoutHash.split('?');
            importSuffix = `${params ? `?${params}` : ''}${hash ? `#${hash}` : ''}`;
            resolvableImport = importPath;
          }
        }

        let result = await rollupPlugin.resolveId?.call(
          rollupPluginContext,
          resolvableImport,
          filePath,
          { isEntry: false },
        );

        if (!result && injectedFilePath) {
          // the import is a file path but it was not resolved by this plugin
          // we do assign it here so that it will be converted to a browser path
          result = resolvableImport;
        }

        let resolvedImportPath: string | undefined = undefined;
        if (typeof result === 'string') {
          resolvedImportPath = result;
        } else if (typeof result === 'object' && typeof result?.id === 'string') {
          resolvedImportPath = result.id;
          savePluginMeta(result.id, result);
        }

        if (!resolvedImportPath) {
          if (
            !['/', './', '../'].some(prefix => resolvableImport.startsWith(prefix)) &&
            adapterOptions.throwOnUnresolvedImport
          ) {
            const errorMessage = red(`Could not resolve import ${cyan(`"${source}"`)}.`);
            if (
              typeof code === 'string' &&
              typeof column === 'number' &&
              typeof line === 'number'
            ) {
              throw new PluginSyntaxError(errorMessage, filePath, code, column, line);
            } else {
              throw new PluginError(errorMessage);
            }
          }
          return undefined;
        }

        // if the resolved import includes a null byte (\0) there is some special logic
        // these often are not valid file paths, so the browser cannot request them.
        // we rewrite them to a special URL which we deconstruct later when we load the file
        if (resolvedImportPath.includes('\0')) {
          const filename = path.basename(
            resolvedImportPath.replace(/\0*/g, '').split('?')[0].split('#')[0],
          );
          // if the resolve import path is outside our normal root, fully resolve the file path for rollup
          const matches = resolvedImportPath.match(OUTSIDE_ROOT_REGEXP);
          if (matches) {
            const upDirs = new Array(parseInt(matches[1], 10) + 1).join(`..${path.sep}`);
            resolvedImportPath = `\0${path.resolve(`${upDirs}${matches[2]}`)}`;
          }
          const urlParam = encodeURIComponent(resolvedImportPath);
          return `${VIRTUAL_FILE_PREFIX}/${filename}?${NULL_BYTE_PARAM}=${urlParam}`;
        }

        // some plugins don't return a file path, so we just return it as is
        if (!isAbsoluteFilePath(resolvedImportPath)) {
          return `${resolvedImportPath}`;
        }

        // file already resolved outsided root dir
        if (isOutsideRootDir(resolvedImportPath)) {
          return `${resolvedImportPath}`;
        }

        const normalizedPath = path.normalize(resolvedImportPath);

        // append a path separator to rootDir so we are actually testing containment
        // of the normalized path within the rootDir folder
        const checkRootDir = rootDir.endsWith(path.sep) ? rootDir : rootDir + path.sep;

        if (!normalizedPath.startsWith(checkRootDir)) {
          const relativePath = path.relative(rootDir, normalizedPath);
          const dirUp = `..${path.sep}`;
          const lastDirUpIndex = relativePath.lastIndexOf(dirUp) + 3;
          const dirUpStrings = relativePath.substring(0, lastDirUpIndex).split(path.sep);
          if (dirUpStrings.length === 0 || dirUpStrings.some(str => !['..', ''].includes(str))) {
            // we expect the relative part to consist of only ../ or ..\\
            const errorMessage =
              red(`\n\nResolved ${cyan(source)} to ${cyan(resolvedImportPath)}.\n\n`) +
              red(
                'This path could not be converted to a browser path. Please file an issue with a reproduction.',
              );
            if (
              typeof code === 'string' &&
              typeof column === 'number' &&
              typeof line === 'number'
            ) {
              throw new PluginSyntaxError(errorMessage, filePath, code, column, line);
            } else {
              throw new PluginError(errorMessage);
            }
          }

          const importPath = toBrowserPath(relativePath.substring(lastDirUpIndex));
          resolvedImportPath = `/__wds-outside-root__/${dirUpStrings.length - 1}/${importPath}`;
        } else {
          const resolveRelativeTo = path.extname(filePath) ? path.dirname(filePath) : filePath;
          const relativeImportFilePath = path.relative(resolveRelativeTo, resolvedImportPath);
          resolvedImportPath = `./${toBrowserPath(relativeImportFilePath)}`;
        }

        return `${resolvedImportPath}${importSuffix}`;
      } catch (error) {
        throw wrapRollupError(filePath, context, error);
      }
    },

    async serve(context) {
      if (!rollupPlugin.load) {
        return;
      }

      if (
        context.path.startsWith(WDS_FILE_PREFIX) &&
        !context.path.startsWith(VIRTUAL_FILE_PREFIX)
      ) {
        return;
      }

      let filePath;
      if (
        context.path.startsWith(VIRTUAL_FILE_PREFIX) &&
        context.URL.searchParams.has(NULL_BYTE_PARAM)
      ) {
        // if this was a special URL constructed in resolveImport to handle null bytes,
        // the file path is stored in the search paramter
        filePath = context.URL.searchParams.get(NULL_BYTE_PARAM) as string;
      } else {
        filePath = path.join(rootDir, context.path);
      }

      try {
        const rollupPluginContext = createRollupPluginContextAdapter(
          rollupPluginContexts.pluginContext,
          wdsPlugin,
          config,
          fileWatcher,
          context,
          pluginMetaPerModule,
        );

        const result = await rollupPlugin.load?.call(rollupPluginContext, filePath);

        if (typeof result === 'string') {
          return { body: result, type: 'js' };
        }
        if (typeof result?.code === 'string') {
          savePluginMeta(filePath, result);
          return { body: result.code, type: 'js' };
        }
      } catch (error) {
        throw wrapRollupError(filePath, context, error);
      }

      return undefined;
    },

    async transform(context) {
      if (!rollupPlugin.transform) {
        return;
      }

      if (context.path.startsWith(WDS_FILE_PREFIX)) {
        return;
      }

      if (context.response.is('js')) {
        const filePath = path.join(rootDir, context.path);
        try {
          const rollupPluginContext = createRollupPluginContextAdapter(
            rollupPluginContexts.transformPluginContext,
            wdsPlugin,
            config,
            fileWatcher,
            context,
            pluginMetaPerModule,
          );

          const result = await rollupPlugin.transform?.call(
            rollupPluginContext as TransformPluginContext,
            context.body as string,
            filePath,
          );

          let transformedCode: string | undefined = undefined;
          if (typeof result === 'string') {
            transformedCode = result;
          }

          if (typeof result === 'object' && typeof result?.code === 'string') {
            savePluginMeta(filePath, result);
            transformedCode = result.code;
          }

          if (transformedCode) {
            transformedFiles.add(context.path);
            return transformedCode;
          }

          return;
        } catch (error) {
          throw wrapRollupError(filePath, context, error);
        }
      }

      if (context.response.is('html')) {
        const documentAst = parseHtml(context.body as string);
        const inlineScripts = queryAll(
          documentAst,
          predicates.AND(
            predicates.hasTagName('script'),
            predicates.NOT(predicates.hasAttr('src')),
          ),
        );

        const filePath = getRequestFilePath(context.url, rootDir);
        let transformed = false;
        try {
          for (const node of inlineScripts) {
            const code = getTextContent(node);

            const rollupPluginContext = createRollupPluginContextAdapter(
              rollupPluginContexts.transformPluginContext,
              wdsPlugin,
              config,
              fileWatcher,
              context,
              pluginMetaPerModule,
            );

            const result = await rollupPlugin.transform?.call(
              rollupPluginContext as TransformPluginContext,
              code,
              filePath,
            );

            let transformedCode: string | undefined = undefined;
            if (typeof result === 'string') {
              transformedCode = result;
            }

            if (typeof result === 'object' && typeof result?.code === 'string') {
              savePluginMeta(filePath, result);
              transformedCode = result.code;
            }

            if (transformedCode) {
              transformedFiles.add(context.path);
              setTextContent(node, transformedCode);
              transformed = true;
            }
          }

          if (transformed) {
            return serializeHtml(documentAst);
          }
        } catch (error) {
          throw wrapRollupError(filePath, context, error);
        }
      }
    },

    fileParsed(context) {
      if (!rollupPlugin.moduleParsed) {
        return;
      }

      const rollupPluginContext = createRollupPluginContextAdapter(
        rollupPluginContexts.transformPluginContext,
        wdsPlugin,
        config,
        fileWatcher,
        context,
        pluginMetaPerModule,
      );
      const filePath = getRequestFilePath(context.url, rootDir);
      const info = rollupPluginContext.getModuleInfo(filePath);
      if (!info) throw new Error(`Missing info for module ${filePath}`);
      rollupPlugin.moduleParsed?.call(rollupPluginContext as TransformPluginContext, info);
    },
  };

  return wdsPlugin;
}