mirror of
https://github.com/wassname/talk.git
synced 2026-06-27 18:07:26 +08:00
[next] Add support for embed (#1762)
* Move talk-server/config to talk-common/config * Refactor build into /src/core/build and use common config * Add embed webpack config * Start implementing embed * Implement embed * Add pym types * Add event emitter to Talk Context * Add MatchMedia test for passing values from the context * Add support for click far away * Integrate pym click events to registerClickFarAway * Add tests * Resolve merge issues * Apply PR review
This commit is contained in:
@@ -1,93 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const paths = require("./paths");
|
||||
|
||||
// Make sure that including paths.js after env.js will read .env variables.
|
||||
delete require.cache[require.resolve("./paths")];
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
if (!NODE_ENV) {
|
||||
throw new Error(
|
||||
"The NODE_ENV environment variable is required but was not specified."
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
|
||||
var dotenvFiles = [
|
||||
`${paths.dotenv}.${NODE_ENV}.local`,
|
||||
`${paths.dotenv}.${NODE_ENV}`,
|
||||
// Don't include `.env.local` for `test` environment
|
||||
// since normally you expect tests to produce the same
|
||||
// results for everyone
|
||||
NODE_ENV !== "test" && `${paths.dotenv}.local`,
|
||||
paths.dotenv,
|
||||
].filter(Boolean);
|
||||
|
||||
// Load environment variables from .env* files. Suppress warnings using silent
|
||||
// if this file is missing. dotenv will never modify any environment variables
|
||||
// that have already been set. Variable expansion is supported in .env files.
|
||||
// https://github.com/motdotla/dotenv
|
||||
// https://github.com/motdotla/dotenv-expand
|
||||
dotenvFiles.forEach(dotenvFile => {
|
||||
if (fs.existsSync(dotenvFile)) {
|
||||
require("dotenv-expand")(
|
||||
require("dotenv").config({
|
||||
path: dotenvFile,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// We support resolving modules according to `NODE_PATH`.
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253.
|
||||
// It works similar to `NODE_PATH` in Node itself:
|
||||
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
|
||||
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
|
||||
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421
|
||||
// We also resolve them to make sure all tools using them work consistently.
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
process.env.NODE_PATH = (process.env.NODE_PATH || "")
|
||||
.split(path.delimiter)
|
||||
.filter(folder => folder && !path.isAbsolute(folder))
|
||||
.map(folder => path.resolve(appDirectory, folder))
|
||||
.join(path.delimiter);
|
||||
|
||||
// Grab NODE_ENV and TALK_* environment variables and prepare them to be
|
||||
// injected into the application via DefinePlugin in Webpack configuration.
|
||||
const REACT_APP = /^TALK_/i;
|
||||
|
||||
function getClientEnvironment(publicUrl) {
|
||||
const raw = Object.keys(process.env)
|
||||
.filter(key => REACT_APP.test(key))
|
||||
.reduce(
|
||||
(env, key) => {
|
||||
env[key] = process.env[key];
|
||||
return env;
|
||||
},
|
||||
{
|
||||
// Useful for determining whether we’re running in production mode.
|
||||
// Most importantly, it switches React into the correct mode.
|
||||
NODE_ENV: process.env.NODE_ENV || "development",
|
||||
// Useful for resolving the correct path to static assets in `public`.
|
||||
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
|
||||
// This should only be used as an escape hatch. Normally you would put
|
||||
// images into the `src` and `import` them in code to get their paths.
|
||||
PUBLIC_URL: publicUrl,
|
||||
}
|
||||
);
|
||||
// Stringify all values so we can feed into Webpack DefinePlugin
|
||||
const stringified = {
|
||||
"process.env": Object.keys(raw).reduce((env, key) => {
|
||||
env[key] = JSON.stringify(raw[key]);
|
||||
return env;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return { raw, stringified };
|
||||
}
|
||||
|
||||
module.exports = getClientEnvironment;
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
projects: [
|
||||
"<rootDir>/jest-client.config.js",
|
||||
"<rootDir>/jest-server.config.js",
|
||||
"<rootDir>/jest/client.config.js",
|
||||
"<rootDir>/jest/server.config.js",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -2,12 +2,12 @@ const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
displayName: "client",
|
||||
rootDir: "../",
|
||||
rootDir: "../../",
|
||||
roots: ["<rootDir>/src/core/client"],
|
||||
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
setupFiles: [
|
||||
"<rootDir>/config/polyfills.js",
|
||||
"<rootDir>/src/core/build/polyfills.js",
|
||||
"<rootDir>/src/core/client/test/setup.ts",
|
||||
],
|
||||
testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"],
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
displayName: "server",
|
||||
rootDir: "../",
|
||||
rootDir: "../../",
|
||||
roots: ["<rootDir>/src"],
|
||||
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
@@ -1,66 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
// A script from `create-react-app` ejected `25.06.2018`.
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const url = require("url");
|
||||
|
||||
// Make sure any symlinks in the project folder are resolved:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/637
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
|
||||
|
||||
const envPublicUrl = process.env.PUBLIC_URL;
|
||||
|
||||
function ensureSlash(p, needsSlash) {
|
||||
const hasSlash = p.endsWith("/");
|
||||
if (hasSlash && !needsSlash) {
|
||||
return p.substr(p, p.length - 1);
|
||||
} else if (!hasSlash && needsSlash) {
|
||||
return `${p}/`;
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
const getPublicUrl = appPackageJson =>
|
||||
envPublicUrl || require(appPackageJson).homepage;
|
||||
|
||||
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
|
||||
// "public path" at which the app is served.
|
||||
// Webpack needs to know it to put the right <script> hrefs into HTML even in
|
||||
// single-page apps that may serve index.html for nested URLs like /todos/42.
|
||||
// We can't use a relative path in HTML because we don't want to load something
|
||||
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
|
||||
function getServedPath(appPackageJson) {
|
||||
const publicUrl = getPublicUrl(appPackageJson);
|
||||
const servedUrl =
|
||||
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : "/");
|
||||
return ensureSlash(servedUrl, true);
|
||||
}
|
||||
|
||||
// config after eject: we're in ./config/
|
||||
module.exports = {
|
||||
dotenv: resolveApp(".env"),
|
||||
appPostCssConfig: resolveApp("config/postcss.config.js"),
|
||||
appJestConfig: resolveApp("config/jest.config.js"),
|
||||
appLoaders: resolveApp("loaders"),
|
||||
appDist: resolveApp("dist/static"),
|
||||
appPublic: resolveApp("public"),
|
||||
appPackageJson: resolveApp("package.json"),
|
||||
appSrc: resolveApp("src"),
|
||||
appTsconfig: resolveApp("src/core/client/tsconfig.json"),
|
||||
appLocales: resolveApp("src/locales"),
|
||||
appThemeVariables: resolveApp("src/core/client/ui/theme/variables.ts"),
|
||||
appThemeVariablesCSS: resolveApp("src/core/client/ui/theme/variables.css"),
|
||||
testsSetup: resolveApp("src/setupTests.js"),
|
||||
appNodeModules: resolveApp("node_modules"),
|
||||
|
||||
publicUrl: getPublicUrl(resolveApp("package.json")),
|
||||
servedPath: getServedPath(resolveApp("package.json")),
|
||||
|
||||
appStreamHtml: resolveApp("src/core/client/stream/index.html"),
|
||||
appStreamLocalesTemplate: resolveApp("src/core/client/stream/locales.ts"),
|
||||
appStreamIndex: resolveApp("src/core/client/stream/index.tsx"),
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import appPaths from "../src/core/build/paths";
|
||||
|
||||
// Make sure any symlinks in the project folder are resolved:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/637
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
const resolveApp = (relativePath: string) =>
|
||||
path.resolve(appDirectory, relativePath);
|
||||
|
||||
// config after eject: we're in ./config/
|
||||
export default {
|
||||
...appPaths,
|
||||
appJestConfig: resolveApp("config/jest.config.js"),
|
||||
};
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
Config,
|
||||
LongRunningExecutor,
|
||||
} from "../scripts/watcher";
|
||||
// Ensure environment variables are read.
|
||||
import "./env";
|
||||
|
||||
const config: Config = {
|
||||
rootDir: path.resolve(__dirname, "../src"),
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
|
||||
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
|
||||
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
|
||||
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
|
||||
const getClientEnvironment = require("./env");
|
||||
const paths = require("./paths");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// In development, we always serve from the root. This makes config easier.
|
||||
const publicPath = "/";
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
|
||||
const publicUrl = "";
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment(publicUrl);
|
||||
|
||||
// This is the development configuration.
|
||||
// It is focused on developer experience and fast rebuilds.
|
||||
// The production configuration is different and lives in a separate file.
|
||||
module.exports = {
|
||||
// Set webpack mode.
|
||||
mode: process.env.NODE_ENV === "production" ? "production" : "development",
|
||||
|
||||
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
|
||||
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
|
||||
devtool: "cheap-module-source-map",
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
|
||||
entry: [
|
||||
// We ship polyfills by default:
|
||||
require.resolve("./polyfills"),
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
require.resolve("react-dev-utils/webpackHotDevClient"),
|
||||
// Finally, this is your app's code:
|
||||
paths.appStreamIndex,
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
// initialization, it doesn't blow up the WebpackDevServer client, and
|
||||
// changing JS code would still trigger a refresh.
|
||||
],
|
||||
output: {
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: true,
|
||||
// This does not produce a real file. It's just the virtual path that is
|
||||
// served by WebpackDevServer in development. This is the JS bundle
|
||||
// containing code from all our entry points, and the Webpack runtime.
|
||||
filename: "static/js/bundle.js",
|
||||
// There are also additional JS chunk files if you use code splitting.
|
||||
chunkFilename: "static/js/[name].chunk.js",
|
||||
// This is the URL that app is served from. We use "/" in development.
|
||||
publicPath: publicPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: info =>
|
||||
path.resolve(info.absoluteResourcePath).replace(/\\/g, "/"),
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ["node_modules", paths.appNodeModules].concat(
|
||||
// It is guaranteed to exist because we tweak it in `env.js`
|
||||
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/290
|
||||
// `web` extension prefixes have been added for better support
|
||||
// for React Native Web.
|
||||
extensions: [
|
||||
".web.js",
|
||||
".mjs",
|
||||
".js",
|
||||
".json",
|
||||
".web.jsx",
|
||||
".jsx",
|
||||
".ts",
|
||||
".tsx",
|
||||
],
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
"react-native": "react-native-web",
|
||||
},
|
||||
plugins: [
|
||||
// Prevents users from importing files from outside of src/ (or node_modules/).
|
||||
// This often causes confusion because we only process files within src/ with babel.
|
||||
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
|
||||
// please link the files into your node_modules/ and let module-resolution kick in.
|
||||
// Make sure your source files are compiled, as they will not be processed in any way.
|
||||
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
|
||||
// Support `tsconfig.json` `path` setting.
|
||||
new TsconfigPathsPlugin({
|
||||
configFile: paths.appTsconfig,
|
||||
extensions: [".js", ".jsx", ".mjs", ".ts", ".tsx"],
|
||||
}),
|
||||
],
|
||||
},
|
||||
resolveLoader: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ["node_modules", paths.appNodeModules, paths.appLoaders].concat(
|
||||
// It is guaranteed to exist because we tweak it in `env.js`
|
||||
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
|
||||
),
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// Disable require.ensure as it's not a standard language feature.
|
||||
{ parser: { requireEnsure: false } },
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(js|jsx|mjs|ts|tsx)$/,
|
||||
enforce: "pre",
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
tsConfigFile: paths.appTsconfig,
|
||||
},
|
||||
loader: require.resolve("tslint-loader"),
|
||||
},
|
||||
],
|
||||
include: paths.appSrc,
|
||||
},
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
{
|
||||
test: paths.appStreamLocalesTemplate,
|
||||
use: [
|
||||
// This is the locales loader that loads available locales
|
||||
// from a particular target.
|
||||
{
|
||||
loader: "locales-loader",
|
||||
options: {
|
||||
pathToLocales: paths.appLocales,
|
||||
|
||||
// Default locale if non could be negotiated.
|
||||
defaultLocale: "en-US",
|
||||
|
||||
// Fallback locale if a translation was not found.
|
||||
// If not set, will use the text that is already
|
||||
// in the code base.
|
||||
fallbackLocale: "en-US",
|
||||
|
||||
// Common fluent files are always included in the locale bundles.
|
||||
commonFiles: ["framework.ftl", "common.ftl"],
|
||||
|
||||
// Locales that come with the main bundle. Others are loaded on demand.
|
||||
bundled: ["en-US"],
|
||||
|
||||
// Target specifies the prefix for fluent files to be loaded.
|
||||
// ${target}-xyz.ftl and ${†arget}.ftl are loaded into the locales.
|
||||
target: "stream",
|
||||
|
||||
// All available locales can be loadable on demand.
|
||||
// To restrict available locales set:
|
||||
// availableLocales: ["en-US"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Loader for our fluent files.
|
||||
{
|
||||
test: /\.ftl$/,
|
||||
use: ["raw-loader"],
|
||||
},
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve("url-loader"),
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: "static/media/[name].[hash:8].[ext]",
|
||||
},
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(js|jsx|mjs|ts|tsx)$/,
|
||||
include: paths.appSrc,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
compilerOptions: {
|
||||
target: "es2015",
|
||||
module: "esnext",
|
||||
jsx: "preserve",
|
||||
noEmit: false,
|
||||
},
|
||||
|
||||
// Overwrites the behavior of `include` and `exclude` to only
|
||||
// include files that are actually being imported and which
|
||||
// are necessary to compile the bundle.
|
||||
onlyCompileBundledFiles: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader turns CSS into JS modules that inject <style> tags.
|
||||
// In production, we use a plugin to extract that CSS to a file, but
|
||||
// in development "style" loader enables hot editing of CSS.
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
require.resolve("style-loader"),
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: true,
|
||||
importLoaders: 1,
|
||||
localIdentName: "[name]-[local]-[hash:base64:5]",
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("postcss-loader"),
|
||||
options: {
|
||||
config: {
|
||||
path: paths.appPostCssConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
// This loader doesn't use a "test" so it will catch all modules
|
||||
// that fall through the other loaders.
|
||||
{
|
||||
// Exclude `js` files to keep "css" loader working as it injects
|
||||
// its runtime that would otherwise processed through "file" loader.
|
||||
// Also exclude `html` and `json` extensions so they get processed
|
||||
// by webpacks internal loaders.
|
||||
exclude: [/\.(js|jsx|mjs|ts|tsx)$/, /\.html$/, /\.json$/],
|
||||
loader: require.resolve("file-loader"),
|
||||
options: {
|
||||
name: "static/media/[name].[hash:8].[ext]",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Make sure to add the new loader(s) before the "file" loader.
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: paths.appStreamHtml,
|
||||
}),
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(env.raw),
|
||||
// Add module names to factory functions so they appear in browser profiler.
|
||||
new webpack.NamedModulesPlugin(),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/240
|
||||
new CaseSensitivePathsPlugin(),
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for Webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/186
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how Webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
|
||||
// You can remove this if you don't use Moment.js:
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
],
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
dgram: "empty",
|
||||
fs: "empty",
|
||||
net: "empty",
|
||||
tls: "empty",
|
||||
child_process: "empty",
|
||||
},
|
||||
// Turn off performance hints during development because we don't do any
|
||||
// splitting or minification in interest of speed. These warnings become
|
||||
// cumbersome.
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
};
|
||||
@@ -1,388 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const ExtractTextPlugin = require("extract-text-webpack-plugin");
|
||||
const ManifestPlugin = require("webpack-manifest-plugin");
|
||||
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
|
||||
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
|
||||
const getClientEnvironment = require("./env");
|
||||
const paths = require("./paths");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// It requires a trailing slash, or the file assets will get an incorrect path.
|
||||
const publicPath = paths.servedPath;
|
||||
// Some apps do not use client-side routing with pushState.
|
||||
// For these, "homepage" can be set to "." to enable relative asset paths.
|
||||
const shouldUseRelativeAssetPaths = publicPath === "./";
|
||||
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
|
||||
const publicUrl = publicPath.slice(0, -1);
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment(publicUrl);
|
||||
|
||||
// Assert this just to be safe.
|
||||
// Development builds of React are slow and not intended for production.
|
||||
if (env.stringified["process.env"].NODE_ENV !== '"production"') {
|
||||
throw new Error("Production builds must have NODE_ENV=production.");
|
||||
}
|
||||
|
||||
// Note: defined here because it will be used more than once.
|
||||
// We use [md5:contenthash:hex:20] instead of [contenthash:8]
|
||||
// because of this bug https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/763.
|
||||
// TODO: Repalce with mini-css-extract-plugin once it supports HMR.
|
||||
// https://github.com/webpack-contrib/mini-css-extract-plugin
|
||||
const cssFilename = "assets/css/[name].[md5:contenthash:hex:20].css";
|
||||
|
||||
// ExtractTextPlugin expects the build output to be flat.
|
||||
// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
|
||||
// However, our output is structured with css, js and media folders.
|
||||
// To have this structure working with relative paths, we have to use custom options.
|
||||
const extractTextPluginOptions = shouldUseRelativeAssetPaths
|
||||
? // Making sure that the publicPath goes back to to build folder.
|
||||
{ publicPath: Array(cssFilename.split("/").length).join("../") }
|
||||
: {};
|
||||
|
||||
// This is the production configuration.
|
||||
// It compiles slowly and is focused on producing a fast and minimal bundle.
|
||||
// The development configuration is different and lives in a separate file.
|
||||
module.exports = {
|
||||
// Don't attempt to continue if there are any errors.
|
||||
bail: true,
|
||||
|
||||
// Set webpack mode.
|
||||
mode: "production",
|
||||
|
||||
// We generate sourcemaps in production. This is slow but gives good results.
|
||||
// You can exclude the *.map files from the build during deployment.
|
||||
devtool: shouldUseSourceMap ? "source-map" : false,
|
||||
|
||||
// In production, we only want to load the polyfills and the app code.
|
||||
entry: [require.resolve("./polyfills"), paths.appStreamIndex],
|
||||
output: {
|
||||
// The dist folder.
|
||||
path: paths.appDist,
|
||||
// Generated JS file names (with nested folders).
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// We don't currently advertise code splitting but Webpack supports it.
|
||||
filename: "assets/js/[name].[chunkhash:8].js",
|
||||
chunkFilename: "assets/js/[name].[chunkhash:8].chunk.js",
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
publicPath: publicPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: info =>
|
||||
path
|
||||
.relative(paths.appSrc, info.absoluteResourcePath)
|
||||
.replace(/\\/g, "/"),
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ["node_modules", paths.appNodeModules].concat(
|
||||
// It is guaranteed to exist because we tweak it in `env.js`
|
||||
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/290
|
||||
// `web` extension prefixes have been added for better support
|
||||
// for React Native Web.
|
||||
extensions: [
|
||||
".web.js",
|
||||
".mjs",
|
||||
".js",
|
||||
".json",
|
||||
".web.jsx",
|
||||
".jsx",
|
||||
".ts",
|
||||
".tsx",
|
||||
],
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
"react-native": "react-native-web",
|
||||
},
|
||||
plugins: [
|
||||
// Prevents users from importing files from outside of src/ (or node_modules/).
|
||||
// This often causes confusion because we only process files within src/ with babel.
|
||||
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
|
||||
// please link the files into your node_modules/ and let module-resolution kick in.
|
||||
// Make sure your source files are compiled, as they will not be processed in any way.
|
||||
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
|
||||
// Support `tsconfig.json` `path` setting.
|
||||
new TsconfigPathsPlugin({
|
||||
configFile: paths.appTsconfig,
|
||||
extensions: [".js", ".jsx", ".mjs", ".ts", ".tsx"],
|
||||
}),
|
||||
],
|
||||
},
|
||||
resolveLoader: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ["node_modules", paths.appNodeModules, paths.appLoaders].concat(
|
||||
// It is guaranteed to exist because we tweak it in `env.js`
|
||||
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
|
||||
),
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// Disable require.ensure as it's not a standard language feature.
|
||||
{ parser: { requireEnsure: false } },
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(js|jsx|mjs|ts|tsx)$/,
|
||||
enforce: "pre",
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
tsConfigFile: paths.appTsconfig,
|
||||
},
|
||||
loader: require.resolve("tslint-loader"),
|
||||
},
|
||||
],
|
||||
include: paths.appSrc,
|
||||
},
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
{
|
||||
test: paths.appStreamLocalesTemplate,
|
||||
use: [
|
||||
// This is the locales loader that loads available locales
|
||||
// from a particular target.
|
||||
{
|
||||
loader: "locales-loader",
|
||||
options: {
|
||||
pathToLocales: paths.appLocales,
|
||||
|
||||
// Default locale if non could be negotiated.
|
||||
defaultLocale: "en-US",
|
||||
|
||||
// Fallback locale if a translation was not found.
|
||||
// If not set, will use the text that is already
|
||||
// in the code base.
|
||||
fallbackLocale: "en-US",
|
||||
|
||||
// Common fluent files are always included in the locale bundles.
|
||||
commonFiles: ["framework.ftl", "common.ftl"],
|
||||
|
||||
// Locales that come with the main bundle. Others are loaded on demand.
|
||||
bundled: ["en-US"],
|
||||
|
||||
// Target specifies the prefix for fluent files to be loaded.
|
||||
// ${target}-xyz.ftl and ${†arget}.ftl are loaded into the locales.
|
||||
target: "stream",
|
||||
|
||||
// All available locales can be loadable on demand.
|
||||
// To restrict available locales set:
|
||||
// availableLocales: ["en-US"]
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Loader for our fluent files.
|
||||
{
|
||||
test: /\.ftl$/,
|
||||
use: ["raw-loader"],
|
||||
},
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve("url-loader"),
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: "assets/media/[name].[hash:8].[ext]",
|
||||
},
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(js|jsx|mjs|ts|tsx)$/,
|
||||
include: paths.appSrc,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
compact: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
compilerOptions: {
|
||||
target: "es2015",
|
||||
module: "esnext",
|
||||
jsx: "preserve",
|
||||
noEmit: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// The notation here is somewhat confusing.
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader normally turns CSS into JS modules injecting <style>,
|
||||
// but unlike in development configuration, we do something different.
|
||||
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders
|
||||
// (second argument), then grabs the result CSS and puts it into a
|
||||
// separate file in our build process. This way we actually ship
|
||||
// a single CSS file in production instead of JS code injecting <style>
|
||||
// tags. If you use code splitting, however, any async bundles will still
|
||||
// use the "style" loader inside the async code so CSS from them won't be
|
||||
// in the main CSS file.
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: ExtractTextPlugin.extract(
|
||||
Object.assign(
|
||||
{
|
||||
fallback: {
|
||||
loader: require.resolve("style-loader"),
|
||||
options: {
|
||||
hmr: false,
|
||||
},
|
||||
},
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: true,
|
||||
importLoaders: 1,
|
||||
minimize: true,
|
||||
sourceMap: shouldUseSourceMap,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("postcss-loader"),
|
||||
options: {
|
||||
config: {
|
||||
path: paths.appPostCssConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
extractTextPluginOptions
|
||||
)
|
||||
),
|
||||
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
|
||||
},
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
// This loader doesn't use a "test" so it will catch all modules
|
||||
// that fall through the other loaders.
|
||||
{
|
||||
// Exclude `js` files to keep "css" loader working as it injects
|
||||
// its runtime that would otherwise processed through "file" loader.
|
||||
// Also exclude `html` and `json` extensions so they get processed
|
||||
// by webpacks internal loaders.
|
||||
exclude: [/\.(js|jsx|mjs|ts|tsx)$/, /\.html$/, /\.json$/],
|
||||
loader: require.resolve("file-loader"),
|
||||
options: {
|
||||
name: "assets/media/[name].[hash:8].[ext]",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Make sure to add the new loader(s) before the "file" loader.
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: paths.appStreamHtml,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
}),
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(env.raw),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
|
||||
// It is absolutely essential that NODE_ENV was set to production here.
|
||||
// Otherwise React will be compiled in the very slow development mode.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// Minify the code.
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
warnings: false,
|
||||
// Disabled because of an issue with Uglify breaking seemingly valid code:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2376
|
||||
// Pending further investigation:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2011
|
||||
comparisons: false,
|
||||
},
|
||||
mangle: {
|
||||
safari10: true,
|
||||
},
|
||||
output: {
|
||||
comments: false,
|
||||
// Turned on because emoji and regex is not minified properly using default
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2488
|
||||
ascii_only: true,
|
||||
},
|
||||
sourceMap: shouldUseSourceMap,
|
||||
},
|
||||
}),
|
||||
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
|
||||
new ExtractTextPlugin({
|
||||
filename: cssFilename,
|
||||
}),
|
||||
// Generate a manifest file which contains a mapping of all asset filenames
|
||||
// to their corresponding output file so that tools can pick it up without
|
||||
// having to parse `index.html`.
|
||||
new ManifestPlugin({
|
||||
fileName: "asset-manifest.json",
|
||||
}),
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how Webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
|
||||
// You can remove this if you don't use Moment.js:
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
],
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
dgram: "empty",
|
||||
fs: "empty",
|
||||
net: "empty",
|
||||
tls: "empty",
|
||||
child_process: "empty",
|
||||
},
|
||||
};
|
||||
@@ -1,36 +1,21 @@
|
||||
"use strict";
|
||||
import errorOverlayMiddleware from "react-dev-utils/errorOverlayMiddleware";
|
||||
import ignoredFiles from "react-dev-utils/ignoredFiles";
|
||||
import noopServiceWorkerMiddleware from "react-dev-utils/noopServiceWorkerMiddleware";
|
||||
import { Configuration } from "webpack-dev-server";
|
||||
import paths from "./paths";
|
||||
|
||||
const errorOverlayMiddleware = require("react-dev-utils/errorOverlayMiddleware");
|
||||
const noopServiceWorkerMiddleware = require("react-dev-utils/noopServiceWorkerMiddleware");
|
||||
const ignoredFiles = require("react-dev-utils/ignoredFiles");
|
||||
const config = require("./webpack.config.dev");
|
||||
const paths = require("./paths");
|
||||
interface WebpackDevServerConfig {
|
||||
allowedHost: any;
|
||||
serverPort: number;
|
||||
publicPath: string;
|
||||
}
|
||||
|
||||
const protocol = process.env.HTTPS === "true" ? "https" : "http";
|
||||
const host = process.env.HOST || "0.0.0.0";
|
||||
const serverPort = process.env.PORT || 3000;
|
||||
const doczPort = process.env.DOCZ_PORT || 3030;
|
||||
|
||||
module.exports = function(proxy, allowedHost) {
|
||||
export default function({
|
||||
allowedHost,
|
||||
serverPort,
|
||||
publicPath,
|
||||
}: WebpackDevServerConfig): Configuration {
|
||||
return {
|
||||
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
|
||||
// websites from potentially accessing local content through DNS rebinding:
|
||||
// https://github.com/webpack/webpack-dev-server/issues/887
|
||||
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
|
||||
// However, it made several existing use cases such as development in cloud
|
||||
// environment or subdomains in development significantly more complicated:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2271
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2233
|
||||
// While we're investigating better solutions, for now we will take a
|
||||
// compromise. Since our WDS configuration only serves files in the `public`
|
||||
// folder we won't consider accessing them a vulnerability. However, if you
|
||||
// use the `proxy` feature, it gets more dangerous because it can expose
|
||||
// remote code execution vulnerabilities in backends like Django and Rails.
|
||||
// So we will disable the host check normally, but enable it if you have
|
||||
// specified the `proxy` setting. Finally, we let you override it if you
|
||||
// really know what you're doing with a special environment variable.
|
||||
disableHostCheck:
|
||||
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === "true",
|
||||
// Enable gzip compression of generated files.
|
||||
compress: true,
|
||||
// Silence WebpackDevServer's own logs since they're generally not useful.
|
||||
@@ -61,7 +46,7 @@ module.exports = function(proxy, allowedHost) {
|
||||
hot: true,
|
||||
// It is important to tell WebpackDevServer to use the same "root" path
|
||||
// as we specified in the config. In development, we always serve from /.
|
||||
publicPath: config.output.publicPath,
|
||||
publicPath,
|
||||
// WebpackDevServer is noisy by default so we emit custom message instead
|
||||
// by listening to the compiler events with `compiler.plugin` calls above.
|
||||
quiet: true,
|
||||
@@ -72,9 +57,6 @@ module.exports = function(proxy, allowedHost) {
|
||||
watchOptions: {
|
||||
ignored: ignoredFiles(paths.appSrc),
|
||||
},
|
||||
// Enable HTTPS if the HTTPS environment variable is set to 'true'
|
||||
https: protocol === "https",
|
||||
host: host,
|
||||
overlay: false,
|
||||
historyApiFallback: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
@@ -82,13 +64,14 @@ module.exports = function(proxy, allowedHost) {
|
||||
disableDotRule: true,
|
||||
},
|
||||
public: allowedHost,
|
||||
proxy: proxy || {
|
||||
index: "embed.html",
|
||||
proxy: {
|
||||
// Proxy to the graphql server.
|
||||
"/api": {
|
||||
target: `http://localhost:${serverPort}`,
|
||||
},
|
||||
},
|
||||
before(app) {
|
||||
before(app: any) {
|
||||
// This lets us open files from the runtime error overlay.
|
||||
app.use(errorOverlayMiddleware());
|
||||
// This service worker file is effectively a 'no-op' that will reset any
|
||||
@@ -99,4 +82,4 @@ module.exports = function(proxy, allowedHost) {
|
||||
app.use(noopServiceWorkerMiddleware());
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
// Apply all the configuration provided in the .env file.
|
||||
require("dotenv").config();
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
const extensions = [".ts", ".tsx", ".js"];
|
||||
const paths = require("./config/paths");
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require("./config/env");
|
||||
|
||||
// const stringify = require('json-stringify-safe');
|
||||
|
||||
export default {
|
||||
title: "Talk 5.0",
|
||||
source: "./src",
|
||||
|
||||
Generated
+351
-3
@@ -1577,6 +1577,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/case-sensitive-paths-webpack-plugin": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.1.2.tgz",
|
||||
"integrity": "sha512-ah/PG3Iz3CLuHk8bczITiGvDcNLogZQfJDL+e7pcJXKdD/5Qp1FB6lxSjPsfXw8fHbhEfaJIq9om3hK6ir391g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/webpack": "*"
|
||||
}
|
||||
},
|
||||
"@types/cheerio": {
|
||||
"version": "0.22.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.8.tgz",
|
||||
@@ -1599,6 +1608,12 @@
|
||||
"integrity": "sha512-UWUmNYhaIGDx8Kv0NSqFRwP6HWnBMXam4nBacOrjIiPBKKCdWMCe77+Nbn6rI9+Us9c+BhE26u84xeYQv2bKeA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/clean-css": {
|
||||
"version": "3.4.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-3.4.30.tgz",
|
||||
"integrity": "sha1-AFLBNvUkgAJCjjY4s33ko5gYZB0=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/commander": {
|
||||
"version": "2.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz",
|
||||
@@ -1659,6 +1674,15 @@
|
||||
"@types/enzyme": "*"
|
||||
}
|
||||
},
|
||||
"@types/eventemitter2": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/eventemitter2/-/eventemitter2-4.1.0.tgz",
|
||||
"integrity": "sha512-IyrCYFL+FakW3gVd/x2b0QIpcVrdgcNCkj985xoBVinc0rNwoV87IbBx7KlS5aP+bx7uIZxVypLCiSwmI4jZrg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eventemitter2": "*"
|
||||
}
|
||||
},
|
||||
"@types/events": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
|
||||
@@ -1701,12 +1725,74 @@
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/extract-text-webpack-plugin": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.3.tgz",
|
||||
"integrity": "sha512-KvRd5e5k+BVwWBlCm4uZFcV+M2mikKhORahruKxPkriFIDhG6b0fZ8PIYyiok+pk6W5bqFCsQmRk5D86pTmNpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/webpack": "*"
|
||||
}
|
||||
},
|
||||
"@types/fs-extra": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.4.tgz",
|
||||
"integrity": "sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/graphql": {
|
||||
"version": "0.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-0.13.3.tgz",
|
||||
"integrity": "sha512-YNGkX5HtcDtufyQquww05yoWYiDdZPPubLafXqukYqGmpawHjodAXwufhTemqDdgGk48WU7RX2Ouj0VTc9b3AA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/html-minifier": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier/-/html-minifier-3.5.2.tgz",
|
||||
"integrity": "sha512-yikK28/KlVyf8g9i/k+TDFlteLuZ6QQTUdVqvKtzEB+8DSLCTjxfh6IK45KnW4rYFI3Y8T4LWpYJMTmfJleWaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/clean-css": "*",
|
||||
"@types/relateurl": "*",
|
||||
"@types/uglify-js": "*"
|
||||
}
|
||||
},
|
||||
"@types/html-webpack-plugin": {
|
||||
"version": "2.30.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-2.30.4.tgz",
|
||||
"integrity": "sha512-jq9jZdJAsJPBWitnlUPeWYJ01Aaekiq0/7bAihZ08lnBqTHVUvR+uuG+3FuUjZ2FCQR7PsmmOJpFF6/DD2Ngtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/html-minifier": "*",
|
||||
"@types/tapable": "*",
|
||||
"@types/webpack": "*"
|
||||
}
|
||||
},
|
||||
"@types/http-proxy": {
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.16.2.tgz",
|
||||
"integrity": "sha512-GgqePmC3rlsn1nv+kx5OviPuUBU2omhnlXOaJSXFgOdsTcScNFap+OaCb2ip9Bm4m5L8EOehgT5d9M4uNB90zg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/events": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/http-proxy-middleware": {
|
||||
"version": "0.17.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy-middleware/-/http-proxy-middleware-0.17.5.tgz",
|
||||
"integrity": "sha512-mUqVzfaiOknDT2QJ7g8f2c37G4ZDqDNt08QdUkFCu19Ey5+2SZ0rWHMG00GRJ7g+SgHvl/9weZYuWLXr7RgiCg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/connect": "*",
|
||||
"@types/http-proxy": "*",
|
||||
"@types/node": "*",
|
||||
"winston": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"@types/ioredis": {
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-3.2.12.tgz",
|
||||
@@ -1895,6 +1981,12 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/relateurl": {
|
||||
"version": "0.2.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz",
|
||||
"integrity": "sha1-a9p9uGU/piZD9e5p6facEaOS46Y=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/relay-runtime": {
|
||||
"version": "github:coralproject/patched#ba8d413696e97b4f67450de3525cc319b9980cba",
|
||||
"from": "github:coralproject/patched#types/relay-runtime",
|
||||
@@ -1924,12 +2016,44 @@
|
||||
"integrity": "sha512-yxzBCIjE3lp9lYjfBbIK/LRCoXgCLLbIIBIje7eNCcUIIR2CZZtyX5uto2hVoMSMqLrsRrT6mwwUEd0yFgOwpA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tapable": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz",
|
||||
"integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tough-cookie": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz",
|
||||
"integrity": "sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uglify-js": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.3.tgz",
|
||||
"integrity": "sha512-MAT0BW2ruO0LhQKjvlipLGCF/Yx0y/cj+tT67tK3QIQDrM2+9R78HgJ54VlrE8AbfjYJJBCQCEPM5ZblPVTuww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/uglifyjs-webpack-plugin": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.1.0.tgz",
|
||||
"integrity": "sha512-QoCJYq+zNtuvKw4nutaIxQXKBpvc0Hd6U7BUVi2Cest2FrkGTYDBD6YpSq5d9IHjo94SjXk+6KDqQVOcEzFJZA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/webpack": "*"
|
||||
}
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.3.tgz",
|
||||
@@ -1939,6 +2063,47 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/webpack": {
|
||||
"version": "4.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.4.7.tgz",
|
||||
"integrity": "sha512-nwjQSPaVdEyM60ZfE/+SadTEgsuP7uXfHZ2n7c/a+G5f7K7esdlCu4f+po4DPfjJN7LeDC0e7kr4uOzLg5VOGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/tapable": "*",
|
||||
"@types/uglify-js": "*",
|
||||
"source-map": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/webpack-dev-server": {
|
||||
"version": "2.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-2.9.5.tgz",
|
||||
"integrity": "sha512-fzk3xf+OUuby8CZTZY2xPWBKq3mVhAiJxqRplRIaWfX9BFqWESs0M2epibYry2/KwnVL1KRiUeHabEGm/2XyBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*",
|
||||
"@types/http-proxy-middleware": "*",
|
||||
"@types/serve-static": "*",
|
||||
"@types/webpack": "*"
|
||||
}
|
||||
},
|
||||
"@types/webpack-manifest-plugin": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack-manifest-plugin/-/webpack-manifest-plugin-1.3.2.tgz",
|
||||
"integrity": "sha512-ythLsrDoSLkEOrmKF22MbrweS4hHdMaM1C/2Fp1OOh7jPawy8ah4ajJPrLeEim8uAfZVe/V/jTEDc7VtSogU/w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/webpack": "*"
|
||||
}
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz",
|
||||
@@ -5669,12 +5834,50 @@
|
||||
"has": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"colornames": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz",
|
||||
"integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=",
|
||||
"dev": true
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
|
||||
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
|
||||
"dev": true
|
||||
},
|
||||
"colorspace": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.1.tgz",
|
||||
"integrity": "sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color": "3.0.x",
|
||||
"text-hex": "1.0.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"color": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
|
||||
"integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.1",
|
||||
"color-string": "^1.5.2"
|
||||
}
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.2.tgz",
|
||||
"integrity": "sha1-JuRYFLw8mny9Z1FkikFDRRSnc6k=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
|
||||
@@ -6248,6 +6451,13 @@
|
||||
"requires": {
|
||||
"node-fetch": "2.0.0",
|
||||
"whatwg-fetch": "2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"whatwg-fetch": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
|
||||
"integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
@@ -6985,6 +7195,17 @@
|
||||
"debug": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz",
|
||||
"integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colorspace": "1.1.x",
|
||||
"enabled": "1.0.x",
|
||||
"kuler": "1.0.x"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
|
||||
@@ -8055,6 +8276,15 @@
|
||||
"hoist-non-react-statics": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"enabled": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz",
|
||||
"integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"env-variable": "0.0.x"
|
||||
}
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
@@ -8095,6 +8325,12 @@
|
||||
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
|
||||
"dev": true
|
||||
},
|
||||
"env-variable": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.4.tgz",
|
||||
"integrity": "sha512-+jpGxSWG4vr6gVxUHOc4p+ilPnql7NzZxOZBxNldsKGjCF+97df3CbuX7XMaDa5oAVkKQj4rKp38rYdC4VcpDg==",
|
||||
"dev": true
|
||||
},
|
||||
"enzyme": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.3.0.tgz",
|
||||
@@ -8360,6 +8596,12 @@
|
||||
"through": "~2.3.1"
|
||||
}
|
||||
},
|
||||
"eventemitter2": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz",
|
||||
"integrity": "sha1-YZegldX7a1folC9v1+qtY6CclFI=",
|
||||
"dev": true
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
|
||||
@@ -8760,6 +9002,12 @@
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-safe-stringify": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.4.tgz",
|
||||
"integrity": "sha512-mNlGUdKOeGNleyrmgbKYtbnCr9KZkZXU7eM89JRo8vY10f7Ul1Fbj07hUBW3N4fC0xM+fmfFfa2zM7mIizhpNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"fastparse": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
|
||||
@@ -8816,6 +9064,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fecha": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz",
|
||||
"integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==",
|
||||
"dev": true
|
||||
},
|
||||
"figures": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
|
||||
@@ -13918,6 +14172,15 @@
|
||||
"webpack-log": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"kuler": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.0.tgz",
|
||||
"integrity": "sha512-oyy6pu/yWRjiVfCoJebNUKFL061sNtrs9ejKTbirIwY3oiHmENVCSkHhxDV85Dkm7JYR/czMCBeoM87WilTdSg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colornames": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"latest-version": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
|
||||
@@ -14325,6 +14588,33 @@
|
||||
"wrap-ansi": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"logform": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/logform/-/logform-1.9.1.tgz",
|
||||
"integrity": "sha512-ZHrZE8VSf7K3xKxJiQ1aoTBp2yK+cEbFcgarsjzI3nt3nE/3O0heNSppoOQMUJVMZo/xiVwCxiXIabaZApsKNQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colors": "^1.2.1",
|
||||
"fast-safe-stringify": "^2.0.4",
|
||||
"fecha": "^2.3.3",
|
||||
"ms": "^2.1.1",
|
||||
"triple-beam": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"colors": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.3.1.tgz",
|
||||
"integrity": "sha512-jg/vxRmv430jixZrC+La5kMbUWqIg32/JsYNZb94+JEmzceYbWKTsv1OuTp+7EaqiaWRR2tPcykibwCRgclIsw==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"loglevel": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
|
||||
@@ -15526,6 +15816,12 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"one-time": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz",
|
||||
"integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=",
|
||||
"dev": true
|
||||
},
|
||||
"onetime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
|
||||
@@ -18440,6 +18736,12 @@
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||
},
|
||||
"pym.js": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/pym.js/-/pym.js-1.3.2.tgz",
|
||||
"integrity": "sha512-/fFzHFB4BTsJQP5id0wzUdYsDT4VPkfAxk+uENBE9/aP6YbvhAEpcuJHdjxqisqfXOCrcz7duTK1fbtF2rz8RQ==",
|
||||
"dev": true
|
||||
},
|
||||
"q": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||
@@ -20793,6 +21095,12 @@
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"stack-trace": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
|
||||
"dev": true
|
||||
},
|
||||
"stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
@@ -21256,6 +21564,12 @@
|
||||
"integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=",
|
||||
"dev": true
|
||||
},
|
||||
"text-hex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
||||
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
|
||||
"dev": true
|
||||
},
|
||||
"text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
@@ -21521,6 +21835,12 @@
|
||||
"integrity": "sha512-bWLv9BbWbbd7mlqqs2oQYnLD/U/ZqeJeJwbO0FG2zA1aTq+HTvxfHNKFa/HGCVyJpDiioUYaBhfiT6rgk+l4mg==",
|
||||
"dev": true
|
||||
},
|
||||
"triple-beam": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
|
||||
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==",
|
||||
"dev": true
|
||||
},
|
||||
"trough": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/trough/-/trough-1.0.2.tgz",
|
||||
@@ -23267,9 +23587,10 @@
|
||||
}
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
|
||||
"integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
|
||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-mimetype": {
|
||||
"version": "2.1.0",
|
||||
@@ -23325,6 +23646,33 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"winston": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/winston/-/winston-3.0.0.tgz",
|
||||
"integrity": "sha512-7QyfOo1PM5zGL6qma6NIeQQMh71FBg/8fhkSAePqtf5YEi6t+UrPDcUuHhuuUasgso49ccvMEsmqr0GBG2qaMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "^2.6.0",
|
||||
"diagnostics": "^1.0.1",
|
||||
"is-stream": "^1.1.0",
|
||||
"logform": "^1.9.0",
|
||||
"one-time": "0.0.4",
|
||||
"readable-stream": "^2.3.6",
|
||||
"stack-trace": "0.0.x",
|
||||
"triple-beam": "^1.3.0",
|
||||
"winston-transport": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"winston-transport": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.2.0.tgz",
|
||||
"integrity": "sha512-0R1bvFqxSlK/ZKTH86nymOuKv/cT1PQBMuDdA7k7f0S9fM44dNH6bXnuxwXPrN8lefJgtZq08BKdyZ0DZIy/rg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"readable-stream": "^2.3.6",
|
||||
"triple-beam": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
|
||||
+28
-10
@@ -4,22 +4,28 @@
|
||||
"description": "A better commenting experience from Mozilla, The Washington Post, and The New York Times.",
|
||||
"scripts": {
|
||||
"build": "npm-run-all compile --parallel build:*",
|
||||
"build:client": "node ./scripts/build.js",
|
||||
"build:client": "ts-node ./scripts/build.ts",
|
||||
"build:server": "tsc -p ./src/tsconfig.json",
|
||||
"compile": "npm-run-all --parallel compile:*",
|
||||
"compile:css-types": "tcm src/core/client/",
|
||||
"compile:relay-stream": "ts-node ./scripts/compileRelay --src ./src/core/client/stream --schema tenant",
|
||||
"compile:schema": "node ./scripts/generateSchemaTypes.js",
|
||||
"docz": "docz",
|
||||
"lint-fix": "npm run lint:server -- --fix && npm run lint:client -- --fix && npm run lint:scripts -- --fix",
|
||||
"lint:client": "tslint --project ./src/core/client/tsconfig.json",
|
||||
"lint:scripts": "tslint --project ./tsconfig.json",
|
||||
"lint:server": "tslint --project ./src/tsconfig.json",
|
||||
"lint": "npm-run-all --parallel lint:*",
|
||||
"start:development": "cross-env NODE_ENV=development ts-node --project ./src/tsconfig.json -r tsconfig-paths/register ./src/index.ts",
|
||||
"start:webpackDevServer": "node ./scripts/start.js",
|
||||
"start": "node dist/index.js",
|
||||
"start:development": "cross-env NODE_ENV=development ts-node --project ./src/tsconfig.json -r tsconfig-paths/register ./src/index.ts",
|
||||
"start:webpackDevServer": "ts-node ./scripts/start.ts",
|
||||
"lint": "npm-run-all --parallel lint:*",
|
||||
"lint:server": "tslint --project ./src/tsconfig.json",
|
||||
"lint:client": "tslint --project ./src/core/client/tsconfig.json",
|
||||
"lint:client-embed": "tslint --project ./src/core/client/embed/tsconfig.json",
|
||||
"lint:scripts": "tslint --project ./tsconfig.json",
|
||||
"lint-fix": "npm run lint:server -- --fix && npm run lint:client -- --fix && npm run lint:client-embed -- --fix && npm run lint:scripts -- --fix",
|
||||
"test": "node scripts/test.js --env=jsdom",
|
||||
"tscheck": "npm-run-all --parallel tscheck:*",
|
||||
"tscheck:server": "tsc --project ./src/tsconfig.json --noEmit",
|
||||
"tscheck:client": "tsc --project ./src/core/client/tsconfig.json --noEmit",
|
||||
"tscheck:client-embed": "tsc --project ./src/core/client/embed/tsconfig.json --noEmit",
|
||||
"tscheck:scripts": "tsc --project ./tsconfig.json --noEmit",
|
||||
"watch": "cross-env NODE_ENV=development ts-node ./scripts/watcher/bin/watcher.ts --config ./config/watcher.ts"
|
||||
},
|
||||
"author": "",
|
||||
@@ -64,6 +70,7 @@
|
||||
"@babel/preset-react": "7.0.0-beta.49",
|
||||
"@types/bcryptjs": "^2.4.1",
|
||||
"@types/bunyan": "^1.8.4",
|
||||
"@types/case-sensitive-paths-webpack-plugin": "^2.1.2",
|
||||
"@types/chokidar": "^1.7.5",
|
||||
"@types/classnames": "^2.2.4",
|
||||
"@types/commander": "^2.12.2",
|
||||
@@ -72,8 +79,12 @@
|
||||
"@types/dotenv": "^4.0.3",
|
||||
"@types/enzyme": "^3.1.11",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.2",
|
||||
"@types/eventemitter2": "^4.1.0",
|
||||
"@types/express": "^4.16.0",
|
||||
"@types/extract-text-webpack-plugin": "^3.0.3",
|
||||
"@types/fs-extra": "^5.0.4",
|
||||
"@types/graphql": "^0.13.3",
|
||||
"@types/html-webpack-plugin": "^2.30.4",
|
||||
"@types/ioredis": "^3.2.12",
|
||||
"@types/jest": "^23.1.5",
|
||||
"@types/joi": "^13.0.8",
|
||||
@@ -96,7 +107,11 @@
|
||||
"@types/relay-runtime": "github:coralproject/patched#types/relay-runtime",
|
||||
"@types/sane": "^2.0.0",
|
||||
"@types/sinon": "^5.0.1",
|
||||
"@types/uglifyjs-webpack-plugin": "^1.1.0",
|
||||
"@types/uuid": "^3.4.3",
|
||||
"@types/webpack": "^4.4.7",
|
||||
"@types/webpack-dev-server": "^2.9.5",
|
||||
"@types/webpack-manifest-plugin": "^1.3.2",
|
||||
"@types/ws": "^5.1.2",
|
||||
"autoprefixer": "^8.6.5",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
@@ -116,6 +131,7 @@
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"enzyme-to-json": "^3.3.4",
|
||||
"eventemitter2": "^5.0.1",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"final-form": "^4.8.1",
|
||||
"flat": "^4.1.0",
|
||||
@@ -143,6 +159,7 @@
|
||||
"postcss-preset-env": "^5.2.1",
|
||||
"prettier": "^1.13.7",
|
||||
"pstree.remy": "^1.1.0",
|
||||
"pym.js": "^1.3.2",
|
||||
"query-string": "^6.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.4.0",
|
||||
@@ -182,6 +199,7 @@
|
||||
"webpack-cli": "^3.0.2",
|
||||
"webpack-dev-server": "^3.1.4",
|
||||
"webpack-hot-client": "^4.1.1",
|
||||
"webpack-manifest-plugin": "^2.0.3"
|
||||
"webpack-manifest-plugin": "^2.0.3",
|
||||
"whatwg-fetch": "^2.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
"use strict";
|
||||
#!/usr/bin/env ts-node
|
||||
|
||||
import chalk from "chalk";
|
||||
import fs from "fs-extra";
|
||||
import FileSizeReporter from "react-dev-utils/FileSizeReporter";
|
||||
import formatWebpackMessages from "react-dev-utils/formatWebpackMessages";
|
||||
import printBuildError from "react-dev-utils/printBuildError";
|
||||
import webpack from "webpack";
|
||||
|
||||
import paths from "../config/paths";
|
||||
import createWebpackConfig from "../src/core/build/createWebpackConfig";
|
||||
import config, { createClientEnv } from "../src/core/common/config";
|
||||
|
||||
// tslint:disable: no-console
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
// Enforce environment to be production.
|
||||
config.validate().set("env", "production");
|
||||
process.env.BABEL_ENV = "production";
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
@@ -13,20 +25,6 @@ process.on("unhandledRejection", err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require("../config/env");
|
||||
|
||||
const path = require("path");
|
||||
const chalk = require("chalk");
|
||||
const fs = require("fs-extra");
|
||||
const webpack = require("webpack");
|
||||
const config = require("../config/webpack.config.prod");
|
||||
const paths = require("../config/paths");
|
||||
const formatWebpackMessages = require("react-dev-utils/formatWebpackMessages");
|
||||
const printHostingInstructions = require("react-dev-utils/printHostingInstructions");
|
||||
const FileSizeReporter = require("react-dev-utils/FileSizeReporter");
|
||||
const printBuildError = require("react-dev-utils/printBuildError");
|
||||
|
||||
const measureFileSizesBeforeBuild =
|
||||
FileSizeReporter.measureFileSizesBeforeBuild;
|
||||
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
|
||||
@@ -42,15 +40,15 @@ const treatWarningsAsErrors =
|
||||
false &&
|
||||
process.env.CI &&
|
||||
(typeof process.env.CI !== "string" ||
|
||||
process.env.CI.toLowerCase() !== "false");
|
||||
process.env.CI!.toLowerCase() !== "false");
|
||||
|
||||
// First, read the current file sizes in build directory.
|
||||
// This lets us display how much they changed later.
|
||||
measureFileSizesBeforeBuild(paths.appDist)
|
||||
.then(previousFileSizes => {
|
||||
measureFileSizesBeforeBuild(paths.appDistStatic)
|
||||
.then((previousFileSizes: any) => {
|
||||
// Remove all content but keep the directory so that
|
||||
// if you're in it, you don't end up in Trash
|
||||
fs.emptyDirSync(paths.appDist);
|
||||
fs.emptyDirSync(paths.appDistStatic);
|
||||
// Merge with the public folder
|
||||
if (fs.pathExistsSync(paths.appPublic)) {
|
||||
copyPublicFolder();
|
||||
@@ -59,7 +57,7 @@ measureFileSizesBeforeBuild(paths.appDist)
|
||||
return build(previousFileSizes);
|
||||
})
|
||||
.then(
|
||||
({ stats, previousFileSizes, warnings }) => {
|
||||
({ stats, previousFileSizes, warnings }: any) => {
|
||||
if (warnings.length) {
|
||||
console.log(chalk.yellow("Compiled with warnings.\n"));
|
||||
console.log(warnings.join("\n\n"));
|
||||
@@ -81,13 +79,13 @@ measureFileSizesBeforeBuild(paths.appDist)
|
||||
printFileSizesAfterBuild(
|
||||
stats,
|
||||
previousFileSizes,
|
||||
paths.appDist,
|
||||
paths.appDistStatic,
|
||||
WARN_AFTER_BUNDLE_GZIP_SIZE,
|
||||
WARN_AFTER_CHUNK_GZIP_SIZE
|
||||
);
|
||||
console.log();
|
||||
},
|
||||
err => {
|
||||
(err: Error) => {
|
||||
console.log(chalk.red("Failed to compile.\n"));
|
||||
printBuildError(err);
|
||||
process.exit(1);
|
||||
@@ -95,16 +93,18 @@ measureFileSizesBeforeBuild(paths.appDist)
|
||||
);
|
||||
|
||||
// Create the production build and print the deployment instructions.
|
||||
function build(previousFileSizes) {
|
||||
function build(previousFileSizes: any) {
|
||||
console.log("Creating an optimized production build...");
|
||||
|
||||
let compiler = webpack(config);
|
||||
const webpackConfig = createWebpackConfig({
|
||||
env: createClientEnv(config),
|
||||
});
|
||||
const compiler = webpack(webpackConfig);
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
const messages = formatWebpackMessages(stats.toJson({}, true));
|
||||
const messages = formatWebpackMessages(stats.toJson({}));
|
||||
if (messages.errors.length) {
|
||||
// Only keep the first error. Others are often indicative
|
||||
// of the same problem, but confuse the reader with noise.
|
||||
@@ -132,7 +132,7 @@ function build(previousFileSizes) {
|
||||
}
|
||||
|
||||
function copyPublicFolder() {
|
||||
fs.copySync(paths.appPublic, paths.appDist, {
|
||||
fs.copySync(paths.appPublic, paths.appDistStatic, {
|
||||
dereference: true,
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,22 @@
|
||||
"use strict";
|
||||
#!/usr/bin/env ts-node
|
||||
|
||||
import chalk from "chalk";
|
||||
import {
|
||||
choosePort,
|
||||
createCompiler,
|
||||
prepareUrls,
|
||||
} from "react-dev-utils/WebpackDevServerUtils";
|
||||
import webpack from "webpack";
|
||||
import WebpackDevServer from "webpack-dev-server";
|
||||
|
||||
import createDevServerConfig from "../config/webpackDevServer.config";
|
||||
import createWebpackConfig from "../src/core/build/createWebpackConfig";
|
||||
import config, { createClientEnv } from "../src/core/common/config";
|
||||
|
||||
// tslint:disable: no-console
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
// Enforce environment to be development.
|
||||
config.validate().set("env", "development");
|
||||
process.env.BABEL_ENV = "development";
|
||||
process.env.NODE_ENV = "development";
|
||||
|
||||
@@ -13,25 +27,8 @@ process.on("unhandledRejection", err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require("../config/env");
|
||||
|
||||
const fs = require("fs");
|
||||
const chalk = require("chalk");
|
||||
const webpack = require("webpack");
|
||||
const WebpackDevServer = require("webpack-dev-server");
|
||||
const {
|
||||
choosePort,
|
||||
createCompiler,
|
||||
prepareProxy,
|
||||
prepareUrls,
|
||||
} = require("react-dev-utils/WebpackDevServerUtils");
|
||||
const paths = require("../config/paths");
|
||||
const config = require("../config/webpack.config.dev");
|
||||
const createDevServerConfig = require("../config/webpackDevServer.config");
|
||||
|
||||
const PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 8080;
|
||||
const HOST = process.env.HOST || "0.0.0.0";
|
||||
const PORT = parseInt(process.env.DEV_SERVER_PORT!, 10) || 8080;
|
||||
const HOST = "0.0.0.0";
|
||||
|
||||
if (process.env.HOST) {
|
||||
console.log(
|
||||
@@ -51,42 +48,43 @@ if (process.env.HOST) {
|
||||
// We attempt to use the default port but if it is busy, we offer the user to
|
||||
// run on a different port. `choosePort()` Promise resolves to the next free port.
|
||||
choosePort(HOST, PORT)
|
||||
.then(port => {
|
||||
.then((port: number) => {
|
||||
if (port == null) {
|
||||
// We have not found a port.
|
||||
return;
|
||||
}
|
||||
const protocol = process.env.HTTPS === "true" ? "https" : "http";
|
||||
const appName = require(paths.appPackageJson).name;
|
||||
const protocol = "http";
|
||||
const appName = "Talk";
|
||||
const urls = prepareUrls(protocol, HOST, port);
|
||||
const webpackConfig = createWebpackConfig({
|
||||
env: createClientEnv(config),
|
||||
});
|
||||
// Create a webpack compiler that is configured with custom messages.
|
||||
const compiler = createCompiler(webpack, config, appName, urls);
|
||||
// Load proxy config
|
||||
const proxySetting = require(paths.appPackageJson).proxy;
|
||||
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
|
||||
const compiler = createCompiler(webpack, webpackConfig, appName, urls);
|
||||
// Serve webpack assets generated by the compiler over a web sever.
|
||||
const serverConfig = createDevServerConfig(
|
||||
proxyConfig,
|
||||
urls.lanUrlForConfig
|
||||
);
|
||||
const serverConfig = createDevServerConfig({
|
||||
allowedHost: urls.lanUrlForConfig,
|
||||
serverPort: config.get("port"),
|
||||
publicPath: webpackConfig[0].output!.publicPath!,
|
||||
});
|
||||
const devServer = new WebpackDevServer(compiler, serverConfig);
|
||||
// Launch WebpackDevServer.
|
||||
devServer.listen(port, HOST, err => {
|
||||
devServer.listen(port, HOST, (err: Error) => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
console.log(chalk.cyan("Starting the development server...\n"));
|
||||
});
|
||||
|
||||
["SIGINT", "SIGTERM"].forEach(function(sig) {
|
||||
process.on(sig, function() {
|
||||
["SIGINT", "SIGTERM"].forEach((sig: any) => {
|
||||
process.once(sig, () => {
|
||||
devServer.close();
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
.catch((err: Error) => {
|
||||
if (err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
+9
-5
@@ -1,9 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
// Allow importing typescript files.
|
||||
require("ts-node/register");
|
||||
|
||||
// Apply all the configuration provided in the .env file.
|
||||
require("dotenv").config();
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = "test";
|
||||
process.env.NODE_ENV = "test";
|
||||
process.env.PUBLIC_URL = "";
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
@@ -12,10 +19,7 @@ process.on("unhandledRejection", err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require("../config/env");
|
||||
|
||||
const paths = require("../config/paths");
|
||||
const paths = require("../config/paths.ts").default;
|
||||
|
||||
const jest = require("jest");
|
||||
let argv = process.argv.slice(2);
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
import CaseSensitivePathsPlugin from "case-sensitive-paths-webpack-plugin";
|
||||
import ExtractTextPlugin from "extract-text-webpack-plugin";
|
||||
import HtmlWebpackPlugin, { Options } from "html-webpack-plugin";
|
||||
import path from "path";
|
||||
import InterpolateHtmlPlugin from "react-dev-utils/InterpolateHtmlPlugin";
|
||||
import WatchMissingNodeModulesPlugin from "react-dev-utils/WatchMissingNodeModulesPlugin";
|
||||
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
||||
import UglifyJsPlugin from "uglifyjs-webpack-plugin";
|
||||
import webpack, { Configuration } from "webpack";
|
||||
import ManifestPlugin from "webpack-manifest-plugin";
|
||||
import paths from "./paths";
|
||||
|
||||
interface CreateWebpackConfig {
|
||||
publicPath?: string;
|
||||
publicURL?: string;
|
||||
env?: Record<string, string>;
|
||||
disableSourcemaps?: boolean;
|
||||
appendPlugins?: any[];
|
||||
}
|
||||
|
||||
export default function createWebpackConfig({
|
||||
publicPath = "/",
|
||||
publicURL = "",
|
||||
env = process.env as Record<string, string>,
|
||||
appendPlugins = [],
|
||||
disableSourcemaps,
|
||||
}: CreateWebpackConfig = {}): Configuration[] {
|
||||
const envStringified = {
|
||||
"process.env": Object.keys(env).reduce<Record<string, string>>(
|
||||
(result, key) => {
|
||||
result[key] = JSON.stringify(env[key]);
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
),
|
||||
};
|
||||
|
||||
const isProduction = env.NODE_ENV === "production";
|
||||
|
||||
const htmlWebpackConfig: Options = {
|
||||
minify: isProduction && {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
};
|
||||
|
||||
const styleLoader = {
|
||||
loader: require.resolve("style-loader"),
|
||||
options: {
|
||||
hmr: !isProduction,
|
||||
},
|
||||
};
|
||||
|
||||
const cssLoaders = [
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: true,
|
||||
importLoaders: 1,
|
||||
localIdentName: "[name]-[local]-[hash:base64:5]",
|
||||
minimize: isProduction,
|
||||
sourceMap: isProduction && !disableSourcemaps,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("postcss-loader"),
|
||||
options: {
|
||||
config: {
|
||||
path: paths.appPostCssConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const additionalPlugins = isProduction
|
||||
? [
|
||||
// Minify the code.
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
warnings: false,
|
||||
// Disabled because of an issue with Uglify breaking seemingly valid code:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2376
|
||||
// Pending further investigation:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2011
|
||||
comparisons: false,
|
||||
},
|
||||
mangle: {
|
||||
safari10: true,
|
||||
},
|
||||
output: {
|
||||
comments: false,
|
||||
// Turned on because emoji and regex is not minified properly using default
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2488
|
||||
ascii_only: true,
|
||||
},
|
||||
},
|
||||
sourceMap: !disableSourcemaps,
|
||||
}),
|
||||
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
|
||||
new ExtractTextPlugin({
|
||||
// We use [md5:contenthash:hex:20] instead of [contenthash:8]
|
||||
// because of this bug https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/763.
|
||||
// TODO: Repalce with mini-css-extract-plugin once it supports HMR.
|
||||
// https://github.com/webpack-contrib/mini-css-extract-plugin
|
||||
filename: "assets/css/[name].[md5:contenthash:hex:20].css",
|
||||
}),
|
||||
]
|
||||
: [
|
||||
// Add module names to factory functions so they appear in browser profiler.
|
||||
new webpack.NamedModulesPlugin(),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/240
|
||||
new CaseSensitivePathsPlugin(),
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for Webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/186
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
];
|
||||
|
||||
const baseConfig: Configuration = {
|
||||
// Set webpack mode.
|
||||
mode: isProduction ? "production" : "development",
|
||||
|
||||
devtool:
|
||||
!disableSourcemaps && isProduction
|
||||
? // We generate sourcemaps in production. This is slow but gives good results.
|
||||
// You can exclude the *.map files from the build during deployment.
|
||||
"source-map"
|
||||
: // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
|
||||
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
|
||||
"cheap-module-source-map",
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
|
||||
output: {
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: !isProduction,
|
||||
// The dist folder.
|
||||
path: paths.appDistStatic,
|
||||
// Generated JS file names (with nested folders).
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
filename: isProduction
|
||||
? "assets/js/[name].[chunkhash:8].js"
|
||||
: "assets/js/[name].js",
|
||||
chunkFilename: isProduction
|
||||
? "assets/js/[name].[chunkhash:8].chunk.js"
|
||||
: "assets/js/[name].chunk.js",
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
publicPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: (info: any) =>
|
||||
path
|
||||
.relative(paths.appSrc, info.absoluteResourcePath)
|
||||
.replace(/\\/g, "/"),
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js", ".json", ".ts", ".tsx"],
|
||||
plugins: [
|
||||
// Support `tsconfig.json` `path` setting.
|
||||
new TsconfigPathsPlugin({
|
||||
configFile: paths.appTsconfig,
|
||||
extensions: [".js", ".ts", ".tsx"],
|
||||
}),
|
||||
],
|
||||
},
|
||||
resolveLoader: {
|
||||
// Add path to our own loaders.
|
||||
modules: ["node_modules", paths.appLoaders],
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// Disable require.ensure as it's not a standard language feature.
|
||||
{ parser: { requireEnsure: false } },
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(js|ts|tsx)$/,
|
||||
enforce: "pre",
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
tsConfigFile: paths.appTsconfig,
|
||||
},
|
||||
loader: require.resolve("tslint-loader"),
|
||||
},
|
||||
],
|
||||
include: paths.appSrc,
|
||||
},
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
{
|
||||
test: paths.appStreamLocalesTemplate,
|
||||
use: [
|
||||
// This is the locales loader that loads available locales
|
||||
// from a particular target.
|
||||
{
|
||||
loader: "locales-loader",
|
||||
options: {
|
||||
pathToLocales: paths.appLocales,
|
||||
|
||||
// Default locale if non could be negotiated.
|
||||
defaultLocale: "en-US",
|
||||
|
||||
// Fallback locale if a translation was not found.
|
||||
// If not set, will use the text that is already
|
||||
// in the code base.
|
||||
fallbackLocale: "en-US",
|
||||
|
||||
// Common fluent files are always included in the locale bundles.
|
||||
commonFiles: ["framework.ftl", "common.ftl"],
|
||||
|
||||
// Locales that come with the main bundle. Others are loaded on demand.
|
||||
bundled: ["en-US"],
|
||||
|
||||
// Target specifies the prefix for fluent files to be loaded.
|
||||
// ${target}-xyz.ftl and ${†arget}.ftl are loaded into the locales.
|
||||
target: "stream",
|
||||
|
||||
// All available locales can be loadable on demand.
|
||||
// To restrict available locales set:
|
||||
// availableLocales: ["en-US"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Loader for our fluent files.
|
||||
{
|
||||
test: /\.ftl$/,
|
||||
use: ["raw-loader"],
|
||||
},
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: [/\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve("url-loader"),
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: "assets/media/[name].[hash:8].[ext]",
|
||||
},
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
include: paths.appSrc,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
configFile: paths.appTsconfig,
|
||||
compilerOptions: {
|
||||
target: "es2015",
|
||||
module: "esnext",
|
||||
jsx: "preserve",
|
||||
noEmit: false,
|
||||
},
|
||||
|
||||
// Overwrites the behavior of `include` and `exclude` to only
|
||||
// include files that are actually being imported and which
|
||||
// are necessary to compile the bundle.
|
||||
onlyCompileBundledFiles: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader turns CSS into JS modules that inject <style> tags.
|
||||
// In production, we use a plugin to extract that CSS to a file, and
|
||||
// in development "style" loader enables hot editing of CSS.
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader:
|
||||
(isProduction &&
|
||||
ExtractTextPlugin.extract({
|
||||
fallback: styleLoader,
|
||||
use: cssLoaders,
|
||||
})) ||
|
||||
undefined,
|
||||
use:
|
||||
(!isProduction && [
|
||||
require.resolve("style-loader"),
|
||||
...cssLoaders,
|
||||
]) ||
|
||||
undefined,
|
||||
},
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
// This loader doesn't use a "test" so it will catch all modules
|
||||
// that fall through the other loaders.
|
||||
{
|
||||
// Exclude `js` files to keep "css" loader working as it injects
|
||||
// its runtime that would otherwise processed through "file" loader.
|
||||
// Also exclude `html` and `json` extensions so they get processed
|
||||
// by webpacks internal loaders.
|
||||
exclude: [/\.(js|ts|tsx)$/, /\.html$/, /\.json$/],
|
||||
loader: require.resolve("file-loader"),
|
||||
options: {
|
||||
name: "assets/media/[name].[hash:8].[ext]",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Make sure to add the new loader(s) before the "file" loader.
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
|
||||
new webpack.DefinePlugin(envStringified),
|
||||
...additionalPlugins,
|
||||
...appendPlugins,
|
||||
],
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
dgram: "empty",
|
||||
fs: "empty",
|
||||
net: "empty",
|
||||
tls: "empty",
|
||||
child_process: "empty",
|
||||
},
|
||||
// Turn off performance hints during development because we don't do any
|
||||
// splitting or minification in interest of speed. These warnings become
|
||||
// cumbersome.
|
||||
performance: {
|
||||
hints: isProduction && "warning",
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
/* Webpack config for our different target, e.g. stream, admin... */
|
||||
{
|
||||
...baseConfig,
|
||||
entry: {
|
||||
stream: [
|
||||
// We ship polyfills by default
|
||||
paths.appPolyfill,
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
(isProduction && "") ||
|
||||
require.resolve("react-dev-utils/webpackHotDevClient"),
|
||||
paths.appStreamIndex,
|
||||
// Remove deactivated entries.
|
||||
].filter(s => s),
|
||||
},
|
||||
plugins: [
|
||||
...baseConfig.plugins!,
|
||||
// Generates an `stream.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "stream.html",
|
||||
template: paths.appStreamHTML,
|
||||
chunks: ["stream"],
|
||||
inject: "body",
|
||||
...htmlWebpackConfig,
|
||||
}),
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(env),
|
||||
// Generate a manifest file which contains a mapping of all asset filenames
|
||||
// to their corresponding output file so that tools can pick it up without
|
||||
// having to parse `index.html`.
|
||||
new ManifestPlugin({
|
||||
fileName: "asset-manifest.json",
|
||||
}),
|
||||
],
|
||||
},
|
||||
/* Webpack config for our embed */
|
||||
{
|
||||
...baseConfig,
|
||||
entry: [
|
||||
// No polyfills for the embed.
|
||||
(isProduction && "") ||
|
||||
require.resolve("react-dev-utils/webpackHotDevClient"),
|
||||
paths.appEmbedIndex,
|
||||
// Remove deactivated entries.
|
||||
].filter(s => s),
|
||||
output: {
|
||||
...baseConfig.output,
|
||||
library: "Talk",
|
||||
// don't hash the embed, cache-busting must be completed by the requester
|
||||
// as this lives in a static template on the embed site.
|
||||
filename: "assets/js/embed.js",
|
||||
},
|
||||
plugins: [
|
||||
...baseConfig.plugins!,
|
||||
// Generates an `stream.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "embed.html",
|
||||
template: paths.appEmbedHTML,
|
||||
inject: "head",
|
||||
...htmlWebpackConfig,
|
||||
}),
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(env),
|
||||
// Generate a manifest file which contains a mapping of all asset filenames
|
||||
// to their corresponding output file so that tools can pick it up without
|
||||
// having to parse `index.html`.
|
||||
new ManifestPlugin({
|
||||
fileName: "embed-manifest.json",
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
// Make sure any symlinks in the project folder are resolved:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/637
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
|
||||
const resolveApp = (relativePath: string) =>
|
||||
path.resolve(appDirectory, relativePath);
|
||||
|
||||
const resolveSrc = (relativePath: string) =>
|
||||
path.resolve(__dirname, "../../", relativePath);
|
||||
|
||||
export default {
|
||||
appPostCssConfig: resolveSrc("core/build/postcss.config.js"),
|
||||
appLoaders: resolveSrc("core/build/loaders"),
|
||||
appSrc: resolveSrc("."),
|
||||
appTsconfig: resolveSrc("core/client/tsconfig.json"),
|
||||
appPolyfill: resolveSrc("core/build/polyfills.js"),
|
||||
appLocales: resolveSrc("locales"),
|
||||
appThemeVariables: resolveSrc("core/client/ui/theme/variables.ts"),
|
||||
appThemeVariablesCSS: resolveSrc("core/client/ui/theme/variables.css"),
|
||||
appStreamHTML: resolveSrc("core/client/stream/index.html"),
|
||||
appStreamLocalesTemplate: resolveSrc("core/client/stream/locales.ts"),
|
||||
appStreamIndex: resolveSrc("core/client/stream/index.tsx"),
|
||||
appEmbedIndex: resolveSrc("core/client/embed/index.ts"),
|
||||
appEmbedHTML: resolveSrc("core/client/embed/index.html"),
|
||||
|
||||
appDistStatic: resolveApp("dist/static"),
|
||||
appPublic: resolveApp("public"),
|
||||
appPackageJson: resolveApp("package.json"),
|
||||
appNodeModules: resolveApp("node_modules"),
|
||||
};
|
||||
@@ -1,12 +1,9 @@
|
||||
// Allow importing typescript files.
|
||||
require("ts-node/register");
|
||||
|
||||
const kebabCase = require("lodash/kebabCase");
|
||||
const mapKeys = require("lodash/mapKeys");
|
||||
const mapValues = require("lodash/mapValues");
|
||||
const pickBy = require("lodash/pickBy");
|
||||
const flat = require("flat");
|
||||
const paths = require("./paths");
|
||||
const paths = require("./paths").default;
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const postcssFontMagician = require("postcss-font-magician");
|
||||
const postcssFlexbugsFixes = require("postcss-flexbugs-fixes");
|
||||
@@ -0,0 +1,55 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { Decorator } from "./decorators";
|
||||
import PymControl from "./PymControl";
|
||||
|
||||
describe("PymControl", () => {
|
||||
const container: HTMLElement = document.createElement("div");
|
||||
const cleanupDecorator = sinon.mock().once();
|
||||
|
||||
const withMockDecorator: Decorator = sinon
|
||||
.mock()
|
||||
.once()
|
||||
.withArgs(sinon.match.object)
|
||||
.returns(cleanupDecorator);
|
||||
|
||||
let control: PymControl;
|
||||
beforeAll(() => {
|
||||
container.id = "pymcontrol-test-id";
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
afterAll(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
it("should create iframe", () => {
|
||||
control = new PymControl({
|
||||
decorators: [withMockDecorator],
|
||||
id: container.id,
|
||||
url: "http://coralproject.net",
|
||||
title: "iFrame title",
|
||||
});
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
it("should send message", done => {
|
||||
const messages: MessageEvent[] = [];
|
||||
const messageRecorder = (e: MessageEvent) => messages.push(e);
|
||||
const contentWindow = (container.firstChild as HTMLIFrameElement)
|
||||
.contentWindow!;
|
||||
contentWindow.addEventListener("message", messageRecorder, false);
|
||||
control.sendMessage("test", "hello world");
|
||||
|
||||
setTimeout(() => {
|
||||
contentWindow.removeEventListener("message", messageRecorder, false);
|
||||
expect(messages).toHaveLength(1);
|
||||
expect(messages[0].data).toMatchSnapshot();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should remove iframe", () => {
|
||||
control.remove();
|
||||
expect(container.innerHTML).toBe("");
|
||||
});
|
||||
it("should cleanup decorators", () => {
|
||||
cleanupDecorator.verify();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import pym from "pym.js";
|
||||
|
||||
import { CleanupCallback, Decorator } from "./decorators";
|
||||
|
||||
interface PymControlConfig {
|
||||
id: string;
|
||||
url: string;
|
||||
title: string;
|
||||
decorators?: ReadonlyArray<Decorator>;
|
||||
}
|
||||
|
||||
export default class PymControl {
|
||||
private pym: pym.Parent;
|
||||
private cleanups: CleanupCallback[];
|
||||
|
||||
constructor(config: PymControlConfig) {
|
||||
const decorators = config.decorators || [];
|
||||
|
||||
this.pym = new pym.Parent(config.id, config.url, {
|
||||
title: config.title,
|
||||
id: `${config.id}_iframe`,
|
||||
name: `${config.id}_iframe`,
|
||||
});
|
||||
|
||||
this.cleanups = decorators
|
||||
.map(enhance => enhance(this.pym))
|
||||
.filter(cb => cb) as CleanupCallback[];
|
||||
}
|
||||
|
||||
public sendMessage(id: string, raw?: string) {
|
||||
this.pym.sendMessage(id, raw || "");
|
||||
}
|
||||
|
||||
public remove() {
|
||||
this.cleanups.forEach(cb => cb());
|
||||
this.cleanups = [];
|
||||
|
||||
// Remove the pym parent.
|
||||
this.pym.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { createStreamInterface } from "./Stream";
|
||||
|
||||
it("should call eventEmitter.on", () => {
|
||||
const control = {};
|
||||
const cb = () => "";
|
||||
const eventEmitter = {
|
||||
on: sinon
|
||||
.mock()
|
||||
.once()
|
||||
.withArgs("eventName", cb),
|
||||
};
|
||||
const stream = createStreamInterface(control as any, eventEmitter as any);
|
||||
stream.on("eventName", cb);
|
||||
eventEmitter.on.verify();
|
||||
});
|
||||
|
||||
it("should call eventEmitter.off", () => {
|
||||
const control = {};
|
||||
const cb = () => "";
|
||||
const eventEmitter = {
|
||||
off: sinon
|
||||
.mock()
|
||||
.once()
|
||||
.withArgs("eventName", cb),
|
||||
};
|
||||
const stream = createStreamInterface(control as any, eventEmitter as any);
|
||||
stream.off("eventName", cb);
|
||||
eventEmitter.off.verify();
|
||||
});
|
||||
|
||||
it("should call control.login", () => {
|
||||
const control = {
|
||||
sendMessage: sinon
|
||||
.mock()
|
||||
.once()
|
||||
.withArgs("login", "token"),
|
||||
};
|
||||
const eventEmitter = {};
|
||||
const stream = createStreamInterface(control as any, eventEmitter as any);
|
||||
stream.login("token");
|
||||
control.sendMessage.verify();
|
||||
});
|
||||
|
||||
it("should call control.logout", () => {
|
||||
const control = {
|
||||
sendMessage: sinon
|
||||
.mock()
|
||||
.once()
|
||||
.withArgs("logout"),
|
||||
};
|
||||
const eventEmitter = {};
|
||||
const stream = createStreamInterface(control as any, eventEmitter as any);
|
||||
stream.logout();
|
||||
control.sendMessage.verify();
|
||||
});
|
||||
|
||||
it("should call control.remove", () => {
|
||||
const control = {
|
||||
remove: sinon
|
||||
.mock()
|
||||
.once()
|
||||
.withArgs(),
|
||||
};
|
||||
const eventEmitter = {};
|
||||
const stream = createStreamInterface(control as any, eventEmitter as any);
|
||||
stream.remove();
|
||||
control.remove.verify();
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import { EventEmitter2 } from "eventemitter2";
|
||||
import qs from "query-string";
|
||||
|
||||
import {
|
||||
Decorator,
|
||||
withAutoHeight,
|
||||
withClickEvent,
|
||||
withCommentID,
|
||||
withEventEmitter,
|
||||
withIOSSafariWidthWorkaround,
|
||||
} from "./decorators";
|
||||
import PymControl from "./PymControl";
|
||||
import { ensureEndSlash } from "./utils";
|
||||
|
||||
interface CreatePymControlConfig {
|
||||
assetID?: string;
|
||||
assetURL?: string;
|
||||
title?: string;
|
||||
eventEmitter: EventEmitter2;
|
||||
id: string;
|
||||
rootURL: string;
|
||||
}
|
||||
|
||||
export function createPymControl(config: CreatePymControlConfig) {
|
||||
const streamDecorators: ReadonlyArray<Decorator> = [
|
||||
withIOSSafariWidthWorkaround,
|
||||
withAutoHeight,
|
||||
withClickEvent,
|
||||
withCommentID,
|
||||
withEventEmitter(config.eventEmitter),
|
||||
];
|
||||
|
||||
const query = qs.stringify({
|
||||
assetID: config.assetID,
|
||||
assetURL: config.assetURL,
|
||||
});
|
||||
const url = `${ensureEndSlash(config.rootURL)}stream.html?${query}`;
|
||||
return new PymControl({
|
||||
id: config.id,
|
||||
title: config.title || "Talk Embed Stream",
|
||||
decorators: streamDecorators,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
type EventCallback = (data: any) => void;
|
||||
|
||||
export function createStreamInterface(
|
||||
control: PymControl,
|
||||
eventEmitter: EventEmitter2
|
||||
) {
|
||||
return {
|
||||
on(eventName: string, callback: EventCallback) {
|
||||
return eventEmitter.on(eventName, callback);
|
||||
},
|
||||
off(eventName: string, callback: EventCallback) {
|
||||
return eventEmitter.off(eventName, callback);
|
||||
},
|
||||
login(token: string) {
|
||||
control.sendMessage("login", token);
|
||||
},
|
||||
logout() {
|
||||
control.sendMessage("logout");
|
||||
},
|
||||
remove() {
|
||||
return control.remove();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type StreamInterface = ReturnType<typeof createStreamInterface>;
|
||||
|
||||
export interface CreateConfig {
|
||||
assetID?: string;
|
||||
assetURL?: string;
|
||||
title?: string;
|
||||
eventEmitter: EventEmitter2;
|
||||
id: string;
|
||||
rootURL: string;
|
||||
}
|
||||
export default function create(config: CreateConfig) {
|
||||
return createStreamInterface(createPymControl(config), config.eventEmitter);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PymControl should create iframe 1`] = `"<iframe src=\\"http://coralproject.net/?initialWidth=0&childId=pymcontrol-test-id&parentTitle=&parentUrl=http%3A%2F%2Flocalhost%2F\\" width=\\"100%\\" scrolling=\\"no\\" marginheight=\\"0\\" frameborder=\\"0\\" title=\\"iFrame title\\" id=\\"pymcontrol-test-id_iframe\\" name=\\"pymcontrol-test-id_iframe\\"></iframe>"`;
|
||||
|
||||
exports[`PymControl should send message 1`] = `"pymxPYMxpymcontrol-test-idxPYMxtestxPYMxhello world"`;
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Basic integration test should render iframe 1`] = `"<iframe src=\\"http://localhost/stream.html?&initialWidth=0&childId=basic-integration-test-id&parentTitle=&parentUrl=http%3A%2F%2Flocalhost%2F\\" width=\\"100%\\" scrolling=\\"no\\" marginheight=\\"0\\" frameborder=\\"0\\" title=\\"Talk Embed Stream\\" id=\\"basic-integration-test-id_iframe\\" name=\\"basic-integration-test-id_iframe\\" style=\\"width: 1px; min-width: 100%;\\"></iframe>"`;
|
||||
@@ -0,0 +1,11 @@
|
||||
import pym from "pym.js";
|
||||
|
||||
export type CleanupCallback = () => void;
|
||||
export type Decorator = (pym: pym.Parent) => CleanupCallback | void;
|
||||
export { default as withAutoHeight } from "./withAutoHeight";
|
||||
export { default as withClickEvent } from "./withClickEvent";
|
||||
export { default as withCommentID } from "./withCommentID";
|
||||
export { default as withEventEmitter } from "./withEventEmitter";
|
||||
export {
|
||||
default as withIOSSafariWidthWorkaround,
|
||||
} from "./withIOSSafariWidthWorkaround";
|
||||
@@ -0,0 +1,16 @@
|
||||
import withAutoHeight from "./withAutoHeight";
|
||||
|
||||
it("should set height", () => {
|
||||
const fakePym = {
|
||||
onMessage: (type: string, callback: (height: string) => void) => {
|
||||
expect(type).toBe("height");
|
||||
callback("100");
|
||||
},
|
||||
el: document.createElement("div"),
|
||||
};
|
||||
fakePym.el.innerHTML = "<span>Hello World </span>";
|
||||
withAutoHeight(fakePym as any);
|
||||
expect(fakePym.el.innerHTML).toBe(
|
||||
'<span style="height: 100px;">Hello World </span>'
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Decorator } from "./";
|
||||
|
||||
const withAutoHeight: Decorator = pym => {
|
||||
// Resize parent iframe height when child height changes
|
||||
let cachedHeight: string;
|
||||
pym.onMessage("height", (height: string) => {
|
||||
if (height !== cachedHeight) {
|
||||
(pym.el.firstChild! as HTMLElement).style.height = `${height}px`;
|
||||
cachedHeight = height;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default withAutoHeight;
|
||||
@@ -0,0 +1,19 @@
|
||||
import simulant from "simulant";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { CleanupCallback } from ".";
|
||||
import withClickEvent from "./withClickEvent";
|
||||
|
||||
it("should send click events", () => {
|
||||
const pymMock = {
|
||||
sendMessage: sinon
|
||||
.mock()
|
||||
.once()
|
||||
.withArgs("click", ""),
|
||||
};
|
||||
const cleanup = withClickEvent(pymMock as any) as CleanupCallback;
|
||||
simulant.fire(document.body, "click");
|
||||
cleanup();
|
||||
simulant.fire(document.body, "click");
|
||||
pymMock.sendMessage.verify();
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Decorator } from "./";
|
||||
|
||||
const withClickEvent: Decorator = pym => {
|
||||
const handleClick = () => pym.sendMessage("click", "");
|
||||
|
||||
// If the user clicks outside the embed, then tell the embed.
|
||||
document.addEventListener("click", handleClick, true);
|
||||
|
||||
// Return cleanup callback.
|
||||
return () => {
|
||||
// Remove the event listeners.
|
||||
document.removeEventListener("click", handleClick, true);
|
||||
};
|
||||
};
|
||||
|
||||
export default withClickEvent;
|
||||
@@ -0,0 +1,36 @@
|
||||
import withCommentID from "./withCommentID";
|
||||
|
||||
it("should add commentID", () => {
|
||||
const previousLocation = location.toString();
|
||||
const previousState = window.history.state;
|
||||
const fakePym = {
|
||||
onMessage: (type: string, callback: (id: string) => void) => {
|
||||
if (type === "view-comment") {
|
||||
callback("comment-id");
|
||||
}
|
||||
},
|
||||
};
|
||||
withCommentID(fakePym as any);
|
||||
expect(location.toString()).toBe("http://localhost/?commentId=comment-id");
|
||||
window.history.replaceState(previousState, document.title, previousLocation);
|
||||
});
|
||||
|
||||
it("should remove commentID", () => {
|
||||
const previousLocation = location.toString();
|
||||
const previousState = window.history.state;
|
||||
window.history.replaceState(
|
||||
previousState,
|
||||
document.title,
|
||||
"http://localhost/?commentId=comment-id"
|
||||
);
|
||||
const fakePym = {
|
||||
onMessage: (type: string, callback: () => void) => {
|
||||
if (type === "view-all-comments") {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
};
|
||||
withCommentID(fakePym as any);
|
||||
expect(location.toString()).toBe("http://localhost/");
|
||||
window.history.replaceState(previousState, document.title, previousLocation);
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import qs from "query-string";
|
||||
|
||||
import { buildURL } from "../utils";
|
||||
import { Decorator } from "./";
|
||||
|
||||
const withCommentID: Decorator = pym => {
|
||||
// Remove the comment id from the query.
|
||||
pym.onMessage("view-all-comments", () => {
|
||||
const search = qs.stringify({
|
||||
...qs.parse(location.search),
|
||||
commentId: undefined,
|
||||
});
|
||||
|
||||
// Remove the commentId url param.
|
||||
const url = buildURL({ search });
|
||||
|
||||
// Change the url.
|
||||
window.history.replaceState({}, document.title, url);
|
||||
});
|
||||
|
||||
// Add the permalink comment id to the query.
|
||||
pym.onMessage("view-comment", (id: string) => {
|
||||
const search = qs.stringify({
|
||||
...qs.parse(location.search),
|
||||
commentId: id,
|
||||
});
|
||||
|
||||
// Remove the commentId url param.
|
||||
const url = buildURL({ search });
|
||||
|
||||
// Change the url.
|
||||
window.history.replaceState({}, document.title, url);
|
||||
});
|
||||
};
|
||||
|
||||
export default withCommentID;
|
||||
@@ -0,0 +1,21 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import withEventEmitter from "./withEventEmitter";
|
||||
|
||||
it("should emit events from pym to eventEmitter", () => {
|
||||
const eventEmitterMock = {
|
||||
emit: sinon
|
||||
.mock()
|
||||
.once()
|
||||
.withArgs("eventName", "value"),
|
||||
};
|
||||
const fakePym = {
|
||||
onMessage: (type: string, callback: (raw: string) => void) => {
|
||||
expect(type).toBe("event");
|
||||
callback(JSON.stringify({ eventName: "eventName", value: "value" }));
|
||||
},
|
||||
el: document.createElement("div"),
|
||||
};
|
||||
withEventEmitter(eventEmitterMock as any)(fakePym as any);
|
||||
eventEmitterMock.emit.verify();
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { EventEmitter2 } from "eventemitter2";
|
||||
|
||||
import { Decorator } from "./";
|
||||
|
||||
const withEventEmitter = (eventEmitter: EventEmitter2): Decorator => pym => {
|
||||
// Pass events from iframe to the event emitter.
|
||||
pym.onMessage("event", (raw: string) => {
|
||||
const { eventName, value } = JSON.parse(raw);
|
||||
eventEmitter.emit(eventName, value);
|
||||
});
|
||||
};
|
||||
|
||||
export default withEventEmitter;
|
||||
@@ -0,0 +1,12 @@
|
||||
import withIOSSafariWidthWorkaround from "./withIOSSafariWidthWorkaround";
|
||||
|
||||
it("should set width workaround", () => {
|
||||
const fakePym = {
|
||||
el: document.createElement("div"),
|
||||
};
|
||||
fakePym.el.innerHTML = "<span>Hello World</span>";
|
||||
withIOSSafariWidthWorkaround(fakePym as any);
|
||||
expect(fakePym.el.innerHTML).toBe(
|
||||
'<span style="width: 1px; min-width: 100%;">Hello World</span>'
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Decorator } from "./";
|
||||
|
||||
const withIOSSafariWidthWorkaround: Decorator = pym => {
|
||||
// Workaround: IOS Safari ignores `width` but respects `min-width` value.
|
||||
(pym.el.firstChild! as HTMLElement).style.width = "1px";
|
||||
(pym.el.firstChild! as HTMLElement).style.minWidth = "100%";
|
||||
};
|
||||
|
||||
export default withIOSSafariWidthWorkaround;
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Talk 5.0 – Embed Stream</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 style="text-align: center" }>Talk 5.0 – Embed Stream</h1>
|
||||
<div id="coralStreamEmbed"></div>
|
||||
<script>
|
||||
window.TalkEmbed = Talk.render(document.getElementById('coralStreamEmbed'));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as Talk from "./";
|
||||
|
||||
describe("Basic integration test", () => {
|
||||
const container: HTMLElement = document.createElement("div");
|
||||
let streamInterface: ReturnType<typeof Talk.render>;
|
||||
beforeAll(() => {
|
||||
container.id = "basic-integration-test-id";
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
afterAll(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
it("should render iframe", () => {
|
||||
streamInterface = Talk.render({
|
||||
id: "basic-integration-test-id",
|
||||
});
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
it("should remove iframe", () => {
|
||||
streamInterface.remove();
|
||||
expect(container.innerHTML).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { EventEmitter2 } from "eventemitter2";
|
||||
import qs from "query-string";
|
||||
|
||||
import createStreamInterface from "./Stream";
|
||||
|
||||
export interface Config {
|
||||
assetID?: string;
|
||||
assetURL?: string;
|
||||
rootURL?: string;
|
||||
id?: string;
|
||||
events?: (eventEmitter: EventEmitter2) => void;
|
||||
}
|
||||
|
||||
export function render(config: Config = {}) {
|
||||
// Parse query params
|
||||
const query = qs.parse(location.search);
|
||||
const eventEmitter = new EventEmitter2({ wildcard: true });
|
||||
|
||||
if (config.events) {
|
||||
config.events(eventEmitter);
|
||||
}
|
||||
|
||||
return createStreamInterface({
|
||||
assetID: config.assetID || query.assetID,
|
||||
assetURL: config.assetURL || query.assetURL,
|
||||
id: config.id || "talk-embed-stream",
|
||||
rootURL: config.rootURL || location.origin,
|
||||
eventEmitter,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "es5"],
|
||||
"types": ["jest"],
|
||||
"paths": {}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../types/pym.d.ts",
|
||||
"../../../types/simulant.d.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import buildURL from "./buildURL";
|
||||
|
||||
it("should default to window.location", () => {
|
||||
const url = buildURL();
|
||||
expect(url).toBe("http://localhost/");
|
||||
});
|
||||
|
||||
it("should build from parameters", () => {
|
||||
const url = buildURL({
|
||||
protocol: "https",
|
||||
hostname: "hostname",
|
||||
port: "8080",
|
||||
pathname: "/pathname",
|
||||
search: "search",
|
||||
hash: "#hash",
|
||||
});
|
||||
expect(url).toBe("https//hostname:8080/pathname?search#hash");
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
export default function buildURL({
|
||||
protocol = window.location.protocol,
|
||||
hostname = window.location.hostname,
|
||||
port = window.location.port,
|
||||
pathname = window.location.pathname,
|
||||
search = window.location.search,
|
||||
hash = window.location.hash,
|
||||
} = {}) {
|
||||
if (search && search[0] !== "?") {
|
||||
search = `?${search}`;
|
||||
} else if (search === "?") {
|
||||
search = "";
|
||||
}
|
||||
return `${protocol}//${hostname}${
|
||||
port ? `:${port}` : ""
|
||||
}${pathname}${search}${hash}`;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import ensureEndSlash from "./ensureEndSlash";
|
||||
|
||||
it("should add slash to the end", () => {
|
||||
const path = ensureEndSlash("/test");
|
||||
expect(path).toBe("/test/");
|
||||
});
|
||||
|
||||
it("should not add slash to the end if it's already there", () => {
|
||||
const path = ensureEndSlash("/test/");
|
||||
expect(path).toBe("/test/");
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function ensureEndSlash(p: string) {
|
||||
return p.match(/\/$/) ? p : `${p}/`;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as buildURL } from "./buildURL";
|
||||
export { default as ensureEndSlash } from "./ensureEndSlash";
|
||||
@@ -1,19 +1,31 @@
|
||||
import { LocalizationProvider } from "fluent-react/compat";
|
||||
import { MessageContext } from "fluent/compat";
|
||||
import { Child as PymChild } from "pym.js";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Formatter } from "react-timeago";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import { UIContext } from "talk-ui/components";
|
||||
import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
|
||||
|
||||
export interface TalkContext {
|
||||
// relayEnvironment for our relay framework.
|
||||
/** relayEnvironment for our relay framework. */
|
||||
relayEnvironment: Environment;
|
||||
|
||||
// localMessages for our i18n framework.
|
||||
/** localMessages for our i18n framework. */
|
||||
localeMessages: MessageContext[];
|
||||
|
||||
// formatter for timeago.
|
||||
/** formatter for timeago. */
|
||||
timeagoFormatter?: Formatter;
|
||||
|
||||
/**
|
||||
* A way to listen for clicks that are e.g. outside of the
|
||||
* current frame for `ClickOutside`
|
||||
*/
|
||||
registerClickFarAway?: ClickFarAwayRegister;
|
||||
|
||||
/** A pym child that interacts with the pym parent. */
|
||||
pym?: PymChild;
|
||||
}
|
||||
|
||||
const { Provider, Consumer } = React.createContext<TalkContext>({} as any);
|
||||
@@ -32,7 +44,12 @@ export const TalkContextProvider: StatelessComponent<{
|
||||
}> = ({ value, children }) => (
|
||||
<Provider value={value}>
|
||||
<LocalizationProvider messages={value.localeMessages}>
|
||||
<UIContext.Provider value={{ timeagoFormatter: value.timeagoFormatter }}>
|
||||
<UIContext.Provider
|
||||
value={{
|
||||
timeagoFormatter: value.timeagoFormatter,
|
||||
registerClickFarAway: value.registerClickFarAway,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UIContext.Provider>
|
||||
</LocalizationProvider>
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
import { EventEmitter2 } from "eventemitter2";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { noop } from "lodash";
|
||||
import { Child as PymChild } from "pym.js";
|
||||
import React from "react";
|
||||
import { Formatter } from "react-timeago";
|
||||
import { Environment, Network, RecordSource, Store } from "relay-runtime";
|
||||
|
||||
import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
|
||||
|
||||
import { generateMessages, LocalesData, negotiateLanguages } from "../i18n";
|
||||
import { fetchQuery } from "../network";
|
||||
import { TalkContext } from "./TalkContext";
|
||||
|
||||
interface CreateContextArguments {
|
||||
// Locales that the user accepts, usually `navigator.languages`.
|
||||
/** Locales that the user accepts, usually `navigator.languages`. */
|
||||
userLocales: ReadonlyArray<string>;
|
||||
|
||||
// Locales data that is returned by our `locales-loader`.
|
||||
/** Locales data that is returned by our `locales-loader`. */
|
||||
localesData: LocalesData;
|
||||
|
||||
// Init will be called after the context has been created.
|
||||
/** Init will be called after the context has been created. */
|
||||
init?: ((context: TalkContext) => void | Promise<void>);
|
||||
|
||||
/** A pym child that interacts with the pym parent. */
|
||||
pym?: PymChild;
|
||||
|
||||
/** Supports emitting and listening to events. */
|
||||
eventEmitter?: EventEmitter2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,6 +57,8 @@ export default async function createContext({
|
||||
init = noop,
|
||||
userLocales,
|
||||
localesData,
|
||||
pym,
|
||||
eventEmitter = new EventEmitter2({ wildcard: true }),
|
||||
}: CreateContextArguments): Promise<TalkContext> {
|
||||
// Initialize Relay.
|
||||
const relayEnvironment = new Environment({
|
||||
@@ -54,6 +66,21 @@ export default async function createContext({
|
||||
store: new Store(new RecordSource()),
|
||||
});
|
||||
|
||||
// Listen for outside clicks.
|
||||
let registerClickFarAway: ClickFarAwayRegister | undefined;
|
||||
if (pym) {
|
||||
registerClickFarAway = cb => {
|
||||
pym.onMessage("click", cb);
|
||||
// Return unlisten callback.
|
||||
return () => {
|
||||
const index = pym.messageHandlers.click.indexOf(cb);
|
||||
if (index > -1) {
|
||||
pym.messageHandlers.click.splice(index, 1);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize i18n.
|
||||
const locales = negotiateLanguages(userLocales, localesData);
|
||||
|
||||
@@ -69,6 +96,9 @@ export default async function createContext({
|
||||
relayEnvironment,
|
||||
localeMessages,
|
||||
timeagoFormatter,
|
||||
pym,
|
||||
eventEmitter,
|
||||
registerClickFarAway,
|
||||
};
|
||||
|
||||
// Run custom initializations.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html prefix="og: http://ogp.me/ns#">
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Relay Experiments</title>
|
||||
<title>Talk - Stream</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app" aria-role="application" onclick="void(0)"></div>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import pym from "pym.js";
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
@@ -23,6 +24,7 @@ async function main() {
|
||||
init,
|
||||
localesData,
|
||||
userLocales: navigator.languages,
|
||||
pym: new pym.Child({ polling: 100 }),
|
||||
});
|
||||
|
||||
const Index: StatelessComponent = () => (
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
}
|
||||
},
|
||||
"include": ["./**/*", "../../types/**/*.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules", "./embed"]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,14 @@ import React from "react";
|
||||
import simulant from "simulant";
|
||||
import sinon from "sinon";
|
||||
|
||||
import ClickOutside from "./ClickOutside";
|
||||
import UIContext from "../UIContext";
|
||||
|
||||
import {
|
||||
ClickFarAwayCallback,
|
||||
ClickFarAwayRegister,
|
||||
ClickOutside,
|
||||
default as ClickOutsideWithContext,
|
||||
} from "./ClickOutside";
|
||||
|
||||
let container: HTMLElement;
|
||||
|
||||
@@ -60,3 +67,57 @@ it("should ignore click inside", () => {
|
||||
expect(onClickOutside.calledOnce).toEqual(false);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it("should detect click far away", () => {
|
||||
let emitFarAwayClick: ClickFarAwayCallback = Function;
|
||||
const unlisten = sinon.spy();
|
||||
const registerClickFarAway: ClickFarAwayRegister = cb => {
|
||||
emitFarAwayClick = cb;
|
||||
return unlisten;
|
||||
};
|
||||
const onClickOutside = sinon.spy();
|
||||
const wrapper = mount(
|
||||
<ClickOutside
|
||||
onClickOutside={onClickOutside}
|
||||
registerClickFarAway={registerClickFarAway}
|
||||
>
|
||||
<button id="click-outside-test-button">Push Me</button>
|
||||
</ClickOutside>,
|
||||
{
|
||||
attachTo: container,
|
||||
}
|
||||
);
|
||||
|
||||
expect(onClickOutside.calledOnce).toEqual(false);
|
||||
emitFarAwayClick();
|
||||
expect(onClickOutside.calledOnce).toEqual(true);
|
||||
expect(unlisten.calledOnce).toEqual(false);
|
||||
wrapper.unmount();
|
||||
expect(unlisten.calledOnce).toEqual(true);
|
||||
});
|
||||
|
||||
it("should get registerClickFarAway from context", () => {
|
||||
const registerClickFarAway: ClickFarAwayRegister = sinon.spy();
|
||||
const onClickOutside = sinon.spy();
|
||||
const context: any = {
|
||||
registerClickFarAway,
|
||||
};
|
||||
const wrapper = mount(
|
||||
<UIContext.Provider value={context}>
|
||||
<ClickOutsideWithContext
|
||||
onClickOutside={onClickOutside}
|
||||
registerClickFarAway={registerClickFarAway}
|
||||
>
|
||||
<button id="click-outside-test-button">Push Me</button>
|
||||
</ClickOutsideWithContext>
|
||||
</UIContext.Provider>,
|
||||
{
|
||||
attachTo: container,
|
||||
}
|
||||
);
|
||||
|
||||
expect(wrapper.find(ClickOutside).prop("registerClickFarAway")).toEqual(
|
||||
registerClickFarAway
|
||||
);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
@@ -1,13 +1,30 @@
|
||||
import React from "react";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { findDOMNode } from "react-dom";
|
||||
|
||||
import UIContext from "../UIContext";
|
||||
|
||||
export type ClickFarAwayCallback = () => void;
|
||||
export type ClickFarAwayUnlistenCallback = () => void;
|
||||
|
||||
export type ClickFarAwayRegister = (
|
||||
callback: ClickFarAwayCallback
|
||||
) => ClickFarAwayUnlistenCallback;
|
||||
|
||||
interface Props {
|
||||
onClickOutside: () => void;
|
||||
|
||||
/**
|
||||
* A way to listen for clicks that are e.g. outside of the
|
||||
* current frame for `ClickOutside`
|
||||
*/
|
||||
registerClickFarAway?: ClickFarAwayRegister;
|
||||
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
class ClickOutside extends React.Component<Props> {
|
||||
export class ClickOutside extends React.Component<Props> {
|
||||
public domNode: Element | null = null;
|
||||
private unlisten?: ClickFarAwayUnlistenCallback;
|
||||
|
||||
public handleClick = (e: MouseEvent) => {
|
||||
const { onClickOutside } = this.props;
|
||||
@@ -17,17 +34,43 @@ class ClickOutside extends React.Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
public handleClickFarAway = () => {
|
||||
const { onClickOutside } = this.props;
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
onClickOutside && onClickOutside();
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.domNode = findDOMNode(this) as Element;
|
||||
document.addEventListener("click", this.handleClick, true);
|
||||
|
||||
// Listen to far away clicks.
|
||||
if (this.props.registerClickFarAway) {
|
||||
this.unlisten = this.props.registerClickFarAway(this.handleClickFarAway);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener("click", this.handleClick, true);
|
||||
|
||||
// Unlisten to far away clicks.
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
this.unlisten = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
export default ClickOutside;
|
||||
|
||||
const ClickOutsideWithContext: StatelessComponent<Props> = props => (
|
||||
<UIContext.Consumer>
|
||||
{({ registerClickFarAway }) => (
|
||||
<ClickOutside {...props} registerClickFarAway={registerClickFarAway} />
|
||||
)}
|
||||
</UIContext.Consumer>
|
||||
);
|
||||
|
||||
export default ClickOutsideWithContext;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ClickOutside, ClickFarAwayRegister } from "./ClickOutside";
|
||||
@@ -1,9 +1,11 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { MediaQueryMatchers } from "react-responsive";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import { MatchMedia } from "./MatchMedia";
|
||||
import UIContext from "../UIContext";
|
||||
import { default as MatchMediaWithContext, MatchMedia } from "./MatchMedia";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof MatchMedia> = {
|
||||
@@ -25,3 +27,20 @@ it("map new speech prop to older aural prop", () => {
|
||||
const wrapper = shallow(<MatchMedia {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should get mediaQueryValues from context", () => {
|
||||
const mediaQueryValues: Partial<MediaQueryMatchers> = {
|
||||
width: 100,
|
||||
};
|
||||
const context: any = {
|
||||
mediaQueryValues,
|
||||
};
|
||||
const wrapper = mount(
|
||||
<UIContext.Provider value={context}>
|
||||
<MatchMediaWithContext maxWidth="xs">
|
||||
<span>Hello World</span>
|
||||
</MatchMediaWithContext>
|
||||
</UIContext.Provider>
|
||||
);
|
||||
expect(wrapper.find(MatchMedia).prop("values")).toEqual(mediaQueryValues);
|
||||
});
|
||||
|
||||
@@ -2,9 +2,18 @@ import React from "react";
|
||||
import { MediaQueryMatchers } from "react-responsive";
|
||||
import { Formatter } from "react-timeago";
|
||||
|
||||
import { ClickFarAwayRegister } from "../ClickOutside";
|
||||
|
||||
export interface UIContextProps {
|
||||
/** Allows to integrate translated strings into `RelativeTime` Component */
|
||||
timeagoFormatter?: Formatter | null;
|
||||
/** Allows testing `MatchMedia` by setting media query values */
|
||||
mediaQueryValues?: Partial<MediaQueryMatchers>;
|
||||
/**
|
||||
* A way to listen for clicks that are e.g. outside of the
|
||||
* current frame for `ClickOutside`
|
||||
*/
|
||||
registerClickFarAway?: ClickFarAwayRegister;
|
||||
}
|
||||
|
||||
const UIContext = React.createContext<UIContextProps>({} as any);
|
||||
|
||||
@@ -90,5 +90,9 @@ const config = convict({
|
||||
|
||||
export type Config = typeof config;
|
||||
|
||||
export const createClientEnv = (c: Config) => ({
|
||||
NODE_ENV: c.get("env"),
|
||||
});
|
||||
|
||||
// Setup the base configuration.
|
||||
export default config;
|
||||
@@ -3,10 +3,10 @@ import http from "http";
|
||||
import { Redis } from "ioredis";
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import { Config } from "talk-common/config";
|
||||
import { notFoundMiddleware } from "talk-server/app/middleware/notFound";
|
||||
import { createPassport } from "talk-server/app/middleware/passport";
|
||||
import { JWTSigningConfig } from "talk-server/app/middleware/passport/jwt";
|
||||
import { Config } from "talk-server/config";
|
||||
import { handleSubscriptions } from "talk-server/graph/common/subscriptions/middleware";
|
||||
import { Schemas } from "talk-server/graph/schemas";
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { Config } from "talk-common/config";
|
||||
import {
|
||||
createJWTSigningConfig,
|
||||
extractJWTFromRequest,
|
||||
parseAuthHeader,
|
||||
} from "talk-server/app/middleware/passport/jwt";
|
||||
import { Config } from "talk-server/config";
|
||||
import { Request } from "talk-server/types/express";
|
||||
|
||||
describe("parseAuthHeader", () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid from "uuid";
|
||||
|
||||
import { Db } from "mongodb";
|
||||
import { Strategy } from "passport-strategy";
|
||||
import { Config } from "talk-server/config";
|
||||
import { Config } from "talk-common/config";
|
||||
import { retrieveUser, User } from "talk-server/models/user";
|
||||
import { Request } from "talk-server/types/express";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
GraphQLOptions,
|
||||
} from "apollo-server-express";
|
||||
import { FieldDefinitionNode, GraphQLError, ValidationContext } from "graphql";
|
||||
import { Config } from "talk-server/config";
|
||||
import { Config } from "talk-common/config";
|
||||
|
||||
// Sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L46-L57
|
||||
const NoIntrospection = (context: ValidationContext) => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { RedisPubSub } from "graphql-redis-subscriptions";
|
||||
import { Config } from "talk-server/config";
|
||||
import { Config } from "talk-common/config";
|
||||
import { createRedisClient } from "talk-server/services/redis";
|
||||
|
||||
export async function createPubSub(config: Config): Promise<RedisPubSub> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GraphQLSchema } from "graphql";
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import { Config } from "talk-server/config";
|
||||
import { Config } from "talk-common/config";
|
||||
import { graphqlMiddleware } from "talk-server/graph/common/middleware";
|
||||
|
||||
import Context from "./context";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GraphQLSchema } from "graphql";
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import { Config } from "talk-server/config";
|
||||
import { Config } from "talk-common/config";
|
||||
import { graphqlMiddleware } from "talk-server/graph/common/middleware";
|
||||
import { Request } from "talk-server/types/express";
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import express, { Express } from "express";
|
||||
import http from "http";
|
||||
|
||||
import config, { Config } from "talk-common/config";
|
||||
import { createJWTSigningConfig } from "talk-server/app/middleware/passport/jwt";
|
||||
import getManagementSchema from "talk-server/graph/management/schema";
|
||||
import { Schemas } from "talk-server/graph/schemas";
|
||||
import getTenantSchema from "talk-server/graph/tenant/schema";
|
||||
|
||||
import { attachSubscriptionHandlers, createApp, listenAndServe } from "./app";
|
||||
import config, { Config } from "./config";
|
||||
import logger from "./logger";
|
||||
import { createMongoDB } from "./services/mongodb";
|
||||
import { createRedisClient } from "./services/redis";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Db, MongoClient } from "mongodb";
|
||||
import { Config } from "talk-server/config";
|
||||
import { Config } from "talk-common/config";
|
||||
|
||||
/**
|
||||
* create will connect to the MongoDB instance identified in the configuration.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import RedisClient, { Redis } from "ioredis";
|
||||
import { Config } from "talk-server/config";
|
||||
import { Config } from "talk-common/config";
|
||||
|
||||
/**
|
||||
* create will connect to the Redis instance identified in the configuration.
|
||||
|
||||
Vendored
+174
@@ -0,0 +1,174 @@
|
||||
declare module "pym.js" {
|
||||
export interface ChildSettings {
|
||||
/**
|
||||
* Callback invoked after receiving a resize event from the parent,
|
||||
* sets module:pym.Child#settings.renderCallback
|
||||
*/
|
||||
renderCallback?: Function;
|
||||
/** xdomain to validate messages received */
|
||||
xdomain?: string;
|
||||
/** polling frequency in milliseconds to send height to parent */
|
||||
polling?: number;
|
||||
/**
|
||||
* parent container id used when navigating the child
|
||||
* iframe to a new page but we want to keep it responsive.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* if passed it will be override the default parentUrl query string
|
||||
* parameter name expected on the iframe src
|
||||
*/
|
||||
parenturlparam?: string;
|
||||
}
|
||||
|
||||
/** The Child half of a responsive iframe. */
|
||||
export class Child {
|
||||
/** The id of the parent container */
|
||||
id: string;
|
||||
|
||||
/** The timerId in order to be able to stop when polling is enabled */
|
||||
timerId: string;
|
||||
|
||||
/** The initial width of the parent page */
|
||||
parentWidth: string;
|
||||
|
||||
/** The URL of the parent page from window.location.href. */
|
||||
parentUrl: string;
|
||||
|
||||
/** The title of the parent page from document.title. */
|
||||
parentTitle: string;
|
||||
|
||||
/** Stores the registered messageHandlers for each messageType */
|
||||
messageHandlers: Record<string, Array<(message: string) => void>>;
|
||||
|
||||
/** RegularExpression to validate the received messages */
|
||||
messageRegex: RegExp;
|
||||
|
||||
constructor(config: ChildSettings);
|
||||
|
||||
/** Navigate parent to a given url. */
|
||||
navigateParentTo(url: string): void;
|
||||
|
||||
/**
|
||||
* Bind a callback to a given messageType from the child.
|
||||
* Reserved message names are: "width".
|
||||
*/
|
||||
onMessage(messageType: string, callback: (message: string) => void): void;
|
||||
|
||||
/** Unbind child event handlers and timers. */
|
||||
remove(): void;
|
||||
|
||||
/** Scroll parent to a given element id. */
|
||||
scrollParentTo(hash: string): void;
|
||||
|
||||
/** Scroll parent to a given child element id. */
|
||||
scrollParentToChildEl(id: string): void;
|
||||
|
||||
/** Scroll parent to a particular child offset. */
|
||||
scrollParentToChildPos(pos: number): void;
|
||||
|
||||
/** Transmit the current iframe height to the parent. */
|
||||
sendHeight(): void;
|
||||
|
||||
/** Send a message to the the Parent. */
|
||||
sendMessage(messageType: string, message: string): void;
|
||||
}
|
||||
|
||||
export interface ParentSettings {
|
||||
/** xdomain to validate messages received */
|
||||
xdomain?: string;
|
||||
/** if passed it will be assigned to the iframe title attribute */
|
||||
title?: string;
|
||||
/** if passed it will be assigned to the iframe name attribute */
|
||||
name?: string;
|
||||
/** if passed it will be assigned to the iframe id attribute */
|
||||
id?: string;
|
||||
/** if passed it will be assigned to the iframe allowfullscreen attribute */
|
||||
allowfullscreen?: boolean;
|
||||
/**
|
||||
* if passed it will be assigned to the iframe sandbox attribute
|
||||
* (we do not validate the syntax so be careful!!)
|
||||
*/
|
||||
sandbox?: boolean;
|
||||
/**
|
||||
* if passed it will be override the default parentUrl query string
|
||||
* parameter name passed to the iframe src
|
||||
*/
|
||||
parenturlparam?: string;
|
||||
/**
|
||||
* if passed it will be override the default parentUrl query string
|
||||
* parameter value passed to the iframe src
|
||||
*/
|
||||
parenturlvalue?: string;
|
||||
/**
|
||||
* if passed and different than false it will strip the querystring
|
||||
* params parentUrl and parentTitle passed to the iframe src
|
||||
*/
|
||||
optionalparams?: string;
|
||||
/** if passed it will activate scroll tracking on the parent */
|
||||
trackscroll?: boolean;
|
||||
/**
|
||||
* if passed it will set the throttle wait in order to fire
|
||||
* scroll messaging. Defaults to 100 ms.
|
||||
*/
|
||||
scrollwait?: number;
|
||||
}
|
||||
|
||||
/** The Parent half of a response iframe. */
|
||||
export class Parent {
|
||||
/** The container DOM object */
|
||||
el: HTMLElement;
|
||||
|
||||
/** The id of the container element */
|
||||
id: string;
|
||||
|
||||
/** The contained child iframe */
|
||||
iframe: HTMLElement;
|
||||
|
||||
/** Stores the registered messageHandlers for each messageType */
|
||||
messageHandlers: Record<string, Array<(message: string) => void>>;
|
||||
|
||||
/** RegularExpression to validate the received messages */
|
||||
messageRegex: RegExp;
|
||||
|
||||
/**
|
||||
* The parent instance settings, updated by the values
|
||||
* passed in the config object
|
||||
*/
|
||||
settings: ParentSettings;
|
||||
|
||||
/** The url that will be set as the iframe's src */
|
||||
url: string;
|
||||
|
||||
constructor(id: string, url: string, config: ParentSettings);
|
||||
|
||||
/**
|
||||
* Bind a callback to a given messageType from the child.
|
||||
* Reserved message names are: "height", "scrollTo" and "navigateTo".
|
||||
*/
|
||||
onMessage(messageType: string, callback: (message: string) => void): void;
|
||||
|
||||
/** Remove this parent from the page and unbind it's event handlers. */
|
||||
remove(): void;
|
||||
|
||||
/** Send a message to the the child. */
|
||||
sendMessage(messageType: string, message: string): void;
|
||||
|
||||
/**
|
||||
* Transmit the current viewport and iframe position to the child.
|
||||
* Sends viewport width, viewport height
|
||||
* and iframe bounding rect top-left-bottom-right
|
||||
* all separated by spaces
|
||||
*
|
||||
* You shouldn't need to call this directly.
|
||||
*/
|
||||
sendViewportAndIFramePosition(): void;
|
||||
|
||||
/**
|
||||
* Transmit the current iframe width to the child.
|
||||
* You shouldn't need to call this directly.
|
||||
*/
|
||||
sendWidth(): void;
|
||||
}
|
||||
export function autoInit(doNotRaiseEvents: boolean): void;
|
||||
}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
declare module "react-dev-utils/InterpolateHtmlPlugin" {
|
||||
import { Plugin } from "webpack";
|
||||
export default class InterpolateHtmlPlugin extends Plugin {
|
||||
constructor(env: Record<string, string>);
|
||||
}
|
||||
}
|
||||
|
||||
declare module "react-dev-utils/ModuleScopePlugin" {
|
||||
import { Plugin } from "webpack";
|
||||
export default class ModuleScopePlugin extends Plugin {
|
||||
constructor(rootPath: string, ignore: ReadonlyArray<string>);
|
||||
}
|
||||
}
|
||||
|
||||
declare module "react-dev-utils/WatchMissingNodeModulesPlugin" {
|
||||
import { Plugin } from "webpack";
|
||||
export default class ModuleScopePlugin extends Plugin {
|
||||
constructor(nodeModulesPath: string);
|
||||
}
|
||||
}
|
||||
|
||||
declare module "react-dev-utils/errorOverlayMiddleware";
|
||||
declare module "react-dev-utils/ignoredFiles";
|
||||
declare module "react-dev-utils/noopServiceWorkerMiddleware";
|
||||
declare module "react-dev-utils/WebpackDevServerUtils";
|
||||
declare module "react-dev-utils/FileSizeReporter";
|
||||
declare module "react-dev-utils/formatWebpackMessages";
|
||||
declare module "react-dev-utils/printBuildError";
|
||||
+8
-2
@@ -13,8 +13,14 @@
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"noErrorTruncation": true,
|
||||
"lib": ["es6", "esnext.asynciterable"]
|
||||
"lib": ["dom", "es6", "esnext.asynciterable"]
|
||||
},
|
||||
"include": ["./src/**/.*.js", "./scripts/**/*", "./config/**/*", "*.js"],
|
||||
"include": [
|
||||
"./src/**/.*.js",
|
||||
"./src/types/**/*.d.ts",
|
||||
"./scripts/**/*",
|
||||
"./config/**/*",
|
||||
"*.js"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user