summaryrefslogtreecommitdiff
path: root/config/plugins/graphql_known_operations_plugin.js
blob: 164b34c1dd13ee2deebf8f79c2ccacd37d27a716 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/* eslint-disable no-underscore-dangle */
const yaml = require('js-yaml');

const PLUGIN_NAME = 'GraphqlKnownOperationsPlugin';
const GRAPHQL_PATH_REGEX = /(query|mutation)\.graphql$/;
const OPERATION_NAME_SOURCE_REGEX = /^\s*module\.exports.*oneQuery.*"(\w+)"/gm;

/**
 * Returns whether a given webpack module is a "graphql" module
 */
const isGraphqlModule = (module) => {
  return GRAPHQL_PATH_REGEX.test(module.resource);
};

/**
 * Returns graphql operation names we can parse from the given module
 *
 * Since webpack gives us the source **after** the graphql-tag/loader runs,
 * we can look for specific lines we're guaranteed to have from the
 * graphql-tag/loader.
 */
const getOperationNames = (module) => {
  const originalSource = module.originalSource();

  if (!originalSource) {
    return [];
  }

  const matches = originalSource.source().toString().matchAll(OPERATION_NAME_SOURCE_REGEX);

  return Array.from(matches).map((match) => match[1]);
};

const createFileContents = (knownOperations) => {
  const sourceData = Array.from(knownOperations.values()).sort((a, b) => a.localeCompare(b));

  return yaml.dump(sourceData);
};

/**
 * Creates a webpack4 compatible "RawSource"
 *
 * Inspired from https://sourcegraph.com/github.com/FormidableLabs/webpack-stats-plugin@e050ff8c362d5ddd45c66ade724d4a397ace3e5c/-/blob/lib/stats-writer-plugin.js?L144
 */
const createWebpackRawSource = (source) => {
  const buff = Buffer.from(source, 'utf-8');

  return {
    source() {
      return buff;
    },
    size() {
      return buff.length;
    },
  };
};

const onSucceedModule = ({ module, knownOperations }) => {
  if (!isGraphqlModule(module)) {
    return;
  }

  getOperationNames(module).forEach((x) => knownOperations.add(x));
};

const onCompilerEmit = ({ compilation, knownOperations, filename }) => {
  const contents = createFileContents(knownOperations);
  const source = createWebpackRawSource(contents);

  const asset = compilation.getAsset(filename);
  if (asset) {
    compilation.updateAsset(filename, source);
  } else {
    compilation.emitAsset(filename, source);
  }
};

/**
 * Webpack plugin that outputs a file containing known graphql operations.
 *
 * A lot of the mechanices was expired from [this example][1].
 *
 * [1]: https://sourcegraph.com/github.com/FormidableLabs/webpack-stats-plugin@e050ff8c362d5ddd45c66ade724d4a397ace3e5c/-/blob/lib/stats-writer-plugin.js?L136
 */
class GraphqlKnownOperationsPlugin {
  constructor({ filename }) {
    this._filename = filename;
  }

  apply(compiler) {
    const knownOperations = new Set();

    compiler.hooks.emit.tap(PLUGIN_NAME, (compilation) => {
      onCompilerEmit({
        compilation,
        knownOperations,
        filename: this._filename,
      });
    });

    compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
      compilation.hooks.succeedModule.tap(PLUGIN_NAME, (module) => {
        onSucceedModule({
          module,
          knownOperations,
        });
      });
    });
  }
}

module.exports = GraphqlKnownOperationsPlugin;