UNPKG

lasso

Version:

Lasso.js is a build tool and runtime library for building and bundling all of the resources needed by a web application

539 lines (442 loc) 19.8 kB
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } var nodePath = require('path'); var extend = require('raptor-util').extend; var inherit = require('raptor-util').inherit; var Dependency = require('./Dependency'); var CONTENT_TYPE_CSS = require('../content-types').CSS; var CONTENT_TYPE_JS = require('../content-types').JS; var ok = require('assert').ok; var typePathRegExp = /^([A-Za-z0-9_\-]{2,})\s*:\s*(.+)$/; // Hack: {2,} is used because Windows file system paths start with "c:\" var readStream = require('../util').readStream; var RequireHandler = require('./RequireHandler'); var equal = require('assert').equal; var globNormalizer = require('./glob').normalizer; var dependencyResource = require('./dependency-resource'); var logger = require('raptor-logging').logger(module); var slice = Array.prototype.slice; function createDefaultNormalizer(registry) { function parsePath(path) { var typePathMatches = typePathRegExp.exec(path); if (typePathMatches) { return { type: typePathMatches[1], path: typePathMatches[2] }; } else { var type = registry.typeForPath(path); if (!type) { type = 'package'; } return { type: type, path: path }; } } return function (dependency) { if (typeof dependency === 'string') { dependency = parsePath(dependency); } else if (!Array.isArray(dependency)) { dependency = Object.assign({}, dependency); // the dependency doesn't have a type so try to infer it from the path if (!dependency.type) { if (dependency.package) { dependency.type = 'package'; dependency.path = dependency.package; delete dependency.package; } else if (dependency.path) { var parsed = parsePath(dependency.path); dependency.type = parsed.type; dependency.path = parsed.path; } else if (dependency.intersection) { dependency.type = 'intersection'; dependency.dependencies = dependency.intersection; delete dependency.intersection; } else if (dependency.dependencies) { dependency.type = 'dependencies'; } } } return dependency; }; } function DependencyRegistry() { this.registeredTypes = {}; this.extensions = {}; this.requireExtensions = {}; this._normalizers = []; this._finalNormalizers = []; this.addNormalizer(createDefaultNormalizer(this)); this.registerDefaults(); this.requireExtensions = {}; this.requireExtensionNames = undefined; } DependencyRegistry.prototype = { __DependencyRegistry: true, registerDefaults: function () { this.registerStyleSheetType('css', require('./dependency-resource')); this.registerJavaScriptType('js', require('./dependency-resource')); this.registerJavaScriptType('comment', require('./dependency-comment')); this.registerPackageType('package', require('./dependency-package')); this.registerPackageType('intersection', require('./dependency-intersection')); this.registerPackageType('dependencies', require('./dependency-dependencies')); this.registerExtension('browser.json', 'package'); this.registerExtension('optimizer.json', 'package'); }, typeForPath: function (path) { // Find the type from the longest matching file extension. // For example if we are trying to infer the type of "jquery-1.8.3.js" then we will try: // a) "8.3.js" // b) "3.js" // c) "js" path = nodePath.basename(path); var type = this.extensions[path]; if (type) { // This is to handle the case where the extension // is the actual filename. For example: "browser.json" return type; } var dotPos = path.indexOf('.'); if (dotPos === -1) { return null; } do { type = path.substring(dotPos + 1); if (this.extensions.hasOwnProperty(type)) { return this.extensions[type]; } // move to the next dot position dotPos = path.indexOf('.', dotPos + 1); } while (dotPos !== -1); var lastDot = path.lastIndexOf('.'); return path.substring(lastDot + 1); }, addNormalizer: function (normalizerFunc) { ok(typeof normalizerFunc === 'function', 'function expected'); this._normalizers.unshift(normalizerFunc); // Always run the glob normalizer first this._finalNormalizers = [globNormalizer].concat(this._normalizers); }, registerType: function (type, mixins) { equal(typeof type, 'string', '"type" should be a string'); equal(typeof mixins, 'object', '"mixins" should be a object'); var isPackageDependency = mixins._packageDependency === true; var hasReadFunc = mixins.read; if (isPackageDependency && hasReadFunc) { throw new Error('Manifest dependency of type "' + type + '" is not expected to have a read() method.'); } if (mixins.init) { mixins.doInit = mixins.init; delete mixins.init; } mixins = extend({}, mixins); var properties = mixins.properties || {}; var childProperties = Object.create(Dependency.prototype.properties); extend(childProperties, properties); mixins.properties = childProperties; var calculateKey = mixins.calculateKey; if (calculateKey) { mixins.doCalculateKey = calculateKey; delete mixins.calculateKey; } var getLastModified = mixins.getLastModified || mixins.lastModified; if (getLastModified) { mixins.doGetLastModified = getLastModified; delete mixins.getLastModified; delete mixins.lastModified; } if (!isPackageDependency && mixins.read) { // Wrap the read method to ensure that it always returns a stream // instead of possibly using a callback var oldRead = mixins.read; delete mixins.read; mixins.doRead = function (lassoContext) { return readStream(() => { return oldRead.call(this, lassoContext); }); }; } var _this = this; function Ctor(dependencyConfig, dirname, filename) { this.__dependencyRegistry = _this; Dependency.call(this, dependencyConfig, dirname, filename); } inherit(Ctor, Dependency); extend(Ctor.prototype, mixins); this.registeredTypes[type] = Ctor; }, registerRequireExtension: function (ext, options) { this.requireExtensionNames = undefined; equal(typeof ext, 'string', '"ext" should be a string'); if (ext.charAt(0) === '.') { ext = ext.substring(1); } if (typeof options === 'function') { options = { read: options }; } ok(options.read || options.createReadStream, '"read" or "createReadStream" is required'); this.requireExtensions[ext] = options; }, /** * In addition to registering a require extension using the "registerRequireExtension", * this method also registers a new dependency type with possibly additional properties. * * For example, if you just use "registerRequireExtension('foo', ...)", then only the following is supported: * - var foo = require('./hello.foo'); * - "require: ./hello.foo" * * However, if you use registerRequireType with custom proeprties then all of the following are supported: * - var foo = require('./hello.foo'); * - "require: ./hello.foo" * - "hello.foo", * - { "type": "foo", "path": "hello.foo", "hello": "world" } * * For an example, please see: * https://github.com/lasso-js/lasso-marko/blob/master/lasso-marko-plugin.js * * * dependency that can be required. Howev * @param {String} type The extension/type to register * @param {Object} mixins [description] */ registerRequireType: function (type, options) { equal(typeof type, 'string', '"type" should be a string'); equal(typeof options, 'object', '"options" should be a object'); var userRead = options.read; var userCreateReadStream = options.createReadStream; var userGetLastModified = options.getLastModified; var extensionOptions = extend({}, options); if (userRead) { extensionOptions.read = function (path, lassoContext, callback) { // Chop off the first path argument return userRead.apply(this, slice.call(arguments, 1)); }; } if (userCreateReadStream) { extensionOptions.userCreateReadStream = function (path, lassoContext) { // Chop off the first path argument return userCreateReadStream.apply(this, slice.call(arguments, 1)); }; } if (userGetLastModified) { extensionOptions.getLastModified = (() => { var _ref = _asyncToGenerator(function* (path, lassoContext) { // Chop off the first path argument return userGetLastModified.apply(this, slice.call(arguments, 1)); }); return function (_x, _x2) { return _ref.apply(this, arguments); }; })(); } this.registerRequireExtension(type, extensionOptions); this.registerPackageType(type, { properties: { path: 'string' }, init(lassoContext) { var _this2 = this; return _asyncToGenerator(function* () { _this2.path = _this2.resolvePath(_this2.path); })(); }, getSourceFile() { return this.path; }, getDependencies(lassoContext) { var _this3 = this; return _asyncToGenerator(function* () { var path = _this3.path; return [{ type: 'require', path: path }]; })(); } }); }, getRequireExtensionNames() { if (this.requireExtensionNames === undefined) { var extensionsLookup = {}; let nodeRequireExtensions = require.extensions; // eslint-disable-line node/no-deprecated-api for (let ext in nodeRequireExtensions) { if (ext !== '.node') { extensionsLookup[ext] = true; } } for (let ext in this.requireExtensions) { if (ext.charAt(0) !== '.') { ext = '.' + ext; } extensionsLookup[ext] = true; } this.requireExtensionNames = Object.keys(extensionsLookup); } return this.requireExtensionNames; }, createRequireHandler(path, lassoContext, userOptions) { ok(path, '"path" is required'); ok(lassoContext, '"lassoContext" is required'); ok(userOptions, '"userOptions" is required'); ok(typeof path === 'string', '"path" should be a string'); ok(typeof lassoContext === 'object', '"lassoContext" should be an object'); return new RequireHandler(userOptions, lassoContext, path); }, getRequireHandler: function (path, lassoContext) { ok(path, '"path" is required'); ok(lassoContext, '"lassoContext" is required'); ok(typeof path === 'string', '"path" should be a string'); ok(typeof lassoContext === 'object', '"lassoContext" should be an object'); var basename = nodePath.basename(path); var lastDot = basename.lastIndexOf('.'); var ext; if (lastDot === -1) { return null; } ext = basename.substring(lastDot + 1); var userOptions = this.requireExtensions[ext]; if (!userOptions) { return null; } return new RequireHandler(userOptions, lassoContext, path); }, registerJavaScriptType: function (type, mixins) { equal(typeof type, 'string', '"type" should be a string'); equal(typeof mixins, 'object', '"mixins" should be a object'); mixins.contentType = CONTENT_TYPE_JS; this.registerType(type, mixins); }, registerStyleSheetType: function (type, mixins) { equal(typeof type, 'string', '"type" should be a string'); equal(typeof mixins, 'object', '"mixins" should be a object'); mixins.contentType = CONTENT_TYPE_CSS; this.registerType(type, mixins); }, registerPackageType: function (type, mixins) { equal(typeof type, 'string', '"type" should be a string'); equal(typeof mixins, 'object', '"mixins" should be a object'); mixins._packageDependency = true; this.registerType(type, mixins); }, registerExtension: function (extension, type) { equal(typeof extension, 'string', '"extension" should be a string'); equal(typeof type, 'string', '"type" should be a string'); this.extensions[extension] = type; }, getType: function (type) { return this.registeredTypes[type]; }, createDependency: function (config, dirname, filename) { ok(config, '"config" is required'); ok(dirname, '"dirname" is required'); equal(typeof config, 'object', 'Invalid dependency: ' + require('util').inspect(config)); var type = config.type; var Ctor = this.registeredTypes[type]; if (!Ctor) { throw new Error('Dependency of type "' + type + '" is not supported. (dependency=' + require('util').inspect(config) + ', package="' + filename + '"). Registered types:\n' + Object.keys(this.registeredTypes).join(', ')); } return new Ctor(config, dirname, filename); }, normalizeDependencies(dependencies, dirname, filename) { var _this4 = this; return _asyncToGenerator(function* () { logger.debug('normalizeDependencies() BEGIN: ', dependencies, 'count:', dependencies.length); if (dependencies.length === 0) { return dependencies; } var i = 0; var j = 0; dependencies = dependencies.concat([]); var normalizers = _this4._finalNormalizers; var normalizerCount = normalizers.length; var context = { dirname: dirname, filename: filename }; function handleNormalizedDependency(dependencies, i, normalizedDependency) { if (normalizedDependency) { if (Array.isArray(normalizedDependency)) { // Remove one dependencies.splice.apply(dependencies, [i, 1].concat(normalizedDependency)); j = 0; // Continue at the same dependency index, but restart normalizing at the beginning return null; } else { dependencies[i] = normalizedDependency; } } j++; return normalizedDependency; }; const handleDependencyNormalization = (() => { var _ref2 = _asyncToGenerator(function* () { while (i < dependencies.length) { let dependency = dependencies[i]; if (!dependency.__Dependency) { while (j < normalizerCount) { let normalizeFunc = normalizers[j]; let normalizedDependency = yield normalizeFunc(dependency, context); const handledNormalizedDep = handleNormalizedDependency(dependencies, i, normalizedDependency); dependency = handledNormalizedDep || dependency; // Stop looping and we will pick up where we left off when // the async normalizer finishes return handleDependencyNormalization(); } // Restart with the first normalizer for the next dependency j = 0; // Convert the dependency object to an actual Dependency instance dependency = _this4.createDependency(dependency, dirname, filename); } dependencies[i] = dependency; i++; } logger.debug('normalizeDependencies() DONE!'); return dependencies; }); return function handleDependencyNormalization() { return _ref2.apply(this, arguments); }; })(); return handleDependencyNormalization(); })(); }, /** * This method is used to create a new JavaScript or CSS * type that allows the code to be transformed using a custom * transform function. This was introduced because we wanted to * be able to easily use the babel transpiler on individual * JS dependencies without registering a global transform. */ createResourceTransformType(transformFunc) { var transformType = extend({}, dependencyResource); extend(transformType, { isExternalResource: function () { return false; }, read(context) { var _this5 = this; return _asyncToGenerator(function* () { var readResult = yield dependencyResource.read.call(_this5, {}); return new Promise(function (resolve, reject) { function callback(err, res) { return err ? reject(err) : resolve(res); } if (typeof readResult === 'string') { return transformFunc(readResult, callback); } else if (readResult) { var code = ''; readResult.on('data', function (data) { code += data; }).on('end', function () { transformFunc(code, callback); }); } }); })(); } }); return transformType; } }; module.exports = DependencyRegistry;