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

584 lines (486 loc) 22.9 kB
var nodePath = require('path'); var ok = require('assert').ok; var equal = require('assert').equal; const VAR_REQUIRE_PROCESS = 'process=require("process")'; const VAR_REQUIRE_BUFFER = 'Buffer=require("buffer").Buffer'; var inspectCache = require('./inspect-cache'); var Deduper = require('./util/Deduper'); var normalizeMain = require('lasso-modules-client/transport').normalizeMain; var lassoCachingFS = require('lasso-caching-fs'); var lassoPackageRoot = require('lasso-package-root'); var normalizeFSPath = require('./util/normalizeFSPath'); var crypto = require('crypto'); function buildAsyncInfo(path, asyncBlocks, lassoContext) { if (asyncBlocks.length === 0) { return null; } var key = 'require-async|' + normalizeFSPath(path); var asyncInfo = lassoContext.data[key]; if (!asyncInfo) { asyncInfo = lassoContext.data[key] = { asyncMeta: {}, asyncBlocks: asyncBlocks }; asyncBlocks.forEach(function(asyncBlock, i) { if (!asyncBlock.hasInlineDependencies && !asyncBlock.hasFunctionBody) { // only generate a unique package name if the async // function call was provided a `function` as the // last argument or if an array of dependencies were // inlined. return; } var hash = '_' + crypto.createHash('sha1') .update(key) .update(String(i)) .digest('hex') .substring(0, 6); var name = asyncBlock.name = hash; asyncInfo.asyncMeta[name] = asyncBlock.dependencies; }); } return asyncInfo; } function create(config, lasso) { config = config || {}; var globals = config.globals; var getClientPath = config.getClientPath; var readyDependency = lasso.dependencies.createDependency({ type: 'commonjs-ready', inline: 'end', slot: config.lastSlot }, __dirname); var runtimeDependency = lasso.dependencies.createDependency({ type: 'commonjs-runtime' }, __dirname); function handleMetaRemap(metaEntry, deduper) { var from = metaEntry.from; var to = metaEntry.to; var remapKey = deduper.remapKey(from, to); if (!deduper.hasRemap(remapKey)) { var fromPath = getClientPath(from); var toPath; if (to === false) { toPath = false; } else { toPath = getClientPath(to); } deduper.addDependency(remapKey, { type: 'commonjs-remap', from: fromPath, to: toPath, fromFile: from }); } } function handleMetaInstalled(metaEntry, deduper) { var packageName = metaEntry.packageName; var searchPath = metaEntry.searchPath; var fromDir = metaEntry.fromDir; var basename = nodePath.basename(searchPath); if (basename === 'node_modules') { var childName = packageName; var parentPath = lassoPackageRoot.getRootDir(fromDir); var pkg = lassoCachingFS.readPackageSync(nodePath.join(searchPath, packageName, 'package.json')); var childVersion = (pkg && pkg.version) || '0'; let key = deduper.installedKey(parentPath, childName, childVersion); if (!deduper.hasInstalled(key)) { var clientParentPath = getClientPath(parentPath); deduper.addDependency(key, { type: 'commonjs-installed', parentPath: clientParentPath, childName: childName, childVersion: childVersion, parentDir: parentPath }); } } else { let key = deduper.searchPathKey(searchPath); if (!deduper.hasSearchPath(key)) { var clientSearchPath = getClientPath(searchPath); if (!clientSearchPath.endsWith('/')) { // Search paths should always end with a forward slash clientSearchPath += '/'; } // This is a non-standard standard search path entry deduper.addDependency(key, { type: 'commonjs-search-path', path: clientSearchPath }); } } } function handleMetaMain(metaEntry, deduper) { var dir = metaEntry.dir; var main = metaEntry.main; var key = deduper.mainKey(dir, main); if (!deduper.hasMain(key)) { var dirClientPath = getClientPath(metaEntry.dir); var relativePath = normalizeMain(metaEntry.dir, metaEntry.main); deduper.addDependency(key, { type: 'commonjs-main', dir: dirClientPath, main: relativePath, _sourceFile: metaEntry.main }); } } function handleMetaBuiltin(metaEntry, deduper) { var name = metaEntry.name; var target = metaEntry.target; var key = deduper.builtinKey(name, target); if (!deduper.hasBuiltin(key)) { var targetClientPath = getClientPath(metaEntry.target); deduper.addDependency(key, { type: 'commonjs-builtin', name: name, target: targetClientPath, _sourceFile: metaEntry.target }); } } function handleMeta(meta, deduper) { for (var i = 0; i < meta.length; i++) { var metaEntry = meta[i]; switch (metaEntry.type) { case 'remap': handleMetaRemap(metaEntry, deduper); break; case 'installed': handleMetaInstalled(metaEntry, deduper); break; case 'main': handleMetaMain(metaEntry, deduper); break; case 'builtin': handleMetaBuiltin(metaEntry, deduper); break; default: throw new Error('Unsupported meta entry: ' + JSON.stringify(metaEntry)); } } } return { properties: { path: 'string', from: 'string', run: 'boolean', wait: 'boolean', resolved: 'object', virtualModule: 'object', requireHandler: 'object' }, async init (lassoContext) { if (!this.path && !this.virtualModule) { let error = new Error('Invalid "require" dependency. "path" property is required'); console.error(module.id, error.stack, this); throw error; } if (!this.resolved) { var virtualModule = this.virtualModule; if (virtualModule) { let path = virtualModule.path || this.path; ok(path, '"path" is required for a virtual module'); this.path = path; if (!this.from) { this.from = nodePath.dirname(path); } var clientPath = virtualModule.clientPath || getClientPath(path); this.resolved = { path, clientPath }; } else if (this.path) { let from = this.from = this.from || this.getParentManifestDir(); let path = this.path; let fromFile = this.getParentManifestPath(); let fromFileRelPath = fromFile ? nodePath.relative(process.cwd(), fromFile) : '(unknown)'; this.resolved = lassoContext.resolveCached(path, from); if (!this.resolved) { throw new Error('Module not found: ' + path + ' (from "' + from + '" and referenced in "' + fromFileRelPath + '")'); } this.meta = this.resolved.meta; } } if (this.run === true) { if (this.wait == null && config.runImmediately === true) { this.wait = false; } this.wait = this.wait !== false; } ok(this.path); }, toString: function() { return '[require: ' + this.path + ']'; }, calculateKey() { // This is a unique key that prevents the same dependency from being // added to the dependency graph repeatedly var key = 'modules-require:' + this.path + '@' + this.from; if (this.run) { key += '|run'; if (this.wait === false) { key += '|wait'; } } return key; }, getDir: function() { return nodePath.dirname(this.resolved.path); }, getRequireHandler: function(lassoContext) { var resolved = this.resolved; var requireHandler = this.requireHandler; if (!requireHandler) { var virtualModule = this.virtualModule; if (virtualModule) { var virtualPath = resolved.path || virtualModule.path || ''; requireHandler = lassoContext.dependencyRegistry.createRequireHandler(virtualPath, lassoContext, virtualModule); requireHandler.includePathArg = false; } } if (!requireHandler) { requireHandler = lassoContext.dependencyRegistry.getRequireHandler(resolved.path, lassoContext); if (!requireHandler) { return null; } } var transforms = config.transforms; if (transforms) { let defaultCreateReadStream = requireHandler.createReadStream.bind(requireHandler); let transformedRequireHandler = Object.create(requireHandler); transformedRequireHandler.createReadStream = function createdTransformedReadStream() { var inStream = defaultCreateReadStream(); return transforms.apply(resolved.path, inStream, lassoContext); }; return transformedRequireHandler; } else { return requireHandler; } }, async getDependencies (lassoContext) { ok(lassoContext, '"lassoContext" argument expected'); var requireHandler; // the array of dependencies that we will be returning var dependencies = []; var deduper = new Deduper(lassoContext, dependencies); // Include client module system if needed and we haven't included it yet if (config.includeClient !== false) { deduper.addRuntime(runtimeDependency); } if (this.meta) { handleMeta(this.meta, deduper); } if (!lassoContext.isAsyncBundlingPhase()) { // Add a dependency that will trigger all of the deferred // run modules to run once all of the code has been loaded // for the page deduper.addReady(readyDependency); } var resolved = this.resolved; if (resolved.voidRemap) { // This module has been remapped to a void/empty object // because it is a server-side only module. Nothing // else to do. return dependencies; } var run = this.run === true; var wait = this.wait !== false; if (resolved.type) { // This is not really a require dependency since a type was provided dependencies.push({ type: resolved.type, path: resolved.path }); return dependencies; } requireHandler = this.getRequireHandler(lassoContext); if (!requireHandler) { // This is not really a dependency that compiles down to a CommonJS module // so just add it to the dependency graph dependencies.push(resolved.path); return dependencies; } var dirname = nodePath.dirname(resolved.path); return requireHandler.init() .then(() => { if (requireHandler.getDependencies) { // A require extension can provide its own `getDependencies` method to provide // additional dependencies beyond what will automatically be discovered using // static code analysis on the CommonJS code associated with the dependency. return requireHandler.getDependencies().then((additionalDependencies) => { if (additionalDependencies && additionalDependencies.length) { additionalDependencies.forEach((dep) => { dependencies.push(dep); }); } }); } }) .then(() => { // Fixes https://github.com/lasso-js/lasso-require/issues/21 // Static JavaScript objects should not need to be inspected if (requireHandler.object) { // Don't actually inspect the required module if it is an object. // For example, this would be the case with require('./foo.json') return requireHandler.getLastModified() .then((lastModified) => { return { createReadStream: requireHandler.createReadStream.bind(requireHandler), lastModified: lastModified }; }); } return inspectCache.inspectCached( resolved.path, requireHandler, lassoContext, config); }) .then((inspectResult) => { var asyncMeta; var asyncBlocks; if (inspectResult && inspectResult.asyncBlocks && inspectResult.asyncBlocks.length) { var asyncInfo = buildAsyncInfo(resolved.path, inspectResult.asyncBlocks, lassoContext); if (asyncInfo) { asyncBlocks = asyncInfo.asyncBlocks; asyncMeta = asyncInfo.asyncMeta; } } // require was for a source file var additionalVars; ok(inspectResult, 'inspectResult should not be null'); // the requires that were read from inspection (may remain undefined if no inspection result) var requires = inspectResult.requires; if (inspectResult.foundGlobals && (inspectResult.foundGlobals.process || inspectResult.foundGlobals.Buffer)) { additionalVars = []; var addGlobalVar = (path, varCode) => { let resolved = lassoContext.resolve(path, dirname); if (resolved.meta) { handleMeta(resolved.meta, deduper); } var requireKey = deduper.requireKey(path, dirname); if (!deduper.hasRequire(requireKey)) { deduper.addDependency(requireKey, { type: 'require', path: path, from: dirname }); } additionalVars.push(varCode); }; if (inspectResult.foundGlobals.process) { addGlobalVar('process', VAR_REQUIRE_PROCESS); } if (inspectResult.foundGlobals.Buffer) { addGlobalVar('buffer', VAR_REQUIRE_BUFFER); } } ok(inspectResult.createReadStream, 'createReadStream expected after inspectResult'); ok(inspectResult.lastModified, 'lastModified expected after inspectResult'); equal(typeof inspectResult.lastModified, 'number', 'lastModified should be a number'); var globalVars = globals ? globals[this.resolved.path] : null; // Also check if the directory has an browser.json and if so we should include that as well var lassoJsonPath = nodePath.join(dirname, 'browser.json'); if (lassoContext.cachingFs.existsSync(lassoJsonPath)) { dependencies.push({ type: 'package', path: lassoJsonPath }); } else { lassoJsonPath = nodePath.join(dirname, 'optimizer.json'); if (lassoContext.cachingFs.existsSync(lassoJsonPath)) { // TODO Show a deprecation warning here dependencies.push({ type: 'package', path: lassoJsonPath }); } } // Include all additional dependencies (these were the ones found in the source code) if (requires && requires.length) { requires.forEach(function(inspectResultRequire) { var inspectResultResolved = inspectResultRequire.resolved; var meta = inspectResultResolved.meta; if (meta) { handleMeta(meta, deduper); } var path = inspectResultRequire.path; var requireKey = deduper.requireKey(path, dirname); if (!deduper.hasRequire(requireKey)) { deduper.addDependency(requireKey, { type: 'require', path: inspectResultRequire.path, from: dirname, resolved: inspectResultResolved }); } }); } var defKey = deduper.defKey(resolved.clientPath); if (!deduper.hasDef(defKey)) { var defDependency = { type: 'commonjs-def', path: resolved.clientPath, file: resolved.path }; if (requireHandler.getDefaultBundleName) { // A `virtualModule` object provided a // `getDefaultBundleName` that we will use to // create the define dependency. defDependency.getDefaultBundleName = requireHandler.getDefaultBundleName.bind(requireHandler); } if (additionalVars) { defDependency._additionalVars = additionalVars; } if (requireHandler.object) { // If true, then the module will not be wrapped inside a factory function defDependency.object = true; } if (globalVars) { defDependency.globals = globalVars; } // Pass along the createReadStream and the lastModified to the def dependency defDependency.requireCreateReadStream = inspectResult.createReadStream; defDependency.inspected = inspectResult; defDependency.asyncBlocks = asyncBlocks; defDependency.requireLastModified = inspectResult.lastModified; deduper.addDependency(defKey, defDependency); } // Do we also need to add dependency to run the dependency? if (run === true) { var runKey = deduper.runKey(resolved.clientPath, wait); if (!deduper.hasRun(runKey)) { var runDependency = { type: 'commonjs-run', path: resolved.clientPath, wait: wait, file: resolved.path }; if (requireHandler.getDefaultBundleName) { // A `virtualModule` object provided a // `getDefaultBundleName` that we will use to // create the run dependency. runDependency.getDefaultBundleName = requireHandler.getDefaultBundleName.bind(requireHandler); } if (wait === false) { runDependency.wait = false; } deduper.addDependency(runKey, runDependency); } } if (asyncMeta) { return { dependencies: dependencies, async: asyncMeta, dirname: nodePath.dirname(resolved.path), filename: resolved.path }; } else { return dependencies; } }); } }; } exports.create = create;