├── .circleci └── config.yml ├── .editorconfig ├── .flowconfig ├── .github ├── dependabot.yml └── workflows │ └── node.js.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── loader.js ├── main.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── index.js.flow ├── index.ts └── tests.ts └── tsconfig.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | secops: apollo/circleci-secops-orb@2.0.6 5 | 6 | workflows: 7 | security-scans: 8 | jobs: 9 | - secops/gitleaks: 10 | context: 11 | - platform-docker-ro 12 | - github-orb 13 | - secops-oidc 14 | git-base-revision: <<#pipeline.git.base_revision>><><> 15 | git-revision: << pipeline.git.revision >> 16 | - secops/semgrep: 17 | context: 18 | - secops-oidc 19 | - github-orb 20 | git-base-revision: <<#pipeline.git.base_revision>><><> 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{js,json}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectError 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "daily" 10 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [10.x, 12.x, 14.x, 15.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run build --if-present 29 | - run: npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea/ 3 | 4 | # don't commit compiled files 5 | lib 6 | yarn-error.log 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ### vNEXT 4 | 5 | * Loader: allow a space between `#` and `import` word in gql files.
6 | [@kidroca](https://github.com/kidroca) in [#458](https://github.com/apollographql/graphql-tag/pull/458) 7 | 8 | ### v2.12.6 9 | 10 | * Update peer dependencies to allow `graphql` ^16.0.0.
11 | [@brainkim](https://github.com/brainkim) in [#530](https://github.com/apollographql/graphql-tag/pull/530) 12 | 13 | ### v2.12.5 14 | 15 | * Also publish `src/` directory to npm, enabling source maps.
16 | [@maclockard](https://github.com/maclockard) in [#403](https://github.com/apollographql/graphql-tag/pull/403) 17 | 18 | ### v2.12.4 (2021-04-29) 19 | 20 | * Allow fragments to be imported by name when using the webpack loader.
21 | [@dobesv](https://github.com/dobesv) in [#257](https://github.com/apollographql/graphql-tag/pull/257) 22 | 23 | ### v2.12.3 24 | 25 | * Update `tslib` dependency to version 2.1.0.
26 | [@benjamn](http://github.com/benjamn) in [#381](https://github.com/apollographql/graphql-tag/pull/381) 27 | 28 | ### v2.12.2 29 | 30 | * Avoid using `Object.assign` to attach extra properties to `gql`.
31 | [@benjamn](http://github.com/benjamn) in [#380](https://github.com/apollographql/graphql-tag/pull/380) 32 | 33 | ### v2.12.1 34 | 35 | * To accommodate older versions of TypeScript, usage of the `import type ...` syntax (introduced by [#325](https://github.com/apollographql/graphql-tag/pull/325)) has been removed, fixing issue [#345](https://github.com/apollographql/graphql-tag/issues/345).
36 | [@benjamn](http://github.com/benjamn) in [#352](https://github.com/apollographql/graphql-tag/pull/352) 37 | 38 | ### v2.12.0 39 | 40 | * The `graphql-tag` repository has been converted to TypeScript, adding type safety and enabling both ECMAScript and CommonJS module exports. While these changes are intended to be as backwards-compatible as possible, we decided to bump the minor version to reflect the significant refactoring.
41 | [@PowerKiKi](http://github.com/PowerKiKi) and [@benjamn](http://github.com/benjamn) in [#325](https://github.com/apollographql/graphql-tag/pull/325) 42 | 43 | ### v2.11.0 (2020-07-28) 44 | 45 | * `package.json` `sideEffects` changes to clearly identify that `graphql-tag` doesn't have side effects.
46 | [@hwillson](http://github.com/hwillson) in [#313](https://github.com/apollographql/graphql-tag/pull/313) 47 | 48 | ### v2.10.4 (2020-07-08) 49 | 50 | * Bump dev/peer deps to accommodate `graphql` 15.
51 | [@adriencohen](https://github.com/adriencohen) in [#299](https://github.com/apollographql/graphql-tag/pull/299) 52 | 53 | ### v2.10.3 (2020-02-05) 54 | 55 | * Further adjustments to the TS `index.d.ts` declaration file.
56 | [@Guillaumez](https://github.com/Guillaumez) in [#289](https://github.com/apollographql/graphql-tag/pull/289) 57 | 58 | ### v2.10.2 (2020-02-04) 59 | 60 | * Update/fix the existing TS `index.d.ts` declaration file.
61 | [@hwillson](https://github.com/hwillson) in [#285](https://github.com/apollographql/graphql-tag/pull/285) 62 | 63 | ### v2.10.1 64 | 65 | * Fix failures in IE11 by avoiding unsupported (by IE11) constructor arguments to `Set` by [rocwang](https://github.com/rocwang) in [#190](https://github.com/apollographql/graphql-tag/pull/190) 66 | 67 | ### v2.10.0 68 | * Add support for `graphql@14` by [timsuchanek](https://github.com/timsuchanek) in [#210](https://github.com/apollographql/graphql-tag/pull/210), [#211](https://github.com/apollographql/graphql-tag/pull/211) 69 | 70 | ### v2.9.1 71 | * Fix IE11 support by using a regular for-loop by [vitorbal](https://github.com/vitorbal) in [#176](https://github.com/apollographql/graphql-tag/pull/176) 72 | 73 | ### v2.9.0 74 | * Remove duplicate exports in named exports by [wacii](https://github.com/wacii) in [#170](https://github.com/apollographql/graphql-tag/pull/170) 75 | * Add `experimentalFragmentVariables` compatibility by [lucasconstantino](https://github.com/lucasconstantino) in [#167](https://github.com/apollographql/graphql-tag/pull/167/) 76 | 77 | ### v2.8.0 78 | 79 | * Update `graphql` to ^0.13, support testing all compatible versions [jnwng](https://github.com/jnwng) in 80 | [PR #156](https://github.com/apollographql/graphql-tag/pull/156) 81 | * Export single queries as both default and named [stonexer](https://github.com/stonexer) in 82 | [PR #154](https://github.com/apollographql/graphql-tag/pull/154) 83 | 84 | ### v2.7.{0,1,2,3} 85 | 86 | * Merge and then revert [PR #141](https://github.com/apollographql/graphql-tag/pull/141) due to errors being thrown 87 | 88 | ### v2.6.1 89 | 90 | * Accept `graphql@^0.12.0` as peerDependency [jnwng](https://github.com/jnwng) 91 | addressing [#134](https://github.com/apollographql/graphql-tag/issues/134) 92 | 93 | ### v2.6.0 94 | 95 | * Support multiple query definitions when using Webpack loader [jfaust](https://github.com/jfaust) in 96 | [PR #122](https://github.com/apollographql/graphql-tag/pull/122) 97 | 98 | ### v2.5.0 99 | 100 | * Update graphql to ^0.11.0, add graphql@^0.11.0 to peerDependencies [pleunv](https://github.com/pleunv) in 101 | [PR #124](https://github.com/apollographql/graphql-tag/pull/124) 102 | 103 | ### v2.4.{1,2} 104 | 105 | * Temporarily reverting [PR #99](https://github.com/apollographql/graphql-tag/pull/99) to investigate issues with 106 | bundling 107 | 108 | ### v2.4.0 109 | 110 | * Add support for descriptors [jamiter](https://github.com/jamiter) in 111 | [PR #99](https://github.com/apollographql/graphql-tag/pull/99) 112 | 113 | ### v2.3.0 114 | 115 | * Add flow support [michalkvasnicak](https://github.com/michalkvasnicak) in 116 | [PR #98](https://github.com/apollographql/graphql-tag/pull/98) 117 | 118 | ### v2.2.2 119 | 120 | * Make parsing line endings kind agnostic [vlasenko](https://github.com/vlasenko) in 121 | [PR #95](https://github.com/apollographql/graphql-tag/pull/95) 122 | 123 | ### v2.2.1 124 | 125 | * Fix #61: split('/n') does not work on Windows [dnalborczyk](https://github.com/dnalborczyk) in 126 | [PR #89](https://github.com/apollographql/graphql-tag/pull/89) 127 | 128 | ### v2.2.0 129 | 130 | * Bumping `graphql` peer dependency to ^0.10.0 [dotansimha](https://github.com/dotansimha) in 131 | [PR #85](https://github.com/apollographql/graphql-tag/pull/85) 132 | 133 | ### v2.1.0 134 | 135 | * Add support for calling `gql` as a function [matthewerwin](https://github.com/matthewerwin) in 136 | [PR #66](https://github.com/apollographql/graphql-tag/pull/66) 137 | * Including yarn.lock file [PowerKiKi](https://github.com/PowerKiKi) in 138 | [PR #72](https://github.com/apollographql/graphql-tag/pull/72) 139 | * Ignore duplicate fragments when using the Webpack loader [czert](https://github.com/czert) in 140 | [PR #52](https://github.com/apollographql/graphql-tag/pull/52) 141 | * Fixing `graphql-tag/loader` by properly stringifying GraphQL Source [jnwng](https://github.com/jnwng) in 142 | [PR #65](https://github.com/apollographql/graphql-tag/pull/65) 143 | 144 | ### v2.0.0 145 | 146 | Restore dependence on `graphql` module [abhiaiyer91](https://github.com/abhiaiyer91) in 147 | [PR #46](https://github.com/apollographql/graphql-tag/pull/46) addressing 148 | [#6](https://github.com/apollographql/graphql-tag/issues/6) 149 | 150 | * Added `graphql` as a 151 | [peerDependency](https://github.com/apollographql/graphql-tag/commit/ac061dd16440e75c166c85b4bff5ba06c79c9356) 152 | 153 | ### v1.3.2 154 | 155 | * Add typescript definitions for the bundledPrinter [PR #63](https://github.com/apollographql/graphql-tag/pull/63) 156 | 157 | ### v1.3.1 158 | 159 | * Making sure not to log deprecation warnings for internal use of deprecated module [jnwng](https://github.com/jnwng) 160 | addressing [#54](https://github.com/apollographql/graphql-tag/issues/54#issuecomment-283301475) 161 | 162 | ### v1.3.0 163 | 164 | * Bump bundled `graphql` packages to v0.9.1 [jnwng](https://github.com/jnwng) in 165 | [PR #55](https://github.com/apollographql/graphql-tag/pull/55). 166 | * Deprecate the `graphql/language/parser` and `graphql/language/printer` exports [jnwng](https://github.com/jnwng) in 167 | [PR #55](https://github.com/apollographql/graphql-tag/pull/55) 168 | 169 | ### v1.2.4 170 | 171 | Restore Node < 6 compatibility. [DragosRotaru](https://github.com/DragosRotaru) in 172 | [PR #41](https://github.com/apollographql/graphql-tag/pull/41) addressing 173 | [#39](https://github.com/apollographql/graphql-tag/issues/39) 174 | 175 | ### v1.2.1 176 | 177 | Fixed an issue with fragment imports. [PR #35](https://github.com/apollostack/graphql-tag/issues/35). 178 | 179 | ### v1.2.0 180 | 181 | Added ability to import other GraphQL documents with fragments using `#import` comments. 182 | [PR #33](https://github.com/apollostack/graphql-tag/pull/33) 183 | 184 | ### v1.1.2 185 | 186 | Fix issue with interpolating undefined values [Issue #19](https://github.com/apollostack/graphql-tag/issues/19) 187 | 188 | ### v1.1.1 189 | 190 | Added typescript definitions for the below. 191 | 192 | ### v1.1.0 193 | 194 | We now emit warnings if you use the same name for two different fragments. 195 | 196 | You can disable this with: 197 | 198 | ```js 199 | import { disableFragmentWarnings } from 'graphql-tag'; 200 | 201 | disableFragmentWarnings(); 202 | ``` 203 | 204 | ### v1.0.0 205 | 206 | Releasing 0.1.17 as 1.0.0 in order to be explicit about Semantic Versioning. 207 | 208 | ### v0.1.17 209 | 210 | * Allow embedding fragments inside document strings, as in 211 | 212 | ```js 213 | import gql from 'graphql-tag'; 214 | 215 | const fragment = gql` 216 | fragment Foo on Bar { 217 | field 218 | } 219 | `; 220 | 221 | const query = gql` 222 | { 223 | ...Foo 224 | } 225 | ${Foo} 226 | `; 227 | ``` 228 | 229 | See also http://dev.apollodata.com/react/fragments.html 230 | 231 | ### v0.1.16 232 | 233 | * Add caching to Webpack loader. [PR #16](https://github.com/apollostack/graphql-tag/pull/16) 234 | 235 | ### v0.1.15 236 | 237 | * Add Webpack loader to `graphql-tag/loader`. 238 | 239 | ### v0.1.14 240 | 241 | Changes were not tracked before this version. 242 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the Apollo SecOps team 2 | # Please customize this file as needed prior to merging. 3 | 4 | * @apollographql/client-typescript 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Apollo Contributor Guide 2 | 3 | Excited about Apollo and want to make it better? We’re excited too! 4 | 5 | Apollo is a community of developers just like you, striving to create the best tools and libraries around GraphQL. We welcome anyone who wants to contribute or provide constructive feedback, no matter the age or level of experience. If you want to help but don't know where to start, let us know, and we'll find something for you. 6 | 7 | Oh, and if you haven't already, sign up for the [Apollo Slack](http://www.apollodata.com/#slack). 8 | 9 | Here are some ways to contribute to the project, from easiest to most difficult: 10 | 11 | * [Reporting bugs](#reporting-bugs) 12 | * [Improving the documentation](#improving-the-documentation) 13 | * [Responding to issues](#responding-to-issues) 14 | * [Small bug fixes](#small-bug-fixes) 15 | * [Suggesting features](#suggesting-features) 16 | * [Big pull requests](#big-prs) 17 | 18 | ## Issues 19 | 20 | ### Reporting bugs 21 | 22 | If you encounter a bug, please file an issue on GitHub via the repository of the sub-project you think contains the bug. If an issue you have is already reported, please add additional information or add a 👍 reaction to indicate your agreement. 23 | 24 | While we will try to be as helpful as we can on any issue reported, please include the following to maximize the chances of a quick fix: 25 | 26 | 1. **Intended outcome:** What you were trying to accomplish when the bug occurred, and as much code as possible related to the source of the problem. 27 | 2. **Actual outcome:** A description of what actually happened, including a screenshot or copy-paste of any related error messages, logs, or other output that might be related. Places to look for information include your browser console, server console, and network logs. Please avoid non-specific phrases like “didn’t work” or “broke”. 28 | 3. **How to reproduce the issue:** Instructions for how the issue can be reproduced by a maintainer or contributor. Be as specific as possible, and only mention what is necessary to reproduce the bug. If possible, try to isolate the exact circumstances in which the bug occurs and avoid speculation over what the cause might be. 29 | 30 | Creating a good reproduction really helps contributors investigate and resolve your issue quickly. In many cases, the act of creating a minimal reproduction illuminates that the source of the bug was somewhere outside the library in question, saving time and effort for everyone. 31 | 32 | ### Improving the documentation 33 | 34 | Improving the documentation, examples, and other open source content can be the easiest way to contribute to the library. If you see a piece of content that can be better, open a PR with an improvement, no matter how small! If you would like to suggest a big change or major rewrite, we’d love to hear your ideas but please open an issue for discussion before writing the PR. 35 | 36 | ### Responding to issues 37 | 38 | In addition to reporting issues, a great way to contribute to Apollo is to respond to other peoples' issues and try to identify the problem or help them work around it. If you’re interested in taking a more active role in this process, please go ahead and respond to issues. And don't forget to say "Hi" on Apollo Slack! 39 | 40 | ### Small bug fixes 41 | 42 | For a small bug fix change (less than 20 lines of code changed), feel free to open a pull request. We’ll try to merge it as fast as possible and ideally publish a new release on the same day. The only requirement is, make sure you also add a test that verifies the bug you are trying to fix. 43 | 44 | ### Suggesting features 45 | 46 | Most of the features in Apollo came from suggestions by you, the community! We welcome any ideas about how to make Apollo better for your use case. Unless there is overwhelming demand for a feature, it might not get implemented immediately, but please include as much information as possible that will help people have a discussion about your proposal: 47 | 48 | 1. **Use case:** What are you trying to accomplish, in specific terms? Often, there might already be a good way to do what you need and a new feature is unnecessary, but it’s hard to know without information about the specific use case. 49 | 2. **Could this be a plugin?** In many cases, a feature might be too niche to be included in the core of a library, and is better implemented as a companion package. If there isn’t a way to extend the library to do what you want, could we add additional plugin APIs? It’s important to make the case for why a feature should be part of the core functionality of the library. 50 | 3. **Is there a workaround?** Is this a more convenient way to do something that is already possible, or is there some blocker that makes a workaround unfeasible? 51 | 52 | Feature requests will be labeled as such, and we encourage using GitHub issues as a place to discuss new features and possible implementation designs. Please refrain from submitting a pull request to implement a proposed feature until there is consensus that it should be included. This way, you can avoid putting in work that can’t be merged in. 53 | 54 | Once there is a consensus on the need for a new feature, proceed as listed below under “Big PRs”. 55 | 56 | ## Big PRs 57 | 58 | This includes: 59 | 60 | - Big bug fixes 61 | - New features 62 | 63 | For significant changes to a repository, it’s important to settle on a design before starting on the implementation. This way, we can make sure that major improvements get the care and attention they deserve. Since big changes can be risky and might not always get merged, it’s good to reduce the amount of possible wasted effort by agreeing on an implementation design/plan first. 64 | 65 | 1. **Open an issue.** Open an issue about your bug or feature, as described above. 66 | 2. **Reach consensus.** Some contributors and community members should reach an agreement that this feature or bug is important, and that someone should work on implementing or fixing it. 67 | 3. **Agree on intended behavior.** On the issue, reach an agreement about the desired behavior. In the case of a bug fix, it should be clear what it means for the bug to be fixed, and in the case of a feature, it should be clear what it will be like for developers to use the new feature. 68 | 4. **Agree on implementation plan.** Write a plan for how this feature or bug fix should be implemented. What modules need to be added or rewritten? Should this be one pull request or multiple incremental improvements? Who is going to do each part? 69 | 5. **Submit PR.** In the case where multiple dependent patches need to be made to implement the change, only submit one at a time. Otherwise, the others might get stale while the first is reviewed and merged. Make sure to avoid “while we’re here” type changes - if something isn’t relevant to the improvement at hand, it should be in a separate PR; this especially includes code style changes of unrelated code. 70 | 6. **Review.** At least one core contributor should sign off on the change before it’s merged. Look at the “code review” section below to learn about factors are important in the code review. If you want to expedite the code being merged, try to review your own code first! 71 | 7. **Merge and release!** 72 | 73 | ### Code review guidelines 74 | 75 | It’s important that every piece of code in Apollo packages is reviewed by at least one core contributor familiar with that codebase. Here are some things we look for: 76 | 77 | 1. **Required CI checks pass.** This is a prerequisite for the review, and it is the PR author's responsibility. As long as the tests don’t pass, the PR won't get reviewed. 78 | 2. **Simplicity.** Is this the simplest way to achieve the intended goal? If there are too many files, redundant functions, or complex lines of code, suggest a simpler way to do the same thing. In particular, avoid implementing an overly general solution when a simple, small, and pragmatic fix will do. 79 | 3. **Testing.** Do the tests ensure this code won’t break when other stuff changes around it? When it does break, will the tests added help us identify which part of the library has the problem? Did we cover an appropriate set of edge cases? Look at the test coverage report if there is one. Are all significant code paths in the new code exercised at least once? 80 | 4. **No unnecessary or unrelated changes.** PRs shouldn’t come with random formatting changes, especially in unrelated parts of the code. If there is some refactoring that needs to be done, it should be in a separate PR from a bug fix or feature, if possible. 81 | 5. **Code has appropriate comments.** Code should be commented, or written in a clear “self-documenting” way. 82 | 6. **Idiomatic use of the language.** In TypeScript, make sure the typings are specific and correct. In ES2015, make sure to use imports rather than require and const instead of var, etc. Ideally a linter enforces a lot of this, but use your common sense and follow the style of the surrounding code. 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-tag 2 | [![npm version](https://badge.fury.io/js/graphql-tag.svg)](https://badge.fury.io/js/graphql-tag) 3 | [![Build Status](https://travis-ci.org/apollographql/graphql-tag.svg?branch=master)](https://travis-ci.org/apollographql/graphql-tag) 4 | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](http://www.apollodata.com/#slack) 5 | 6 | Helpful utilities for parsing GraphQL queries. Includes: 7 | 8 | - `gql` A JavaScript template literal tag that parses GraphQL query strings into the standard GraphQL AST. 9 | - `/loader` A webpack loader to preprocess queries 10 | 11 | `graphql-tag` uses [the reference `graphql` library](https://github.com/graphql/graphql-js) under the hood as a peer dependency, so in addition to installing this module, you'll also have to install `graphql`. 12 | 13 | ### gql 14 | 15 | The `gql` template literal tag can be used to concisely write a GraphQL query that is parsed into a standard GraphQL AST. It is the recommended method for passing queries to [Apollo Client](https://github.com/apollographql/apollo-client). While it is primarily built for Apollo Client, it generates a generic GraphQL AST which can be used by any GraphQL client. 16 | 17 | ```js 18 | import gql from 'graphql-tag'; 19 | 20 | const query = gql` 21 | { 22 | user(id: 5) { 23 | firstName 24 | lastName 25 | } 26 | } 27 | ` 28 | ``` 29 | 30 | The above query now contains the following syntax tree. 31 | 32 | ```js 33 | { 34 | "kind": "Document", 35 | "definitions": [ 36 | { 37 | "kind": "OperationDefinition", 38 | "operation": "query", 39 | "name": null, 40 | "variableDefinitions": null, 41 | "directives": [], 42 | "selectionSet": { 43 | "kind": "SelectionSet", 44 | "selections": [ 45 | { 46 | "kind": "Field", 47 | "alias": null, 48 | "name": { 49 | "kind": "Name", 50 | "value": "user", 51 | ... 52 | } 53 | } 54 | ] 55 | } 56 | } 57 | ] 58 | } 59 | ``` 60 | 61 | #### Fragments 62 | 63 | The `gql` tag can also be used to define reusable fragments, which can easily be added to queries or other fragments. 64 | 65 | ```js 66 | import gql from 'graphql-tag'; 67 | 68 | const userFragment = gql` 69 | fragment User_user on User { 70 | firstName 71 | lastName 72 | } 73 | ` 74 | ``` 75 | 76 | The above `userFragment` document can be embedded in another document using a template literal placeholder. 77 | 78 | ```js 79 | const query = gql` 80 | { 81 | user(id: 5) { 82 | ...User_user 83 | } 84 | } 85 | ${userFragment} 86 | ` 87 | ``` 88 | 89 | **Note:** _While it may seem redundant to have to both embed the `userFragment` variable in the template literal **AND** spread the `...User_user` fragment in the graphQL selection set, this requirement makes static analysis by tools such as `eslint-plugin-graphql` possible._ 90 | 91 | #### Why use this? 92 | 93 | GraphQL strings are the right way to write queries in your code, because they can be statically analyzed using tools like [eslint-plugin-graphql](https://github.com/apollographql/eslint-plugin-graphql). However, strings are inconvenient to manipulate, if you are trying to do things like add extra fields, merge multiple queries together, or other interesting stuff. 94 | 95 | That's where this package comes in - it lets you write your queries with [ES2015 template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) and compile them into an AST with the `gql` tag. 96 | 97 | #### Caching parse results 98 | 99 | This package only has one feature - it caches previous parse results in a simple dictionary. This means that if you call the tag on the same query multiple times, it doesn't waste time parsing it again. It also means you can use `===` to compare queries to check if they are identical. 100 | 101 | 102 | ### Importing graphQL files 103 | 104 | _To add support for importing `.graphql`/`.gql` files, see [Webpack loading and preprocessing](#webpack-loading-and-preprocessing) below._ 105 | 106 | Given a file `MyQuery.graphql` 107 | 108 | ```graphql 109 | query MyQuery { 110 | ... 111 | } 112 | ``` 113 | 114 | If you have configured [the webpack graphql-tag/loader](#webpack-loading-and-preprocessing), you can import modules containing graphQL queries. The imported value will be the pre-built AST. 115 | 116 | ```js 117 | import MyQuery from 'query.graphql' 118 | ``` 119 | 120 | #### Importing queries by name 121 | 122 | You can also import query and fragment documents by name. 123 | 124 | ```graphql 125 | query MyQuery1 { 126 | ... 127 | } 128 | 129 | query MyQuery2 { 130 | ... 131 | } 132 | ``` 133 | 134 | And in your JavaScript: 135 | 136 | ```javascript 137 | import { MyQuery1, MyQuery2 } from 'query.graphql' 138 | ``` 139 | 140 | ### Preprocessing queries and fragments 141 | 142 | Preprocessing GraphQL queries and fragments into ASTs at build time can greatly improve load times. 143 | 144 | #### Babel preprocessing 145 | 146 | GraphQL queries can be compiled at build time using [babel-plugin-graphql-tag](https://github.com/gajus/babel-plugin-graphql-tag). Pre-compiling queries decreases script initialization time and reduces bundle sizes by potentially removing the need for `graphql-tag` at runtime. 147 | 148 | #### TypeScript preprocessing 149 | 150 | Try this custom transformer to pre-compile your GraphQL queries in TypeScript: [ts-transform-graphql-tag](https://github.com/firede/ts-transform-graphql-tag). 151 | 152 | #### React Native and Next.js preprocessing 153 | 154 | Preprocessing queries via the webpack loader is not always possible. [babel-plugin-import-graphql](https://www.npmjs.com/package/babel-plugin-import-graphql) supports importing graphql files directly into your JavaScript by preprocessing GraphQL queries into ASTs at compile-time. 155 | 156 | E.g.: 157 | 158 | ```javascript 159 | import myImportedQuery from './productsQuery.graphql' 160 | 161 | class ProductsPage extends React.Component { 162 | ... 163 | } 164 | ``` 165 | 166 | #### Webpack loading and preprocessing 167 | 168 | Using the included `graphql-tag/loader` it is possible to maintain query logic that is separate from the rest of your application logic. With the loader configured, imported graphQL files will be converted to AST during the webpack build process. 169 | 170 | _**Example webpack configuration**_ 171 | 172 | ```js 173 | { 174 | ... 175 | loaders: [ 176 | { 177 | test: /\.(graphql|gql)$/, 178 | exclude: /node_modules/, 179 | loader: 'graphql-tag/loader' 180 | } 181 | ], 182 | ... 183 | } 184 | ``` 185 | 186 | #### Create React App 187 | 188 | Preprocessing GraphQL imports is supported in **create-react-app** >= v2 using [evenchange4/graphql.macro](https://github.com/evenchange4/graphql.macro). 189 | 190 | For **create-react-app** < v2, you'll either need to eject or use [react-app-rewire-inline-import-graphql-ast](https://www.npmjs.com/package/react-app-rewire-inline-import-graphql-ast). 191 | 192 | #### Testing 193 | 194 | Testing environments that don't support Webpack require additional configuration. For [Jest](https://facebook.github.io/jest/) use [jest-transform-graphql](https://github.com/remind101/jest-transform-graphql). 195 | 196 | #### Support for fragments 197 | 198 | With the webpack loader, you can import fragments by name: 199 | 200 | In a file called `query.gql`: 201 | 202 | ```graphql 203 | fragment MyFragment1 on MyType1 { 204 | ... 205 | } 206 | 207 | fragment MyFragment2 on MyType2 { 208 | ... 209 | } 210 | ``` 211 | 212 | And in your JavaScript: 213 | 214 | ```javascript 215 | import { MyFragment1, MyFragment2 } from 'query.gql' 216 | ``` 217 | 218 | Note: If your fragment references other fragments, the resulting document will 219 | have multiple fragments in it. In this case you must still specify the fragment name when using the fragment. For example, with `@apollo/client` you would specify the `fragmentName` option when using the fragment for cache operations. 220 | 221 | ### Warnings 222 | 223 | This package will emit a warning if you have multiple fragments of the same name. You can disable this with: 224 | 225 | ```js 226 | import { disableFragmentWarnings } from 'graphql-tag'; 227 | 228 | disableFragmentWarnings() 229 | ``` 230 | 231 | ### Experimental Fragment Variables 232 | 233 | This package exports an `experimentalFragmentVariables` flag that allows you to use experimental support for [parameterized fragments](https://github.com/facebook/graphql/issues/204). 234 | 235 | You can enable / disable this with: 236 | 237 | ```js 238 | import { enableExperimentalFragmentVariables, disableExperimentalFragmentVariables } from 'graphql-tag'; 239 | ``` 240 | 241 | Enabling this feature allows you to declare documents of the form. 242 | 243 | ```graphql 244 | fragment SomeFragment ($arg: String!) on SomeType { 245 | someField 246 | } 247 | ``` 248 | 249 | ### Resources 250 | 251 | You can easily generate and explore a GraphQL AST on [astexplorer.net](https://astexplorer.net/#/drYr8X1rnP/1). 252 | -------------------------------------------------------------------------------- /loader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const os = require('os'); 4 | const gql = require('./main.js'); 5 | 6 | // Takes `source` (the source GraphQL query string) 7 | // and `doc` (the parsed GraphQL document) and tacks on 8 | // the imported definitions. 9 | function expandImports(source, doc) { 10 | const lines = source.split(/\r\n|\r|\n/); 11 | let outputCode = ` 12 | var names = {}; 13 | function unique(defs) { 14 | return defs.filter( 15 | function(def) { 16 | if (def.kind !== 'FragmentDefinition') return true; 17 | var name = def.name.value 18 | if (names[name]) { 19 | return false; 20 | } else { 21 | names[name] = true; 22 | return true; 23 | } 24 | } 25 | ) 26 | } 27 | `; 28 | 29 | lines.some((line) => { 30 | const result = line.match(/^#\s?import (.+)$/); 31 | if (result) { 32 | const importFile = result[1]; 33 | const parseDocument = `require(${importFile})`; 34 | const appendDef = `doc.definitions = doc.definitions.concat(unique(${parseDocument}.definitions));`; 35 | outputCode += appendDef + os.EOL; 36 | } 37 | return (line.length !== 0 && line[0] !== '#'); 38 | }); 39 | 40 | return outputCode; 41 | } 42 | 43 | module.exports = function(source) { 44 | this.cacheable(); 45 | const doc = gql`${source}`; 46 | let headerCode = ` 47 | var doc = ${JSON.stringify(doc)}; 48 | doc.loc.source = ${JSON.stringify(doc.loc.source)}; 49 | `; 50 | 51 | let outputCode = ""; 52 | 53 | // Allow multiple query/mutation definitions in a file. This parses out dependencies 54 | // at compile time, and then uses those at load time to create minimal query documents 55 | // We cannot do the latter at compile time due to how the #import code works. 56 | let operationCount = doc.definitions.reduce(function(accum, op) { 57 | if (op.kind === "OperationDefinition" || op.kind === "FragmentDefinition") { 58 | return accum + 1; 59 | } 60 | 61 | return accum; 62 | }, 0); 63 | 64 | if (operationCount < 1) { 65 | outputCode += ` 66 | module.exports = doc; 67 | ` 68 | } else { 69 | outputCode += ` 70 | // Collect any fragment/type references from a node, adding them to the refs Set 71 | function collectFragmentReferences(node, refs) { 72 | if (node.kind === "FragmentSpread") { 73 | refs.add(node.name.value); 74 | } else if (node.kind === "VariableDefinition") { 75 | var type = node.type; 76 | if (type.kind === "NamedType") { 77 | refs.add(type.name.value); 78 | } 79 | } 80 | 81 | if (node.selectionSet) { 82 | node.selectionSet.selections.forEach(function(selection) { 83 | collectFragmentReferences(selection, refs); 84 | }); 85 | } 86 | 87 | if (node.variableDefinitions) { 88 | node.variableDefinitions.forEach(function(def) { 89 | collectFragmentReferences(def, refs); 90 | }); 91 | } 92 | 93 | if (node.definitions) { 94 | node.definitions.forEach(function(def) { 95 | collectFragmentReferences(def, refs); 96 | }); 97 | } 98 | } 99 | 100 | var definitionRefs = {}; 101 | (function extractReferences() { 102 | doc.definitions.forEach(function(def) { 103 | if (def.name) { 104 | var refs = new Set(); 105 | collectFragmentReferences(def, refs); 106 | definitionRefs[def.name.value] = refs; 107 | } 108 | }); 109 | })(); 110 | 111 | function findOperation(doc, name) { 112 | for (var i = 0; i < doc.definitions.length; i++) { 113 | var element = doc.definitions[i]; 114 | if (element.name && element.name.value == name) { 115 | return element; 116 | } 117 | } 118 | } 119 | 120 | function oneQuery(doc, operationName) { 121 | // Copy the DocumentNode, but clear out the definitions 122 | var newDoc = { 123 | kind: doc.kind, 124 | definitions: [findOperation(doc, operationName)] 125 | }; 126 | if (doc.hasOwnProperty("loc")) { 127 | newDoc.loc = doc.loc; 128 | } 129 | 130 | // Now, for the operation we're running, find any fragments referenced by 131 | // it or the fragments it references 132 | var opRefs = definitionRefs[operationName] || new Set(); 133 | var allRefs = new Set(); 134 | var newRefs = new Set(); 135 | 136 | // IE 11 doesn't support "new Set(iterable)", so we add the members of opRefs to newRefs one by one 137 | opRefs.forEach(function(refName) { 138 | newRefs.add(refName); 139 | }); 140 | 141 | while (newRefs.size > 0) { 142 | var prevRefs = newRefs; 143 | newRefs = new Set(); 144 | 145 | prevRefs.forEach(function(refName) { 146 | if (!allRefs.has(refName)) { 147 | allRefs.add(refName); 148 | var childRefs = definitionRefs[refName] || new Set(); 149 | childRefs.forEach(function(childRef) { 150 | newRefs.add(childRef); 151 | }); 152 | } 153 | }); 154 | } 155 | 156 | allRefs.forEach(function(refName) { 157 | var op = findOperation(doc, refName); 158 | if (op) { 159 | newDoc.definitions.push(op); 160 | } 161 | }); 162 | 163 | return newDoc; 164 | } 165 | 166 | module.exports = doc; 167 | ` 168 | 169 | for (const op of doc.definitions) { 170 | if (op.kind === "OperationDefinition" || op.kind === "FragmentDefinition") { 171 | if (!op.name) { 172 | if (operationCount > 1) { 173 | throw new Error("Query/mutation names are required for a document with multiple definitions"); 174 | } else { 175 | continue; 176 | } 177 | } 178 | 179 | const opName = op.name.value; 180 | outputCode += ` 181 | module.exports["${opName}"] = oneQuery(doc, "${opName}"); 182 | ` 183 | } 184 | } 185 | } 186 | 187 | const importOutputCode = expandImports(source, doc); 188 | const allCode = headerCode + os.EOL + importOutputCode + os.EOL + outputCode + os.EOL; 189 | 190 | return allCode; 191 | }; 192 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | // For backwards compatibility, make sure require("graphql-tag") returns 2 | // the gql function, rather than an exports object. 3 | module.exports = require('./lib/graphql-tag.umd.js').gql; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-tag", 3 | "version": "2.12.6", 4 | "description": "A JavaScript template literal tag that parses GraphQL queries", 5 | "main": "./main.js", 6 | "module": "./lib/index.js", 7 | "jsnext:main": "./lib/index.js", 8 | "types": "./lib/index.d.ts", 9 | "sideEffects": false, 10 | "scripts": { 11 | "prebuild": "rimraf lib", 12 | "build": "tsc && rollup -c && npm run flow", 13 | "flow": "cp src/index.js.flow lib/graphql-tag.umd.js.flow", 14 | "test": "npm run test:ts3 && npm run test:ts4", 15 | "test:ts3": "npm i typescript@3.7.x graphql@15.x.x && npm run test:mocha", 16 | "test:ts4": "npm i typescript@4.x.x graphql@16.x.x && npm run test:mocha", 17 | "test:mocha": "npm run build && mocha lib/tests.cjs.js", 18 | "prepublish": "npm run build" 19 | }, 20 | "files": [ 21 | "lib/", 22 | "src/", 23 | "loader.js", 24 | "main.js" 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/apollographql/graphql-tag.git" 29 | }, 30 | "author": "", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/apollographql/graphql-tag/issues" 34 | }, 35 | "homepage": "https://github.com/apollographql/graphql-tag#readme", 36 | "dependencies": { 37 | "tslib": "^2.1.0" 38 | }, 39 | "devDependencies": { 40 | "@types/chai": "^4.2.14", 41 | "@types/mocha": "^8.2.0", 42 | "@types/node": "^18.0.6", 43 | "chai": "^4.2.0", 44 | "graphql": "^16.0.1", 45 | "mocha": "^9.0.1", 46 | "rimraf": "^3.0.2", 47 | "rollup": "^2.33.1", 48 | "rollup-plugin-sourcemaps": "^0.6.3", 49 | "source-map-support": "^0.5.19", 50 | "test-all-versions": "^5.0.1", 51 | "typescript": "^4.4.4" 52 | }, 53 | "peerDependencies": { 54 | "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" 55 | }, 56 | "engines": { 57 | "node": ">=10" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import sourceMapsPlugin from 'rollup-plugin-sourcemaps'; 2 | 3 | const globals = { 4 | tslib: 'tslib', 5 | graphql: 'graphql', 6 | chai: 'chai', 7 | 'source-map-support/register': 'sourceMapSupport', 8 | }; 9 | 10 | const { hasOwnProperty } = Object.prototype; 11 | function external(id) { 12 | return hasOwnProperty.call(globals, id); 13 | } 14 | 15 | export default [ 16 | { 17 | input: 'lib/index.js', 18 | plugins: [ 19 | sourceMapsPlugin(), 20 | ], 21 | external, 22 | output: [ 23 | { 24 | file: 'lib/graphql-tag.umd.js', 25 | format: 'umd', 26 | globals, 27 | name: 'graphql-tag', 28 | sourcemap: true, 29 | exports: 'named' 30 | } 31 | ], 32 | }, 33 | { 34 | input: 'lib/tests.js', 35 | plugins: [ 36 | sourceMapsPlugin(), 37 | ], 38 | external, 39 | output: [ 40 | { 41 | file: 'lib/tests.cjs.js', 42 | format: 'commonjs', 43 | globals, 44 | name: 'graphql-tag/tests', 45 | sourcemap: true, 46 | exports: 'named' 47 | } 48 | ], 49 | }, 50 | ]; 51 | -------------------------------------------------------------------------------- /src/index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { DocumentNode } from 'graphql'; 4 | 5 | declare export default function gql(literals: any, ...placeholders: any[]): DocumentNode; 6 | declare export function resetCaches(): void; 7 | declare export function disableFragmentWarnings(): void; 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'graphql'; 2 | 3 | import { 4 | DocumentNode, 5 | DefinitionNode, 6 | Location, 7 | } from 'graphql/language/ast'; 8 | 9 | // A map docString -> graphql document 10 | const docCache = new Map(); 11 | 12 | // A map fragmentName -> [normalized source] 13 | const fragmentSourceMap = new Map>(); 14 | 15 | let printFragmentWarnings = true; 16 | let experimentalFragmentVariables = false; 17 | 18 | // Strip insignificant whitespace 19 | // Note that this could do a lot more, such as reorder fields etc. 20 | function normalize(string: string) { 21 | return string.replace(/[\s,]+/g, ' ').trim(); 22 | } 23 | 24 | function cacheKeyFromLoc(loc: Location) { 25 | return normalize(loc.source.body.substring(loc.start, loc.end)); 26 | } 27 | 28 | // Take a unstripped parsed document (query/mutation or even fragment), and 29 | // check all fragment definitions, checking for name->source uniqueness. 30 | // We also want to make sure only unique fragments exist in the document. 31 | function processFragments(ast: DocumentNode) { 32 | const seenKeys = new Set(); 33 | const definitions: DefinitionNode[] = []; 34 | 35 | ast.definitions.forEach(fragmentDefinition => { 36 | if (fragmentDefinition.kind === 'FragmentDefinition') { 37 | var fragmentName = fragmentDefinition.name.value; 38 | var sourceKey = cacheKeyFromLoc(fragmentDefinition.loc!); 39 | 40 | // We know something about this fragment 41 | let sourceKeySet = fragmentSourceMap.get(fragmentName)!; 42 | if (sourceKeySet && !sourceKeySet.has(sourceKey)) { 43 | // this is a problem because the app developer is trying to register another fragment with 44 | // the same name as one previously registered. So, we tell them about it. 45 | if (printFragmentWarnings) { 46 | console.warn("Warning: fragment with name " + fragmentName + " already exists.\n" 47 | + "graphql-tag enforces all fragment names across your application to be unique; read more about\n" 48 | + "this in the docs: http://dev.apollodata.com/core/fragments.html#unique-names"); 49 | } 50 | } else if (!sourceKeySet) { 51 | fragmentSourceMap.set(fragmentName, sourceKeySet = new Set); 52 | } 53 | 54 | sourceKeySet.add(sourceKey); 55 | 56 | if (!seenKeys.has(sourceKey)) { 57 | seenKeys.add(sourceKey); 58 | definitions.push(fragmentDefinition); 59 | } 60 | } else { 61 | definitions.push(fragmentDefinition); 62 | } 63 | }); 64 | 65 | return { 66 | ...ast, 67 | definitions, 68 | }; 69 | } 70 | 71 | function stripLoc(doc: DocumentNode) { 72 | const workSet = new Set>(doc.definitions); 73 | 74 | workSet.forEach(node => { 75 | if (node.loc) delete node.loc; 76 | Object.keys(node).forEach(key => { 77 | const value = node[key]; 78 | if (value && typeof value === 'object') { 79 | workSet.add(value); 80 | } 81 | }); 82 | }); 83 | 84 | const loc = doc.loc as Record; 85 | if (loc) { 86 | delete loc.startToken; 87 | delete loc.endToken; 88 | } 89 | 90 | return doc; 91 | } 92 | 93 | function parseDocument(source: string) { 94 | var cacheKey = normalize(source); 95 | if (!docCache.has(cacheKey)) { 96 | const parsed = parse(source, { 97 | experimentalFragmentVariables, 98 | allowLegacyFragmentVariables: experimentalFragmentVariables, 99 | } as any); 100 | if (!parsed || parsed.kind !== 'Document') { 101 | throw new Error('Not a valid GraphQL document.'); 102 | } 103 | docCache.set( 104 | cacheKey, 105 | // check that all "new" fragments inside the documents are consistent with 106 | // existing fragments of the same name 107 | stripLoc(processFragments(parsed)), 108 | ); 109 | } 110 | return docCache.get(cacheKey)!; 111 | } 112 | 113 | // XXX This should eventually disallow arbitrary string interpolation, like Relay does 114 | export function gql( 115 | literals: string | readonly string[], 116 | ...args: any[] 117 | ) { 118 | 119 | if (typeof literals === 'string') { 120 | literals = [literals]; 121 | } 122 | 123 | let result = literals[0]; 124 | 125 | args.forEach((arg, i) => { 126 | if (arg && arg.kind === 'Document') { 127 | result += arg.loc.source.body; 128 | } else { 129 | result += arg; 130 | } 131 | result += literals[i + 1]; 132 | }); 133 | 134 | return parseDocument(result); 135 | } 136 | 137 | export function resetCaches() { 138 | docCache.clear(); 139 | fragmentSourceMap.clear(); 140 | } 141 | 142 | export function disableFragmentWarnings() { 143 | printFragmentWarnings = false; 144 | } 145 | 146 | export function enableExperimentalFragmentVariables() { 147 | experimentalFragmentVariables = true; 148 | } 149 | 150 | export function disableExperimentalFragmentVariables() { 151 | experimentalFragmentVariables = false; 152 | } 153 | 154 | const extras = { 155 | gql, 156 | resetCaches, 157 | disableFragmentWarnings, 158 | enableExperimentalFragmentVariables, 159 | disableExperimentalFragmentVariables, 160 | }; 161 | 162 | export namespace gql { 163 | export const { 164 | gql, 165 | resetCaches, 166 | disableFragmentWarnings, 167 | enableExperimentalFragmentVariables, 168 | disableExperimentalFragmentVariables, 169 | } = extras; 170 | } 171 | 172 | gql.default = gql; 173 | 174 | export default gql; 175 | -------------------------------------------------------------------------------- /src/tests.ts: -------------------------------------------------------------------------------- 1 | import 'source-map-support/register'; 2 | 3 | import { assert } from 'chai'; 4 | import { DocumentNode, FragmentDefinitionNode } from 'graphql'; 5 | 6 | import gql from './index'; 7 | const loader = require('../loader'); 8 | 9 | describe('gql', () => { 10 | it('parses queries', () => { 11 | assert.equal(gql`{ testQuery }`.kind, 'Document'); 12 | }); 13 | 14 | it('parses queries when called as a function', () => { 15 | assert.equal(gql('{ testQuery }').kind, 'Document'); 16 | }); 17 | 18 | it('parses queries with weird substitutions', () => { 19 | const obj = Object.create(null); 20 | assert.equal(gql`{ field(input: "${obj.missing}") }`.kind, 'Document'); 21 | assert.equal(gql`{ field(input: "${null}") }`.kind, 'Document'); 22 | assert.equal(gql`{ field(input: "${0}") }`.kind, 'Document'); 23 | }); 24 | 25 | it('allows interpolation of documents generated by the webpack loader', () => { 26 | const sameFragment = "fragment SomeFragmentName on SomeType { someField }"; 27 | 28 | const jsSource = loader.call( 29 | { cacheable() {} }, 30 | sameFragment, 31 | ); 32 | const module = { exports: Object.create(null) }; 33 | 34 | Function("module", jsSource)(module); 35 | 36 | const document = gql`query { ...SomeFragmentName } ${module.exports}`; 37 | assert.equal(document.kind, 'Document'); 38 | assert.equal(document.definitions.length, 2); 39 | assert.equal(document.definitions[0].kind, 'OperationDefinition'); 40 | assert.equal(document.definitions[1].kind, 'FragmentDefinition'); 41 | }); 42 | 43 | it('parses queries through webpack loader', () => { 44 | const jsSource = loader.call({ cacheable() {} }, '{ testQuery }'); 45 | const module = { exports: Object.create(null) }; 46 | Function("module", jsSource)(module); 47 | assert.equal(module.exports.kind, 'Document'); 48 | }); 49 | 50 | it('parses single query through webpack loader', () => { 51 | const jsSource = loader.call({ cacheable() {} }, ` 52 | query Q1 { testQuery } 53 | `); 54 | const module = { exports: Object.create(null) }; 55 | Function("module", jsSource)(module); 56 | 57 | assert.equal(module.exports.kind, 'Document'); 58 | assert.exists(module.exports.Q1); 59 | assert.equal(module.exports.Q1.kind, 'Document'); 60 | assert.equal(module.exports.Q1.definitions.length, 1); 61 | }); 62 | 63 | it('parses single query and exports as default', () => { 64 | const jsSource = loader.call({ cacheable() {} }, ` 65 | query Q1 { testQuery } 66 | `); 67 | const module = { exports: Object.create(null) }; 68 | Function("module", jsSource)(module); 69 | assert.deepEqual(module.exports.definitions, module.exports.Q1.definitions); 70 | }); 71 | 72 | it('parses multiple queries through webpack loader', () => { 73 | const jsSource = loader.call({ cacheable() {} }, ` 74 | query Q1 { testQuery } 75 | query Q2 { testQuery2 } 76 | `); 77 | const module = { exports: Object.create(null) }; 78 | Function("module", jsSource)(module); 79 | 80 | assert.exists(module.exports.Q1); 81 | assert.exists(module.exports.Q2); 82 | assert.equal(module.exports.Q1.kind, 'Document'); 83 | assert.equal(module.exports.Q2.kind, 'Document'); 84 | assert.equal(module.exports.Q1.definitions.length, 1); 85 | assert.equal(module.exports.Q2.definitions.length, 1); 86 | }); 87 | 88 | it('parses fragments with variable definitions', () => { 89 | gql.enableExperimentalFragmentVariables(); 90 | 91 | const parsed: any = gql`fragment A ($arg: String!) on Type { testQuery }`; 92 | assert.equal(parsed.kind, 'Document'); 93 | assert.exists(parsed.definitions[0].variableDefinitions); 94 | 95 | gql.disableExperimentalFragmentVariables() 96 | }); 97 | 98 | // see https://github.com/apollographql/graphql-tag/issues/168 99 | it('does not nest queries needlessly in named exports', () => { 100 | const jsSource = loader.call({ cacheable() {} }, ` 101 | query Q1 { testQuery } 102 | query Q2 { testQuery2 } 103 | query Q3 { test Query3 } 104 | `); 105 | const module = { exports: Object.create(null) }; 106 | Function("module", jsSource)(module); 107 | 108 | assert.notExists(module.exports.Q2.Q1); 109 | assert.notExists(module.exports.Q3.Q1); 110 | assert.notExists(module.exports.Q3.Q2); 111 | }); 112 | 113 | it('tracks fragment dependencies from multiple queries through webpack loader', () => { 114 | const jsSource = loader.call({ cacheable() {} }, ` 115 | fragment F1 on F { testQuery } 116 | fragment F2 on F { testQuery2 } 117 | fragment F3 on F { testQuery3 } 118 | query Q1 { ...F1 } 119 | query Q2 { ...F2 } 120 | query Q3 { 121 | ...F1 122 | ...F2 123 | } 124 | `); 125 | const module = { exports: Object.create(null) }; 126 | Function("module", jsSource)(module); 127 | 128 | assert.exists(module.exports.Q1); 129 | assert.exists(module.exports.Q2); 130 | assert.exists(module.exports.Q3); 131 | const Q1 = module.exports.Q1.definitions; 132 | const Q2 = module.exports.Q2.definitions; 133 | const Q3 = module.exports.Q3.definitions; 134 | 135 | assert.equal(Q1.length, 2); 136 | assert.equal(Q1[0].name.value, 'Q1'); 137 | assert.equal(Q1[1].name.value, 'F1'); 138 | 139 | assert.equal(Q2.length, 2); 140 | assert.equal(Q2[0].name.value, 'Q2'); 141 | assert.equal(Q2[1].name.value, 'F2'); 142 | 143 | assert.equal(Q3.length, 3); 144 | assert.equal(Q3[0].name.value, 'Q3'); 145 | assert.equal(Q3[1].name.value, 'F1'); 146 | assert.equal(Q3[2].name.value, 'F2'); 147 | 148 | const F1 = module.exports.F1.definitions; 149 | const F2 = module.exports.F2.definitions; 150 | const F3 = module.exports.F3.definitions; 151 | 152 | assert.equal(F1.length, 1); 153 | assert.equal(F1[0].name.value, 'F1'); 154 | assert.equal(F2.length, 1); 155 | assert.equal(F2[0].name.value, 'F2'); 156 | assert.equal(F3.length, 1); 157 | assert.equal(F3[0].name.value, 'F3'); 158 | 159 | }); 160 | 161 | it('tracks fragment dependencies across nested fragments', () => { 162 | const jsSource = loader.call({ cacheable() {} }, ` 163 | fragment F11 on F { testQuery } 164 | fragment F22 on F { 165 | ...F11 166 | testQuery2 167 | } 168 | fragment F33 on F { 169 | ...F22 170 | testQuery3 171 | } 172 | 173 | query Q1 { 174 | ...F33 175 | } 176 | 177 | query Q2 { 178 | id 179 | } 180 | `); 181 | 182 | const module = { exports: Object.create(null) }; 183 | Function("module", jsSource)(module); 184 | 185 | assert.exists(module.exports.Q1); 186 | assert.exists(module.exports.Q2); 187 | 188 | const Q1 = module.exports.Q1.definitions; 189 | const Q2 = module.exports.Q2.definitions; 190 | 191 | assert.equal(Q1.length, 4); 192 | assert.equal(Q1[0].name.value, 'Q1'); 193 | assert.equal(Q1[1].name.value, 'F33'); 194 | assert.equal(Q1[2].name.value, 'F22'); 195 | assert.equal(Q1[3].name.value, 'F11'); 196 | 197 | assert.equal(Q2.length, 1); 198 | 199 | const F11 = module.exports.F11.definitions; 200 | const F22 = module.exports.F22.definitions; 201 | const F33 = module.exports.F33.definitions; 202 | 203 | assert.equal(F11.length, 1); 204 | assert.equal(F11[0].name.value, 'F11'); 205 | assert.equal(F22.length, 2); 206 | assert.equal(F22[0].name.value, 'F22'); 207 | assert.equal(F22[1].name.value, 'F11'); 208 | assert.equal(F33.length, 3); 209 | assert.equal(F33[0].name.value, 'F33'); 210 | assert.equal(F33[1].name.value, 'F22'); 211 | assert.equal(F33[2].name.value, 'F11'); 212 | }); 213 | 214 | it('correctly imports other files through the webpack loader', () => { 215 | const query = `#import "./fragment_definition.graphql" 216 | query { 217 | author { 218 | ...authorDetails 219 | } 220 | }`; 221 | const jsSource = loader.call({ cacheable() {} }, query); 222 | const module = { exports: Object.create(null) }; 223 | const require = (path: string) => { 224 | assert.equal(path, './fragment_definition.graphql'); 225 | return gql` 226 | fragment authorDetails on Author { 227 | firstName 228 | lastName 229 | }`; 230 | }; 231 | Function("module,require", jsSource)(module, require); 232 | assert.equal(module.exports.kind, 'Document'); 233 | const definitions = module.exports.definitions; 234 | assert.equal(definitions.length, 2); 235 | assert.equal(definitions[0].kind, 'OperationDefinition'); 236 | assert.equal(definitions[1].kind, 'FragmentDefinition'); 237 | }); 238 | 239 | it('importing files also works with a space between `#` and `import`', () => { 240 | const query = `# import "./fragment_definition.graphql" 241 | query { 242 | author { 243 | ...authorDetails 244 | } 245 | }`; 246 | const jsSource = loader.call({ cacheable() {} }, query); 247 | const module = { exports: Object.create(null) }; 248 | const require = (path: string) => { 249 | assert.equal(path, './fragment_definition.graphql'); 250 | return gql` 251 | fragment authorDetails on Author { 252 | firstName 253 | lastName 254 | }`; 255 | }; 256 | Function("module,require", jsSource)(module, require); 257 | assert.equal(module.exports.kind, 'Document'); 258 | const definitions = module.exports.definitions; 259 | assert.equal(definitions.length, 2); 260 | assert.equal(definitions[0].kind, 'OperationDefinition'); 261 | assert.equal(definitions[1].kind, 'FragmentDefinition'); 262 | }); 263 | 264 | it('tracks fragment dependencies across fragments loaded via the webpack loader', () => { 265 | const query = `#import "./fragment_definition.graphql" 266 | fragment F111 on F { 267 | ...F222 268 | } 269 | 270 | query Q1 { 271 | ...F111 272 | } 273 | 274 | query Q2 { 275 | a 276 | } 277 | `; 278 | const jsSource = loader.call({ cacheable() {} }, query); 279 | const module = { exports: Object.create(null) }; 280 | const require = (path: string) => { 281 | assert.equal(path, './fragment_definition.graphql'); 282 | return gql` 283 | fragment F222 on F { 284 | f1 285 | f2 286 | }`; 287 | }; 288 | Function("module,require", jsSource)(module, require); 289 | 290 | assert.exists(module.exports.Q1); 291 | assert.exists(module.exports.Q2); 292 | 293 | const Q1 = module.exports.Q1.definitions; 294 | const Q2 = module.exports.Q2.definitions; 295 | 296 | assert.equal(Q1.length, 3); 297 | assert.equal(Q1[0].name.value, 'Q1'); 298 | assert.equal(Q1[1].name.value, 'F111'); 299 | assert.equal(Q1[2].name.value, 'F222'); 300 | 301 | assert.equal(Q2.length, 1); 302 | }); 303 | 304 | it('does not complain when presented with normal comments', (done) => { 305 | assert.doesNotThrow(() => { 306 | const query = `#normal comment 307 | query { 308 | author { 309 | ...authorDetails 310 | } 311 | }`; 312 | const jsSource = loader.call({ cacheable() {} }, query); 313 | const module = { exports: Object.create(null) }; 314 | Function("module", jsSource)(module); 315 | assert.equal(module.exports.kind, 'Document'); 316 | done(); 317 | }); 318 | }); 319 | 320 | it('returns the same object for the same query', () => { 321 | assert.isTrue(gql`{ sameQuery }` === gql`{ sameQuery }`); 322 | }); 323 | 324 | it('returns the same object for the same query, even with whitespace differences', () => { 325 | assert.isTrue(gql`{ sameQuery }` === gql` { sameQuery, }`); 326 | }); 327 | 328 | const fragmentAst = gql` 329 | fragment UserFragment on User { 330 | firstName 331 | lastName 332 | } 333 | `; 334 | 335 | it('returns the same object for the same fragment', () => { 336 | assert.isTrue(gql`fragment same on Same { sameQuery }` === 337 | gql`fragment same on Same { sameQuery }`); 338 | }); 339 | 340 | it('returns the same object for the same document with substitution', () => { 341 | // We know that calling `gql` on a fragment string will always return 342 | // the same document, so we can reuse `fragmentAst` 343 | assert.isTrue(gql`{ ...UserFragment } ${fragmentAst}` === 344 | gql`{ ...UserFragment } ${fragmentAst}`); 345 | }); 346 | 347 | it('can reference a fragment that references as fragment', () => { 348 | const secondFragmentAst = gql` 349 | fragment SecondUserFragment on User { 350 | ...UserFragment 351 | } 352 | ${fragmentAst} 353 | `; 354 | 355 | const ast = gql` 356 | { 357 | user(id: 5) { 358 | ...SecondUserFragment 359 | } 360 | } 361 | ${secondFragmentAst} 362 | `; 363 | 364 | assert.deepEqual(ast, gql` 365 | { 366 | user(id: 5) { 367 | ...SecondUserFragment 368 | } 369 | } 370 | fragment SecondUserFragment on User { 371 | ...UserFragment 372 | } 373 | fragment UserFragment on User { 374 | firstName 375 | lastName 376 | } 377 | `); 378 | }); 379 | 380 | describe('fragment warnings', () => { 381 | let warnings = []; 382 | const oldConsoleWarn = console.warn; 383 | beforeEach(() => { 384 | gql.resetCaches(); 385 | warnings = []; 386 | console.warn = (w: string) => warnings.push(w); 387 | }); 388 | afterEach(() => { 389 | console.warn = oldConsoleWarn; 390 | }); 391 | 392 | it('warns if you use the same fragment name for different fragments', () => { 393 | const frag1 = gql`fragment TestSame on Bar { fieldOne }`; 394 | const frag2 = gql`fragment TestSame on Bar { fieldTwo }`; 395 | 396 | assert.isFalse(frag1 === frag2); 397 | assert.equal(warnings.length, 1); 398 | }); 399 | 400 | it('does not warn if you use the same fragment name for the same fragment', () => { 401 | const frag1 = gql`fragment TestDifferent on Bar { fieldOne }`; 402 | const frag2 = gql`fragment TestDifferent on Bar { fieldOne }`; 403 | 404 | assert.isTrue(frag1 === frag2); 405 | assert.equal(warnings.length, 0); 406 | }); 407 | 408 | it('does not warn if you use the same embedded fragment in two different queries', () => { 409 | const frag1 = gql`fragment TestEmbedded on Bar { field }`; 410 | const query1 = gql`{ bar { fieldOne ...TestEmbedded } } ${frag1}`; 411 | const query2 = gql`{ bar { fieldTwo ...TestEmbedded } } ${frag1}`; 412 | 413 | assert.isFalse(query1 === query2); 414 | assert.equal(warnings.length, 0); 415 | }); 416 | 417 | it('does not warn if you use the same fragment name for embedded and non-embedded fragments', () => { 418 | const frag1 = gql`fragment TestEmbeddedTwo on Bar { field }`; 419 | gql`{ bar { ...TestEmbedded } } ${frag1}`; 420 | gql`{ bar { ...TestEmbedded } } fragment TestEmbeddedTwo on Bar { field }`; 421 | 422 | assert.equal(warnings.length, 0); 423 | }); 424 | }); 425 | 426 | describe('unique fragments', () => { 427 | beforeEach(() => { 428 | gql.resetCaches(); 429 | }); 430 | 431 | it('strips duplicate fragments from the document', () => { 432 | const frag1 = gql`fragment TestDuplicate on Bar { field }`; 433 | const query1 = gql`{ bar { fieldOne ...TestDuplicate } } ${frag1} ${frag1}`; 434 | const query2 = gql`{ bar { fieldOne ...TestDuplicate } } ${frag1}`; 435 | 436 | assert.equal(query1.definitions.length, 2); 437 | assert.equal(query1.definitions[1].kind, 'FragmentDefinition'); 438 | // We don't test strict equality between the two queries because the source.body parsed from the 439 | // document is not the same, but the set of definitions should be. 440 | assert.deepEqual(query1.definitions, query2.definitions); 441 | }); 442 | 443 | it('ignores duplicate fragments from second-level imports when using the webpack loader', () => { 444 | // take a require function and a query string, use the webpack loader to process it 445 | const load = ( 446 | require: (path: string) => DocumentNode | null, 447 | query: string, 448 | ): DocumentNode | null => { 449 | const jsSource = loader.call({ cacheable() {} }, query); 450 | const module = { exports: Object.create(null) }; 451 | Function("require,module", jsSource)(require, module); 452 | return module.exports; 453 | } 454 | 455 | const test_require = (path: string) => { 456 | switch (path) { 457 | case './friends.graphql': 458 | return load(test_require, [ 459 | '#import "./person.graphql"', 460 | 'fragment friends on Hero { friends { ...person } }', 461 | ].join('\n')); 462 | case './enemies.graphql': 463 | return load(test_require, [ 464 | '#import "./person.graphql"', 465 | 'fragment enemies on Hero { enemies { ...person } }', 466 | ].join('\n')); 467 | case './person.graphql': 468 | return load(test_require, 'fragment person on Person { name }\n'); 469 | default: 470 | return null; 471 | }; 472 | }; 473 | 474 | const result = load(test_require, [ 475 | '#import "./friends.graphql"', 476 | '#import "./enemies.graphql"', 477 | 'query { hero { ...friends ...enemies } }', 478 | ].join('\n'))!; 479 | 480 | assert.equal(result.kind, 'Document'); 481 | assert.equal(result.definitions.length, 4, 'after deduplication, only 4 fragments should remain'); 482 | assert.equal(result.definitions[0].kind, 'OperationDefinition'); 483 | 484 | // the rest of the definitions should be fragments and contain one of 485 | // each: "friends", "enemies", "person". Order does not matter 486 | const fragments = result.definitions.slice(1) as FragmentDefinitionNode[]; 487 | assert(fragments.every(fragment => fragment.kind === 'FragmentDefinition')) 488 | assert(fragments.some(fragment => fragment.name.value === 'friends')) 489 | assert(fragments.some(fragment => fragment.name.value === 'enemies')) 490 | assert(fragments.some(fragment => fragment.name.value === 'person')) 491 | }); 492 | }); 493 | 494 | // How to make this work? 495 | // it.only('can reference a fragment passed as a document via shorthand', () => { 496 | // const ast = gql` 497 | // { 498 | // user(id: 5) { 499 | // ...${userFragmentDocument} 500 | // } 501 | // } 502 | // `; 503 | // 504 | // assert.deepEqual(ast, gql` 505 | // { 506 | // user(id: 5) { 507 | // ...UserFragment 508 | // } 509 | // } 510 | // fragment UserFragment on User { 511 | // firstName 512 | // lastName 513 | // } 514 | // `); 515 | // }); 516 | }); 517 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "noImplicitAny": true, 5 | "strictNullChecks": true, 6 | "noUnusedParameters": false, 7 | "noUnusedLocals": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "node", 10 | "importHelpers": true, 11 | "removeComments": true, 12 | "sourceMap": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "module": "es2015", 16 | "esModuleInterop": true, 17 | "lib": ["es2015", "dom"], 18 | "types": ["node", "chai", "mocha"] 19 | }, 20 | "include": ["src/**/*.ts"] 21 | } 22 | --------------------------------------------------------------------------------