419 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			419 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
 | 
						|
var _fs = _interopRequireDefault(require("fs"));
 | 
						|
 | 
						|
var _module = _interopRequireDefault(require("module"));
 | 
						|
 | 
						|
var _querystring = _interopRequireDefault(require("querystring"));
 | 
						|
 | 
						|
var _loaderRunner = _interopRequireDefault(require("loader-runner"));
 | 
						|
 | 
						|
var _queue = _interopRequireDefault(require("neo-async/queue"));
 | 
						|
 | 
						|
var _jsonParseBetterErrors = _interopRequireDefault(require("json-parse-better-errors"));
 | 
						|
 | 
						|
var _schemaUtils = require("schema-utils");
 | 
						|
 | 
						|
var _readBuffer = _interopRequireDefault(require("./readBuffer"));
 | 
						|
 | 
						|
var _serializer = require("./serializer");
 | 
						|
 | 
						|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 | 
						|
 | 
						|
/* eslint-disable no-console */
 | 
						|
const writePipe = _fs.default.createWriteStream(null, {
 | 
						|
  fd: 3
 | 
						|
});
 | 
						|
 | 
						|
const readPipe = _fs.default.createReadStream(null, {
 | 
						|
  fd: 4
 | 
						|
});
 | 
						|
 | 
						|
writePipe.on('finish', onTerminateWrite);
 | 
						|
readPipe.on('end', onTerminateRead);
 | 
						|
writePipe.on('close', onTerminateWrite);
 | 
						|
readPipe.on('close', onTerminateRead);
 | 
						|
readPipe.on('error', onError);
 | 
						|
writePipe.on('error', onError);
 | 
						|
const PARALLEL_JOBS = +process.argv[2] || 20;
 | 
						|
let terminated = false;
 | 
						|
let nextQuestionId = 0;
 | 
						|
const callbackMap = Object.create(null);
 | 
						|
 | 
						|
function onError(error) {
 | 
						|
  console.error(error);
 | 
						|
}
 | 
						|
 | 
						|
function onTerminateRead() {
 | 
						|
  terminateRead();
 | 
						|
}
 | 
						|
 | 
						|
function onTerminateWrite() {
 | 
						|
  terminateWrite();
 | 
						|
}
 | 
						|
 | 
						|
function writePipeWrite(...args) {
 | 
						|
  if (!terminated) {
 | 
						|
    writePipe.write(...args);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function writePipeCork() {
 | 
						|
  if (!terminated) {
 | 
						|
    writePipe.cork();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function writePipeUncork() {
 | 
						|
  if (!terminated) {
 | 
						|
    writePipe.uncork();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function terminateRead() {
 | 
						|
  terminated = true;
 | 
						|
  readPipe.removeAllListeners();
 | 
						|
}
 | 
						|
 | 
						|
function terminateWrite() {
 | 
						|
  terminated = true;
 | 
						|
  writePipe.removeAllListeners();
 | 
						|
}
 | 
						|
 | 
						|
function terminate() {
 | 
						|
  terminateRead();
 | 
						|
  terminateWrite();
 | 
						|
}
 | 
						|
 | 
						|
function toErrorObj(err) {
 | 
						|
  return {
 | 
						|
    message: err.message,
 | 
						|
    details: err.details,
 | 
						|
    stack: err.stack,
 | 
						|
    hideStack: err.hideStack
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
function toNativeError(obj) {
 | 
						|
  if (!obj) return null;
 | 
						|
  const err = new Error(obj.message);
 | 
						|
  err.details = obj.details;
 | 
						|
  err.missing = obj.missing;
 | 
						|
  return err;
 | 
						|
}
 | 
						|
 | 
						|
function writeJson(data) {
 | 
						|
  writePipeCork();
 | 
						|
  process.nextTick(() => {
 | 
						|
    writePipeUncork();
 | 
						|
  });
 | 
						|
  const lengthBuffer = Buffer.alloc(4);
 | 
						|
  const messageBuffer = Buffer.from(JSON.stringify(data, _serializer.replacer), 'utf-8');
 | 
						|
  lengthBuffer.writeInt32BE(messageBuffer.length, 0);
 | 
						|
  writePipeWrite(lengthBuffer);
 | 
						|
  writePipeWrite(messageBuffer);
 | 
						|
}
 | 
						|
 | 
						|
const queue = (0, _queue.default)(({
 | 
						|
  id,
 | 
						|
  data
 | 
						|
}, taskCallback) => {
 | 
						|
  try {
 | 
						|
    const resolveWithOptions = (context, request, callback, options) => {
 | 
						|
      callbackMap[nextQuestionId] = callback;
 | 
						|
      writeJson({
 | 
						|
        type: 'resolve',
 | 
						|
        id,
 | 
						|
        questionId: nextQuestionId,
 | 
						|
        context,
 | 
						|
        request,
 | 
						|
        options
 | 
						|
      });
 | 
						|
      nextQuestionId += 1;
 | 
						|
    };
 | 
						|
 | 
						|
    const buildDependencies = [];
 | 
						|
 | 
						|
    _loaderRunner.default.runLoaders({
 | 
						|
      loaders: data.loaders,
 | 
						|
      resource: data.resource,
 | 
						|
      readResource: _fs.default.readFile.bind(_fs.default),
 | 
						|
      context: {
 | 
						|
        version: 2,
 | 
						|
        fs: _fs.default,
 | 
						|
        loadModule: (request, callback) => {
 | 
						|
          callbackMap[nextQuestionId] = (error, result) => callback(error, ...result);
 | 
						|
 | 
						|
          writeJson({
 | 
						|
            type: 'loadModule',
 | 
						|
            id,
 | 
						|
            questionId: nextQuestionId,
 | 
						|
            request
 | 
						|
          });
 | 
						|
          nextQuestionId += 1;
 | 
						|
        },
 | 
						|
        resolve: (context, request, callback) => {
 | 
						|
          resolveWithOptions(context, request, callback);
 | 
						|
        },
 | 
						|
        // eslint-disable-next-line consistent-return
 | 
						|
        getResolve: options => (context, request, callback) => {
 | 
						|
          if (callback) {
 | 
						|
            resolveWithOptions(context, request, callback, options);
 | 
						|
          } else {
 | 
						|
            return new Promise((resolve, reject) => {
 | 
						|
              resolveWithOptions(context, request, (err, result) => {
 | 
						|
                if (err) {
 | 
						|
                  reject(err);
 | 
						|
                } else {
 | 
						|
                  resolve(result);
 | 
						|
                }
 | 
						|
              }, options);
 | 
						|
            });
 | 
						|
          }
 | 
						|
        },
 | 
						|
 | 
						|
        // Not an arrow function because it uses this
 | 
						|
        getOptions(schema) {
 | 
						|
          // loaders, loaderIndex will be defined by runLoaders
 | 
						|
          const loader = this.loaders[this.loaderIndex]; // Verbatim copy from
 | 
						|
          // https://github.com/webpack/webpack/blob/v5.31.2/lib/NormalModule.js#L471-L508
 | 
						|
          // except eslint/prettier differences
 | 
						|
          // -- unfortunate result of getOptions being synchronous functions.
 | 
						|
 | 
						|
          let {
 | 
						|
            options
 | 
						|
          } = loader;
 | 
						|
 | 
						|
          if (typeof options === 'string') {
 | 
						|
            if (options.substr(0, 1) === '{' && options.substr(-1) === '}') {
 | 
						|
              try {
 | 
						|
                options = (0, _jsonParseBetterErrors.default)(options);
 | 
						|
              } catch (e) {
 | 
						|
                throw new Error(`Cannot parse string options: ${e.message}`);
 | 
						|
              }
 | 
						|
            } else {
 | 
						|
              options = _querystring.default.parse(options, '&', '=', {
 | 
						|
                maxKeys: 0
 | 
						|
              });
 | 
						|
            }
 | 
						|
          } // eslint-disable-next-line no-undefined
 | 
						|
 | 
						|
 | 
						|
          if (options === null || options === undefined) {
 | 
						|
            options = {};
 | 
						|
          }
 | 
						|
 | 
						|
          if (schema) {
 | 
						|
            let name = 'Loader';
 | 
						|
            let baseDataPath = 'options';
 | 
						|
            let match; // eslint-disable-next-line no-cond-assign
 | 
						|
 | 
						|
            if (schema.title && (match = /^(.+) (.+)$/.exec(schema.title))) {
 | 
						|
              [, name, baseDataPath] = match;
 | 
						|
            }
 | 
						|
 | 
						|
            (0, _schemaUtils.validate)(schema, options, {
 | 
						|
              name,
 | 
						|
              baseDataPath
 | 
						|
            });
 | 
						|
          }
 | 
						|
 | 
						|
          return options;
 | 
						|
        },
 | 
						|
 | 
						|
        emitWarning: warning => {
 | 
						|
          writeJson({
 | 
						|
            type: 'emitWarning',
 | 
						|
            id,
 | 
						|
            data: toErrorObj(warning)
 | 
						|
          });
 | 
						|
        },
 | 
						|
        emitError: error => {
 | 
						|
          writeJson({
 | 
						|
            type: 'emitError',
 | 
						|
            id,
 | 
						|
            data: toErrorObj(error)
 | 
						|
          });
 | 
						|
        },
 | 
						|
        exec: (code, filename) => {
 | 
						|
          const module = new _module.default(filename, void 0);
 | 
						|
          module.paths = _module.default._nodeModulePaths((void 0).context); // eslint-disable-line no-underscore-dangle
 | 
						|
 | 
						|
          module.filename = filename;
 | 
						|
 | 
						|
          module._compile(code, filename); // eslint-disable-line no-underscore-dangle
 | 
						|
 | 
						|
 | 
						|
          return module.exports;
 | 
						|
        },
 | 
						|
        addBuildDependency: filename => {
 | 
						|
          buildDependencies.push(filename);
 | 
						|
        },
 | 
						|
        options: {
 | 
						|
          context: data.optionsContext
 | 
						|
        },
 | 
						|
        webpack: true,
 | 
						|
        'thread-loader': true,
 | 
						|
        sourceMap: data.sourceMap,
 | 
						|
        target: data.target,
 | 
						|
        minimize: data.minimize,
 | 
						|
        resourceQuery: data.resourceQuery,
 | 
						|
        rootContext: data.rootContext
 | 
						|
      }
 | 
						|
    }, (err, lrResult) => {
 | 
						|
      const {
 | 
						|
        result,
 | 
						|
        cacheable,
 | 
						|
        fileDependencies,
 | 
						|
        contextDependencies,
 | 
						|
        missingDependencies
 | 
						|
      } = lrResult;
 | 
						|
      const buffersToSend = [];
 | 
						|
      const convertedResult = Array.isArray(result) && result.map(item => {
 | 
						|
        const isBuffer = Buffer.isBuffer(item);
 | 
						|
 | 
						|
        if (isBuffer) {
 | 
						|
          buffersToSend.push(item);
 | 
						|
          return {
 | 
						|
            buffer: true
 | 
						|
          };
 | 
						|
        }
 | 
						|
 | 
						|
        if (typeof item === 'string') {
 | 
						|
          const stringBuffer = Buffer.from(item, 'utf-8');
 | 
						|
          buffersToSend.push(stringBuffer);
 | 
						|
          return {
 | 
						|
            buffer: true,
 | 
						|
            string: true
 | 
						|
          };
 | 
						|
        }
 | 
						|
 | 
						|
        return {
 | 
						|
          data: item
 | 
						|
        };
 | 
						|
      });
 | 
						|
      writeJson({
 | 
						|
        type: 'job',
 | 
						|
        id,
 | 
						|
        error: err && toErrorObj(err),
 | 
						|
        result: {
 | 
						|
          result: convertedResult,
 | 
						|
          cacheable,
 | 
						|
          fileDependencies,
 | 
						|
          contextDependencies,
 | 
						|
          missingDependencies,
 | 
						|
          buildDependencies
 | 
						|
        },
 | 
						|
        data: buffersToSend.map(buffer => buffer.length)
 | 
						|
      });
 | 
						|
      buffersToSend.forEach(buffer => {
 | 
						|
        writePipeWrite(buffer);
 | 
						|
      });
 | 
						|
      setImmediate(taskCallback);
 | 
						|
    });
 | 
						|
  } catch (e) {
 | 
						|
    writeJson({
 | 
						|
      type: 'job',
 | 
						|
      id,
 | 
						|
      error: toErrorObj(e)
 | 
						|
    });
 | 
						|
    taskCallback();
 | 
						|
  }
 | 
						|
}, PARALLEL_JOBS);
 | 
						|
 | 
						|
function dispose() {
 | 
						|
  terminate();
 | 
						|
  queue.kill();
 | 
						|
  process.exit(0);
 | 
						|
}
 | 
						|
 | 
						|
function onMessage(message) {
 | 
						|
  try {
 | 
						|
    const {
 | 
						|
      type,
 | 
						|
      id
 | 
						|
    } = message;
 | 
						|
 | 
						|
    switch (type) {
 | 
						|
      case 'job':
 | 
						|
        {
 | 
						|
          queue.push(message);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
      case 'result':
 | 
						|
        {
 | 
						|
          const {
 | 
						|
            error,
 | 
						|
            result
 | 
						|
          } = message;
 | 
						|
          const callback = callbackMap[id];
 | 
						|
 | 
						|
          if (callback) {
 | 
						|
            const nativeError = toNativeError(error);
 | 
						|
            callback(nativeError, result);
 | 
						|
          } else {
 | 
						|
            console.error(`Worker got unexpected result id ${id}`);
 | 
						|
          }
 | 
						|
 | 
						|
          delete callbackMap[id];
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
      case 'warmup':
 | 
						|
        {
 | 
						|
          const {
 | 
						|
            requires
 | 
						|
          } = message; // load modules into process
 | 
						|
 | 
						|
          requires.forEach(r => require(r)); // eslint-disable-line import/no-dynamic-require, global-require
 | 
						|
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
      default:
 | 
						|
        {
 | 
						|
          console.error(`Worker got unexpected job type ${type}`);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
  } catch (e) {
 | 
						|
    console.error(`Error in worker ${e}`);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function readNextMessage() {
 | 
						|
  (0, _readBuffer.default)(readPipe, 4, (lengthReadError, lengthBuffer) => {
 | 
						|
    if (lengthReadError) {
 | 
						|
      console.error(`Failed to communicate with main process (read length) ${lengthReadError}`);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    const length = lengthBuffer.length && lengthBuffer.readInt32BE(0);
 | 
						|
 | 
						|
    if (length === 0) {
 | 
						|
      // worker should dispose and exit
 | 
						|
      dispose();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    (0, _readBuffer.default)(readPipe, length, (messageError, messageBuffer) => {
 | 
						|
      if (terminated) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (messageError) {
 | 
						|
        console.error(`Failed to communicate with main process (read message) ${messageError}`);
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      const messageString = messageBuffer.toString('utf-8');
 | 
						|
      const message = JSON.parse(messageString, _serializer.reviver);
 | 
						|
      onMessage(message);
 | 
						|
      setImmediate(() => readNextMessage());
 | 
						|
    });
 | 
						|
  });
 | 
						|
} // start reading messages from main process
 | 
						|
 | 
						|
 | 
						|
readNextMessage(); |