Skip to content

Commit

Permalink
fix: webpack resolutions for #882, add webpack example
Browse files Browse the repository at this point in the history
  • Loading branch information
acao committed Nov 20, 2019
1 parent aa8d8bb commit ea9df3e
Show file tree
Hide file tree
Showing 13 changed files with 2,246 additions and 75 deletions.
Empty file.
17 changes: 17 additions & 0 deletions packages/examples/graphiql-webpack/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
sourceMaps: true,
presets: [
[
require.resolve('@babel/preset-env'),
{
targets: 'latest 2 versions',
modules: false,
},
],
require.resolve('@babel/preset-react'),
],
plugins: [
require.resolve('@babel/plugin-syntax-dynamic-import'),
require.resolve('@babel/plugin-proposal-class-properties'),
],
};
32 changes: 32 additions & 0 deletions packages/examples/graphiql-webpack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "graphiql-example-webpack",
"version": "0.0.4",
"private": true,
"license": "MIT",
"description": "A GraphiQL example with webpack and typescript",
"scripts": {
"build-demo": "webpack",
"start": "webpack-dev-server --watch"
},
"dependencies": {
"express": "^4.17.1",
"express-graphql": "^0.9.0",
"graphiql": "file:../../graphiql",
"graphql": "14.5.6",
"react": "16.10.2"
},
"devDependencies": {
"babel-loader": "^8.0.6",
"html-webpack-plugin": "^3.2.0",
"react-dom": "^16.11.0",
"style-loader": "^1.0.0",
"css-loader": "3.2.0",
"webpack": "^4.41.0",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.9.0",
"@babel/plugin-syntax-dynamic-import": "7.2.0",
"@babel/plugin-proposal-class-properties": "7.7.0",
"@babel/preset-react": "7.7.0",
"@babel/preset-env": "7.7.1"
}
}
25 changes: 25 additions & 0 deletions packages/examples/graphiql-webpack/src/index.html.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>GraphiQL Webpack Example!</title>
</head>

<body>
<style>
body {
padding: 0;
margin: 0;
min-height: 100vh;
}
#root {
height: 100vh;
}
</style>
<div id="root"></div>
</body>
</html>
24 changes: 24 additions & 0 deletions packages/examples/graphiql-webpack/src/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { render } from 'react-dom';
import GraphiQL from 'graphiql';
import 'graphiql/graphiql.css';

const App = () => (
<GraphiQL
style={{ height: '100vh' }}
fetcher={async graphQLParams => {
const data = await fetch('https://swapi.graph.cool', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(graphQLParams),
credentials: 'include',
});
return data.json();
}}
/>
);

render(<App />, document.getElementById('root'));
70 changes: 70 additions & 0 deletions packages/examples/graphiql-webpack/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const isDev = process.env.NODE_ENV === 'development';

module.exports = {
entry: isDev
? [
'react-hot-loader/patch', // activate HMR for React
'webpack-dev-server/client?http://localhost:8080', // bundle the client for webpack-dev-server and connect to the provided endpoint
'webpack/hot/only-dev-server', // bundle the client for hot reloading, only- means to only hot reload for successful updates
'./index.jsx', // the entry point of our app
]
: './index.jsx',
context: path.resolve(__dirname, './src'),
mode: 'development',
devtool: 'inline-source-map',
performance: {
hints: false,
},
module: {
rules: [
{
test: /\.html$/,
use: ['file?name=[name].[ext]'],
},
// for graphql module, which uses mjs still
{
type: 'javascript/auto',
test: /\.mjs$/,
use: [],
include: /node_modules/,
},
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { modules: false }],
'@babel/preset-react',
],
},
},
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.svg$/,
use: [{ loader: 'svg-inline-loader' }],
},
],
},
resolve: {
extensions: ['.js', '.json', '.jsx', '.css', '.mjs'],
},
plugins: [new HtmlWebpackPlugin({ template: 'index.html.ejs' })],
devServer: {
hot: true,
// bypass simple localhost CORS restrictions by setting
// these to 127.0.0.1 in /etc/hosts
allowedHosts: ['local.test.com', 'graphiql.com'],
},
node: {
fs: 'empty',
},
};
3 changes: 3 additions & 0 deletions packages/graphiql/cypress/integration/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"printWidth": 120
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
getDefinitionQueryResultForDefinitionNode,
getDefinitionQueryResultForNamedType,
} from './getDefinition';
import { getASTNodeAtPosition } from 'graphql-language-service-utils';

import { getASTNodeAtPosition, requireFile, resolveFile } from 'graphql-language-service-utils';

const {
FRAGMENT_DEFINITION,
Expand Down Expand Up @@ -159,13 +160,15 @@ export class GraphQLLanguageService {
const customRulesModulePath = extensions.customValidationRules;
if (customRulesModulePath) {
/* eslint-disable no-implicit-coercion */
const rulesPath = require.resolve(`${customRulesModulePath}`);
const rulesPath = resolveFile(customRulesModulePath);
if (rulesPath) {
customRules = require(`${rulesPath}`)(this._graphQLConfig);
const customValidationRules = await requireFile(rulesPath);
if (customValidationRules) {
customRules = customValidationRules(this._graphQLConfig);
}
}
/* eslint-enable no-implicit-coercion */
}

const schema = await this._graphQLCache
.getSchema(projectName, queryHasExtensions)
.catch(() => null);
Expand Down
4 changes: 4 additions & 0 deletions packages/graphql-language-service-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
],
"main": "dist/index.js",
"types": "src/index.ts",
"engines": {
"node": ">= 9.7.3"
},
"engineStrict": true,
"scripts": {
"test": "node ../../resources/runTests.js",
"build": "yarn run build-ts && yarn run build-flow",
Expand Down
90 changes: 90 additions & 0 deletions packages/graphql-language-service-utils/src/file.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import path from 'path';

import {
getFileExtension,
getPathWithoutExtension,
resolveFile,
requireFile,
} from './file';

describe('getFileExtension', () => {
it('should resolve an extension', () => {
const extension = getFileExtension('example/example.txt');
expect(extension).toEqual('txt');
});
it('should resolve null when no extension is present', () => {
const extension = getFileExtension('example/example');
expect(extension).toEqual(null);
});

it('should return an extension with multiple dots in the path', () => {
const extension = getFileExtension(
'example.example/example/something.esm.js',
);
expect(extension).toEqual('js');
});
});

describe('getPathWithoutExtension', () => {
it('should resolve when path has extension', () => {
const extension = getPathWithoutExtension('example/example.txt', 'txt');
expect(extension).toEqual('example/example');
});
it('should resolve when path has no extension', () => {
const extension = getPathWithoutExtension(
'example/example.example/example',
null,
);
expect(extension).toEqual('example/example.example/example');
});
});

describe('requireFile', () => {
it('should require file with extension', async () => {
const file = await requireFile('../package.json');
expect(file.name).toEqual('graphql-language-service-interface');
});
it('should fail when requiring an invalid extension', () => {
expect(() => requireFile('../.npmignore')).toThrowError(
`cannot require() module with extension 'npmignore'`,
);
});
it('should require file with no extension using js', async () => {
const config = await requireFile(path.join(__dirname, '../../../jest.config'));
await expect(config.collectCoverage).toEqual(true);
});
it('should require file with no extension using json', async () => {
const file = await requireFile(path.join(__dirname, '../package'));
expect(file.name).toEqual('graphql-language-service-interface');
});
});

describe('resolveFile', () => {
it('should resolve when path has extension', () => {
const resolvedPath = resolveFile('../package.json');
expect(resolvedPath).toEqual(
require.resolve(path.join(__dirname, '../package.json')),
);
});

it('should resolve when path has extension', () => {
const resolvedPath = resolveFile(path.join(__dirname, '../package'));
expect(resolvedPath).toEqual(
require.resolve(path.join(__dirname, '../package.json')),
);
});
it('should resolve when path has extension', () => {
const resolvedPath = resolveFile('../../../.eslintrc.js');
expect(resolvedPath).toEqual(
require.resolve(path.join(__dirname, '../../../.eslintrc.js')),
);
});
it('should resolve when path has no extension', () => {
const resolvedPath = resolveFile(
path.join(__dirname, '../../../.eslintrc'),
);
expect(resolvedPath).toEqual(
require.resolve(path.join(__dirname, '../../../.eslintrc.js')),
);
});
});
81 changes: 81 additions & 0 deletions packages/graphql-language-service-utils/src/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import fs from 'fs';

export function getFileExtension(filePath: string): string | null {
const pathParts = /^.+\.([^.]+)$/.exec(filePath);
// if there's a file extension
if (pathParts && pathParts.length > 1) {
return pathParts[1];
}
return null;
}

export function getPathWithoutExtension(
filePath: string,
extension: string | null,
) {
let pathWithoutExtension = filePath;
if (extension) {
pathWithoutExtension = filePath.substr(
0,
filePath.length - (extension.length + 1),
);
}
return pathWithoutExtension;
}

// these make webpack happy

export function resolveFile(filePath: string) {
const extension = getFileExtension(filePath);
const pathWithoutExtension = getPathWithoutExtension(filePath, extension);
switch (extension) {
case 'js': {
return require.resolve(pathWithoutExtension + '.js');
}
case 'json': {
return require.resolve(pathWithoutExtension + '.json');
}
default: {
if (fs.existsSync(filePath + `.js`)) {
return require.resolve(filePath + '.js');
}
if (fs.existsSync(filePath + `.json`)) {
return require.resolve(filePath + '.json');
}
if (extension) {
throw Error(
`cannot require.resolve() module with extension '${extension}'`,
);
}
}
}
}

// again, explicit with the extensions
// dynamic imports, aka import(packageName).then()
// is available in node 9.7+, and most modern browsers

export function requireFile(filePath: string) {
const extension = getFileExtension(filePath);
const pathWithoutExtension = getPathWithoutExtension(filePath, extension);
switch (extension) {
case 'js': {
return import(pathWithoutExtension + '.js');
}
case 'json': {
return import(pathWithoutExtension + '.json');
}
default: {
if (fs.existsSync(filePath + `.js`)) {
return import(filePath + '.js');
}
if (fs.existsSync(filePath + `.json`)) {
return import(filePath + '.json');
}
if (extension) {
throw Error(`cannot require() module with extension '${extension}'`);
}
throw Error(`No extension found, and no supported file found to match '${filePath}'`);
}
}
}
2 changes: 2 additions & 0 deletions packages/graphql-language-service-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export { getASTNodeAtPosition, pointToOffset } from './getASTNodeAtPosition';
export { Position, Range, locToRange, offsetToPosition } from './Range';

export { validateWithCustomRules } from './validateWithCustomRules';

export { requireFile, resolveFile } from './file';
Loading

0 comments on commit ea9df3e

Please sign in to comment.