Esempio n. 1
0
 sourceMap.sources = sourceMap.sources.map(function (sourceUrl, i) {
     // Work around a weird bug in node-sass' source map generation.
     // It uses options.filename as the url for imported sources.
     if (i > 0) {
         sourceUrl = data.imports[i - 1];
     }
     if (/^file:\/\//.test(sourceUrl)) {
         return sourceUrl;
     } else {
         return urlTools.fsFilePathToFileUrl(sourceUrl);
     }
 });
Esempio n. 2
0
  return async function bundleWebpack(assetGraph) {
    let webpack;
    try {
      webpack = require('webpack');
    } catch (e) {
      if (configPath) {
        throw new Error(
          `Webpack config path given (${configPath}), but webpack itself could not be found. Please install it in your project: ${
            e.stack
          }`
        );
      }
    }

    const triedConfigPaths = [];

    async function loadWebpackConfig() {
      // First try to look for the config in the assetGraph root:
      configPath = configPath
        ? pathModule.resolve(process.cwd(), configPath)
        : pathModule.resolve(
            urlTools.fileUrlToFsPath(assetGraph.root),
            'webpack.config.js'
          );
      triedConfigPaths.push(configPath);
      try {
        return require.main.require(configPath);
      } catch (err) {
        if (err.code !== 'MODULE_NOT_FOUND' || configPath) {
          throw err;
        }
        // webpack.config.js was not found in the assetgraph's root.
        // Look for it in the directory that contains the nearest package.json:
        const readPkgUpResult = await readPkgUp();
        if (readPkgUpResult.path) {
          const alternativeConfigPath = pathModule.resolve(
            pathModule.dirname(readPkgUpResult.path),
            'webpack.config.js'
          );
          if (alternativeConfigPath !== configPath) {
            configPath = alternativeConfigPath;
            triedConfigPaths.push(configPath);
            return require.main.require(configPath);
          }
        }
        throw new Error('Could not load webpack config');
      }
    }

    let config;
    try {
      config = await loadWebpackConfig();
    } catch (err) {
      if (webpack) {
        assetGraph.info(
          new Error(
            `Webpack is installed, but could not load the webpack config (tried ${triedConfigPaths.join(
              ' '
            )}): ${err.message}`
          )
        );
      }
      return;
    }
    config.context =
      config.context || urlTools.fileUrlToFsPath(assetGraph.root);
    config.output = config.output || {};
    if (typeof config.output.path === 'string') {
      config.output.path = pathModule.resolve(
        config.context,
        config.output.path
      );
    }
    config.output.devtoolModuleFilenameTemplate =
      config.output.devtoolModuleFilenameTemplate ||
      'file://[absolute-resource-path]';

    let basePath = config.output.path;
    if (config.output.publicPath) {
      basePath = pathModule.resolve(
        config.context,
        config.output.publicPath.replace(/^\//, '')
      );
    }

    const plugins = Array.isArray(config.plugins) ? config.plugins : [];

    for (const plugin of plugins) {
      if (
        plugin.constructor.name === 'UglifyJsPlugin' &&
        (plugin.options.mangle || plugin.options.compress)
      ) {
        plugin.options.mangle = plugin.options.compress = false;
        assetGraph.info(
          new Error(
            'UglifyJsPlugin detected, turning off mangling and compression so outgoing relations can be detected. This is fine for assetgraph-builder, which will run UglifyJs later anyway'
          )
        );
      }
    }

    const hasHtmlWebpackPlugin = plugins.some(
      plugin => plugin.constructor.name === 'HtmlWebpackPlugin'
    );

    if (!hasHtmlWebpackPlugin) {
      // HtmlWebpackPlugin is not in the picture, so we can assume that the
      // relevant entry points are referenced by assets that we have
      // in the graph. Only build the actually referenced entry points:
      let seenReferencedEntryPoints = false;
      const potentialBundlePaths = assetGraph
        .findRelations({
          to: {
            url: { $regex: /\.js(?:\?|$)/ }
          }
        })
        .map(relation => {
          return urlTools.fileUrlToFsPath(
            assetGraph.resolveUrl(relation.baseUrl, relation.to.url)
          );
        });

      if (!config.entry || potentialBundlePaths.length === 0) {
        assetGraph.info(
          new Error('No potential webpack bundles found, skipping')
        );
        return;
      }

      /* eslint-disable no-inner-declarations */
      function createAbsolutePath(name) {
        return pathModule.resolve(
          basePath,
          name
            ? config.output.filename.replace(/\[name]/g, name)
            : config.output.filename
        );
      }

      if (
        config.entry &&
        typeof config.entry === 'object' &&
        !Array.isArray(config.entry)
      ) {
        config.entry = Object.keys(config.entry).reduce(
          (entryPointsToBeBuilt, entryPointName) => {
            if (
              potentialBundlePaths.includes(createAbsolutePath(entryPointName))
            ) {
              entryPointsToBeBuilt[entryPointName] =
                config.entry[entryPointName];
            }
            return entryPointsToBeBuilt;
          },
          {}
        );

        seenReferencedEntryPoints = Object.keys(config.entry).length > 0;
      } else {
        // array or string
        if (potentialBundlePaths.includes(createAbsolutePath())) {
          seenReferencedEntryPoints = true;
        }
      }

      if (!seenReferencedEntryPoints) {
        assetGraph.info(
          new Error('No matching webpack bundles found, skipping')
        );
        return;
      }
    }

    // Webpack 4: Disable name mangling so that we can detect relations from the bundle
    // (same reason as why we disable UglifyJsPlugin)
    config.optimization = config.optimization || {};
    config.optimization.minimize = false;

    const compiler = webpack(config);
    const outputFileSystem = new webpack.MemoryOutputFileSystem();
    compiler.outputFileSystem = outputFileSystem;
    const stats = await Promise.fromNode(cb => compiler.run(cb));
    if (stats.compilation && stats.compilation.errors) {
      for (const error of stats.compilation.errors) {
        assetGraph.warn(error);
      }
    }
    const statsObj = stats.toJson({ assets: true });
    let existingAssetsWereReplaced;
    const assetInfos = [];
    const urlByAssetName = {};
    for (const asset of statsObj.assets) {
      const url = urlTools.fsFilePathToFileUrl(
        pathModule.resolve(basePath, asset.name)
      );
      const outputPath = urlTools.fsFilePathToFileUrl(
        pathModule.resolve(config.output.path, asset.name)
      );
      assetInfos.push({
        url,
        output: outputPath,
        name: asset.name
      });
      urlByAssetName[asset.name] = url;
      const existingAsset = assetGraph.findAssets({ url })[0];
      if (existingAsset) {
        assetGraph.info(
          new Error(`Replacing previously built artifact: ${url}`)
        );
        existingAssetsWereReplaced = true;
      }
    }
    assetGraph.info(stats.toString({ colors: true }));
    if (existingAssetsWereReplaced) {
      assetGraph.info(
        new Error(
          `Please remove ${
            config.output.path
          } before building with assetgraph to speed up the process`
        )
      );
    }

    await assetGraph.loadAssets(
      assetInfos.map(({ url, output }) => ({
        url,
        isInitial: /\.html$/.test(url),
        rawSrc: outputFileSystem.readFileSync(urlTools.fileUrlToFsPath(output))
      }))
    );

    // Pick up CSS assets and attach them to each entry point
    for (const chunkName of Object.keys(statsObj.assetsByChunkName)) {
      let assets = statsObj.assetsByChunkName[chunkName];
      if (!Array.isArray(assets)) {
        assets = [assets];
      }
      const javaScriptAssets = assets.filter(asset => /\.js$/.test(asset));
      const cssAssets = assets.filter(asset => /\.css$/.test(asset));
      if (javaScriptAssets.length === 1 && cssAssets.length > 0) {
        const entryPointRelations = assetGraph.findRelations(relation => {
          if (
            relation.type !== 'HtmlScript' ||
            !relation.to ||
            !relation.to.url
          ) {
            return;
          }
          const absoluteUrl = assetGraph.resolveUrl(
            relation.from.nonInlineAncestor.url,
            relation.to.url
          );
          return absoluteUrl === urlByAssetName[javaScriptAssets[0]];
        });
        for (const entryPointRelation of entryPointRelations) {
          for (const cssAsset of cssAssets) {
            entryPointRelation.from.addRelation(
              {
                type: 'HtmlStyle',
                hrefType: 'rootRelative',
                to: assetGraph.findAssets({ url: urlByAssetName[cssAsset] })[0]
              },
              'last'
            );
          }
        }
      }
    }

    // Fix up sources: [...] urls in source maps:
    for (const relation of assetGraph.findRelations({
      type: 'SourceMapSource',
      from: {
        url: {
          $in: assetInfos.map(url => url.url)
        }
      }
    })) {
      if (relation.href.startsWith('file://webpack/')) {
        // Some fake webpack url, eg. "webpack/bootstrap 5c89cbd3893270d2912a"
        // that got file:// prepended due to devtoolFallbackModuleFilenameTemplate
        // Strip the file:// prefix and make it relative so that it sort of
        // looks like the webpack output:
        relation.to.url = relation.to.url.replace('file', 'webpack');
        relation.hrefType = 'relative';
      } else {
        relation.hrefType = 'rootRelative';
      }
    }

    for (const asset of assetGraph.findAssets({
      type: 'JavaScript',
      url: { $in: assetInfos.map(assetInfo => assetInfo.url) }
    })) {
      let replacementsMade = false;
      let requireEnsureAstTemplate;
      let jsonpScriptSrcAst;
      estraverse.traverse(asset.parseTree, {
        enter(node) {
          // Webpack 4 moves this to a separate function:
          // function jsonpScriptSrc(chunkId) {
          //   return __webpack_require__.p + "" + chunkId + ".bundle.js"
          // }

          if (
            node.type === 'FunctionDeclaration' &&
            node.id.name === 'jsonpScriptSrc' &&
            node.body.body.length === 1 &&
            node.body.body[0].type === 'ReturnStatement'
          ) {
            jsonpScriptSrcAst = node.body.body[0].argument;
          }

          // Detect the __webpack_require__.e = function requireEnsure(chunkId, callback) { ... declaration at the top of a bundle
          if (
            node.type === 'AssignmentExpression' &&
            node.operator === '=' &&
            node.left.type === 'MemberExpression' &&
            node.left.object.type === 'Identifier' &&
            node.left.object.name === '__webpack_require__' &&
            node.left.property.type === 'Identifier' &&
            node.left.property.name === 'e' &&
            node.right.type === 'FunctionExpression' &&
            node.right.id &&
            node.right.id.type === 'Identifier' &&
            node.right.id.name === 'requireEnsure'
          ) {
            node.right.params.push({
              type: 'Identifier',
              name: '_assetgraph_url'
            });
            // Replace script.src = ... in the function body:
            estraverse.traverse(node.right.body, {
              enter(node) {
                if (
                  node.type === 'ExpressionStatement' &&
                  node.expression.type === 'AssignmentExpression' &&
                  node.expression.left.type === 'MemberExpression' &&
                  node.expression.left.object.type === 'Identifier' &&
                  node.expression.left.object.name === 'script' &&
                  node.expression.left.property.type === 'Identifier' &&
                  node.expression.left.property.name === 'src'
                ) {
                  const templateAst = node.expression.right;
                  // Quick and dirty evaluator
                  requireEnsureAstTemplate = chunkId => {
                    let result = '';
                    (function visit(node) {
                      if (
                        node.type === 'MemberExpression' &&
                        node.object.type === 'Identifier' &&
                        node.object.name === '__webpack_require__' &&
                        node.property.type === 'Identifier' &&
                        node.property.name === 'p'
                      ) {
                        // FIXME: Fall back to config.output.path instead of hardcoding /dist/:
                        result += config.output.publicPath || '/dist/';
                      } else if (
                        node.type === 'BinaryExpression' &&
                        node.operator === '+'
                      ) {
                        visit(node.left);
                        visit(node.right);
                      } else if (
                        node.type === 'LogicalExpression' &&
                        node.operator === '||'
                      ) {
                        const resultBeforeLeft = result;
                        visit(node.left);
                        if (result === resultBeforeLeft) {
                          visit(node.right);
                        }
                      } else if (
                        node.type === 'MemberExpression' &&
                        node.computed &&
                        node.property.type === 'Identifier' &&
                        node.property.name === 'chunkId' &&
                        node.object.type === 'ObjectExpression'
                      ) {
                        for (const propertyNode of node.object.properties) {
                          if (
                            propertyNode.key.type === 'Literal' &&
                            String(propertyNode.key.value) === String(chunkId)
                          ) {
                            visit(propertyNode.value);
                          }
                        }
                      } else if (node.type === 'Literal') {
                        result += String(node.value);
                      } else if (
                        node.type === 'Identifier' &&
                        node.name === 'chunkId'
                      ) {
                        result += chunkId;
                      } else if (
                        node.type === 'CallExpression' &&
                        node.callee.type === 'Identifier' &&
                        node.callee.name === 'jsonpScriptSrc'
                      ) {
                        visit(jsonpScriptSrcAst);
                      } else {
                        throw new Error(
                          `unsupported ${
                            node.type
                          }: ${require('escodegen').generate(node)}`
                        );
                      }
                    })(templateAst);
                    return result;
                  };
                  node.expression.right = {
                    type: 'BinaryExpression',
                    operator: '||',
                    left: {
                      type: 'Identifier',
                      name: '_assetgraph_url'
                    },
                    right: node.expression.right
                  };
                }
              }
            });
            replacementsMade = true;
          } else if (
            node.type === 'VariableDeclaration' &&
            node.declarations.length === 1 &&
            node.declarations[0].type === 'VariableDeclarator' &&
            node.declarations[0].id.type === 'Identifier' &&
            node.declarations[0].id.name === 'map' &&
            node.declarations[0].init &&
            node.declarations[0].init.type === 'ObjectExpression' &&
            node.declarations[0].init.properties.length > 0 &&
            node.declarations[0].init.properties.every(
              propertyNode =>
                propertyNode.value.type === 'ArrayExpression' &&
                propertyNode.value.elements.length < 3
            )
          ) {
            // var map = {
            //     './splita': [
            //         1,
            //         1
            //     ],
            //     './splita.js': [
            //         1,
            //         1
            //     ],
            //     './splitb': [
            //         2,
            //         0
            //     ],
            //     './splitb.js': [
            //         2,
            //         0
            //     ]
            // };
            // Add a 3rd element to each value that uses .toString('url')
            for (const propertyNode of node.declarations[0].init.properties) {
              // Not sure this is necessary yet, but pad with undefined so we always
              // add to the 3rd position
              while (propertyNode.value.elements.length < 2) {
                propertyNode.value.elements.push({
                  type: 'Identifier',
                  value: 'undefined'
                });
              }
              propertyNode.value.elements.push({
                type: 'CallExpression',
                callee: {
                  type: 'MemberExpression',
                  computed: false,
                  object: {
                    type: 'Literal',
                    value: requireEnsureAstTemplate(
                      String(propertyNode.value.elements[1].value)
                    )
                  },
                  property: {
                    type: 'Identifier',
                    name: 'toString'
                  }
                },
                arguments: [
                  {
                    type: 'Literal',
                    value: 'url'
                  }
                ]
              });
            }
            replacementsMade = true;
          } else if (
            node.type === 'FunctionDeclaration' &&
            node.id.type === 'Identifier' &&
            node.id.name === 'webpackAsyncContext'
          ) {
            // function webpackAsyncContext(req) {
            //     var ids = map[req];
            //     if (!ids)
            //         return Promise.reject(new Error('Cannot find module \'' + req + '\'.'));
            //     return __webpack_require__.e(ids[1]).then(function () {
            //         return __webpack_require__(ids[0]);
            //     });
            // }
            // Add ids[2] to the argument list in the __webpack_require__.e call so that the JavaScriptStaticUrl is passed on
            const lastStatement = node.body.body[node.body.body.length - 1];
            if (
              lastStatement &&
              lastStatement.type === 'ReturnStatement' &&
              lastStatement.argument.type === 'CallExpression' &&
              lastStatement.argument.callee.type === 'MemberExpression' &&
              lastStatement.argument.callee.object.type === 'CallExpression' &&
              lastStatement.argument.callee.object.callee.type ===
                'MemberExpression' &&
              lastStatement.argument.callee.object.callee.object.type ===
                'Identifier' &&
              lastStatement.argument.callee.object.callee.object.name ===
                '__webpack_require__' &&
              lastStatement.argument.callee.object.callee.property.type ===
                'Identifier' &&
              lastStatement.argument.callee.object.callee.property.name ===
                'e' &&
              lastStatement.argument.callee.object.arguments.length === 1 &&
              lastStatement.argument.callee.object.arguments[0].type ===
                'MemberExpression' &&
              lastStatement.argument.callee.object.arguments[0].computed &&
              lastStatement.argument.callee.object.arguments[0].object.type ===
                'Identifier' &&
              lastStatement.argument.callee.object.arguments[0].object.name ===
                'ids' &&
              lastStatement.argument.callee.object.arguments[0].property
                .type === 'Literal'
            ) {
              lastStatement.argument.callee.object.arguments.push({
                type: 'MemberExpression',
                computed: true,
                object: {
                  type: 'Identifier',
                  name: 'ids'
                },
                property: {
                  type: 'Literal',
                  value: 2
                }
              });
              replacementsMade = true;
            }
          } else if (
            node.type === 'CallExpression' &&
            node.callee.type === 'MemberExpression' &&
            node.callee.object.type === 'Identifier' &&
            node.callee.object.name === '__webpack_require__' &&
            node.callee.property.type === 'Identifier' &&
            node.callee.property.name === 'e' &&
            node.arguments.length >= 1 &&
            node.arguments[0].type === 'Literal' &&
            typeof node.arguments[0].value === 'number' &&
            requireEnsureAstTemplate
          ) {
            node.arguments.push({
              type: 'CallExpression',
              callee: {
                type: 'MemberExpression',
                computed: false,
                object: {
                  type: 'Literal',
                  value: requireEnsureAstTemplate(node.arguments[0].value)
                },
                property: {
                  type: 'Identifier',
                  name: 'toString'
                }
              },
              arguments: [
                {
                  type: 'Literal',
                  value: 'url'
                }
              ]
            });
            replacementsMade = true;
          } else if (
            node.type === 'FunctionExpression' &&
            node.params.length === 3 &&
            node.params[0].type === 'Identifier' &&
            node.params[0].name === 'module' &&
            node.params[1].type === 'Identifier' &&
            node.params[1].name === 'exports' &&
            node.params[2].type === 'Identifier' &&
            node.params[2].name === '__webpack_require__' &&
            node.body.type === 'BlockStatement' &&
            node.body.body.length === 1 &&
            node.body.body[0].type === 'ExpressionStatement' &&
            node.body.body[0].expression.type === 'AssignmentExpression' &&
            node.body.body[0].expression.left.type === 'MemberExpression' &&
            node.body.body[0].expression.left.object.type === 'Identifier' &&
            node.body.body[0].expression.left.object.name === 'module' &&
            node.body.body[0].expression.left.property.type === 'Identifier' &&
            node.body.body[0].expression.left.property.name === 'exports' &&
            node.body.body[0].expression.right.type === 'BinaryExpression' &&
            node.body.body[0].expression.right.operator === '+' &&
            node.body.body[0].expression.right.left.type ===
              'MemberExpression' &&
            node.body.body[0].expression.right.left.object.type ===
              'Identifier' &&
            node.body.body[0].expression.right.left.object.name ===
              node.params[2].name &&
            node.body.body[0].expression.right.left.property.type ===
              'Identifier' &&
            node.body.body[0].expression.right.left.property.name === 'p' &&
            node.body.body[0].expression.right.right.type === 'Literal' &&
            typeof node.body.body[0].expression.right.right.value === 'string'
          ) {
            node.body.body[0].expression.right = {
              type: 'CallExpression',
              range: node.body.body[0].expression.right.range,
              callee: {
                type: 'MemberExpression',
                computed: false,
                object: {
                  type: 'Literal',
                  // FIXME: Fall back to config.output.path instead of hardcoding /dist/:
                  value:
                    (config.output.publicPath || '/dist/') +
                    node.body.body[0].expression.right.right.value
                },
                property: {
                  type: 'Identifier',
                  name: 'toString'
                }
              },
              arguments: [
                {
                  type: 'Literal',
                  value: 'url'
                }
              ]
            };
            replacementsMade = true;
          }
        }
      });
      if (replacementsMade) {
        asset.markDirty();
        for (const relation of [...asset.outgoingRelations]) {
          asset.removeRelation(relation);
        }
        asset._outgoingRelations = undefined;
        asset.isPopulated = false;
        asset.populate();
      }
    }
  };
Esempio n. 3
0
  /**
   * Some asset classes support inspection and manipulation using a high
   * level interface. If you modify the parse tree, you have to call
   * `asset.markDirty()` so any cached serializations of the asset are
   * invalidated.
   *
   * These are the formats you'll get:
   *
   * `Html` and `Xml`:
   *     jsdom document object (https://github.com/tmpvar/jsdom).
   *
   * `Css`
   *     CSSOM CSSStyleSheet object (https://github.com/NV/CSSOM).
   *
   * `JavaScript`
   *     estree AST object (via acorn).
   *
   * `Json`
   *     Regular JavaScript object (the result of JSON.parse on the decoded source).
   *
   * `CacheManifest`
   *     A JavaScript object with a key for each section present in the
   *     manifest (`CACHE`, `NETWORK`, `REMOTE`). The value is an array with
   *     an item for each entry in the section. Refer to the source for
   *     details.
   *
   * @member {Oject} Asset#parseTree
   */

  /**
   * Load the Asset
   *
   * Returns a promise that is resolved when the asset is loaded.
   * This is Asset's only async method, as soon as it is
   * loaded, everything can happen synchronously.
   *
   * Usually you'll want to use `transforms.loadAssets`, which will
   * handle this automatically.
   *
   * @async
   * @return {Promise<Asset>} The loaded Asset
   */
  async load({ metadataOnly = false } = {}) {
    try {
      if (!this.isLoaded) {
        if (!this.url) {
          throw new Error('Asset.load: No url, cannot load');
        }

        const url = this.url;
        const protocol = url.substr(0, url.indexOf(':')).toLowerCase();
        if (protocol === 'file') {
          const pathname = urlTools.fileUrlToFsPath(url);
          if (metadataOnly) {
            const stats = await fs.statAsync(pathname);
            if (stats.isDirectory()) {
              this.fileRedirectTargetUrl = urlTools.fsFilePathToFileUrl(
                pathname.replace(/(\/)?$/, '/index.html')
              );
              // Make believe it's loaded:
              this._rawSrc = Buffer.from([]);
            }
          } else {
            try {
              this._rawSrc = await fs.readFileAsync(pathname);
              this._updateRawSrcAndLastKnownByteLength(this._rawSrc);
            } catch (err) {
              if (err.code === 'EISDIR' || err.code === 'EINVAL') {
                this.fileRedirectTargetUrl = urlTools.fsFilePathToFileUrl(
                  pathname.replace(/(\/)?$/, '/index.html')
                );
                this.isRedirect = true;
              } else {
                throw err;
              }
            }
          }
        } else if (protocol === 'http' || protocol === 'https') {
          const { headers = {}, ...requestOptions } =
            this.assetGraph.requestOptions || {};
          const firstIncomingRelation = this.incomingRelations[0];
          let Referer;

          if (
            firstIncomingRelation &&
            firstIncomingRelation.from.protocol &&
            firstIncomingRelation.from.protocol.startsWith('http')
          ) {
            Referer = firstIncomingRelation.from.url;
          }

          const response = await this.assetGraph.teepee.request({
            ...requestOptions,
            headers: {
              ...headers,
              Referer
            },
            method: metadataOnly ? 'HEAD' : 'GET',
            url,
            json: false
          });
          this.statusCode = response.statusCode;
          if (!metadataOnly) {
            this._rawSrc = response.body;
            this._updateRawSrcAndLastKnownByteLength(this._rawSrc);
          }
          if (response.headers.location) {
            this.location = response.headers.location;
            this.isRedirect = true;
          }
          const contentTypeHeaderValue = response.headers['content-type'];
          if (contentTypeHeaderValue) {
            const matchContentType = contentTypeHeaderValue.match(
              /^\s*([\w\-+.]+\/[\w-+.]+)(?:\s|;|$)/i
            );
            if (matchContentType) {
              this.contentType = matchContentType[1].toLowerCase();

              const matchCharset = contentTypeHeaderValue.match(
                /;\s*charset\s*=\s*(['"]|)\s*([\w-]+)\s*\1(?:\s|;|$)/i
              );
              if (matchCharset) {
                this._encoding = matchCharset[2].toLowerCase();
              }
            } else {
              const err = new Error(
                `Invalid Content-Type response header received: ${contentTypeHeaderValue}`
              );
              err.asset = this;
              this.assetGraph.warn(err);
            }
          } else if (response.statusCode >= 200 && response.statusCode < 300) {
            const err = new Error('No Content-Type response header received');
            err.asset = this;
            this.assetGraph.warn(err);
          }
          if (response.headers.etag) {
            this.etag = response.headers.etag;
          }
          if (response.headers['cache-control']) {
            this.cacheControl = response.headers['cache-control'];
          }
          if (response.headers['content-security-policy']) {
            this.contentSecurityPolicy =
              response.headers['content-security-policy'];
          }
          if (response.headers['content-security-policy-report-only']) {
            this.contentSecurityPolicyReportOnly =
              response.headers['content-security-policy-report-only'];
          }
          for (const headerName of ['date', 'last-modified']) {
            if (response.headers[headerName]) {
              this[
                headerName.replace(/-([a-z])/, ($0, ch) => ch.toUpperCase())
              ] = new Date(response.headers[headerName]);
            }
          }
        } else if (!knownAndUnsupportedProtocols[protocol]) {
          const err = new Error(
            `No resolver found for protocol: ${protocol}\n\tIf you think this protocol should exist, please contribute it here:\n\thttps://github.com/Munter/schemes#contributing`
          );
          if (this.assetGraph) {
            this.assetGraph.warn(err);
          } else {
            throw err;
          }
        }
      }

      // Try to upgrade to a subclass based on the currently available type information:
      this._inferredType = undefined;
      let type = this._inferType();

      if (
        (!type || type === 'Image') &&
        (this._rawSrc || typeof this._text === 'string') &&
        !this.hasOwnProperty('contentType')
      ) {
        const detectedContentType = await determineFileType(
          this._rawSrc || this._text
        );
        if (detectedContentType) {
          if (detectedContentType !== 'text/plain') {
            // Setting text/plain explicitly here would fool _inferType later
            this.contentType = detectedContentType;
          }
          const typeFromDetectedContentType =
            AssetGraph.typeByContentType[this.contentType];
          if (typeFromDetectedContentType) {
            if (
              !type ||
              (type === 'Image' &&
                AssetGraph[typeFromDetectedContentType].prototype.isImage) ||
              (type === 'Font' &&
                AssetGraph[typeFromDetectedContentType].prototype.isFont)
            ) {
              type = typeFromDetectedContentType;
            }
          }
        }
      }
      this._tryUpgrade(type);

      this.emit('load', this);
      if (this.assetGraph) {
        this.populate(true);
      }
      return this;
    } catch (err) {
      err.message = err.message || err.code || err.name;
      const includingAssetUrls = this.incomingRelations.map(
        incomingRelation => {
          return incomingRelation.from.urlOrDescription;
        }
      );
      if (includingAssetUrls.length > 0) {
        err.message += `\nIncluding assets:\n    ${includingAssetUrls.join(
          '\n    '
        )}\n`;
      }
      err.asset = this;
      throw err;
    }
  }
            .parEach(function (labelDefinition) {
                var resolverName = labelDefinition.type || (/\.jsb\d+$/.test(labelDefinition.url) ? 'senchaJsBuilder' : 'fixedDirectory');

                try {
                    assetGraph.resolverByProtocol[labelDefinition.name] = resolvers[resolverName](urlTools.fsFilePathToFileUrl(labelDefinition.url));
                } catch (err) {
                    err.message = 'transforms.registerLabelsAsCustomProtocols: Error initializing resolver: ' + err.message;
                    return this(err);
                }
                this();
            })
Esempio n. 5
0
        var walker = new uglifyJs.TreeWalker(function (node, descend) {
            var stack = walker.stack,
                parentNode = walker.parent();
            [node.start, node.end].forEach(function (token) {
                if (token && token.comments_before) {
                    token.comments_before.forEach(function (comment) {
                        var matchSourceUrlOrSourceMappingUrl = comment.value.match(/[@#]\s*source(Mapping)?URL=([^\s\n]*)/);
                        if (matchSourceUrlOrSourceMappingUrl && seenComments.indexOf(comment) === -1) {
                            if (matchSourceUrlOrSourceMappingUrl[1] === 'Mapping') {
                                outgoingRelations.push(new AssetGraph.JavaScriptSourceMappingUrl({
                                    from: this,
                                    node: comment,
                                    to: {
                                        url: matchSourceUrlOrSourceMappingUrl[2],
                                        // Source maps are currently served as application/json, so prevent the target asset
                                        // from being loaded as a Json asset:
                                        type: 'SourceMap'
                                    }
                                }));
                            } else {
                                outgoingRelations.push(new AssetGraph.JavaScriptSourceUrl({
                                    from: this,
                                    node: comment,
                                    to: {
                                        url: matchSourceUrlOrSourceMappingUrl[2]
                                    }
                                }));
                            }
                            seenComments.push(comment);
                        }
                    }, this);
                }
            }, this);

            // Detect global 'use strict' directives
            if (parentNode === this.parseTree &&
                node instanceof uglifyJs.AST_Directive &&
                node.value === 'use strict') {
                this.strict = true;
            }

            if (node instanceof uglifyJs.AST_Call) {
                var parentParentNode = stack[stack.length - 3];
                if (node.expression instanceof uglifyJs.AST_Dot && node.expression.property === 'module' &&
                    node.expression.expression instanceof uglifyJs.AST_SymbolRef &&
                    node.expression.expression.name === 'angular') {

                    var diveIntoAngularMethodCall = function (argumentNodes, templateCacheVariableName) {
                        var angularWalker = new uglifyJs.TreeWalker(function (node) {
                            var parentNode = angularWalker.parent();
                            if (node instanceof uglifyJs.AST_Object) {
                                node.properties.forEach(function (keyValue) {
                                    if (keyValue.key === 'templateUrl' && keyValue.value instanceof uglifyJs.AST_String) {
                                        outgoingRelations.push(new AssetGraph.JavaScriptAngularJsTemplate({
                                            from: this,
                                            to: {
                                                type: 'Html',
                                                url: keyValue.value.value
                                            },
                                            node: keyValue,
                                            parentNode: node
                                        }));
                                    } else if (keyValue.key === 'template' && keyValue.value instanceof uglifyJs.AST_String) {
                                        outgoingRelations.push(new AssetGraph.JavaScriptAngularJsTemplate({
                                            from: this,
                                            to: new AssetGraph.Html({
                                                text: keyValue.value.value
                                            }),
                                            node: keyValue,
                                            parentNode: node
                                        }));
                                    }
                                }, this);
                            } else if (node instanceof uglifyJs.AST_SimpleStatement) {
                                if (parentNode instanceof uglifyJs.AST_Function) {
                                    // Use the statements array of the function instead:
                                    parentNode = parentNode.body;
                                }
                                if (node.body instanceof uglifyJs.AST_Call &&
                                    node.body.expression instanceof uglifyJs.AST_Dot &&
                                    node.body.expression.property === 'put' &&
                                    node.body.expression.expression instanceof uglifyJs.AST_SymbolRef &&
                                    node.body.expression.expression.name === templateCacheVariableName &&
                                    node.body.args.length === 2 &&
                                    node.body.args[0] instanceof uglifyJs.AST_String &&
                                    node.body.args[1] instanceof uglifyJs.AST_String) {

                                    outgoingRelations.push(new AssetGraph.JavaScriptAngularJsTemplateCacheAssignment({
                                        from: this,
                                        to: new AssetGraph.Html({
                                            isExternalizable: false,
                                            text: node.body.args[1].value
                                        }),
                                        node: node,
                                        parentNode: parentNode
                                    }));
                                }
                            }
                        }.bind(this));
                        argumentNodes.forEach(function (argumentNode) {
                            argumentNode.walk(angularWalker);
                        });
                    }.bind(this);

                    var stackPosition = stack.length - 1;
                    while (stack[stackPosition - 1] instanceof uglifyJs.AST_Dot && stack[stackPosition - 2] instanceof uglifyJs.AST_Call) {
                        var callNode = stack[stackPosition - 2],
                            methodName = stack[stackPosition - 1].property,
                            argumentNodes = callNode.args,
                            templateCacheVariableName;

                        if (methodName === 'run' &&
                            argumentNodes.length > 0 &&
                            argumentNodes[0] instanceof uglifyJs.AST_Array &&
                            argumentNodes[0].elements.length === 2 &&
                            argumentNodes[0].elements[0] instanceof uglifyJs.AST_String &&
                            argumentNodes[0].elements[0].value === '$templateCache' &&
                            argumentNodes[0].elements[1] instanceof uglifyJs.AST_Function) {

                            templateCacheVariableName = argumentNodes[0].elements[1].argnames[0].name;
                        }
                        diveIntoAngularMethodCall(argumentNodes, templateCacheVariableName);
                        stackPosition -= 2;
                    }
                }

                if (node.expression instanceof uglifyJs.AST_Symbol && node.expression.name === 'INCLUDE') {
                    if (node.args.length === 1 && node.args[0] instanceof uglifyJs.AST_String) {
                        outgoingRelations.push(new AssetGraph.JavaScriptInclude({
                            from: this,
                            to: {
                                url: node.args[0].value
                            },
                            node: node,
                            detachableNode: parentNode instanceof uglifyJs.AST_Seq ? node : parentNode,
                            parentNode: parentNode instanceof uglifyJs.AST_Seq ? parentNode : parentParentNode
                        }));
                    } else {
                        syntaxErrors.push(new errors.SyntaxError({
                            message: 'Invalid INCLUDE syntax: Must take a single string argument:' + node.print_to_string(),
                            asset: this
                        }));
                    }
                } else if (node.expression instanceof uglifyJs.AST_Symbol && node.expression.name === 'GETTEXT') {
                    if (node.args.length === 1) {
                        // TRHTML(GETTEXT(...)) is covered by TRHTML below:
                        if (!(parentNode instanceof uglifyJs.AST_Call) || !(parentNode.expression instanceof uglifyJs.AST_Symbol) || parentNode.expression.name !== 'TRHTML') {
                            node.args[0] = tryFoldConstantToString(node.args[0]);
                            if (node.args[0] instanceof uglifyJs.AST_String) {
                                outgoingRelations.push(new AssetGraph.JavaScriptGetText({
                                    parentNode: parentNode,
                                    from: this,
                                    to: {
                                        url: node.args[0].value
                                    },
                                    node: node
                                }));
                            } else {
                                syntaxErrors.push(new errors.SyntaxError({
                                    message: 'Invalid GETTEXT syntax: ' + node.print_to_string(),
                                    asset: this
                                }));
                            }
                        }
                    } else {
                        syntaxErrors.push(new errors.SyntaxError({
                            message: 'Invalid GETTEXT syntax: ' + node.print_to_string(),
                            asset: this
                        }));
                    }
                } else if (node.expression instanceof uglifyJs.AST_Symbol && node.expression.name === 'GETSTATICURL') {
                    outgoingRelations.push(new AssetGraph.JavaScriptGetStaticUrl({
                        from: this,
                        parentNode: parentNode,
                        node: node,
                        to: new AssetGraph.StaticUrlMap({
                            parseTree: node.clone().args
                        })
                    }));
                } else if (node.expression instanceof uglifyJs.AST_Symbol && node.expression.name === 'TRHTML') {
                    var outgoingRelation;
                    if (node.args[0] instanceof uglifyJs.AST_Call && node.args[0].expression instanceof uglifyJs.AST_Symbol && node.args[0].expression.name === 'GETTEXT' && node.args[0].args.length === 1) {

                        node.args[0].args[0] = tryFoldConstantToString(node.args[0].args[0]);
                        if (node.args[0].args[0] instanceof uglifyJs.AST_String) {
                            outgoingRelation = new AssetGraph.JavaScriptTrHtml({
                                from: this,
                                parentNode: parentNode,
                                node: node,
                                to: {
                                    url: node.args[0].args[0].value
                                }
                            });
                        }
                    } else {
                        node.args[0] = tryFoldConstantToString(node.args[0]);
                        if (node.args[0] instanceof uglifyJs.AST_String) {
                            outgoingRelation = new AssetGraph.JavaScriptTrHtml({
                                from: this,
                                parentNode: parentNode,
                                node: node,
                                to: new AssetGraph.Html({
                                    node: node,
                                    text: node.args[0].value
                                })
                            });
                        }
                    }
                    if (outgoingRelation) {
                        outgoingRelations.push(outgoingRelation);
                    } else {
                        syntaxErrors.push(new errors.SyntaxError({
                            message: 'Invalid TRHTML syntax: ' + node.print_to_string(),
                            asset: this
                        }));
                    }
                } else if (node.expression instanceof uglifyJs.AST_Symbol &&
                           (node.expression.name === 'require' || node.expression.name === 'requirejs') &&
                           ((node.args.length === 2 && node.args[1] instanceof uglifyJs.AST_Function) || node.args.length === 1) &&
                           node.args[0] instanceof uglifyJs.AST_Array) {
                    // There's no 3 argument version of require, so check whether the require is succeeded by define('moduleName', ...);
                    // like flattenRequireJs and r.js would output. If it is, don't model it as a relation.
                    var parentIndex = stack.length - 1,
                        isSucceededByDefineWithStringArg = false,
                        seenDefine = false,
                        walkFn = function (_node) {
                            if (_node === node) {
                                seenDefine = true;
                            } else if (seenDefine && isNamedDefineNode(_node)) {
                                isSucceededByDefineWithStringArg = true;
                            }
                        };
                    OUTER:
                    while (parentIndex >= 0) {
                        if (stack[parentIndex] instanceof uglifyJs.AST_Block) {
                            var blockNode = stack[parentIndex];
                            for (var i = blockNode.body.indexOf(stack[parentIndex + 1]) ; i < blockNode.body.length ; i += 1) {
                                if (blockNode.body[i] instanceof uglifyJs.AST_SimpleStatement && isNamedDefineNode(blockNode.body[i].body)) {
                                    isSucceededByDefineWithStringArg = true;
                                    break OUTER;
                                }
                            }
                            break OUTER;
                        } else if (stack[parentIndex] instanceof uglifyJs.AST_Seq) {
                            stack[parentIndex].walk(new uglifyJs.TreeWalker(walkFn));
                            break OUTER;
                        } else {
                            parentIndex -= 1;
                        }
                    }

                    if (!isSucceededByDefineWithStringArg) {
                        var arrayNode = node.args[0];
                        arrayNode.elements.forEach(function (arrayItemAst, i) {
                            arrayItemAst = arrayNode.elements[i] = tryFoldConstantToString(arrayItemAst);
                            if (arrayItemAst instanceof uglifyJs.AST_String) {
                                if (['require', 'exports', 'module'].indexOf(arrayItemAst.value) === -1) {
                                    var outgoingRelation = new AssetGraph.JavaScriptAmdRequire({
                                        requireJsConfig: this.assetGraph && this.assetGraph.requireJsConfig, // Hmmm
                                        from: this,
                                        callNode: node,
                                        arrayNode: arrayNode,
                                        node: arrayItemAst
                                    });
                                    outgoingRelation.to = {
                                        url: outgoingRelation.targetUrl,
                                        isRequired: true
                                    };
                                    outgoingRelations.push(outgoingRelation);
                                }
                            } else {
                                infos.push(new errors.SyntaxError('Skipping non-string JavaScriptAmdRequire item: ' + node.print_to_string()));
                            }
                        }, this);
                    }
                } else if (this.isRequired && node.expression instanceof uglifyJs.AST_Symbol && node.expression.name === 'define') {
                    if (node.args.length === 2 && node.args[0] instanceof uglifyJs.AST_Array) {
                        var arrayNode = node.args[0];
                        arrayNode.elements.forEach(function (arrayItemAst, i) {
                            arrayNode.elements[i] = arrayItemAst = tryFoldConstantToString(arrayItemAst);
                            if (arrayItemAst instanceof uglifyJs.AST_String) {
                                if (['require', 'exports', 'module'].indexOf(arrayItemAst.value) === -1) {
                                    var outgoingRelation = new AssetGraph.JavaScriptAmdDefine({
                                        requireJsConfig: this.assetGraph && this.assetGraph.requireJsConfig, // Hmmm
                                        from: this,
                                        callNode: node,
                                        arrayNode: arrayNode,
                                        node: arrayItemAst
                                    });
                                    outgoingRelation.to = {
                                        url: outgoingRelation.targetUrl,
                                        isRequired: true
                                    };
                                    outgoingRelations.push(outgoingRelation);
                                }
                            } else {
                                infos.push(new errors.SyntaxError('Skipping non-string JavaScriptAmdDefine item: ' + node.print_to_string()));
                            }
                        }, this);
                    }
                    // Keep track of the fact that we're in the body of a define(function () {...}) that might contain
                    // JavaScriptRequireJsCommonJsCompatibilityRequire relations
                    var lastArgument = node.args.length > 0 && node.args[node.args.length - 1];
                    if (lastArgument && lastArgument instanceof uglifyJs.AST_Function) {
                        nestedDefineNodes.push(node);
                        descend();
                        nestedDefineNodes.pop();
                        return true; // Tell the TreeWalker not to descend again
                    }
                } else if (node.expression instanceof uglifyJs.AST_Symbol && node.expression.name === 'require' &&
                           node.args.length === 1 && node.args[0] instanceof uglifyJs.AST_String) {
                    if (nestedDefineNodes.length > 0) {
                        var parentDefineNode = nestedDefineNodes[nestedDefineNodes.length - 1];
                        if (!(parentDefineNode.args[0] instanceof uglifyJs.AST_String)) {
                            var outgoingRelation = new AssetGraph.JavaScriptRequireJsCommonJsCompatibilityRequire({
                                parentDefineNode: parentDefineNode,
                                requireJsConfig: this.assetGraph && this.assetGraph.requireJsConfig, // Hmmm
                                from: this,
                                node: node
                            });
                            outgoingRelation.to = {
                                url: outgoingRelation.targetUrl,
                                isRequired: true
                            };
                            outgoingRelations.push(outgoingRelation);
                        }
                    } else if (!this.isRequired) {
                        var baseUrl = this.nonInlineAncestor.url;
                        if (/^file:/.test(baseUrl)) {
                            var resolvedCommonJsModuleName = resolveCommonJsModuleName(urlTools.fileUrlToFsPath(baseUrl), node.args[0].value);

                            // Skip built-in and unresolvable modules (they just resolve to 'fs', 'util', etc., not a file name):
                            if (!resolvedCommonJsModuleName) {
                                warnings.push(new errors.SyntaxError({message: 'Couldn\'t resolve ' + node.print_to_string() + ', skipping', relationType: 'JavaScriptCommonJsRequire'}));
                            } else if (/^\//.test(resolvedCommonJsModuleName)) {
                                outgoingRelations.push(new AssetGraph.JavaScriptCommonJsRequire({
                                    from: this,
                                    to: {
                                        url: urlTools.fsFilePathToFileUrl(resolvedCommonJsModuleName)
                                    },
                                    node: node
                                }));
                            }
                        } else {
                            warnings.push(new errors.SyntaxError({message: 'Skipping JavaScriptCommonJsRequire (only supported from file: urls): ' + node.print_to_string(), relationType: 'JavaScriptCommonJsRequire'}));
                        }
                    }
                }
            }
        }.bind(this));