UNPKG

@bazel/rollup

Version:
229 lines (184 loc) 6.95 kB
/** * @license * Copyright 2020 The Bazel Authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const path = require('path'); const rollup = require('rollup'); const loadConfigFile = require('rollup/dist/loadConfigFile'); const crypto = require('crypto') const worker = require('@bazel/worker'); const MNEMONIC = 'Rollup'; const PID = process.pid; // Store the cache forever to re-use on each build let cacheMap = Object.create(null); // Generate a unique cache ID based on the given json data function computeCacheKey(cacheKeyData) { const hash = crypto.createHash('sha256'); const hashContent = JSON.stringify(cacheKeyData); return hash.update(hashContent).digest('hex'); } async function runRollup(cacheKeyData, inputOptions, outputOptions) { const cacheKey = computeCacheKey(cacheKeyData); let cache = cacheMap[cacheKey]; const rollupStartTime = Date.now(); const bundle = await rollup.rollup({...inputOptions, cache}); const rollupEndTime = Date.now(); worker.debug( `${MNEMONIC}[${PID}][${cacheKey}].rollup()`, (rollupEndTime - rollupStartTime) / 1000); cacheMap[cacheKey] = bundle.cache; try { await bundle.write(outputOptions); } catch (e) { worker.log(e); return false; } const bundleEndTime = Date.now(); worker.debug(`${MNEMONIC}[${PID}][${cacheKey}].write()`, (bundleEndTime - rollupEndTime) / 1000); return true; } // Run rollup, will use + re-populate the cache // Essentially the same as the rollup CLI but using bazel APIs // for CLI arguments and FS watching // See: https://github.com/rollup/rollup/blob/v2.23.1/cli/run/index.ts#L11 async function runRollupBundler(args /*, inputs */) { const {inputOptions, outputOptions} = await parseCLIArgs(args); const cacheKeyData = [ inputOptions.input, // Include changes to externals in the cache key because rollup currently // ignores such changes when using the caching API // See https://github.com/rollup/rollup/issues/3874 inputOptions.external ]; return runRollup(cacheKeyData, inputOptions, outputOptions); } // Processing of --environment CLI options into environment vars // https://github.com/rollup/rollup/blob/v1.31.0/cli/run/index.ts#L50-L57 function extractEnvironmentVariables(vars) { vars.split(',').forEach(pair => { const [key, ...value] = pair.split(':'); if (value.length) { process.env[key] = value.join(':'); } else { process.env[key] = String(true); } }); } // Parse a subset of supported CLI arguments required for the rollup_bundle rule API. // Returns input/outputOptions for the rollup.bundle/write() API // input: https://rollupjs.org/guide/en/#inputoptions-object // output: https://rollupjs.org/guide/en/#outputoptions-object async function parseCLIArgs(args) { // Options which the CLI args or config file can override const defaultInputOptions = { onwarn(...warnArgs) { worker.log(...warnArgs); }, }; // Options which can override the config file let inputOptions = {}; let outputOptions = {}; let configFile = null; // Input files to rollup let inputs = []; // Followed by suppported rollup CLI options for (let i = 0; i < args.length; i++) { const arg = args[i]; // Non-option is assumed to be an input file if (!arg.startsWith('--')) { inputs.push(arg); continue; } const option = arg.slice(2); switch (option) { case 'config': configFile = path.resolve(args[++i]); break; case 'silent': inputOptions.onwarn = () => {}; break; case 'format': case 'output.dir': case 'output.file': case 'sourcemap': outputOptions[option.replace('output.', '')] = args[++i]; break; case 'preserveSymlinks': inputOptions[option] = true; break; // Common rollup CLI args, but not required for use case 'environment': extractEnvironmentVariables(args[++i]); break; default: throw new Error(`${MNEMONIC}: invalid or unsupported argument ${arg}`); } } // If outputting a directory then rollup_bundle.bzl passed a series // of name=path files as the input. // TODO: do some not have the =? if (outputOptions.dir) { inputs = inputs.reduce((m, nameInput) => { const [name, input] = nameInput.split('=', 2); m[name] = input; return m; }, {}); } // Additional options passed via config file if (configFile) { const {options, warnings} = await loadConfigFile(configFile); // Flush any config file warnings to stderr warnings.flush(); // Does NOT support (unlike rollup CLI): // * multiple configs for multiple outputs if (options.length !== 1) { throw new Error('Array configs unsupported'); } const config = options[0]; if (config.output) { outputOptions = {...config.output[0], ...outputOptions}; } inputOptions = {...config, ...inputOptions}; // Delete from our copied inputOptions, not the config which // may be external and persisted across runs delete inputOptions.output; } // Provide default inputOptions which can be overwritten inputOptions = {...defaultInputOptions, ...inputOptions}; // The inputs are the rule entry_point[s] inputOptions.input = inputs; return {inputOptions, outputOptions}; } async function main(args) { // Bazel will pass a special argument to the program when it's running us as a worker if (worker.runAsWorker(args)) { worker.log(`Running ${MNEMONIC} as a Bazel worker`); await worker.runWorkerLoop(runRollupBundler); } else { // Running standalone so stdout is available as usual console.log(`Running ${MNEMONIC} as a standalone process`); console.error( `Started a new process to perform this action. Your build might be misconfigured, try --strategy=${MNEMONIC}=worker`); // Parse the options from the bazel-supplied options file. // The first argument to the program is prefixed with '@' // because Bazel does that for param files. Strip it first. const paramFile = process.argv[2].replace(/^@/, ''); const args = require('fs').readFileSync(paramFile, 'utf-8').trim().split('\n'); return (await runRollupBundler(args)) ? 0 : 1; } } if (require.main == module) { (async () => process.exitCode = await main(process.argv.slice(2)))(); }